-
Notifications
You must be signed in to change notification settings - Fork 646
Import wildcard #263
base: master
Are you sure you want to change the base?
Import wildcard #263
Conversation
I added the Go tests, waiting for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Thanks for working on it. I like how simple it is. I have a few suggestions.
runtime/module.go
Outdated
} | ||
|
||
// Fall back on __dict__ | ||
dictAttr, raised := GetAttr(f, module, NewStr("__dict__"), nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's simpler to use the dict field of the Object struct: module.dict
runtime/module.go
Outdated
} | ||
|
||
func loadMembersFromIterable(f *Frame, module, iterable *Object, filterF func(*Object) bool) *BaseException { | ||
iter, raised := Iter(f, iterable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use seqForEach from seq.go here?
runtime/module.go
Outdated
|
||
memberName, raised := Next(f, iter) | ||
for ; raised == nil; memberName, raised = Next(f, iter) { | ||
member, raised := GetAttr(f, module, toStrUnsafe(memberName), nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
memberName may not be a string so this unsafe conversion is not .. safe :)
It's worth looking at the CPython source to see what happens with non-string members in __all__
or in the globals dict.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I read the source code and it seems to raise several errors:
- lines 48888 and 4893 seem to be unexpected errors, don't think we have to map these
- line 4895 if the module doesn't contain either one of all and dict. It doesn't seem like it's possible in the Go code
- line 4909 if all or dict (whichever gets chosen) is not iterable. Throws IndexError
- line 4924 if the member name is not a string, or the module does not contain the member
- lines 4926 and 4928 throw errors if locals is malformed, the member name is already validated from above.
Looking over the exceptions, there are already a few covered ones, without changing the code:
- IndexError when trying to iterate over a non-iterable object
- AttributeError when the module does not have a requested member
So, what I should focus on is:
- TypeError is the member name is not actually a string
- SystemError if the Globals (locals) is malformed. I don't know how to replicate that in real-life though, so I couldn't test in the shell, I just went through the code.
WDYT?
runtime/module.go
Outdated
if raised != nil { | ||
return raised | ||
} | ||
if filterF != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if filterF != nil && filterF(memberName) {
runtime/module.go
Outdated
continue | ||
} | ||
} | ||
raised = f.Globals().SetItem(f, memberName, member) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe stash the globals dict in a local variable at the top of the function.
runtime/module_test.go
Outdated
@@ -204,6 +204,54 @@ func TestImportNativeModule(t *testing.T) { | |||
} | |||
} | |||
|
|||
func TestLoadMembers(t *testing.T) { | |||
f := NewRootFrame() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend using the the runInvokeTestCase pattern used in other tests if at all possible. It takes care of a number of things like frame creation and handling exceptions. You could return the globals dictionary that gets populated and check that against your expectation. Something like:
fun := wrapFuncForTest(f *Frame, m *Object) (*Dict, *BaseException) {
raised := LoadMembers(f, m)
if raised != nil {
return nil, raised
}
return f.Globals(), nil
}
cases := []invokeTestCase{
{args: fooAllDefinedModule, want: newTestDict(var1, val1, var2, val2)},
}
for _, cas := range cases {
if err := runInvokeTestCase(fun, &cas); err != "" {
t.Error(err)
}
}
runtime/module_test.go
Outdated
}, | ||
{ | ||
fooAllUndefinedModule, | ||
newTestDict(var1, val1, var3, val3), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be good to test some of the corner cases like when __all__
is not iterable.
@trotterdylan implemented all your suggestions. Thanks a lot for all the help, it's my first time coding in Go, but I'm learning. The last travis job failed inexplicably. Tests passed on linux, but failed on osx, in apparently a part of the code I did not touch. If it's not flakiness and it's because of my code, how can I investigate it? There are 2 more things I might have to do in terms of functionality. The second one is the exception raised if the frame Globals is not a dictionary. In Python you get a SystemError. Wondering if it's worth doing. |
Sorry for the delay in responding. I've been occupied with other things. Re: Travis failure: This looks like unrelated flakiness. I'm not sure what's up with that but don't worry about it for the purposes of this PR. Re: PySequence_GetItem: Good find. It looks like there's no specification that Given that CPython implicitly requires a "sequence" (i.e. a tuple or a list) we should probably require the same. From experimentation, it seems like it calls getitem on increasing values of an index until it gets an IndexError. So test.py:
It's very awkward behavior IMO, but we should replicate it as best we can. Re: Globals being not a dict: I don't think this can happen in our case because we're pulling globals from f.Globals() which returns a |
Sorry for not responding. I was away on a business trip, but I can start working on this again tomorrow.
Meanwhile, I will do more investigation then come up with a solution. |
CPython and Grumpy are a bit different in how getitem is implemented. Here's the CPython code: https://github.com/python/cpython/blob/2.7/Objects/abstract.c#L135 The difference stems from the fact that CPython has two different I suspect this is an optimization and I'm not convinced we need to make our implementation this complicated right now. If you just call GetItem() directly by passing increasing integer objects in, the behavior will be very close. The only difference is we'll get an |
Just a heads up: I've made some fairly significant changes to the import code that will conflict with your changes. It's probably worth reviewing b9a0c8a to get a sense of how this affects you. Let me know if you have any questions. |
Thanks for letting me know! |
@trotterdylan thanks for suggesting doing everything in Go! Once I got familiar with the code, I managed to write the same functionality with so fewer lines. :D
I intentionally simplified the Python test since I will now be moving detailed checks to module_test.go. I'll start writing the test for LoadMembers.
I think it makes sense now to close #254 in favor of this approach.