I'm attempting to add typing to a method that returns a generator. Whenever I run this program with the return type specified, a TypeError is raised.
Adding quotes or removing the typing fixes the error, but this seems like hack. Surely there is a correct way of doing this.
def inbox_files(self) -> "Generator[RecordsFile]":
...
# OR
def inbox_files(self):
...
from typing import Generator, List
from .records_file import RecordsFile
Class Marshaller:
...
def inbox_files(self) -> Generator[RecordsFile]:
return self._search_directory(self._inbox)
def _search_directory(self, directory: str) -> RecordsFile:
for item_name in listdir(directory):
item_path = path.join(item_name, directory)
if path.isdir(item_path):
yield from self._search_directory(item_path)
elif path.isfile(item_path):
yield RecordsFile(item_path)
else:
print(f"[WARN] Unknown item found: {item_path}")
The following stack trace is produced:
Traceback (most recent call last):
File "./bin/data_marshal", line 8, in <module>
from src.app import App
File "./src/app.py", line 9, in <module>
from .marshaller import Marshaller
File "./src/marshaller.py", line 9, in <module>
class Marshaller:
File "./src/marshaller.py", line 29, in Marshaller
def inbox_files(self) -> Generator[RecordsFile]:
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 254, in inner
return func(*args, **kwds)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 630, in __getitem__
_check_generic(self, params)
File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py", line 208, in _check_generic
raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
TypeError: Too few parameters for typing.Generator; actual 1, expected 3
¯\_(ツ)_/¯
You have to explicitly specify the send type and the return type, even if both are None.
def inbox_files(self) -> Generator[RecordsFile,None,None]:
return self._search_directory(self._inbox)
Note that the yield type is what you might think of as the return type. The send type is the type of value you can pass to the generator's send method. The return type is the type of value that could be embedded in the StopIteration exception raised by next after all possible value have been yielded. Consider:
def foo():
yield 3
return "hi"
f = foo()
The first call to next(f) will return 3; the second will raise StopIteration("hi").
)
A generator that you cannot send into or return from is simply an iterable or an iterator (either, apparently can be used).
def inbox_files(self) -> Iterable[RecordsFile]: # Or Iterator[RecordsFile]
return self._search_directory(self._inbox)
_search_directory itself also returns a generator/iterable, not an instance of RecordsFile:
def _search_directory(self, directory: str) -> Iterable[RecordsFile]:
This answer was useful, but I was confused since I was sure I had used Generator[] with just one parameter in the past and it worked.
I traced it back to using "from __future__ import annotations". Only one parameter seems to be required in that case.
Related
Current Implementation which needs optimization
import subprocess
childprocess = subprocess.Popen(
['python',
'/full_path_to_directory/called_script.py',
'arg1',
'arg2'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
returnVal = childprocess.communicate()[0]
print(retVal)
Is this a correct way to call another script(called_script.py) within the current working directory?
Is there a better way to call the other script? I used import script but it gives me below error
called_script.py
def func(arg1, arg2, arg3):
#doSomething
#sys.out.write(returnVal)
if __name__ == "__main__":
func(arg1, arg2, arg3)
Implementation 2 (throws exception and errored out)
caller_script.py
Both of them are under the same path (i.e. /home/bin)
import called_script
returnVal = called_script.func(arg1,arg2,arg3)
print(returnVal)
Output:
nullNone
Traceback (most recent call last):
File "/path_to_caller/caller_script.py", line 89, in <module>
l.simple_bind_s(binddn, pw)
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 206, in simple_bind_s
msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 200, in simple_bind
return self._ldap_call(self._l.simple_bind,who,cred,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 96, in _ldap_call
result = func(*args,**kwargs)
TypeError: argument 2 must be string or read-only buffer, not None
Another alternative I used and gave me an error is
Implementation 3(throws exception and errors out)
caller_script.py
import ldap
returnVal = subprocess.call(['python','called_script.py','arg1','arg2'])
print(returnVal)
l = ldap.initialize(cp.get('some_config_ref','some_url'))
try:
l.protocol_version = ldap.VERSION3
l.simple_bind_s(binddn, returnVal)
except ldap.INVALID_CREDENTIALS:
sys.stderr.write("Your username or password is incorrect.")
sys.exit(1)
except ldap.LDAPError, e:
if type(e.message) == dict and e.message.has_key('xyz'):
sys.stderr.write(e.message['xyz'])
else:
sys.stderr.write(e)
sys.exit(1)
Output:
returnVal0Traceback (most recent call last):
File "./path_to_script/caller_script.py", line 88, in <module>
l.simple_bind_s(binddn, pw)
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 206, in simple_bind_s
msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 200, in simple_bind
return self._ldap_call(self._l.simple_bind,who,cred,EncodeControlTuples(serverctrls),EncodeControlTuples(clientctrls))
File "/usr/lib64/python2.6/site-packages/ldap/ldapobject.py", line 96, in _ldap_call
result = func(*args,**kwargs)
TypeError: argument 2 must be string or read-only buffer, not int
Here is an example where you are calling a function from another file, you pass one value, a list, which can have an arbitrary amount of numbers, and you get the sum. Make sure they are in the same directory or you will need the path. The function in your example "script.py" does not allow you to pass a value.
called_script.py
def add_many(list_add):
the_sum = sum(list_add)
return the_sum
caller_script.py
import called_script
a_list = [1, 2, 3, 4]
the_sum = called_script.add_many(a_list)
print(the_sum)
I coded a field(a) on classA which is to automatically take the contents of another field(b) in another classB
After updating my own developing on a module I tried to fill in a form on tryton, then I tried to save the form.
But there was an error
Traceback (most recent call last):
File "/trytond/wsgi.py", line 104, in dispatch_request
return endpoint(request, **request.view_args)
File "/trytond/protocols/dispatcher.py", line 48, in rpc
request, database_name, *request.rpc_params)
File "/trytond/wsgi.py", line 72, in auth_required
return wrapped(*args, **kwargs)
File "/trytond/protocols/wrappers.py", line 131, in wrapper
return func(request, pool, *args, **kwargs)
File "/trytond/protocols/dispatcher.py", line 197, in _dispatch
result = rpc.result(meth(*c_args, **c_kwargs))
File "/trytond/model/modelsql.py", line 832, in read
getter_results = field.get(ids, cls, field_list, values=result)
File "/trytond/model/fields/function.py", line 106, in get
return dict((name, call(name)) for name in names)
File "/trytond/model/fields/function.py", line 106, in <genexpr>
return dict((name, call(name)) for name in names)
File "/trytond/model/fields/function.py", line 101, in call
return dict((r.id, method(r, name)) for r in records)
File "/trytond/model/fields/function.py", line 101, in <genexpr>
return dict((r.id, method(r, name)) for r in records)
File "/trytond/modules/module_designing/design.py", line 15702, in On_change_design
('Description', '=', self.id),
ValueError: not enough values to unpack (expected 1, got 0)
, the method mentioned on the error is this : (this method I used it on my field(b) on a class B to call another field(a) on another class A)
def On_change_design(self,Name):
Design = Pool().get('design.classA')
design, = Design.search([
('classB', '=', self.id),
])
return design.id
field(b) = fields.Function(fields.Many2One('design.classA', 'test'), 'On_change_design')
the field(b) which will take the contain of the field(a)
this is how I was coded the field(a):
field(a) = fields.Function(fields.Char('area '),'on_change_parameters')
Any help will be appreciated, I want to know what's wrong and what I should do.
Or can anyone help me and tell how I can code the method onchange to make the field(b) take automatically the contents of another field(a) from another class(a)
Function fields are computed after you save. On your function you are performing a search into a related table and unpacking the result. This has no problem when the search returns a single record but in your case the search does not return any record so this makes the code crash.
You should use a safer code, that tests if the serach return any result before unpacking. Something like this:
def on_change_design(self,Name):
Design = Pool().get('design.classA')
designs = Design.search([
('classB', '=', self.id),
], limit=1)
if designs:
design, = designs
return design.id
return None
Note that I also added a limit on the search to ensure a maximum of one record is returned. This will also prevent to crash when multiple records are returned but you may want a diferent behaviour. I also added a explicit None return to make it clar that function will return None when no search is found.
Problem:
I'm parsing some config file and one field should be parsed to an Enum. Let's say there may be 3 possible values:
`foo`, `bar`, `baz`
And I want to parse them into the following enum's values:
class ConfigField(Enum):
FOO = 'foo'
BAR = 'bar'
BAZ = 'baz'
What have I tried so far:
I have written the following function to do that:
def parse_config_field(x: str) -> ConfigField:
for key, val in ConfigField.__members__.items():
if x == val.value:
return val
else:
raise ValueError('invalid ConfigField: ' + str(x))
But I think this is ugly and too complicated for something so simple and straightforward.
I also considered just lowercasing enum field's names:
def parse_config_field2(x: str) -> ConfigField:
value = ConfigField.__members__.get(x.lower(), None)
if value is None:
raise ValueError('invalid ConfigField: ' + str(x))
else:
return value
Which is slightly shorter, but still pretty ugly and what's worse, it creates direct dependence between config values (in some text config file) and ConfigField which I'm not comfortable with.
I just recently switched from python2 to python3 so I'm not 100% familiar with Enums and I'm hoping there may be a better way to do this.
Question:
Is there a better way to do this? I'm looking for simpler (and perhaps more "built-in" / "pythonic") solution than my parse_config_field - perhaps something like:
value = ConfigField.get_by_value(cfg_string)
I believe you are looking for accessing by value: ConfigField("foo"), which gives <ConfigField.FOO: 'foo'>.
A ValueError will be raised for parsing using not existing values:
>>> ConfigField("not foo")
ValueError: 'not foo' is not a valid ConfigField
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "C:\Program Files\Python38\lib\enum.py", line 309, in __call__
return cls.__new__(cls, value)
File "C:\Program Files\Python38\lib\enum.py", line 600, in __new__
raise exc
File "C:\Program Files\Python38\lib\enum.py", line 584, in __new__
result = cls._missing_(value)
File "C:\Program Files\Python38\lib\enum.py", line 613, in _missing_
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 'not foo' is not a valid ConfigField
For the purpose of a unit test, I made a class whose instance is an iterable that would yield a certain sequence and then raise an exception:
class Iter:
def __init__(self, seq):
self.seq = seq
self.pos = 0
def __next__(self):
if self.pos == len(self.seq):
raise Exception
value = self.seq[self.pos]
self.pos += 1
return value
def __iter__(self):
return self
so that:
for value in Iter((1, 2, 3)):
print(value)
would output:
1
2
3
Traceback (most recent call last):
File "test.py", line 25, in <module>
for value in mocked_iterable:
File "test.py", line 11, in __next__
raise Exception
Exception
But why reinvent the wheel when MagicMock already has a side_effect attribute that should do the same? Per the documentation, the side_effect attribute can be an iterable that yields either a value to be returned from the call to the mock, or an exception to raise, so it suits the purpose of mimicking the aforementioned class perfectly. I therefore created a MagicMock object and made its __iter__ method return the object itself, and made its __next__ method to have a side effect of the desired sequence and the exception:
from unittest.mock import MagicMock
mocked_iterable = MagicMock()
mocked_iterable.__iter__.return_value = mocked_iterable
mocked_iterable.__next__.side_effect = [1, 2, 3, Exception]
for value in mocked_iterable:
print(value)
However, this outputs:
...
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1005, in _mock_call
ret_val = effect(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1793, in __iter__
return iter(ret_val)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 944, in _mock_call
self.called = True
RecursionError: maximum recursion depth exceeded
But the question is, why is there any recursion?
I found that I can work around this "bug" by putting the self reference in __iter__'s side_effect attribute instead:
mocked_iterable = MagicMock()
mocked_iterable.__iter__.side_effect = [mocked_iterable]
mocked_iterable.__next__.side_effect = [1, 2, 3, Exception]
for value in mocked_iterable:
print(value)
This correctly outputs:
1
2
3
Traceback (most recent call last):
File "test.py", line 6, in <module>
for value in mocked_iterable:
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 939, in __call__
return _mock_self._mock_call(*args, **kwargs)
File "C:\Program Files (x86)\Python36-32\lib\unittest\mock.py", line 1000, in _mock_call
raise result
Exception
But is the recursion error indeed a bug, or a feature of mock with an unintended consequence?
I agree that this is indeed a bug. Although this is an edge case.
As we can see in the source code. mock module expects that iter(ret_val) will return the unchanged iterator if ret_val has already been an iterator.
Well, it actually does but still needs to call ret_val's __iter__ method.
I have a function that uses the input function as follows:
def get_valid_input(input_str: str, valid_options: tuple):
while True:
response = input(input_str)
if response in valid_options:
return response
raise InvalidOptionException('Invalid option')
I need to test that the exception InvalidOptionException, which simple extends Exception, is raised when a response is not a valid option. I have the following test:
class UtilTest(unittest.TestCase):
#patch('builtins.input', lambda: 'a')
def test_when_input_is_invalid_then_exception_is_raised(self):
self.assertRaises(
InvalidOptionException,
get_valid_input,
'', ('y', 'n')
)
But, when I ran the test, I am getting the following error:
Error
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py", line 1179, in patched
return func(*args, **keywargs)
File "/Users/user/mathsistor/chumme/test/test_util.py", line 13, in test_when_input_is_invalid_then_exception_is_raised
'', ('y', 'n')
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py", line 728, in assertRaises
return context.handle('assertRaises', args, kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py", line 177, in handle
callable_obj(*args, **kwargs)
File "/Users/user/mathsistor/chumme/util.py", line 14, in get_valid_input
response = input(input_str)
TypeError: <lambda>() takes 0 positional arguments but 1 was given
How can I solve this?
The input function takes one argument (the prompt string). To mock it you must use a function/lambda with one argument:
#patch('builtins.input', lambda _: 'a')
It's customary to use the underscore when you don't plan on using the argument.