In Python 3.7, I wrote an Environment helper class which allows me to load OS environment variables through reflection. It also converts the environment variable into a native bool or tuple, based on type hints declared in a derived class.
It works, but the linter in Visual Studio Code gives me this problem report:
Class
'Environment'has no'__annotations__'member
Here is the helper class.
import os
from abc import ABC
class Environment(ABC):
@classmethod
def from_os(cls):
convert = {
bool: lambda d: d == "1",
(): lambda d: tuple(d.split(',')),
str: lambda d: d
}
values = [
convert[value](os.environ[key])
for key,value in cls.__annotations__.items()
]
return cls(*values)
Again, the code actually works. What can I do to make Pylint happy? Perhaps there is a different way I can iterate over the attributes (and attribute type hints) in order to achieve the same result without using __annotations__?
I can make the linter stop complaining by cheating and adding a pylint suppression hint:
for key,value in cls.__annotations__.items() # pylint: disable=no-member
...but that is not the solution I'm looking for.
In case it helps, here is an example of how my helper class is used to fetch an arbitrary set of environment variables:
from dataclasses import dataclass
@dataclass
class FWMonitoringEnv(Environment):
# The names of these attributes are used
# to find and load a corresponding environment variable.
# This happens when "FWMonitoringEnv.from_os()" is called.
preempt : bool
split_routes : bool
tag_key : str
vpc_summary_route : str
route_table_id : str
fw_trust_enis : ()
fw_mgmt_ips : ()
api_key_name : str
region : str
Elsewhere in the code I simply execute this:
env = FWMonitoringEnv.from_os()
While I'm at it, there is another code hygiene issue I'd like to fix. Is there a way I can make my Environment class force the derived class to be a @dataclass? For example, could that be accomplished with Python 3.7 type hints?
You can use https://docs.python.org/3/library/inspect.html#inspect.signature method to get all class annotations.
Example
pprint(inspect.signature(cls).parameters)
mappingproxy(OrderedDict([('preempt', <Parameter "preempt: bool">),
('split_routes', <Parameter "split_routes: bool">),
('tag_key', <Parameter "tag_key: str">),
('vpc_summary_route',
<Parameter "vpc_summary_route: str">),
('route_table_id', <Parameter "route_table_id: str">),
('fw_trust_enis', <Parameter "fw_trust_enis: ()">),
('fw_mgmt_ips', <Parameter "fw_mgmt_ips: ()">),
('api_key_name', <Parameter "api_key_name: str">),
('region', <Parameter "region: str">)]))
When incorporated into your code, it then looks as follows:
def from_os(cls):
convert = {
bool: lambda d: d == '1',
(): lambda d: tuple(d.split(',')),
str: lambda d: d
}
values = [
convert[val.annotation](os.environ[val.name])
for val in signature(cls).parameters.values()
]
return cls(*values)
I believe it is a better way to get it "true" way :-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With