Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Environment Variables Through Reflection

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?

like image 860
Brent Arias Avatar asked Feb 11 '26 17:02

Brent Arias


1 Answers

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 :-)

like image 100
Alexandr Shurigin Avatar answered Feb 14 '26 13:02

Alexandr Shurigin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!