{"id":13415613,"url":"https://github.com/sloria/environs","last_synced_at":"2025-05-12T15:33:03.061Z","repository":{"id":8162043,"uuid":"57014273","full_name":"sloria/environs","owner":"sloria","description":"simplified environment variable parsing","archived":false,"fork":false,"pushed_at":"2025-05-06T21:08:03.000Z","size":495,"stargazers_count":1289,"open_issues_count":9,"forks_count":93,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-05-06T22:21:40.919Z","etag":null,"topics":["configuration","django","environment-variables","flask","marshmallow","python","twelve-factor"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sloria.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-04-25T05:34:37.000Z","updated_at":"2025-05-06T21:08:06.000Z","dependencies_parsed_at":"2023-01-13T14:39:23.319Z","dependency_job_id":"5cd67ba3-1419-4e42-80cf-cc9bcc30ff12","html_url":"https://github.com/sloria/environs","commit_stats":{"total_commits":384,"total_committers":30,"mean_commits":12.8,"dds":0.3828125,"last_synced_commit":"ddd66d322d8f21b8081c3ff98ae333cf667ed392"},"previous_names":[],"tags_count":54,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloria%2Fenvirons","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloria%2Fenvirons/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloria%2Fenvirons/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sloria%2Fenvirons/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sloria","download_url":"https://codeload.github.com/sloria/environs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253765982,"owners_count":21960825,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","django","environment-variables","flask","marshmallow","python","twelve-factor"],"created_at":"2024-07-30T21:00:50.757Z","updated_at":"2025-05-12T15:33:03.038Z","avatar_url":"https://github.com/sloria.png","language":"Python","readme":"# environs: simplified environment variable parsing\n\n[![Latest version](https://badgen.net/pypi/v/environs)](https://pypi.org/project/environs/)\n[![Build Status](https://github.com/sloria/environs/actions/workflows/build-release.yml/badge.svg)](https://github.com/sloria/environs/actions/workflows/build-release.yml)\n\n**environs** is a Python library for parsing environment variables.\nIt allows you to store configuration separate from your code, as per\n[The Twelve-Factor App](https://12factor.net/config) methodology.\n\n## Contents\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n\n- [Features](#features)\n- [Install](#install)\n- [Basic usage](#basic-usage)\n- [Supported types](#supported-types)\n- [Reading `.env` files](#reading-env-files)\n  - [Reading a specific file](#reading-a-specific-file)\n- [Handling prefixes](#handling-prefixes)\n- [Variable expansion](#variable-expansion)\n- [Validation](#validation)\n- [Deferred validation](#deferred-validation)\n- [URL schemes](#url-schemes)\n- [Serialization](#serialization)\n- [Defining custom parser behavior](#defining-custom-parser-behavior)\n- [Usage with Flask](#usage-with-flask)\n- [Usage with Django](#usage-with-django)\n- [Why\\...?](#why%5C)\n  - [Why envvars?](#why-envvars)\n  - [Why not `os.environ`?](#why-not-osenviron)\n  - [Why another library?](#why-another-library)\n- [License](#license)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Features\n\n- Type-casting\n- Read `.env` files into `os.environ` (useful for local development)\n- Validation\n- Define custom parser behavior\n- Framework-agnostic, but integrates well with [Flask](#usage-with-flask) and [Django](#usage-with-django)\n\n## Install\n\n    pip install environs\n\n## Basic usage\n\nWith some environment variables set...\n\n```bash\nexport GITHUB_USER=sloria\nexport MAX_CONNECTIONS=100\nexport SHIP_DATE='1984-06-25'\nexport TTL=42\nexport ENABLE_LOGIN=true\nexport GITHUB_REPOS=webargs,konch,ped\nexport GITHUB_REPO_PRIORITY=\"webargs=2,konch=3\"\nexport COORDINATES=23.3,50.0\nexport LOG_LEVEL=DEBUG\n```\n\nParse them with environs...\n\n```python\nfrom environs import env\n\nenv.read_env()  # read .env file, if it exists\n# required variables\ngh_user = env(\"GITHUB_USER\")  # =\u003e 'sloria'\nsecret = env(\"SECRET\")  # =\u003e raises error if not set\n\n# casting\nmax_connections = env.int(\"MAX_CONNECTIONS\")  # =\u003e 100\nship_date = env.date(\"SHIP_DATE\")  # =\u003e datetime.date(1984, 6, 25)\nttl = env.timedelta(\"TTL\")  # =\u003e datetime.timedelta(seconds=42)\nlog_level = env.log_level(\"LOG_LEVEL\")  # =\u003e logging.DEBUG\n\n# providing a default value\nenable_login = env.bool(\"ENABLE_LOGIN\", False)  # =\u003e True\nenable_feature_x = env.bool(\"ENABLE_FEATURE_X\", False)  # =\u003e False\n\n# parsing lists\ngh_repos = env.list(\"GITHUB_REPOS\")  # =\u003e ['webargs', 'konch', 'ped']\ncoords = env.list(\"COORDINATES\", subcast=float)  # =\u003e [23.3, 50.0]\n\n# parsing dicts\ngh_repos_priorities = env.dict(\n    \"GITHUB_REPO_PRIORITY\", subcast_values=int\n)  # =\u003e {'webargs': 2, 'konch': 3}\n```\n\n## Supported types\n\nThe following are all type-casting methods of `Env`:\n\n- `env.str`\n- `env.bool`\n- `env.int`\n- `env.float`\n- `env.decimal`\n- `env.list` (accepts optional `subcast` and `delimiter` keyword arguments)\n- `env.dict` (accepts optional `subcast_keys`, `subcast_values` and `delimiter` keyword arguments)\n- `env.json`\n- `env.datetime`\n- `env.date`\n- `env.time`\n- `env.timedelta` (assumes value is an integer in seconds, or an ordered duration string like `7h7s` or `7w 7d 7h 7m 7s 7ms 7us`)\n- `env.url`\n  - This returns a `urllib.parse.ParseResult` and therefore expects a `ParseResult` for its default.\n\n```python\nfrom urllib.parse import urlparse\n\nfrom environs import env\n\nMY_API_URL = env.url(\n    \"MY_API_URL\",\n    default=urlparse(\"http://api.example.com\"),\n)\n```\n\nIf you want the return value to be a string, use `env.str` with `validate.URL` instead.\n\n```python\nfrom environs import env, validate\n\nMY_API_URL = env.str(\n    \"MY_API_URL\",\n    default=\"http://api.example.com\",\n    validate=validate.URL(),\n)\n```\n\n- `env.uuid`\n- `env.log_level`\n- `env.path` (casts to a [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html))\n- `env.enum` (casts to any given enum type specified in `enum` keyword argument)\n  - Pass `by_value=True` to parse and validate by the Enum's values.\n\n## Reading `.env` files\n\n```bash\n# .env\nDEBUG=true\nPORT=4567\n```\n\nCall `Env.read_env` before parsing variables.\n\n```python\nfrom environs import env\n\n# Read .env into os.environ\nenv.read_env()\n\nenv.bool(\"DEBUG\")  # =\u003e True\nenv.int(\"PORT\")  # =\u003e 4567\n```\n\n### Reading a specific file\n\nBy default, `Env.read_env` will look for a `.env` file in current\ndirectory and (if no .env exists in the CWD) recurse\nupwards until a `.env` file is found.\n\nYou can also read a specific file:\n\n```python\nfrom environs import env\n\nwith open(\".env.test\", \"w\") as fobj:\n    fobj.write(\"A=foo\\n\")\n    fobj.write(\"B=123\\n\")\n\nenv.read_env(\".env.test\", recurse=False)\n\nassert env(\"A\") == \"foo\"\nassert env.int(\"B\") == 123\n```\n\n## Handling prefixes\n\nPass `prefix` to the constructor if all your environment variables have the same prefix.\n\n```python\nfrom environs import Env\n\n# export MYAPP_HOST=lolcathost\n# export MYAPP_PORT=3000\n\n\nenv = Env(prefix=\"MYAPP_\")\n\nhost = env(\"HOST\", \"localhost\")  # =\u003e 'lolcathost'\nport = env.int(\"PORT\", 5000)  # =\u003e 3000\n```\n\nAlternatively, you can use the `prefixed` context manager.\n\n```python\nfrom environs import env\n\n# export MYAPP_HOST=lolcathost\n# export MYAPP_PORT=3000\n\nwith env.prefixed(\"MYAPP_\"):\n    host = env(\"HOST\", \"localhost\")  # =\u003e 'lolcathost'\n    port = env.int(\"PORT\", 5000)  # =\u003e 3000\n\n# nested prefixes are also supported:\n\n# export MYAPP_DB_HOST=lolcathost\n# export MYAPP_DB_PORT=10101\n\nwith env.prefixed(\"MYAPP_\"):\n    with env.prefixed(\"DB_\"):\n        db_host = env(\"HOST\", \"lolcathost\")\n        db_port = env.int(\"PORT\", 10101)\n```\n\n## Variable expansion\n\n```python\n# export CONNECTION_URL=https://${USER:-sloria}:${PASSWORD}@${HOST:-localhost}/\n# export PASSWORD=secret\n# export YEAR=${CURRENT_YEAR:-2020}\n\nfrom environs import Env\n\nenv = Env(expand_vars=True)\n\nconnection_url = env(\"CONNECTION_URL\")  # =\u003e'https://sloria:secret@localhost'\nyear = env.int(\"YEAR\")  # =\u003e2020\n```\n\n## Validation\n\n```python\n# export TTL=-2\n# export NODE_ENV='invalid'\n# export EMAIL='^_^'\n\nfrom environs import env, validate, ValidationError\n\n\n# built-in validators (provided by marshmallow)\nenv.str(\n    \"NODE_ENV\",\n    validate=validate.OneOf(\n        [\"production\", \"development\"], error=\"NODE_ENV must be one of: {choices}\"\n    ),\n)\n# =\u003e Environment variable \"NODE_ENV\" invalid: ['NODE_ENV must be one of: production, development']\n\n# multiple validators\nenv.str(\"EMAIL\", validate=[validate.Length(min=4), validate.Email()])\n# =\u003e Environment variable \"EMAIL\" invalid: ['Shorter than minimum length 4.', 'Not a valid email address.']\n\n\n# custom validator\ndef validator(n):\n    if n \u003c= 0:\n        raise ValidationError(\"Invalid value.\")\n\n\nenv.int(\"TTL\", validate=validator)\n# =\u003e Environment variable \"TTL\" invalid: ['Invalid value.']\n```\n\n`environs.validate` is equivalent to [`marshmallow.validate`](https://marshmallow.readthedocs.io/en/stable/marshmallow.validate.html), so you can use any of the validators provided by that module.\n\n## Deferred validation\n\nBy default, a validation error is raised immediately upon calling a parser method for an invalid environment variable.\nTo defer validation and raise an exception with the combined error messages for all invalid variables, pass `eager=False` to `Env`.\nCall `env.seal()` after all variables have been parsed.\n\n```python\n# export TTL=-2\n# export NODE_ENV='invalid'\n# export EMAIL='^_^'\n\nfrom environs import Env\nfrom marshmallow.validate import OneOf, Email, Length, Range\n\nenv = Env(eager=False)\n\nTTL = env.int(\"TTL\", validate=Range(min=0, max=100))\nNODE_ENV = env.str(\n    \"NODE_ENV\",\n    validate=OneOf(\n        [\"production\", \"development\"], error=\"NODE_ENV must be one of: {choices}\"\n    ),\n)\nEMAIL = env.str(\"EMAIL\", validate=[Length(min=4), Email()])\n\nenv.seal()\n# environs.EnvValidationError: Environment variables invalid: {'TTL': ['Must be greater than or equal to 0 and less than or equal to 100.'], 'NODE_ENV': ['NODE_ENV must be one of: production, development'], 'EMAIL': ['Shorter than minimum length 4.', 'Not a valid email address.']}\n```\n\n`env.seal()` validates all parsed variables and prevents further parsing (calling a parser method will raise an error).\n\n## URL schemes\n\n`env.url()` supports non-standard URL schemes via the `schemes` argument.\n\n```python\nfrom urllib.parse import urlparse\n\nREDIS_URL = env.url(\n    \"REDIS_URL\", urlparse(\"redis://redis:6379\"), schemes=[\"redis\"], require_tld=False\n)\n```\n\n## Serialization\n\n```python\n# serialize to a dictionary of simple types (numbers and strings)\nenv.dump()\n# {'COORDINATES': [23.3, 50.0],\n# 'ENABLE_FEATURE_X': False,\n# 'ENABLE_LOGIN': True,\n# 'GITHUB_REPOS': ['webargs', 'konch', 'ped'],\n# 'GITHUB_USER': 'sloria',\n# 'MAX_CONNECTIONS': 100,\n# 'MYAPP_HOST': 'lolcathost',\n# 'MYAPP_PORT': 3000,\n# 'SHIP_DATE': '1984-06-25',\n# 'TTL': 42}\n```\n\n## Defining custom parser behavior\n\n```python\n# export DOMAIN='http://myapp.com'\n# export COLOR=invalid\n\nfrom furl import furl\n\n\n# Register a new parser method for paths\n@env.parser_for(\"furl\")\ndef furl_parser(value):\n    return furl(value)\n\n\ndomain = env.furl(\"DOMAIN\")  # =\u003e furl('https://myapp.com')\n\n\n# Custom parsers can take extra keyword arguments\n@env.parser_for(\"choice\")\ndef choice_parser(value, choices):\n    if value not in choices:\n        raise environs.EnvError(\"Invalid!\")\n    return value\n\n\ncolor = env.choice(\"COLOR\", choices=[\"black\"])  # =\u003e raises EnvError\n```\n\n## Usage with Flask\n\n```python\n# myapp/settings.py\n\nfrom environs import Env\n\nenv = Env()\nenv.read_env()\n\n# Override in .env for local development\nDEBUG = env.bool(\"FLASK_DEBUG\", default=False)\n# SECRET_KEY is required\nSECRET_KEY = env.str(\"SECRET_KEY\")\n```\n\nLoad the configuration after you initialize your app.\n\n```python\n# myapp/app.py\n\nfrom flask import Flask\n\napp = Flask(__name__)\napp.config.from_object(\"myapp.settings\")\n```\n\nFor local development, use a `.env` file to override the default\nconfiguration.\n\n```bash\n# .env\nDEBUG=true\nSECRET_KEY=\"not so secret\"\n```\n\nNote: Because environs depends on [python-dotenv](https://github.com/theskumar/python-dotenv),\nthe `flask` CLI will automatically read .env and .flaskenv files.\n\n## Usage with Django\n\nenvirons includes a number of helpers for parsing connection URLs. To\ninstall environs with django support:\n\n    pip install environs[django]\n\nUse `env.dj_db_url`, `env.dj_cache_url` and `env.dj_email_url` to parse the `DATABASE_URL`, `CACHE_URL`\nand `EMAIL_URL` environment variables, respectively.\n\nFor more details on URL patterns, see the following projects that environs is using for converting URLs.\n\n- [dj-database-url](https://github.com/jacobian/dj-database-url)\n- [django-cache-url](https://github.com/epicserve/django-cache-url)\n- [dj-email-url](https://github.com/migonzalvar/dj-email-url)\n\nBasic example:\n\n```python\n# myproject/settings.py\nfrom environs import Env\n\nenv = Env()\nenv.read_env()\n\n# Override in .env for local development\nDEBUG = env.bool(\"DEBUG\", default=False)\n# SECRET_KEY is required\nSECRET_KEY = env.str(\"SECRET_KEY\")\n\n# Parse database URLs, e.g.  \"postgres://localhost:5432/mydb\"\nDATABASES = {\"default\": env.dj_db_url(\"DATABASE_URL\")}\n\n# Parse email URLs, e.g. \"smtp://\"\nemail = env.dj_email_url(\"EMAIL_URL\", default=\"smtp://\")\nEMAIL_HOST = email[\"EMAIL_HOST\"]\nEMAIL_PORT = email[\"EMAIL_PORT\"]\nEMAIL_HOST_PASSWORD = email[\"EMAIL_HOST_PASSWORD\"]\nEMAIL_HOST_USER = email[\"EMAIL_HOST_USER\"]\nEMAIL_USE_TLS = email[\"EMAIL_USE_TLS\"]\n\n# Parse cache URLS, e.g \"redis://localhost:6379/0\"\nCACHES = {\"default\": env.dj_cache_url(\"CACHE_URL\")}\n```\n\nFor local development, use a `.env` file to override the default\nconfiguration.\n\n```bash\n# .env\nDEBUG=true\nSECRET_KEY=\"not so secret\"\n```\n\nFor a more complete example, see\n[django_example.py](https://github.com/sloria/environs/blob/master/examples/django_example.py)\nin the `examples/` directory.\n\n## Why\\...?\n\n### Why envvars?\n\nSee [The 12-factor App](http://12factor.net/config) section on\n[configuration](http://12factor.net/config).\n\n### Why not `os.environ`?\n\nWhile `os.environ` is enough for simple use cases, a typical application\nwill need a way to manipulate and validate raw environment variables.\nenvirons abstracts common tasks for handling environment variables.\n\nenvirons will help you\n\n- cast envvars to the correct type\n- specify required envvars\n- define default values\n- validate envvars\n- parse list and dict values\n- parse dates, datetimes, and timedeltas\n- parse expanded variables\n- serialize your configuration to JSON, YAML, etc.\n\n### Why another library?\n\nThere are many great Python libraries for parsing environment variables.\nIn fact, most of the credit for environs\\' public API goes to the\nauthors of [envparse](https://github.com/rconradharris/envparse) and\n[django-environ](https://github.com/joke2k/django-environ).\n\nenvirons aims to meet three additional goals:\n\n1.  Make it easy to extend parsing behavior and develop plugins.\n2.  Leverage the deserialization and validation functionality provided\n    by a separate library (marshmallow).\n3.  Clean up redundant API.\n\nSee [this GitHub\nissue](https://github.com/rconradharris/envparse/issues/12#issue-151036722)\nwhich details specific differences with envparse.\n\n## License\n\nMIT licensed. See the\n[LICENSE](https://github.com/sloria/environs/blob/master/LICENSE) file\nfor more details.\n","funding_links":[],"categories":["Third-Party Packages","Release Features","Python"],"sub_categories":["Configuration"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsloria%2Fenvirons","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsloria%2Fenvirons","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsloria%2Fenvirons/lists"}