Socket
Socket
Sign inDemoInstall

ezenv

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ezenv

A more convenient interface to environment variables.


Maintainers
1

EZ-Environment

"For easy access, baby!  …That's right.'"—Eazy-E

Have you ever thought handling environment variables in Python should be easier? It is now.

TL;DR:

.. code:: python

>>> import env

>>> env.SERVER_PORT.int
8080

"BAM!" —Emeril Lagasse

The E.Z.E.nvironment module also has its own theme song <https://youtu.be/Igxl7YtS1vQ?t=1m08s>_:

*"We want Eazy!"*

| *EAZY!*
| *Everybody come on!*
| *EAZY!*
| *Who yall came to see?*
| *EAZY!*
| *A little louder come on!*
| *EAZY!*
| *Get those hands in the air!*
| *EAZY!*
| *Come on, come on say it!*
| *EAZY!*
| *A little louder come on!*
| *EAZY!*
| *Come on make some noise!*
|
| *A miracle of modern creation…*
| *EZ E's on the set, hyped up with the bass*
| *And a little bit of what ya love*
| *From a brother who's smooth like a criminal*
| *I mean subliminal…*

Background

It's always been a tad clumsy to access environment variables and combine them with other strings in Python—
compared to shell languages at least. For example, look how easy it is in (ba)sh:

.. code:: shell

⏵ echo "Libraries: $PWD/lib"
Libraries: /usr/local/lib

Unfortunately over in Python-land, required, escaped quotes and brackets serve mostly to complicate and add to visual clutter, reducing speed of comprehension.

.. code:: python

>>> from os import environ
>>> from os.path import join

>>> join(environ['PWD'], 'lib')
'/usr/local/lib'

Even the new-fangled string interpolation doesn't help as much as might be expected:

.. code:: python

>>> print(f'Libraries: {environ["PWD"]}/lib')
Libraries: /usr/local/lib

With that in mind, allow me to introduce the env module. With it I've tried to whittle complexity down, primarily through direct attribute access:

.. code:: python

>>> import env

>>> print('Term:', env.TERM)
Term: xterm-256color

>>> print(f'Libraries: {env.PWD}/lib')
Libraries: /usr/local/lib

But wait, there's more!

Install

.. code:: shell

⏵ pip3 install --user ezenv  # env was taken :-/

 ☛ LGPL licensed. ☚

Environment and options

On import the module loads the environment into its namespace, thereby working like a dictionary with convenient attribute access.

So, no additional mapping instance has to be created or imported, unless you'd like to configure the interface further. The following options are available to customize:

.. code:: python

>>> from env import Environment

>>> env = Environment(
        environ=os.environ,
        sensitive=True|False,  # case: platform default
        writable=False,
        pathsep=os.pathsep,
    )

Param: environ


A mapping of your own choosing may optionally be passed in as the first argument,
for testing and/or other purposes.
I've recently learned that
`os.environb <https://docs.python.org/3/library/os.html#os.environb>`_
(bytes interface) is a thing that could be passed,
for example.


.. ~ Noneify
.. ~ ~~~~~~~~~~~~

.. ~ Enabled by default,
.. ~ this one signals non-existent variables by returning None.
.. ~ It allows one to easily test for a variable and not have to worry about
.. ~ catching exceptions.
.. ~ If the variable is not set,
.. ~ None will be returned instead:

.. ~ .. code:: python

    .. ~ >>> if env.COLORTERM:   # is not None or ''
            .. ~ pass


.. ~ **Default Values**

.. ~ The one drawback to returning ``None`` is that there is no ``.get()`` method
.. ~ to return a default when the variable isn't found.
.. ~ That's easily rectified like so:

.. ~ .. code:: python

    .. ~ >>> env.FOO or 'bar'
    .. ~ 'bar'


.. ~ Blankify
.. ~ ~~~~~~~~~~~~

.. ~ Off by default,
.. ~ this option mimics the behavior of most command-line shells.
.. ~ Namely if the variable isn't found,
.. ~ it doesn't complain and returns an empty string instead.
.. ~ This can make some cases simpler,
.. ~ as fewer checks for errors are needed when checking contents::

    .. ~ if env.LANG.endswith('UTF-8'):
        .. ~ pass

.. ~ instead of the Noneify version::

    .. ~ if env.LANG and env.LANG.endswith('UTF-8'):
        .. ~ pass

.. ~ However,
.. ~ it may be a bug-magnet in the case the variable is misspelled,
.. ~ It is here if you need it for compatibility.
.. ~ Blankify takes precedence over Noneify if enabled.


Param: writable

By default the Environment object/module does not allow modification since writing is rarely needed. This default helps to remind us of that fact, though the object can be easily be changed to writable if need be by enabling this option.

Param: sensitivity 😢


Variables are case-sensitive by default on Unix,
*insensitive* under Windows.

While case sensitivity can be disabled to use variable names in mixed or
lower-case,
be aware that variables and dictionary methods are in the same namespace,
which could potentially be problematic if they are not divided by case.
For this reason, using variable names such as "keys" and "items"
are not a good idea while in insensitive mode.
*shrug*

Workaround: use "get item" / dictionary-style syntax if needed:

.. code:: python

    env['keys']  # :-/

.. ~ varname = 'COLORTERM'
.. ~ env[varname]


Entry Objects
----------------

While using ``env`` at the interactive prompt,
you may be surprised that a variable value is not a simple string but rather
an extended string-like object called an "Entry."
This is most evident at the prompt since it prints a "representation"
form by default:

.. code:: python

    >>> env.PWD                         # a.k.a. repr()
    Entry('PWD', '/usr/local')

The reason behind this custom object is so that the variables can offer
additional functionality,
such as parsing or conversion of the value to another type,
while not crashing on a non-existent attribute access.

No matter however,
as we've seen in the previous sections,
just about any operation renders the string value as normal.
Attributes ``.name`` and ``.value`` are also available for belt &
suspenders types:

.. code:: python

    >>> print(env.PWD)
    /usr/local

    >>> env.PWD.name, env.PWD.value, str(env.PWD)
    ('PWD', '/tmp', '/tmp')

Remember the ``env`` object/module is also a standard dictionary,
while entry values are also strings,
so full Python functionality is available:

.. code:: python

    >>> for key, value in env.items():  # it's a dict*
            print(key, value)

    # USER fred…

    >>> env.USER.title()                # it's a str*
    'Fred'

    >>> env.TERM.partition('-')         # tip: a safer split
    ('xterm', '-', '256color')

*  Sung to the tune, *"It's a Sin,"* by the Pet Shop Boys.


Conversions & Parsing
-----------------------

Another handy feature of Entry objects is convenient type conversion and
parsing of values from strings.
Additional properties for this functionality are available.
For example:

.. code:: python

    >>> env.PI.float
    3.1416

    >>> env.STATUS.int
    5150

    >>> env.DATA.from_json
    {'one': 1, 'two': 2, 'three': 3}


Truthy Values
~~~~~~~~~~~~~~~~~~~~

Variable entries may contain boolean-*like* string values,
such as ``0, 1, yes, no, true, false``, etc.
To interpret them in a case-insensitive manner use the ``.truthy`` property:

.. code:: python

    >>> env.QT_ACCESSIBILITY
    Entry('QT_ACCESSIBILITY', '1')

    >>> env.QT_ACCESSIBILITY.truthy
    True

    >>> env = Environment(writable=True)
    >>> env.QT_ACCESSIBILITY = '0'          # set to '0'

    >>> env.QT_ACCESSIBILITY.truthy
    False


Standard Boolean Tests
++++++++++++++++++++++++

As always, standard tests or ``bool()`` on the entry can be done to check a
string.
Remember, such a test checks merely if the string is empty or not,
and would also return ``True`` on ``'0'`` or ``'false'``.


Paths
~~~~~~~~

Environment vars often contain a list of filesystem paths.
To split such path strings on ``os.pathsep``\
`🔗 <https://docs.python.org/3/library/os.html#os.pathsep>`_,
with optional conversion to ``pathlib.Path``\
`🔗² <https://docs.python.org/3/library/pathlib.html>`_
objects,
use one or more of the following:

.. code:: python

    >>> env.XDG_DATA_DIRS.list
    ['/usr/local/share', '/usr/share', ...]  # strings

    >>> env.SSH_AUTH_SOCK.path
    Path('/run/user/1000/keyring/ssh')

    >>> env.XDG_DATA_DIRS.path_list
    [Path('/usr/local/share'), Path('/usr/share'), ...]

To split on a different character,
simply do the split/partition on the string manually.

.. ~ (There is a ._pathsep variable that can be set on each entry,
.. ~ but not particularly more convenient.)


Examples
---------------

There are generally three cases for environment variables:

**Variable exists, has value:**

.. code:: python

    >>> env.USER                            # exists, repr
    Entry('USER', 'fred')

    >>> env.USER + '_suffix'                # str ops
    'fred_suffix'

    >>> env.USER.title()                    # str ops II
    'Fred'

    >>> print(f'term: {env.TERM}')          # via interpolation
    term: xterm-256color

    >>> bool(env.USER)                      # check exists & not empty
    True

    >>> key_name = 'PI'
    >>> env[key_name]                       # getitem syntax
    '3.1416'

    >>> env.PI.float                        # type conversion
    3.1416

    >>> env.PORT.int or 9000                # type conv. w/ default
    5150

    >>> env.QT_ACCESSIBILITY.truthy         # 0/1/yes/no/true/false
    True

    >>> env.JSON_DATA.from_json.keys()
    ['one', 'three', 'two']

    >>> env.XDG_DATA_DIRS.list
    ['/usr/local/share', '/usr/share']


**Variable exists, but is blank:**

.. code:: python

    >>> 'EMPTY' in env                      # check existence
    True

    >>> env.EMPTY                           # exists but empty
    Entry('EMPTY', '')

    >>> bool(env.EMPTY)                     # check exists & not empty
    False

    >>> env.EMPTY or 'default'              # exists, blank w/ default
    'default'


**Variable doesn't exist:**

.. code:: python

    >>> 'NO_EXISTO' in env                  # check existence
    False

    >>> env.NO_EXISTO or 'default'          # DNE with default
    'default'

    >>> env.NO_EXISTO                       # Doesn't exist repr
    NullEntry('NO_EXISTO')

    >>> bool(env.NO_EXISTO)                 # check exists & not empty
    False

    >>> env.XDG_DATA_DIRz.list              # DNE fallback
    []

    for data_dir in env.XDG_DATA_DIR.list:
        # Don't need to worry if this exists or not,
        # if not, it will be skipped.
        pass



Compatibility
---------------

*"What's the frequency Kenneth?"*

This module attempts compatibility with KR's existing
`env <https://github.com/kennethreitz/env>`_
package by implementing its ``prefix`` and ``map`` functions:

.. code:: python

    >>> env.prefix('XDG_')  # from_prefix preferred
    {'config_dirs': '/etc/xdg/xdg-mate:/etc/xdg', ...}

    >>> env.map(username='USER')
    {'username': 'fred'}

The lowercase transform can be disabled by passing another false-like value
as the second argument to ``prefix().``

While the package above has the coveted ``env`` namespace on PyPI,
ezenv uses the same simple module name and provides an implementation of the
interface.


Tests
---------------

Can be run here:

.. code:: shell

    ⏵ python3 -m env -v

Though this module works under Python2,
several of the tests *don't*,
because Py2 does Unicode differently or
doesn't have the facilities available to handle them by default
(pathlib/f-string).
Haven't had the urge to work around that due to declining interest.

FYI, a reference to the original module object is kept at ``env._module``
just in case it is needed for some reason.


Testing *with* ezenv
~~~~~~~~~~~~~~~~~~~~~

When you've used ``ezenv`` in your project,
it is easy to create a custom environment to operate under:

.. code:: python

    from env import Environment

    def test_foo():
        import mymodule

        mymodule.env = Environment(environ=dict(NO_COLOR='1'))
        assert mymodule.color_is_disabled() == True


Pricing
---------------

*"I'd buy THAT for a dollar!" :-D*

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc