env

the values a project needs to build and ship, and the environment its commands run in

This module answers two questions every release step runs into: where a project’s tokens and settings are kept, and what environment a child command should run with.

An environment value is any named setting a build or a deploy needs, such as GITHUB_TOKEN or PORT. Some of them are secrets that should not sit in plain text. EnvStore keeps them through dockeasy, which puts secrets in the system keychain and every value in an env file (~/.config/fastops/.env unless you name another). dockeasy is imported the first time a value is read or written, not when this module loads.


source

EnvError

def EnvError(
    *args, **kwargs
):

Unspecified run-time error.

When pullup runs inside a frozen desktop app, one built with py2app or a similar tool, the app’s launcher sets variables such as PYTHONHOME that point Python at the copy bundled inside the app. A child process that inherits them starts the wrong interpreter. BUNDLE_ONLY lists those variables. EnvError is raised when a value cannot be stored, or when dockeasy, which does the storing, is not installed.


source

clean_env

def clean_env():

A copy of this process’s environment with the frozen-app interpreter variables removed.


source

strip_bundle

def strip_bundle(
    env, frozen:NoneType=None
):

Remove the frozen-app interpreter variables from env when running frozen, and return it.

strip_bundle removes the BUNDLE_ONLY variables from an environment mapping and sets PYTHONUTF8=1. It changes the mapping you pass and returns that same mapping. By default it only does this when the current process is frozen; pass frozen=True or frozen=False to decide yourself, as the example does. clean_env covers the common case: a copy of this process’s environment with the bundle variables always removed.

env = {'PYTHONHOME': '/Pullup.app/Contents/Resources', 'PATH': '/usr/bin', 'HOME': '/Users/me'}
strip_bundle(dict(env), frozen=True)
{'PATH': '/usr/bin', 'HOME': '/Users/me', 'PYTHONUTF8': '1'}

source

venv_env

def venv_env(
    python:NoneType=None, env:NoneType=None
):

A copy of env that runs commands in the virtual environment of python, or unchanged when python is None.

venv_env returns an environment that runs commands inside a particular virtual environment, much as source .venv/bin/activate would. Pass the path of that environment’s Python. It copies the mapping, sets VIRTUAL_ENV, puts the interpreter’s directory first on PATH, and drops PYTHONHOME and UV_PROJECT_ENVIRONMENT, either of which would send commands to a different interpreter. With no python the mapping comes back as a copy, passed only through strip_bundle.

e = venv_env('/repo/.venv/bin/python', env={'PATH': '/usr/bin', 'PYTHONHOME': '/frozen'})
e['VIRTUAL_ENV'], e['PATH'], 'PYTHONHOME' in e
('/repo/.venv', '/repo/.venv/bin:/usr/bin', False)

source

needs_extra

def needs_extra(
    what
):

An error message for what that ends with the command that installs the missing library.


source

extra

def extra():

The package name that missing-library errors tell the reader to install.


source

use_extra

def use_extra(
    name
):

Set the package name that missing-library errors tell the reader to install.

Some of pullup’s features need another library, and the error raised when it is missing ends with the command that installs it. use_extra changes the package named in that command. A host application that bundles pullup calls it so that the message names the host’s own package. needs_extra builds the message and extra returns the name in use.

_dockeasy imports the four dockeasy functions the store uses, or raises EnvError with the install hint. Some dockeasy versions return coroutines from these functions, so _call runs whatever comes back to completion. It works whether or not an event loop is already running, which is the case inside Jupyter, and it re-raises any exception the call raised.

async def double(x): return x*2
_call(double, 21), _call(lambda x: x*2, 21)
(42, 42)

source

EnvStore

def EnvStore(
    service:str='fastops', path:NoneType=None
):

Read and write one project’s tokens and settings through dockeasy’s keychain and env file.

EnvStore reads and writes the values for one project.

store = EnvStore(path=envfile)
store.set('PULLUP_DEMO_REGISTRY', 'ghcr.io/example', secret=False)
store.get('PULLUP_DEMO_REGISTRY', secret=False)
'ghcr.io/example'

The examples use secret=False, so nothing touches the keychain.

store.values(['PULLUP_DEMO_REGISTRY', 'PULLUP_DEMO_TAG'], secret=False)
{'PULLUP_DEMO_REGISTRY': 'ghcr.io/example'}
store.unset('PULLUP_DEMO_REGISTRY')
{'key': 'PULLUP_DEMO_REGISTRY', 'removed': ['env file']}

source

env_value

def env_value(
    store, key, default:str='', secret:bool=False
):

One value from an environment store, or default when it is unset or the store raises.

env_value is the forgiving read. It returns default when the key is unset and also when the store raises, for example because the keychain is locked. Unlike EnvStore.get, it reads ordinary values unless you pass secret=True. Use it wherever a missing value has a sensible fallback.

env_value(store, 'PULLUP_DEMO_REGISTRY', 'ghcr.io/fallback')
'ghcr.io/fallback'