Fix type resolution for cross-directory thrift includes - #66
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #66 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 2 2
Lines 87 80 -7
Branches 1 1
=========================================
- Hits 87 80 -7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@unmade the TLDR here is that the I am not sure if you had specifically thought about the nested dir and/or cross import setting before, but imho the "flatten everything" is reasonable behavior, given how Thrift |
Never thought of the nester dir. At my company we used flat structure, so it was just due to thriftpy2 < 0.5 that everything worked.
I checked and I think flattening is the right way to go as this is what thrift does itself. So I don't think we need any alternative there. The solution looks good.
I checked it and thankfully
No slop spotted, thanks for the work! 💪 |
| @@ -0,0 +1,9 @@ | |||
| include "sub/child.thrift" | |||
There was a problem hiding this comment.
nit: I'd rather introduce some nested folders as part of existing examples/interfaces folder.
If that's OK with you, I will implement that change. For now I'm thinking of splitting shared.thrift into multiple files under different folder.
There was a problem hiding this comment.
On the other hand I like that we have isolation for the cases, so I might just move cross_dir_interfaces under example folder.
There was a problem hiding this comment.
I defer entirely to you on this. I did feel awkward the way I set it up but I was trying to avoid having this PR shake up too many details about other tests.
8493cfd to
f666a96
Compare
|
I added nesting directly to By a chance I noticed that in some cases value wasn't not prefixed with a module name, specifically in that case: 9: required list<labels.Label> labels = [labels.DEFAULT_LABEL]In the stub file it was produced as follows: labels: List[labels.Label] = field(
default_factory=lambda: [Label(name="default", color=3)] # `[Label(...)]` is missing `labels` module
)I had to rethink how we add module name to the value and I couldn't find anything better than to monkey patch |
e36393a to
9f7048d
Compare
|
Hmm I will have to digest this tomorrow. As far as I remember we had fixed that case a while ago (but I do vaguely remember that being an issue before). Maybe I am misunderstanding the new edge case you found, but I'll look at the commits tomorrow. Don't hold on me re landing though if you feel good about where it ended up. |
|
Ah, apologies if I caused any confusion 😅. Resolving module names is convoluted indeed.
I do remember that as well and briefly checking I think it was done in #61. It is very similar to the case I'm talking about here. The only difference is that #61 fixed it for simple values, say: 7: required dates.DateTime createdWithDefault = dates.EPOCHcreatedWithDefault: dates.DateTime = field(
default_factory=lambda: dates.DateTime( # `DateTime` prefixed with `dates`
year=1970, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)
)But we didn't cover the case when |
Summary
The
thriftpy2bump to>=0.5.0was trickier than I thought. Not only was there the whole_thriftappending inside the module map, but also the behavior changed in terms of how cross-module imports were handled.I switched to building a
__thrift_module_name__->__name__mapping from includes and threading it through type resolution. This has the side effect of also fixing the_thriftsuffix stuff.Also includes new parity/import tests across all flag combinations.
Discussion
thrift-pyialready flattens all output into a single directory with a single__init__.pyithat re-exports every module as a sibling.So when we encounter a cross-directory include like
include "some/nested/path/Foo.thrift", the natural thing to do is resolve it to its leaf module nameFoo, because that's what the generatedFoo.pyiwill actually be called in the output directory. This is also how Thrift itself works when youincludefiles (you ref them in the importing file via their base name only).For same-directory includes, the mapping entries are effectively identity (
Foo_thrift->Foo), which is the same thingremovesuffix("_thrift")was already doing.This is fully backwards compatible, but this regression caused me to wonder if you had actually intended to flatten everything out, or if you only ever ran this from within one directory/child at a time. That said, this is one reasonable interpretation of how cross-dir includes should work.
If there's a case where preserving some of the directory structure in type annotations would be preferable, happy to discuss alternatives (CC @unmade). In my monorepo case, the flattening actually ended up being quite nice, but of course there is an obvious edge case in terms of file basenames colliding (I have a custom hook in my monorepo to avoid that, since I run it via
bazel).I think one obvious followup, is to do some sort of mangling of imported files, so conflicts CAN exist, provided they are valid thrift (and so one file doesn't import 2 conflicting files). But I need to double check whether we can rely on
thriftpy2handling this edge case correctly for us - including any scenarios where it should error instead of parse - so that we can build on it.Disclaimer - any time I have to touch
thriftpy2, I lean pretty heavily on LLMs, because I find the module map stuff pretty confusing. If there are any aspects of this that feel too "slop", let me know and I'm happy to clean it up. I did go in and personally tweak, clean, etc, all generated code but I may have missed a spot.