Full Code of willkg/everett for AI

main 7cf9c37bced7 cached
58 files
245.0 KB
58.6k tokens
299 symbols
1 requests
Download .txt
Showing preview only (261K chars total). Download the full file or copy to clipboard to get everything.
Repository: willkg/everett
Branch: main
Commit: 7cf9c37bced7
Files: 58
Total size: 245.0 KB

Directory structure:
gitextract_fnd3ql0f/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .readthedocs.yaml
├── CODEOWNERS
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── api.rst
│   ├── components.rst
│   ├── conf.py
│   ├── configmanager.rst
│   ├── configuration.rst
│   ├── dev.rst
│   ├── documenting.rst
│   ├── environments.rst
│   ├── history.rst
│   ├── index.rst
│   ├── parsers.rst
│   ├── recipes.rst
│   ├── test_code.py
│   └── testing.rst
├── examples/
│   ├── component_appconfig.py
│   ├── componentapp.py
│   ├── components_subclass.py
│   ├── environments.py
│   ├── handling_exceptions.py
│   ├── msg_builder.py
│   ├── myserver.py
│   ├── myserver_with_environments.py
│   ├── namespaces.py
│   ├── namespaces2.py
│   ├── parser_examples.py
│   ├── recipes_alternate_keys.py
│   ├── recipes_appconfig.py
│   ├── recipes_djangosettings.py
│   ├── recipes_shared.py
│   └── testdebug.py
├── justfile
├── pyproject.toml
├── src/
│   └── everett/
│       ├── __init__.py
│       ├── ext/
│       │   ├── __init__.py
│       │   ├── inifile.py
│       │   └── yamlfile.py
│       ├── manager.py
│       └── sphinxext.py
└── tests/
    ├── basic_component_config.py
    ├── basic_module_config.py
    ├── conftest.py
    ├── data/
    │   ├── config_test.ini
    │   └── config_test_original.ini
    ├── ext/
    │   ├── test_inifile.py
    │   └── test_yamlfile.py
    ├── simple_module_config.py
    ├── test_manager.py
    └── test_sphinxext.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
---
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"
    rebase-strategy: "disabled"


================================================
FILE: .github/workflows/main.yml
================================================
---
name: CI

on:
  push:
    branches:
      - 'main'
  pull_request:
    branches:
      - 'main'

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']

    name: Python ${{ matrix.python-version}}
    steps:
      - uses: actions/checkout@v5.0.0

      - name: Set up Python
        uses: actions/setup-python@v6.0.0
        with:
          python-version: ${{ matrix.python-version }}

      - name: Update pip and install dev requirements
        run: |
          python -m pip install --upgrade pip
          pip install '.[dev]'

      - name: Test
        run: tox


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
uv.lock

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx-generated things
docs/_build/

# PyBuilder
target/
.vscode/


================================================
FILE: .readthedocs.yaml
================================================
---
# Read the Docs configuration file

# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the version of Python and other tools you might need
build:
  os: ubuntu-24.04
  tools:
    python: "3.10"

# Build documentation in the docs/ directory with Sphinx
sphinx:
  configuration: docs/conf.py

# Install dev requirements so that the documentation can build correctly
python:
  install:
    - method: pip
      path: .
      extra_requirements:
        - dev
        - ini
        - sphinx
        - yaml


================================================
FILE: CODEOWNERS
================================================
* @willkg


================================================
FILE: HISTORY.rst
================================================
History
=======

3.5.0 (October 15th, 2025)
--------------------------

Backwards incompatibel changes:

* Drop support for Python 3.9. (#282)

* Deprecate Everett.

  I encourage you to switch from Everett to pydantic-settings.
  See https://github.com/willkg/everett/issues/278

Fixes and features:

* Add support for Python 3.14. (#283)


3.4.0 (October 30th, 2024)
--------------------------

Backwards incompatible changes:

* Drop support for Python 3.8. Thanks, Rob!

Fixes and features:

* Add support for Python 3.13. (#260) Thanks, Rob!

* Add support for underscore as first character in variable names in env files.
  (#263)

* Add ``ChoiceOf`` parser for enforcing configuration values belong in
  specified value domain. (#253)

* Fix ``autocomponentconfig`` to support components with no options. (#244)

* Add ``allow_empty`` option to ``ListOf`` parser that lets you specify whether
  empty strings are a configuration error or not. (#268)


3.3.0 (November 6th, 2023)
--------------------------

Backwards incompatible changes:

* Drop support for Python 3.7. (#220)

Fixes and features:

* Add support for Python 3.12 (#221)

* Fix env file parsing in regards to quotes. (#230)


3.2.0 (March 21st, 2023)
------------------------

Fixes and features:

* Implement ``default_if_empty`` argument which will return the default value
  (if specified) if the value is the empty string. (#205)

* Implement ``parse_time_period`` parser for converting time periods like "10m4s"
  into the total number of seconds that represents.

  ::

      >>> from everett.manager import parse_time_period
      >>> parse_time_period("4m")
      240

  (#203)

* Implement ``parse_data_size`` parser for converting values like "40gb" into
  the total number of bytes that represents.

  ::

      >>> from everett.manager import parse_data_size
      >>> parse_time_period("40gb")
      40000000000

  (#204)

* Fix an ``UnboundLocalError`` when using ``automoduleconfig`` and providing a
  Python dotted path to a thing that either kicks up an ``ImportError`` or
  doesn't exist. Now it raises a more helpful error. (#201)


3.1.0 (October 26th, 2022)
--------------------------

Fixes and features:

* Add support for Python 3.11. (#187)

* Add ``raise_configuration_error`` method on ``ConfigManager``. (#185)

* Improve ``automoduleconfig`` to walk the whole AST and document configuration
  set by assign::

      SOMEVAR = _config("somevar")

  and dict::
     
      SOMEGROUP = {
          "SOMEVAR": _config("somevar"),
      }

  (#184)

* Fix options not showing up on ReadTheDocs. (#186)


3.0.0 (January 13th, 2022)
--------------------------

Backwards incompatible changes:

* Dropped support for Python 3.6. (#176)

* Dropped ``autocomponent`` Sphinx directive in favor of
  ``autocomponentconfig``.

Fixes and features:

* Add support for Python 3.10. (#173)

* Rework namespaces so that you can apply a namespace (``with_namespace()``)
  after binding a component (``with_options()``) (#175)

* Overhauled, simplified, and improved documentation. Files with example output
  are now generated using `cog <https://pypi.org/project/cogapp/>`_.

* Rewrite Sphinx extension.

  This now supports manually documenting configuration using
  ``everett:component`` and ``everett:option`` directives.

  This adds ``:everett:component:`` and ``:everett:option:`` roles for linking
  to specific configuration in the docs.

  It also addsh ``autocomponentconfig`` and ``automoduleconfig`` directives for
  automatically generating documentation.

  When using these directives, items are added to the index and everything is
  linkable making it easier to find and talk to users about specific
  configuration items. (#172)


2.0.1 (August, 23rd, 2021)
--------------------------

Fixes:

* Fix Sphinx warning about roles in Everett sphinxext. (#165)

* Fix ``get_runtime_config`` to work with slots (#166)


2.0.0 (July 27th, 2021)
-----------------------

Backwards incompatible changes:

* This radically reduces the boilerplate required to define components. It also
  improves the connections between things so it's easier to:

  * determine the configuration required for a single component (taking into
    account superclasses, overriding, etc)
  * determine the runtime configuration for a component tree given a
    configuration manager

  Previously, components needed to subclass RequiredConfigMixin and provide a
  "required_config" class attribute. Something like this::

      from everett.component import RequiredConfigMixin, ConfigOptions

      class SomeClass(RequiredConfigMixin):
          required_config = ConfigOptions()
          required_config.add_option(
              "some_option",
              default="42",
          )

  That's been slimmed down and now looks like this::

      from everett.manager import Option

      class SomeClass:
          class Config:
              some_option = Option(default="42")

  That's much simpler and the underlying implementation code is less tangled
  and complex, too.

  If you used ``everett.component.RequiredConfigMixin`` or
  ``everett.component.ConfigOptions``, you'll need to update your classes.

  If you didn't use those things, then you don't have to make any changes.

  See the documentation on components for how it all works now.

* Changed the way configuration variables are referred to in configuration
  error messages. Previously, I tried to use a general way "namespace=something
  key=somethingelse" but that's confusing and won't match up with project
  documentation.

  I changed it to the convention used in the process environment and
  env files. For example, ``FOO_BAR``.

  If you use INI or YAML for configuration, you can specify a ``msg_builder``
  argument when you build the ``ConfigManager`` and build error messages
  tailored to your users.

Fixes:

* Switch to ``src/`` repository layout.

* Added type annotations and type checking during CI. (#155)

* Standardized on f-strings across the codebase.

* Switched Sphinx theme.

* Update of documentation, fleshed out and simplified examples, cleaned up
  language, reworked structure of API section (previously called Library or
  some unhelpful thing like that), etc.


1.0.3 (October 28th, 2020)
--------------------------

Backwards incompatible changes:

* Dropped support for Python 3.4. (#96)

* Dropped support for Python 3.5. (#116)

Fixes:

* Add support for Python 3.7. (#68)

* Add support for Python 3.8. (#102)

* Add support for Python 3.9. (#117)

* Reformatted code with Black, added Makefile, switched to GitHub Actions.

* Fix ``get_runtime_config()`` to infer namespaces. (#118)

* Fix ``RemovedInSphinx50Warning``. (#115)

* Documentation fixes and clarifications.


1.0.2 (February 22nd, 2019)
---------------------------

Fixes:

* Improve documentation.

* Fix problems when there are nested ``BoundConfigs``. Now they work
  correctly. (#90)

* Add "meta" to options letting you declare additional data on the option
  when you're adding it.

  For example, this lets you do things like mark options as "secrets"
  so that you know which ones to ``******`` out when logging your
  configuration. (#88)


1.0.1 (January 8th, 2019)
-------------------------

Fixes:

* Fix documentation issues.

* Package missing ``everett.ext``. Thank you, dsblank! (#84)


1.0.0 (January 7th, 2019)
-------------------------

Backwards incompatible changes:

* Dropped support for Python 2.7. Everett no longer supports Python 2. (#73)

* Dropped support for Python 3.3 and added support for Python 3.7. Thank you,
  pjz! (#68)

* Moved ``ConfigIniEnv`` to a different module. Now you need to import it
  like this::

      from everett.ext.inifile import ConfigIniEnv

  (#79)

Features:

* Everett now logs configuration discovery in the ``everett`` logger at the
  ``logging.DEBUG`` level. This is helpful for trouble-shooting some kinds of
  issues. (#74)

* Everett now has a YAML configuration environment. In order to use it, you
  need to install its requirements::

      $ pip install everett[yaml]

  Then you can import it like this::

      from everett.ext.yamlfile import ConfigYamlEnv

  (#72)

Fixes:

* Everett no longer requires ``configobj``--it's now optional. If you use
  ``ConfigIniEnv``, you can install it with::

      $ pip install everett[ini]

  (#79)

* Fixed list parsing and file discovery in ConfigIniEnv so they match the
  docs and are more consistent with other envs. Thank you, apollo13! (#71)

* Added a ``.basic_config()`` for fast opinionated setup that uses the
  process environment and a ``.env`` file in the current working directory.

* Switching to semver.


0.9 (April 7th, 2017)
---------------------

Changed:

* Rewrite Sphinx extension. The extension is now in the ``everett.sphinxext``
  module and the directive is now ``.. autocomponent::``. It generates better
  documentation and it now indexes Everett components and options.

  This is backwards-incompatible. You will need to update your Sphinx
  configuration and documentation.

* Changed the ``HISTORY.rst`` structure.

* Changed the repr for ``everett.NO_VALUE`` to ``"NO_VALUE"``.

* ``InvalidValueError`` and ``ConfigurationMissingError`` now have
  ``namespace``, ``key``, and ``parser`` attributes allowing you to build your
  own messages.

Fixed:

* Fix an example in the docs where the final key was backwards. Thank you, pjz!

Documentation fixes and updates.


0.8 (January 24th, 2017)
------------------------

Added:

* Add ``:namespace:`` and ``:case:`` arguments to autoconfig directive. These
  make it easier to cater your documentation to your project's needs.

* Add support for Python 3.6.

Minor documentation fixes and updates.


0.7 (January 5th, 2017)
-----------------------

Added:

* Feature: You can now include documentation hints and urls for
  ``ConfigManager`` objects and config options. This will make it easier for
  your users to debug configuration errors they're having with your software.

Fixed:

* Fix ``ListOf`` so it returns empty lists rather than a list with a single
  empty string.

Documentation fixes and updates.


0.6 (November 28th, 2016)
-------------------------

Added:

* Add ``RequiredConfigMixin.get_runtime_config()`` which returns the runtime
  configuration for a component or tree of components. This lets you print
  runtime configuration at startup, generate INI files, etc.

* Add ``ConfigObjEnv`` which lets you use an object for configuration. This
  works with argparse's Namespace amongst other things.

Changed:

* Change ``:show-docstring:`` to take an optional value which is the attribute
  to pull docstring content from. This means you don't have to mix programming
  documentation with user documentation--they can be in different attributes.

* Improve configuration-related exceptions. With Python 3, configuration errors
  all derive from ``ConfigurationError`` and have helpful error messages that
  should make it clear what's wrong with the configuration value. With Python 2,
  you can get other kinds of Exceptions thrown depending on the parser used, but
  configuration error messages should still be helpful.

Documentation fixes and updates.


0.5 (November 8th, 2016)
------------------------

Added:

* Add ``:show-docstring:`` flag to ``autoconfig`` directive.

* Add ``:hide-classname:`` flag to ``autoconfig`` directive.

Changed:

* Rewrite ``ConfigIniEnv`` to use configobj which allows for nested sections in
  INI files. This also allows you to specify multiple INI files and have later
  ones override earlier ones.

Fixed:

* Fix ``autoconfig`` Sphinx directive and add tests--it was all kinds of broken.

Documentation fixes and updates.


0.4 (October 27th, 2016)
------------------------

Added:

* Add ``raw_value`` argument to config calls. This makes it easier to write code
  that prints configuration.

Fixed:

* Fix ``listify(None)`` to return ``[]``.

Documentation fixes and updates.


0.3.1 (October 12th, 2016)
--------------------------

Fixed:

* Fix ``alternate_keys`` with components. Previously it worked for everything
  but components. Now it works with components, too.

Documentation fixes and updates.


0.3 (October 6th, 2016)
-----------------------

Added:

* Add ``ConfigManager.from_dict()`` shorthand for building configuration
  instances.

* Add ``.get_namespace()`` to ``ConfigManager`` and friends for getting
  the complete namespace for a given config instance as a list of strings.

* Add ``alternate_keys`` to config call. This lets you specify a list of keys in
  order to try if the primary key doesn't find a value. This is helpful for
  deprecating keys that you used to use in a backwards-compatible way.

* Add ``root:`` prefix to keys allowing you to look outside of the current
  namespace and at the configuration root for configuration values.

Changed:

* Make ``ConfigDictEnv`` case-insensitive to keys and namespaces.

Documentation fixes and updates.


0.2 (August 16th, 2016)
-----------------------

Added:

* Add ``ConfigEnvFileEnv`` for supporting ``.env`` files. Thank you, Paul!

* Add "on" and "off" as valid boolean values. This makes it easier to use config
  for feature flippers. Thank you, Paul!

Changed:

* Change ``ConfigIniEnv`` to take a single path or list of paths. Thank you,
  Paul!

* Make ``NO_VALUE`` falsy.

Fixed:

* Fix ``__call__`` returning None--it should return ``NO_VALUE``.

Lots of docs updates: finished the section about making your own parsers, added
a section on using dj-database-url, added a section on django-cache-url and
expanded on existing examples.


0.1 (August 1st, 2016)
----------------------

Initial writing.


================================================
FILE: LICENSE
================================================
Mozilla Public License, version 2.0

1. Definitions

1.1. “Contributor”

     means each individual or legal entity that creates, contributes to the
     creation of, or owns Covered Software.

1.2. “Contributor Version”

     means the combination of the Contributions of others (if any) used by a
     Contributor and that particular Contributor’s Contribution.

1.3. “Contribution”

     means Covered Software of a particular Contributor.

1.4. “Covered Software”

     means Source Code Form to which the initial Contributor has attached the
     notice in Exhibit A, the Executable Form of such Source Code Form, and
     Modifications of such Source Code Form, in each case including portions
     thereof.

1.5. “Incompatible With Secondary Licenses”
     means

     a. that the initial Contributor has attached the notice described in
        Exhibit B to the Covered Software; or

     b. that the Covered Software was made available under the terms of version
        1.1 or earlier of the License, but not also under the terms of a
        Secondary License.

1.6. “Executable Form”

     means any form of the work other than Source Code Form.

1.7. “Larger Work”

     means a work that combines Covered Software with other material, in a separate
     file or files, that is not Covered Software.

1.8. “License”

     means this document.

1.9. “Licensable”

     means having the right to grant, to the maximum extent possible, whether at the
     time of the initial grant or subsequently, any and all of the rights conveyed by
     this License.

1.10. “Modifications”

     means any of the following:

     a. any file in Source Code Form that results from an addition to, deletion
        from, or modification of the contents of Covered Software; or

     b. any new file in Source Code Form that contains any Covered Software.

1.11. “Patent Claims” of a Contributor

      means any patent claim(s), including without limitation, method, process,
      and apparatus claims, in any patent Licensable by such Contributor that
      would be infringed, but for the grant of the License, by the making,
      using, selling, offering for sale, having made, import, or transfer of
      either its Contributions or its Contributor Version.

1.12. “Secondary License”

      means either the GNU General Public License, Version 2.0, the GNU Lesser
      General Public License, Version 2.1, the GNU Affero General Public
      License, Version 3.0, or any later versions of those licenses.

1.13. “Source Code Form”

      means the form of the work preferred for making modifications.

1.14. “You” (or “Your”)

      means an individual or a legal entity exercising rights under this
      License. For legal entities, “You” includes any entity that controls, is
      controlled by, or is under common control with You. For purposes of this
      definition, “control” means (a) the power, direct or indirect, to cause
      the direction or management of such entity, whether by contract or
      otherwise, or (b) ownership of more than fifty percent (50%) of the
      outstanding shares or beneficial ownership of such entity.


2. License Grants and Conditions

2.1. Grants

     Each Contributor hereby grants You a world-wide, royalty-free,
     non-exclusive license:

     a. under intellectual property rights (other than patent or trademark)
        Licensable by such Contributor to use, reproduce, make available,
        modify, display, perform, distribute, and otherwise exploit its
        Contributions, either on an unmodified basis, with Modifications, or as
        part of a Larger Work; and

     b. under Patent Claims of such Contributor to make, use, sell, offer for
        sale, have made, import, and otherwise transfer either its Contributions
        or its Contributor Version.

2.2. Effective Date

     The licenses granted in Section 2.1 with respect to any Contribution become
     effective for each Contribution on the date the Contributor first distributes
     such Contribution.

2.3. Limitations on Grant Scope

     The licenses granted in this Section 2 are the only rights granted under this
     License. No additional rights or licenses will be implied from the distribution
     or licensing of Covered Software under this License. Notwithstanding Section
     2.1(b) above, no patent license is granted by a Contributor:

     a. for any code that a Contributor has removed from Covered Software; or

     b. for infringements caused by: (i) Your and any other third party’s
        modifications of Covered Software, or (ii) the combination of its
        Contributions with other software (except as part of its Contributor
        Version); or

     c. under Patent Claims infringed by Covered Software in the absence of its
        Contributions.

     This License does not grant any rights in the trademarks, service marks, or
     logos of any Contributor (except as may be necessary to comply with the
     notice requirements in Section 3.4).

2.4. Subsequent Licenses

     No Contributor makes additional grants as a result of Your choice to
     distribute the Covered Software under a subsequent version of this License
     (see Section 10.2) or under the terms of a Secondary License (if permitted
     under the terms of Section 3.3).

2.5. Representation

     Each Contributor represents that the Contributor believes its Contributions
     are its original creation(s) or it has sufficient rights to grant the
     rights to its Contributions conveyed by this License.

2.6. Fair Use

     This License is not intended to limit any rights You have under applicable
     copyright doctrines of fair use, fair dealing, or other equivalents.

2.7. Conditions

     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
     Section 2.1.


3. Responsibilities

3.1. Distribution of Source Form

     All distribution of Covered Software in Source Code Form, including any
     Modifications that You create or to which You contribute, must be under the
     terms of this License. You must inform recipients that the Source Code Form
     of the Covered Software is governed by the terms of this License, and how
     they can obtain a copy of this License. You may not attempt to alter or
     restrict the recipients’ rights in the Source Code Form.

3.2. Distribution of Executable Form

     If You distribute Covered Software in Executable Form then:

     a. such Covered Software must also be made available in Source Code Form,
        as described in Section 3.1, and You must inform recipients of the
        Executable Form how they can obtain a copy of such Source Code Form by
        reasonable means in a timely manner, at a charge no more than the cost
        of distribution to the recipient; and

     b. You may distribute such Executable Form under the terms of this License,
        or sublicense it under different terms, provided that the license for
        the Executable Form does not attempt to limit or alter the recipients’
        rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

     You may create and distribute a Larger Work under terms of Your choice,
     provided that You also comply with the requirements of this License for the
     Covered Software. If the Larger Work is a combination of Covered Software
     with a work governed by one or more Secondary Licenses, and the Covered
     Software is not Incompatible With Secondary Licenses, this License permits
     You to additionally distribute such Covered Software under the terms of
     such Secondary License(s), so that the recipient of the Larger Work may, at
     their option, further distribute the Covered Software under the terms of
     either this License or such Secondary License(s).

3.4. Notices

     You may not remove or alter the substance of any license notices (including
     copyright notices, patent notices, disclaimers of warranty, or limitations
     of liability) contained within the Source Code Form of the Covered
     Software, except that You may alter any license notices to the extent
     required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

     You may choose to offer, and to charge a fee for, warranty, support,
     indemnity or liability obligations to one or more recipients of Covered
     Software. However, You may do so only on Your own behalf, and not on behalf
     of any Contributor. You must make it absolutely clear that any such
     warranty, support, indemnity, or liability obligation is offered by You
     alone, and You hereby agree to indemnify every Contributor for any
     liability incurred by such Contributor as a result of warranty, support,
     indemnity or liability terms You offer. You may include additional
     disclaimers of warranty and limitations of liability specific to any
     jurisdiction.

4. Inability to Comply Due to Statute or Regulation

   If it is impossible for You to comply with any of the terms of this License
   with respect to some or all of the Covered Software due to statute, judicial
   order, or regulation then You must: (a) comply with the terms of this License
   to the maximum extent possible; and (b) describe the limitations and the code
   they affect. Such description must be placed in a text file included with all
   distributions of the Covered Software under this License. Except to the
   extent prohibited by statute or regulation, such description must be
   sufficiently detailed for a recipient of ordinary skill to be able to
   understand it.

5. Termination

5.1. The rights granted under this License will terminate automatically if You
     fail to comply with any of its terms. However, if You become compliant,
     then the rights granted under this License from a particular Contributor
     are reinstated (a) provisionally, unless and until such Contributor
     explicitly and finally terminates Your grants, and (b) on an ongoing basis,
     if such Contributor fails to notify You of the non-compliance by some
     reasonable means prior to 60 days after You have come back into compliance.
     Moreover, Your grants from a particular Contributor are reinstated on an
     ongoing basis if such Contributor notifies You of the non-compliance by
     some reasonable means, this is the first time You have received notice of
     non-compliance with this License from such Contributor, and You become
     compliant prior to 30 days after Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent
     infringement claim (excluding declaratory judgment actions, counter-claims,
     and cross-claims) alleging that a Contributor Version directly or
     indirectly infringes any patent, then the rights granted to You by any and
     all Contributors for the Covered Software under Section 2.1 of this License
     shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
     license agreements (excluding distributors and resellers) which have been
     validly granted by You or Your distributors under this License prior to
     termination shall survive termination.

6. Disclaimer of Warranty

   Covered Software is provided under this License on an “as is” basis, without
   warranty of any kind, either expressed, implied, or statutory, including,
   without limitation, warranties that the Covered Software is free of defects,
   merchantable, fit for a particular purpose or non-infringing. The entire
   risk as to the quality and performance of the Covered Software is with You.
   Should any Covered Software prove defective in any respect, You (not any
   Contributor) assume the cost of any necessary servicing, repair, or
   correction. This disclaimer of warranty constitutes an essential part of this
   License. No use of  any Covered Software is authorized under this License
   except under this disclaimer.

7. Limitation of Liability

   Under no circumstances and under no legal theory, whether tort (including
   negligence), contract, or otherwise, shall any Contributor, or anyone who
   distributes Covered Software as permitted above, be liable to You for any
   direct, indirect, special, incidental, or consequential damages of any
   character including, without limitation, damages for lost profits, loss of
   goodwill, work stoppage, computer failure or malfunction, or any and all
   other commercial damages or losses, even if such party shall have been
   informed of the possibility of such damages. This limitation of liability
   shall not apply to liability for death or personal injury resulting from such
   party’s negligence to the extent applicable law prohibits such limitation.
   Some jurisdictions do not allow the exclusion or limitation of incidental or
   consequential damages, so this exclusion and limitation may not apply to You.

8. Litigation

   Any litigation relating to this License may be brought only in the courts of
   a jurisdiction where the defendant maintains its principal place of business
   and such litigation shall be governed by laws of that jurisdiction, without
   reference to its conflict-of-law provisions. Nothing in this Section shall
   prevent a party’s ability to bring cross-claims or counter-claims.

9. Miscellaneous

   This License represents the complete agreement concerning the subject matter
   hereof. If any provision of this License is held to be unenforceable, such
   provision shall be reformed only to the extent necessary to make it
   enforceable. Any law or regulation which provides that the language of a
   contract shall be construed against the drafter shall not be used to construe
   this License against a Contributor.


10. Versions of the License

10.1. New Versions

      Mozilla Foundation is the license steward. Except as provided in Section
      10.3, no one other than the license steward has the right to modify or
      publish new versions of this License. Each version will be given a
      distinguishing version number.

10.2. Effect of New Versions

      You may distribute the Covered Software under the terms of the version of
      the License under which You originally received the Covered Software, or
      under the terms of any subsequent version published by the license
      steward.

10.3. Modified Versions

      If you create software not governed by this License, and you want to
      create a new license for such software, you may create and use a modified
      version of this License if you rename the license and remove any
      references to the name of the license steward (except to note that such
      modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
      If You choose to distribute Source Code Form that is Incompatible With
      Secondary Licenses under the terms of this version of the License, the
      notice described in Exhibit B of this License must be attached.

Exhibit A - Source Code Form License Notice

      This Source Code Form is subject to the
      terms of the Mozilla Public License, v.
      2.0. If a copy of the MPL was not
      distributed with this file, You can
      obtain one at
      http://mozilla.org/MPL/2.0/.

If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.

You may add additional accurate notices of copyright ownership.

Exhibit B - “Incompatible With Secondary Licenses” Notice

      This Source Code Form is “Incompatible
      With Secondary Licenses”, as defined by
      the Mozilla Public License, v. 2.0.



================================================
FILE: MANIFEST.in
================================================
include *.rst
include pyproject.toml
include justfile
include LICENSE
include .readthedocs.yaml
recursive-include docs *.rst
recursive-include docs *.py
recursive-include docs Makefile
recursive-include docs_tmpl *.rst
recursive-include examples *.py
recursive-include tests *.env
recursive-include tests *.ini
recursive-include tests *.py


================================================
FILE: README.rst
================================================
.. NOTE: Make sure to edit the template for this file in docs_tmpl/ and
.. not the cog-generated version.

=======
Everett
=======

**Status 2025-10-15: This project is deprecated.**

Everett is a Python configuration library for your app.

:Code:          https://github.com/willkg/everett
:Issues:        https://github.com/willkg/everett/issues
:License:       MPL v2
:Documentation: https://everett.readthedocs.io/


Goals
=====

Goals of Everett:

1. flexible configuration from multiple configured environments
2. easy testing with configuration
3. easy automated documentation of configuration for users

From that, Everett has the following features:

* is flexible for your configuration environment needs and supports
  process environment, env files, dicts, INI files, YAML files,
  and writing your own configuration environments
* facilitates helpful error messages for users trying to configure your
  software
* has a Sphinx extension for documenting configuration including
  ``autocomponentconfig`` and ``automoduleconfig`` directives for
  automatically generating configuration documentation
* facilitates testing of configuration values
* supports parsing values of a variety of types like bool, int, lists of
  things, classes, and others and lets you write your own parsers
* supports key namespaces
* supports component architectures
* works with whatever you're writing--command line tools, web sites, system
  daemons, etc

Everett is inspired by
`python-decouple <https://github.com/henriquebastos/python-decouple>`__
and `configman <https://configman.readthedocs.io/en/latest/>`__.


Install
=======

Run::

    $ pip install everett

Some configuration environments require additional dependencies::


    # For INI support
    $ pip install 'everett[ini]'

    # for YAML support
    $ pip install 'everett[yaml]'


Quick start
===========

Example:

.. [[[cog
   import cog
   with open("examples/myserver.py", "r") as fp:
       cog.outl("\n::\n")
       for line in fp.readlines():
           if line.strip():
               cog.out(f"   {line}")
           else:
               cog.outl()
   cog.outl()
   ]]]

::

   # myserver.py

   """
   Minimal example showing how to use configuration for a web app.
   """

   from everett.manager import ConfigManager

   config = ConfigManager.basic_config(
       doc="Check https://example.com/configuration for documentation."
   )

   host = config("host", default="localhost")
   port = config("port", default="8000", parser=int)
   debug_mode = config(
       "debug",
       default="False",
       parser=bool,
       doc="Set to True for debugmode; False for regular mode",
   )

   print(f"host: {host}")
   print(f"port: {port}")
   print(f"debug_mode: {debug_mode}")

.. [[[end]]]

Then you can run it:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   cog.outl("\n::\n") 
   cog.outl("   $ python myserver.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python myserver.py
   host: localhost
   port: 8000
   debug_mode: False

.. [[[end]]]

You can set environment variables to affect configuration:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   os.environ["PORT"] = "7000"
   cog.outl("\n::\n")
   cog.outl("   $ PORT=7000 python myserver.py")
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   del os.environ["PORT"]
   ]]]

::

   $ PORT=7000 python myserver.py
   host: localhost
   port: 7000
   debug_mode: False

.. [[[end]]]

It checks a ``.env`` file in the current directory:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   with open(".env", "w") as fp:
       fp.write("HOST=127.0.0.1")
   cog.outl("\n::\n")
   cog.outl("   $ echo \"HOST=127.0.0.1\" > .env")
   cog.outl("   $ python myserver.py")
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ echo "HOST=127.0.0.1" > .env
   $ python myserver.py
   host: 127.0.0.1
   port: 8000
   debug_mode: False

.. [[[end]]]

It spits out useful error information if configuration is wrong:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   os.environ["DEBUG"] = "foo"
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   stderr = ret.stderr.decode("utf-8").strip()
   stderr = stderr[stderr.find("everett.InvalidValueError"):]
   cog.outl("\n::\n")
   cog.outl("   $ DEBUG=foo python myserver.py")
   cog.outl("   <traceback>")
   for line in stderr.splitlines():
      cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ DEBUG=foo python myserver.py
   <traceback>
   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value
   DEBUG requires a value parseable by everett.manager.parse_bool
   DEBUG docs: Set to True for debugmode; False for regular mode
   Project docs: Check https://example.com/configuration for documentation.

.. [[[end]]]

You can test your code using ``config_override`` in your tests to test various
configuration values:

.. [[[cog
   import cog
   cog.outl("\n::\n")
   with open("examples/testdebug.py", "r") as fp:
       for line in fp.readlines():
           cog.out(f"   {line}")
   cog.outl()
   ]]]

::

   # testdebug.py
   
   """
   Minimal example showing how to override configuration values when testing.
   """
   
   import unittest
   
   from everett.manager import ConfigManager, config_override
   
   
   class App:
       def __init__(self):
           config = ConfigManager.basic_config()
           self.debug = config("debug", default="False", parser=bool)
   
   
   class TestDebug(unittest.TestCase):
       def test_debug_on(self):
           with config_override(DEBUG="on"):
               app = App()
               self.assertTrue(app.debug)
   
       def test_debug_off(self):
           with config_override(DEBUG="off"):
               app = App()
               self.assertFalse(app.debug)
   
   
   if __name__ == "__main__":
       unittest.main()

.. [[[end]]]

Run that:

.. [[[cog
   import cog
   import os
   import subprocess
   ret = subprocess.run(["python", "examples/testdebug.py"], capture_output=True)
   stderr = ret.stderr.decode("utf-8").strip()
   cog.outl("\n::\n")
   cog.outl("   $ python testdebug.py")
   for line in stderr.splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python testdebug.py
   ..
   ----------------------------------------------------------------------
   Ran 2 tests in 0.000s
   
   OK

.. [[[end]]]

That's perfectly fine for a `12-Factor <https://12factor.net/>`__ app.

When you outgrow that or need different variations of it, you can switch to
creating a ``ConfigManager`` instance that meets your needs.


Why not other libs?
===================

Most other libraries I looked at had one or more of the following issues:

* were tied to a specific web app framework
* didn't allow you to specify configuration sources
* provided poor error messages when users configure things wrong
* had a global configuration object
* made it really hard to override specific configuration when writing tests
* had no facilities for autogenerating configuration documentation


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
SPHINXPROJ    = Everett
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile view

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	which $(SPHINXBUILD)
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

view:
	gnome-open _build/html/index.html


================================================
FILE: docs/api.rst
================================================
===
API
===

This is the API of functions and classes in Everett.

Configuration things:

* :py:class:`everett.manager.ConfigManager`
* :py:class:`everett.manager.Option`

Utility functions:

* :py:class:`everett.manager.get_config_for_class`
* :py:class:`everett.manager.get_runtime_config`

Testing utility functions:

* :py:class:`everett.manager.config_override`

Configuration environments:

* :py:class:`everett.manager.ConfigObjEnv`
* :py:class:`everett.manager.ConfigDictEnv`
* :py:class:`everett.manager.ConfigEnvFileEnv`
* :py:class:`everett.manager.ConfigOSEnv`
* (INI) :py:class:`everett.ext.inifile.ConfigIniEnv`
* (YAML) :py:class:`everett.ext.yamlfile.ConfigYamlEnv`

Errors:

* :py:class:`everett.ConfigurationError`
* :py:class:`everett.InvalidKeyError`
* :py:class:`everett.ConfigurationMissingError`
* :py:class:`everett.InvalidValueError`

Parsers:

* :py:func:`everett.manager.parse_bool`
* :py:func:`everett.manager.parse_class`
* :py:func:`everett.manager.parse_data_size`
* :py:func:`everett.manager.ListOf`


everett
=======

.. automodule:: everett
   :members:

everett.manager
===============

.. automodule:: everett.manager
   :members:
   :special-members: __init__, __call__

everett.ext.inifile
===================

.. automodule:: everett.ext.inifile
   :members:

everett.ext.yamlfile
====================

.. automodule:: everett.ext.yamlfile
   :members:


================================================
FILE: docs/components.rst
================================================
.. NOTE: Make sure to edit the template for this file in docs_tmpl/ and
.. not the cog-generated version.

==========
Components
==========

.. contents::
   :local:


.. versionchanged:: 2.0

   This is redone for v2.0.0 and simplified.


Configuration components
========================

Everett supports configuration components.

There are two big use cases for this:

1. Centralizing configuration specification for your application into a single
   class.

2. Component architectures.


Centralizing configuration
--------------------------

Instead of having configuration-related bits defined across your codebase, you
can define it in a class.

Here's an example with an ``AppConfig``:

.. literalinclude:: ../examples/component_appconfig.py
   :language: python

Let's run it with the defaults:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/component_appconfig.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ python component_appconfig.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python component_appconfig.py
   debug: False

.. [[[end]]]

Now with ``DEBUG=true``:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   os.environ["DEBUG"] = "true"
   ret = subprocess.run(["python", "examples/component_appconfig.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ DEBUG=true python component_appconfig.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   del os.environ["DEBUG"]
   ]]]

::

   $ DEBUG=true python component_appconfig.py
   debug: True

.. [[[end]]]

Let's run a Python shell and do some other things with it:

.. doctest::

   >>> import component_appconfig
   debug: False
   >>> config = component_appconfig.get_config()
   >>> config("badkey")
   Traceback (most recent call last):
       ...
   everett.InvalidKeyError: 'badkey' is not a valid key for this component

Notice how you can't use configuration keys that aren't specified in the bound
component.

Centrally defining configuration like this helps in a few ways:

1. You can reduce some bugs that occur as your application evolves over time.
   Every time you use configuration, the ``ConfigManager`` will enforce that
   the key is a valid option.

2. Your application configuration is centralized in one place instead
   of spread out across your code base.

3. You can automatically document your configuration using the
   ``everett.sphinxext`` Sphinx extension and ``autocomponentconfig`` directive::

       .. autocomponentconfig:: path.to.AppConfig

   Because it's automatically documented, your documentation is always
   up-to-date.


Component architectures
-----------------------

Everett configuration supports component architectures. Say your app needs to
connect to RabbitMQ. With Everett, you can define the component's configuration
needs in the component class.

Here's an example:

.. literalinclude:: ../examples/componentapp.py
   :language: python


That's not wildly exciting, but if the component was in a library of
components, then you can string them together using configuraiton.

For example, what if the destination wasn't a single bucket, but rather
a set of buckets?

::

    dest_config = config("pipeline", default="dest", parser=ListOf(str))

    dest_buckets = []
    for name in dest_config:
        dest_buckets.append(S3Bucket(s3_config.with_namespace(name)))

You can autogenerate configuration documentation for this component in your
Sphinx docs by including the ``everett.sphinxext`` Sphinx extension and
using the ``autocomponentconfig`` directive::

    .. autocomponentconfig:: myapp.S3Bucket


Subclassing
===========

You can subclass components and override configuration options.

For example:

.. literalinclude:: ../examples/components_subclass.py
   :language: python

That prints:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/components_subclass.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ python components_subclass.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python components_subclass.py
   foo_from_b
   bar_from_a

.. [[[end]]]


Getting configuration information for components
================================================

You can get the configuration options for a component class using
:py:func:`everett.manager.get_config_for_class`. This returns a dict of
``configuration key -> (option, class)``. This helps with debugging which
option came from which class.

.. autofunction:: everett.manager.get_config_for_class
   :noindex:


You can get the runtime configuration for a component or tree of components
using :py:func:`everett.manager.get_runtime_config`. This returns a list of
``(namespace, key, value, option, class)`` tuples. The value is the computed
runtime value taking into account the environments specified in the
``ConfigManager`` and class hierarchies.

It'll traverse any instance attributes that are components with options.

.. autofunction:: everett.manager.get_runtime_config
   :noindex:


================================================
FILE: docs/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Path setup --------------------------------------------------------------

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
import os
import sys

cwd = os.getcwd()

# Add ../src/ directory so we can pull in Everett things using autodoc
project_root = os.path.dirname(cwd)
src_root = os.path.join(project_root, "src")
sys.path.insert(0, src_root)

# Add ../examples/ directory so we can use autocomponentconfig with a recipe
sys.path.insert(0, os.path.join(project_root, "examples"))

import everett  # noqa


# -- Project information -----------------------------------------------------

project = "Everett"
copyright = "2016-2022, Will Kahn-Greene"
author = "Will Kahn-Greene"

# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.doctest",
    "everett.sphinxext",
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = everett.__version__
# The full version with the release date.
release = everett.__version__

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"

autodoc_typehints = "description"
autoclass_content = "both"
autodoc_default_options = {
    "class-doc-from": "both",
    "member-order": "bysource",
    "inheireted-members": True,
}


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]

# Custom sidebar templates, maps document names to template names.
#
html_sidebars = {
    "**": [
        "about.html",
        "navigation.html",
        "relations.html",
        "searchbox.html",
    ]
}


================================================
FILE: docs/configmanager.rst
================================================
.. NOTE: Make sure to edit the template for this file in docs_tmpl/ and
.. not the cog-generated version.

======================
Configuration Managers
======================

.. contents::
   :local:


Creating a ConfigManager and specifying environments
====================================================

First, you define a :py:class:`everett.manager.ConfigManager` which specifies
the environments you want to pull configuration from.

Then you can use the :py:class:`everett.manager.ConfigManager` to look up
configuration keys. The :py:class:`everett.manager.ConfigManager` will go
through the environments in order to find a value for the configuration key.
Once it finds a value, it runs it through the parser and returns the parsed
value.

There are a few ways to create a :py:class:`everett.manager.ConfigManager`.

The easiest is to use :py:func:`everett.manager.ConfigManager.basic_config`.

For example:

.. literalinclude:: ../examples/myserver.py
   :language: python

That creates a :py:class:`everett.manager.ConfigManager` that looks up
configuration keys in these environments:

1. the process environment
2. the specified env file which defaults to ``.env``

That works for most cases.

You can create your own :py:class:`everett.manager.ConfigManager` and specify
environments specific to your needs.

For example:

.. literalinclude:: ../examples/myserver_with_environments.py
   :language: python


Specifying configuration documentation
======================================

When building a :py:class:`everett.manager.ConfigManager`, you can specify
documentation for configuration. It will get printed when there are
configuration errors. This is a great place to put a link to configuration
documentation.

For example:

.. literalinclude:: ../examples/myserver.py
   :language: python

Let's run that:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ python myserver.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python myserver.py
   host: localhost
   port: 8000
   debug_mode: False

.. [[[end]]]

Let's set ``DEBUG`` wrong and see what it tells us:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   os.environ["DEBUG"] = "foo"
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   stderr = ret.stderr.decode("utf-8").strip()
   stderr = stderr[stderr.find("everett.InvalidValueError"):]
   cog.outl("\n::\n")
   cog.outl("   $ DEBUG=foo python myserver.py")
   cog.outl("   <traceback>")
   for line in stderr.splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ DEBUG=foo python myserver.py
   <traceback>
   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value
   DEBUG requires a value parseable by everett.manager.parse_bool
   DEBUG docs: Set to True for debugmode; False for regular mode
   Project docs: Check https://example.com/configuration for documentation.

.. [[[end]]]

Here, we see the documentation for the ``DEBUG`` option, the documentation from
the ``ConfigManager``, and the specific Python exception information.


================================================
FILE: docs/configuration.rst
================================================
.. NOTE: Make sure to edit the template for this file in docs_tmpl/ and
.. not the cog-generated version.

=============
Configuration
=============

.. contents::
   :local:


Extracting values
=================

Once you have a configuration manager set up with sources, you can pull
configuration values from it.

Configuration must have a key. Everything else is optional.

Examples:

::

    config("password")

The key is "password".

The value is parsed as a string.

There is no default value provided so if "password" isn't provided in any of
the configuration sources, then this will raise a
:py:class:`everett.ConfigurationError`.

This is what you want to do to require that a configuration value exist.

::

    config("name", raise_error=False)

The key is "name".

The value is parsed as a string.

There is no default value provided and raise_error is set to False, so if
this configuration variable isn't set anywhere, the result of this will be
``everett.NO_VALUE``.

.. Note::

   :py:data:`everett.NO_VALUE` is a falsy value so you can use it in
   comparative contexts::

       debug = config("DEBUG", parser=bool, raise_error=False)
       if not debug:
           pass

::

    config("port", parser=int, default="5432")

The key is "port".

The value is parsed using int.

There is a default provided, so if this configuration variable isn't set in
the specified sources, the default will be false.

Note that the default value is always a string that's parseable by the
parser.

::

    config("username", namespace="db")

The key is "username".

The namespace is "db".

There's no default, so if there's no "username" in namespace "db"
configuration variable set in the sources, this will raise a
:py:class:`everett.ConfigurationError`.

If you're looking up values in the process environment, then the full
key would be ``DB_USERNAME``.

::

    config("password", namespace="postgres", alternate_keys=["db_password", "root:postgres_password"])

The key is "password".

The namespace is "postgres".

If there is no key "password" in namespace "postgres", then it looks for
"db_password" in namespace "postgres". This makes it possible to deprecate
old key names, but still support them.

If there is no key "password" or "db_password" in namespace "postgres", then
it looks at "postgres_password" in the root namespace. This allows you to
have multiple components that share configuration like credentials and
hostnames.

::

    config(
        "debug", default="false", parser=bool,
        doc="Set to True for debugmode; False for regular mode",
    )

You can provide a ``doc`` argument which will give users users who are trying to
configure your software a more helpful error message when they hit a configuration
error.

Example of error message for an option that specifies ``doc`` when trying to
set ``DEBUG`` to ``foo``:

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   os.environ["DEBUG"] = "foo"
   ret = subprocess.run(["python", "examples/myserver.py"], capture_output=True)
   stderr = ret.stderr.decode("utf-8").strip()
   stderr = stderr[stderr.find("everett.InvalidValueError"):]
   cog.outl("\n::\n")
   cog.outl("   $ python example.py")
   cog.outl("   <traceback>")
   for line in stderr.splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python example.py
   <traceback>
   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value
   DEBUG requires a value parseable by everett.manager.parse_bool
   DEBUG docs: Set to True for debugmode; False for regular mode
   Project docs: Check https://example.com/configuration for documentation.

.. [[[end]]]

That last line comes directly from the ``doc`` argument you provide.


.. automethod:: everett.manager.ConfigManager.__call__
   :noindex:

.. autoclass:: everett.ConfigurationError
   :noindex:

.. autoclass:: everett.InvalidValueError
   :noindex:

.. autoclass:: everett.ConfigurationMissingError
   :noindex:

.. autoclass:: everett.InvalidKeyError
   :noindex:


Namespaces
==========

Everett has namespaces for grouping related configuration values.

For example, this uses "username", "password", and "port" configuration keys
in the "db" namespace:

.. literalinclude:: ../examples/namespaces.py
   :language: python

These variables in the environment would be ``DB_USERNAME``, ``DB_PASSWORD``
and ``DB_PORT``.

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/namespaces.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ python namespaces.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python namespaces.py
   Opened database with admin/ou812 on port 5432

.. [[[end]]]


This is helpful when you need to create two of the same thing, but using
separate configuration.

What if we had source and destination databases and needed to have the
configuration keys separated?

.. literalinclude:: ../examples/namespaces2.py
   :language: python

.. [[[cog
   import cog
   import os
   import subprocess
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/namespaces2.py"], capture_output=True)
   cog.outl("\n::\n")
   cog.outl("   $ python namespaces2.py")
   for line in ret.stdout.decode("utf-8").splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   ]]]

::

   $ python namespaces2.py
   Opened database with admin/ou812 on port 5432
   Opened database with admin/P9rwvnnj8CidECMb on port 5432

.. [[[end]]]


Handling exceptions when extracting values
==========================================

When the namespaced key isn't found in any of the sources, then Everett will
raise an exception that is a subclass of
:py:class:`everett.ConfigurationError`. This makes it easier to
programmatically figure out what happened.

If you don't like what Everett prints by default, you can catch
the errors and print something different.

For example:

.. literalinclude:: ../examples/handling_exceptions.py
   :language: python


Also, you can change the structure of the error message by passing in a ``msg_builder``
argument to the :py:class:`everett.manager.ConfigManager`.

For example, say your project is entirely done with INI configuration. Then you'd
want to tailor the message accordingly.

.. literalinclude:: ../examples/msg_builder.py
   :language: python

That prints this:

.. [[[cog
   import cog
   import os
   import subprocess
   os.environ["DEBUG"] = "lizard"
   if os.path.exists(".env"):
       os.remove(".env")
   ret = subprocess.run(["python", "examples/msg_builder.py"], capture_output=True)
   stderr = ret.stderr.decode("utf-8").strip()
   stderr = stderr[stderr.find("everett.InvalidValueError"):]
   cog.outl("\n::\n")
   cog.outl("   $ DEBUG=lizard python msg_builder.py")
   cog.outl("   <traceback>")
   for line in stderr.splitlines():
       cog.outl(f"   {line}")
   cog.outl()
   del os.environ["DEBUG"]
   ]]]

::

   $ DEBUG=lizard python msg_builder.py
   <traceback>
   everett.InvalidValueError: Dear user. debug in section [main] is not correct. Please fix it.

.. [[[end]]]


Trouble-shooting and logging what happened
=============================================

If you have a non-trivial Everett configuration, it might be difficult to
figure out exactly why a key lookup failed.

Everett logs to the ``everett`` logger at the ``logging.DEBUG`` level. You
can enable this logging and get a clearer idea of what's going on.

See `Python logging documentation
<https://docs.python.org/3/library/logging.html>`_ for details on enabling
logging.


================================================
FILE: docs/dev.rst
================================================
==================
Developing Everett
==================

Install for development
=======================

Requirements:

* `uv <https://docs.astral.sh/uv/>`__
* `just <https://just.systems/>`__
* `git <https://git-scm.com/>`__

Clone the repository::

    $ git clone https://github.com/willkg/everett

Create a dev environment::

    $ just devenv

Development recipes are in ``justfile``. You can get a list with
``just --list``.


================================================
FILE: docs/documenting.rst
================================================
=========================
Documenting configuration
=========================

.. contents::
   :local:

It's hard to keep configuration documentation up-to-date as projects change
over time.

Everett comes with a `Sphinx <https://http://www.sphinx-doc.org/en/stable/>`_
extension for documenting configuration. It has ``autocomponentconfig`` and
``automoduleconfig`` directives for automatically generating documentation. It
also has ``everett:component`` and ``everett:option`` directives for manually
documenting configuration. It also comes with ``:everett:option:`` and
``:everett:component:`` roles letting you create links to specific
configuration things in your documentation.

Configuration options are added to the index and have unique links making it
easier to find and point people to specific configuration documentation.

.. versionchanged:: 3.0.0
   Complete rewrite of Sphinx directives.


Directives
==========

.. rst:directive:: automoduleconfig

   **Requires Python 3.8 or higher.**

   Automatically documents the configuration options set in a Python module
   using the specified :py:class:`everett.manager.ConfigManager`.

   The argument is the Python dotted path to the
   :py:class:`everett.manager.ConfigManager` instance.

   .. Note::

      The automoduleconfig directive works by parsing the Python module as an
      AST and then traverses the AST.

      It does not execute the module, so it doesn't evaluate any values.

   .. Note::

      ``automoduleconfig`` requires Python 3.8 or higher.

      If you're using ReadTheDocs, it defaults to Python 3.7. You'll need to
      configure the version of Python to use by adding a configuration file.

      See `ReadTheDocs configuration file documentation
      <https://docs.readthedocs.io/en/stable/config-file/v2.html>`_ for more
      details.

   .. rubric:: Options

   .. rst:directive:option:: show-table
      :type: no value

      If set, will create a table summarizing the options in this module with
      links to the option details.

   .. rst:directive:option:: hide-name
      :type: no value

      If set, this will hide the name derived from the Python dotted path
      and use "Configuration" instead.

      This affects how the options are indexed. If you're documenting multiple
      modules this way, options that exist in multiple modules will create a
      conflict.

   .. rst:directive:option:: show-docstring
      :type: str, empty str, or omitted

      If omitted, this does nothing.

      If set, but with no value, this will include the module docstring in
      the documentation.

      If set with a value of the name of an attribute in the module, this will
      include the value of that attribute in the documentation.

      Example to include the module ``__doc__``:

      ::

          .. automoduleconfig:: myproject.settings._config
             :show-docstring:
    
      Example to include the value of the value of the ``HELP`` attribute:

      ::

          .. automoduleconfig:: myproject.settings._config
             :show-docstring: HELP

   .. rst:directive:option:: namespace
      :type: str

      If set, this prefixes all the option keys with the specified namespace.
      
      For example, if you set namespace to ``source_db``, then key ``host``
      would result in ``source_db_host`` being documented. (Case is dependent
      on the "case" directive option.)

   .. rst:directive:option:: case
      :type: "upper", "lower", or omitted

      Specifies whether to convert the full namespaced key to all uppercase,
      all lowercase, or leave it as is.


.. rst:directive:: autocomponentconfig

   Automatically documents the configuration options for the specified class
   and its superclasses.

   The argument is the Python dotted path to the class.

   .. Warning::

      ``autocomponentconfig`` **imports** the code to be documented. If any of
      the imported modules have side-effects at import, they will be executed
      when building the documentation.

   .. rubric:: Options

   .. rst:directive:option:: show-table
      :type: no value

      If set, will create a table summarizing the options in this component
      with links to the option details.

   .. rst:directive:option:: hide-name
      :type: no value

      If set, this will hide the name of the class and use "Configuration"
      instead.

      This affects how the options are indexed. If you're documenting multiple
      classes this way, options that exist in multiple classes will create a
      conflict.

   .. rst:directive:option:: show-docstring
      :type: str, empty str, or omitted

      If omitted, this does nothing.

      If set, but with no value, this will include the class docstring in the
      documentation.

      If set with a value of the name of an attribute of the class, this will
      include the value of that attribute in the documentation.

      Example to include the class docstring:

      ::

          .. automoduleconfig:: myproject.MyClass
             :show-docstring:
    
      Example to include the value of the value of the ``HELP`` attribute:

      ::

          .. automoduleconfig:: myproject.MyClass
             :show-docstring: HELP

   .. rst:directive:option:: namespace
      :type: str

      If set, this prefixes all the option keys with the specified namespace.
      
      For example, if you set namespace to ``source_db``, then key ``host``
      would result in ``source_db_host`` being documented. (Case is dependent
      on the "case" directive option.)

   .. rst:directive:option:: case
      :type: "upper", "lower", or omitted

      Specifies whether to convert the full namespaced key to all uppercase,
      all lowercase, or leave it as is.


.. rst:directive:: everett:component

   Defines an Everett component which is any Python class that has an inner
   class named ``Config`` which defines configuration options.

   The argument is the Python dotted path to the class.

   Add ``everett:option`` as part of the description.


.. rst:directive:: everett:option

   Defines an Everett configuration option.

   The argument is the option key.

   .. rubric:: Options

   .. rst:directive:option:: parser
      :type: str

      The name of the parser for this option.

   .. rst:directive:option:: default
      :type: str

      If not set, the default is ``NO_VALUE`` which means that this option has
      no default value.

      If set, this is the default value. Enclose the value in double-quotes
      because all default values must be strings.

   .. rst:directive:option:: required
      :type: no value

      If set, this option is required.

      If not set and the option has a default, then this option is not
      required.

      If not set and the option has no default, then this option is required.

      This option is not required::

          .. everett:option:: HOST
             :default: localhost

      These two options are required::

          .. everett:option:: USERNAME
             
          .. everett:option:: PASSWORD
             :required:


Examples
========

Documenting component configuration
-----------------------------------

Here's an example Everett component:

.. literalinclude:: ../examples/recipes_appconfig.py


You can use the ``autocomponentconfig`` directive to extract the configuration
information from the ``AppConfig`` class and document it::

    .. autocomponentconfig:: recipes_appconfig.AppConfig
       :case: upper
       :show-table:


That gives you something that looks like this:

.. autocomponentconfig:: recipes_appconfig.AppConfig
   :case: upper
   :show-table:


You can link to components with the ``:everett:component:`` role and options
using the ``:everett:option:`` role.

Example component link::

    Component link: :everett:component:`recipes_appconfig.AppConfig`


Component link: :everett:component:`recipes_appconfig.AppConfig`

Example option link::

    Option link: :everett:option:`recipes_appconfig.AppConfig.DEBUG`


Option link: :everett:option:`recipes_appconfig.AppConfig.DEBUG`



Documenting module configuration
--------------------------------

You can use ``automoduleconfig`` to document configuration that's set at module
import. This is helpful for Django settings modules.

Example configuration code that sets up a
:py:class:`everett.manager.ConfigManager` and calls it ``_config``:

.. literalinclude:: ../examples/recipes_djangosettings.py
   :language: python

Example documentation directive::

    .. automoduleconfig:: recipes_djangosettings._config
       :hide-name:
       :case: upper
       :show-table:

That gives you this:

.. automoduleconfig:: recipes_djangosettings._config
   :hide-name:
   :case: upper
   :show-table:


================================================
FILE: docs/environments.rst
================================================
==========================
Configuration environments
==========================

.. contents::
   :local:

Dict (ConfigDictEnv)
====================

.. autoclass:: everett.manager.ConfigDictEnv
   :noindex:


Process environment (ConfigOSEnv)
=================================

.. autoclass:: everett.manager.ConfigOSEnv
   :noindex:


ENV files (ConfigEnvFileEnv)
============================

.. autoclass:: everett.manager.ConfigEnvFileEnv
   :noindex:


Python objects (ConfigObjEnv)
=============================

.. autoclass:: everett.manager.ConfigObjEnv
   :noindex:


INI files (ConfigIniEnv)
========================

.. autoclass:: everett.ext.inifile.ConfigIniEnv
   :noindex:


YAML files (ConfigYamlEnv)
==========================

.. autoclass:: everett.ext.yamlfile.ConfigYamlEnv
   :noindex:


Implementing your own configuration environments
================================================

You can implement your own configuration environments. For example, maybe you
want to pull configuration from a database or Redis or a post-it note on the
refrigerator.

They just need to implement the ``.get()`` method. A no-op implementation is
this:

.. literalinclude:: ../examples/environments.py
   :language: python


Generally, environments should return a value if the key exists in that
environment and should return ``NO_VALUE`` if and only if the key does not
exist in that environment.

For exceptions, it depends on what you want to have happen. It's ok to let
exceptions go unhandled--Everett will wrap them in a :py:class:`everett.ConfigurationError`.
If your environment promises never to throw an exception, then you should
handle them all and return ``NO_VALUE`` since with that promise all exceptions
would indicate the key is not in the environment.


================================================
FILE: docs/history.rst
================================================
.. include:: ../HISTORY.rst


================================================
FILE: docs/index.rst
================================================
.. include:: ../README.rst


Contents
========

.. toctree::
   :maxdepth: 2

   configmanager
   configuration
   parsers
   environments
   components
   documenting
   testing
   recipes
   api
   history
   dev


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


================================================
FILE: docs/parsers.rst
================================================
=======
Parsers
=======

.. contents::
   :local:


What's a parser?
================

All parsers are functions that take a string value and return a parsed
instance.

For example:

* ``int`` takes a string value and returns an int.
* ``parse_class`` takes a string value that's a dotted Python path and returns
  the class object
* ``ListOf(str)`` takes a string value that uses a comma delimiter and returns
  a list of strings


.. Note::

   When specifying configuration options, the default value must always be a
   string. When Everett can't find a value for a requested key, it will take
   the default value and pass it through the parser. Because parsers always
   take a string as input, the default value must always be a string.

   Good::

       debug = config("debug", parser=bool, default="false")
                                                    ^^^^^^^

   Bad::

       debug = config("debug", parser=bool, default=False)
                                                    ^^^^^ Not a string


Available parsers
=================

Python types like str, int, float, pathlib.Path
-----------------------------------------------

Python types can convert strings to Python values. You can use these as
parsers:

* ``str``
* ``int``
* ``float``
* ``decimal``
* ``pathlib.Path``


bools
-----

Everett provides a special bool parser that handles more descriptive values for
"true" and "false":

* true: t, true, yes, y, on, 1 (and uppercase versions)
* false: f, false, no, n, off, 0 (and uppercase versions)

.. autofunction:: everett.manager.parse_bool
   :noindex:


classes
-------

Everett provides a ``everett.manager.parse_class`` that takes a string
specifying a module and class and returns the class.

.. autofunction:: everett.manager.parse_class
   :noindex:


data size
---------

Everett provides a ``everett.manager.parse_data_size`` that takes a string
specifying an amount and a data size metric (e.g. kb, kib, tb, etc) and returns
the amount of bytes that represents.

.. autofunction:: everett.manager.parse_data_size
   :noindex:


time period
-----------

Everett provides a ``everett.manager.parse_time_period`` that takes a string
specifying a period of time and returns the total number of seconds represented
by that period.

.. autofunction:: everett.manager.parse_data_size
   :noindex:


ListOf(parser)
--------------

Everett provides a special ``everett.manager.ListOf`` parser which
parses a list of some other type. For example::

    ListOf(str)  # comma-delimited list of strings
    ListOf(int)  # comma-delimited list of ints

.. autofunction:: everett.manager.ListOf
   :noindex:


ChoiceOf(parser, list-of-choices)
---------------------------------

Everett provides a ``everett.manager.ChoiceOf`` parser which can enforce that
configuration values belong to a specificed value domain.

.. autofunction:: everett.manager.ChoiceOf
   :noindex:


dj_database_url
---------------

Everett works with `dj-database-url
<https://pypi.org/project/dj-database-url/>`_. The ``dj_database_url.parse``
function takes a string and returns a Django database connection value.

For example::

    import dj_database_url
    from everett.manager import ConfigManager

    config = ConfigManager.basic_config()
    DATABASES = {
        "default": config("DATABASE_URL", parser=dj_database_url.parse)
    }


That'll pull the ``DATABASE_URL`` value from the environment (it throws an
error if it's not there) and runs it through ``dj_database_url`` which parses
it and returns what Django needs.

With a default::

    import dj_database_url
    from everett.manager import ConfigManager

    config = ConfigManager.basic_config()
    DATABASES = {
        "default": config(
            "DATABASE_URL", default="sqlite:///my.db", parser=dj_database_url.parse
        )
    }


.. Note::

   To use dj-database-url, you'll need to install it separately. Everett doesn't
   depend on it or require it to be installed.


django-cache-url
----------------

Everett works with `django-cache-url <https://pypi.org/project/django-cache-url/>`_.

For example::

    import django_cache_url
    from everett.manager import ConfigManager

    config = ConfigManager.basic_config()
    CACHES = {
        "default": config("CACHE_URL", parser=django_cache_url.parse)
    }


That'll pull the ``CACHE_URL`` value from the environment (it throws an error if
it's not there) and runs it through ``django_cache_url`` which parses it and
returns what Django needs.

With a default::

    import django_cache_url
    from everett.manager import ConfigManager

    config = ConfigManager.basic_config()
    CACHES = {
        "default": config(
            "CACHE_URL", default="locmem://myapp", parser=django_cache_url.parse
        )
    }


.. Note::

   To use django-cache-url, you'll need to install it separately. Everett
   doesn't require it to be installed.


Implementing your own parsers
=============================

Implementing your own parser should be straight-forward. Parsing functions
always take a string and return the Python value you need.

If the value is not parseable, the parsing function should raise a ``ValueError``.

For example, say we wanted to implement a parser that returned yes/no/no-answer
or a parser class that's line delimited:

.. literalinclude:: ../examples/parser_examples.py
   :language: python


================================================
FILE: docs/recipes.rst
================================================
=======
Recipes
=======

This contains some ways of solving problems I've had with applications I use
Everett in. These use cases help me to shape the Everett architecture such that
it's convenient and flexible, but not big and overbearing.

Hopefully they help you, too.

If there are things you're trying to solve and you're using Everett that aren't
covered here, add an item to the `issue tracker
<https://github.com/willkg/everett/issues>`_.


.. contents::
   :local:


Centralizing configuration specification
========================================

It's easy to set up a :py:class:`everett.manager.ConfigManager` and then call it
for configuration. However, with any non-trivial application, it's likely you're
going to refer to configuration options multiple times in different parts of the
code.

One way to do this is to pull out the configuration value and store it in a
global constant or an attribute somewhere and pass that around.

Another way to do this is to create a configuration component, define all the
configuration options there and then pass that component around.

For example, this creates an ``AppConfig`` component which has configuration
for the application:

.. literalinclude:: ../examples/recipes_appconfig.py
   :language: python


Couple of nice things here. First, is that if you do Sphinx documentation, you
can use ``autocomponentconfig`` to automatically document your configuration
based on the code. Second, you can use
:py:func:`everett.manager.get_runtime_config` to print out the runtime
configuration at startup.


Using components that share configuration by passing arguments
==============================================================

Say we have multiple components that share some configuration value that's
probably managed by another component.

For example, a "basedir" configuration value that defines the root directory for
all the things this application does things with.

Let's create an app component which creates two file system components passing
them a basedir:

.. literalinclude:: ../examples/recipes_shared.py
   :language: python


Why do it this way?

In this scenario, the ``basedir`` is defined at the app-scope and is passed to
the reader and writer components when they're created. In this way, ``basedir``
is app configuration, but not reader/writer configuration.


Using components that share configuration using alternate keys
==============================================================

Say we have two components that share a set of credentials. We don't want to
have to specify the same set of credentials twice, so instead, we use alternate
keys which let you specify other keys to look at for a configuration value.
This lets us have both components look at the same keys for their credentials
and then we only have to define them once.

Let's create a db reader and a db writer component:

.. literalinclude:: ../examples/recipes_alternate_keys.py
   :language: python


================================================
FILE: docs/test_code.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
Tests the code in the ../examples/ directory before including it in the docs.
"""

import os
import subprocess
import sys


def main():
    # FIXME(willkg): This is written to run on my machine.
    for fn in os.listdir("../examples/"):
        if not fn.endswith(".py"):
            continue

        print("Running %s..." % fn)
        subprocess.check_output(["python", "../examples/%s" % fn])


if __name__ == "__main__":
    sys.exit(main())


================================================
FILE: docs/testing.rst
================================================
=======
Testing
=======

You can test your code using ``config_override`` in your tests to test various
configuration values.

For example:

.. literalinclude:: ../examples/testdebug.py
   :language: python


.. autofunction:: everett.manager.config_override
   :noindex:


================================================
FILE: examples/component_appconfig.py
================================================
# component_appconfig.py

from everett.manager import ConfigManager, Option


# Central class holding configuration information
class AppConfig:
    class Config:
        debug = Option(
            parser=bool,
            default="false",
            doc="Switch debug mode on and off.",
        )


# Build a ConfigManger to look at the process environment for
# configuration and bound to the configuration options specified in
# AppConfig


def get_config():
    manager = ConfigManager.basic_config(
        doc="Check https://example.com/configuration for docs."
    )

    # Bind the configuration manager to the AppConfig component so that
    # it handles option properties like defaults, parsers, documentation,
    # and so on.
    return manager.with_options(AppConfig())


config = get_config()

debug = config("debug")
print(f"debug: {debug}")


================================================
FILE: examples/componentapp.py
================================================
# componentapp.py

from everett.manager import ConfigManager, Option


class S3Bucket:
    class Config:
        region = Option(doc="AWS S3 region")
        bucket_name = Option(doc="AWS S3 bucket name")

    def __init__(self, config):
        # Bind the configuration to just the configuration this component
        # requires such that this component is self-contained
        self.config = config.with_options(self)

        self.region = self.config("region")
        self.bucket_name = self.config("bucket_name")

    def repr(self):
        return f"<S3Bucket {self.region} {self.bucket_name}>"


config = ConfigManager.from_dict(
    {
        "S3_SOURCE_REGION": "us-east-1",
        "S3_SOURCE_BUCKET_NAME": "mycompany_oldbucket",
        "S3_DEST_REGION": "us-east-1",
        "S3_DEST_BUCKET_NAME": "mycompany_newbucket",
    }
)

s3_config = config.with_namespace("s3")

source_bucket = S3Bucket(s3_config.with_namespace("source"))
dest_bucket = S3Bucket(s3_config.with_namespace("dest"))

print(repr(source_bucket))
print(repr(dest_bucket))


================================================
FILE: examples/components_subclass.py
================================================
# components_subclass.py

from everett.manager import ConfigManager, Option


class ComponentA:
    class Config:
        foo = Option(default="foo_from_a")
        bar = Option(default="bar_from_a")


class ComponentB(ComponentA):
    class Config:
        foo = Option(default="foo_from_b")

    def __init__(self, config):
        self.config = config.with_options(self)


config = ConfigManager.basic_config()
compb = ComponentB(config)

print(compb.config("foo"))
print(compb.config("bar"))


================================================
FILE: examples/environments.py
================================================
# environments.py

from everett import NO_VALUE
from everett.manager import listify


class NoOpEnv(object):
    def get(self, key, namespace=None):
        # The namespace is either None, a string or a list of
        # strings. This converts it into a list.
        namespace = listify(namespace)

        # FIXME: Your code to extract the key in namespace here.

        # At this point, the key doesn't exist in the namespace
        # for this environment, so return a ``NO_VALUE``.
        return NO_VALUE


================================================
FILE: examples/handling_exceptions.py
================================================
# handling_exceptions.py

import logging

from everett import InvalidValueError
from everett.manager import ConfigManager

logging.basicConfig()

config = ConfigManager.from_dict({"debug_mode": "monkey"})

try:
    some_val = config("debug_mode", parser=bool, doc="set debug mode")
except InvalidValueError:
    print("I'm sorry dear user, but DEBUG_MODE must be 'true' or 'false'.")


================================================
FILE: examples/msg_builder.py
================================================
# msg_builder.py

from everett.manager import ConfigManager, ConfigOSEnv


def build_msg_for_ini(namespace, key, parser, msg="", option_doc="", config_doc=""):
    namespace = namespace or ["main"]
    namespace = "_".join(namespace)

    return f"Dear user. {key} in section [{namespace}] is not correct. Please fix it."


config = ConfigManager(
    environments=[ConfigOSEnv()],
    msg_builder=build_msg_for_ini,
)

config("debug", default="false", parser=bool)


================================================
FILE: examples/myserver.py
================================================
# myserver.py

"""
Minimal example showing how to use configuration for a web app.
"""

from everett.manager import ConfigManager

config = ConfigManager.basic_config(
    doc="Check https://example.com/configuration for documentation."
)

host = config("host", default="localhost")
port = config("port", default="8000", parser=int)
debug_mode = config(
    "debug",
    default="False",
    parser=bool,
    doc="Set to True for debugmode; False for regular mode",
)

print(f"host: {host}")
print(f"port: {port}")
print(f"debug_mode: {debug_mode}")


================================================
FILE: examples/myserver_with_environments.py
================================================
# myserver_with_environments.py

"""
Minimal example showing how to use configuration for a web app that pulls
configuration from specified environments.
"""

import os
from everett.ext.inifile import ConfigIniEnv
from everett.manager import ConfigManager, ConfigOSEnv, ConfigDictEnv

config = ConfigManager(
    [
        # Pull from the OS environment first
        ConfigOSEnv(),
        # Fall back to the file specified by the FOO_INI OS environment
        # variable if such file exists
        ConfigIniEnv(os.environ.get("FOO_INI")),
        # Fall back to this dict of defaults
        ConfigDictEnv({"FOO_VAR": "bar"}),
    ],
    doc="Check https://example.com/configuration for documentation.",
)

host = config("host", default="localhost")
port = config("port", default="8000", parser=int)
debug_mode = config(
    "debug",
    default="False",
    parser=bool,
    doc="Set to True for debugmode; False for regular mode",
)

print(f"host: {host}")
print(f"port: {port}")
print(f"debug_mode: {debug_mode}")


================================================
FILE: examples/namespaces.py
================================================
# namespaces.py

from everett.manager import ConfigManager


def open_connection(config):
    username = config("username")
    password = config("password")
    port = config("port", default="5432", parser=int)

    print(f"Opened database with {username}/{password} on port {port}")


config = ConfigManager.from_dict(
    {
        "DB_USERNAME": "admin",
        "DB_PASSWORD": "ou812",
    }
)

# Database configuration keys are all prefixed with "db", so we want to
# retrieve database configuration keys with the "db" namespace
db_config = config.with_namespace("db")

open_connection(db_config)


================================================
FILE: examples/namespaces2.py
================================================
# namespaces2.py

from everett.manager import ConfigManager


def open_connection(config):
    username = config("username")
    password = config("password")
    port = config("port", default="5432", parser=int)

    print(f"Opened database with {username}/{password} on port {port}")


config = ConfigManager.from_dict(
    {
        "SOURCE_DB_USERNAME": "admin",
        "SOURCE_DB_PASSWORD": "ou812",
        "DEST_DB_USERNAME": "admin",
        "DEST_DB_PASSWORD": "P9rwvnnj8CidECMb",
    }
)

# Database configuration keys are all prefixed with "db", so we want to
# retrieve database configuration keys with the "db" namespace
source_db_config = config.with_namespace("source_db")
dest_db_config = config.with_namespace("dest_db")

source_conn = open_connection(source_db_config)
dest_conn = open_connection(dest_db_config)


================================================
FILE: examples/parser_examples.py
================================================
# parser_examples.py

from everett.manager import ConfigManager, get_parser


def parse_ynm(val):
    """Returns True, False or None (empty string)"""
    val = val.strip().lower()
    if not val:
        return None

    return val[0] == "y"


config = ConfigManager.from_dict(
    {"NO_ANSWER": "", "YES": "yes", "ALSO_YES": "y", "NO": "no"}
)

assert config("no_answer", parser=parse_ynm) is None
assert config("yes", parser=parse_ynm) is True
assert config("also_yes", parser=parse_ynm) is True
assert config("no", parser=parse_ynm) is False


class Pairs(object):
    def __init__(self, val_parser):
        self.val_parser = val_parser

    def __call__(self, val):
        val_parser = get_parser(self.val_parser)
        out = []
        for part in val.split(","):
            k, v = part.split(":")
            out.append((k, val_parser(v)))
        return out


config = ConfigManager.from_dict({"FOO": "a:1,b:2,c:3"})

assert config("FOO", parser=Pairs(int)) == [("a", 1), ("b", 2), ("c", 3)]


================================================
FILE: examples/recipes_alternate_keys.py
================================================
# recipes_alternate_keys.py

from everett.manager import ConfigManager, Option


class DatabaseReader:
    class Config:
        username = Option(alternate_keys=["root:db_username"])
        password = Option(alternate_keys=["root:db_password"])

    def __init__(self, config):
        self.config = config.with_options(self)


class DatabaseWriter:
    class Config:
        username = Option(alternate_keys=["root:db_username"])
        password = Option(alternate_keys=["root:db_password"])

    def __init__(self, config):
        self.config = config.with_options(self)


# Define a shared configuration
config = ConfigManager.from_dict({"DB_USERNAME": "foo", "DB_PASSWORD": "bar"})

reader = DatabaseReader(config.with_namespace("reader"))
assert reader.config("username") == "foo"
assert reader.config("password") == "bar"

writer = DatabaseWriter(config.with_namespace("writer"))
assert writer.config("username") == "foo"
assert writer.config("password") == "bar"


# Or define different credentials
config = ConfigManager.from_dict(
    {
        "READER_USERNAME": "joe",
        "READER_PASSWORD": "foo",
        "WRITER_USERNAME": "pete",
        "WRITER_PASSWORD": "bar",
    }
)

reader = DatabaseReader(config.with_namespace("reader"))
assert reader.config("username") == "joe"
assert reader.config("password") == "foo"

writer = DatabaseWriter(config.with_namespace("writer"))
assert writer.config("username") == "pete"
assert writer.config("password") == "bar"


================================================
FILE: examples/recipes_appconfig.py
================================================
# recipes_appconfig.py

import logging

from everett.manager import ConfigManager, Option


TEXT_TO_LOGGING_LEVEL = {
    "CRITICAL": 50,
    "ERROR": 40,
    "WARNING": 30,
    "INFO": 20,
    "DEBUG": 10,
}


def parse_loglevel(value):
    try:
        return TEXT_TO_LOGGING_LEVEL[value.upper()]
    except KeyError as exc:
        raise ValueError(
            f'"{value}" is not a valid logging level. Try CRITICAL, ERROR, '
            "WARNING, INFO, DEBUG"
        ) from exc


class AppConfig:
    class Config:
        debug = Option(
            parser=bool,
            default="false",
            doc="Turns on debug mode for the application",
        )
        loglevel = Option(
            parser=parse_loglevel,
            default="INFO",
            doc=(
                "Log level for the application; CRITICAL, ERROR, WARNING, INFO, DEBUG"
            ),
        )


def init_app():
    manager = ConfigManager.from_dict({})
    config = manager.with_options(AppConfig())

    logging.basicConfig(level=config("loglevel"))

    if config("debug"):
        logging.info("debug mode!")


if __name__ == "__main__":
    init_app()


================================================
FILE: examples/recipes_djangosettings.py
================================================
# recipes_djangosettings.py

from everett.manager import ConfigManager


_config = ConfigManager.basic_config()


DEBUG = _config(
    "debug", parser=bool, default="False", doc="Whether or not DEBUG mode is enabled."
)

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": _config(
            "cache_location", default="127.0.0.1:11211", doc="Memcache cache location."
        ),
        "TIMEOUT": _config(
            "cache_timeout",
            default="500",
            parser=int,
            doc="Timeout to use when accessing cache.",
        ),
        "KEY_PREFIX": _config(
            "cache_key_prefix",
            default="socorro",
            doc="Key prefix to use for all cache keys.",
        ),
    }
}


================================================
FILE: examples/recipes_shared.py
================================================
# recipes_shared.py

import os

from everett.manager import ConfigManager, Option, parse_class


class App:
    class Config:
        basedir = Option()
        reader = Option(parser=parse_class)
        writer = Option(parser=parse_class)

    def __init__(self, config):
        self.config = config.with_options(self)

        self.basedir = self.config("basedir")
        self.reader = self.config("reader")(config, self.basedir)
        self.writer = self.config("writer")(config, self.basedir)


class FilesystemReader:
    class Config:
        file_type = Option(default="json")

    def __init__(self, config, basedir):
        self.config = config.with_options(self)
        self.read_dir = os.path.join(basedir, "read")


class FilesystemWriter:
    class Config:
        file_type = Option(default="json")

    def __init__(self, config, basedir):
        self.config = config.with_options(self)
        self.write_dir = os.path.join(basedir, "write")


config = ConfigManager.from_dict(
    {
        "BASEDIR": "/tmp",
        "READER": "__main__.FilesystemReader",
        "WRITER": "__main__.FilesystemWriter",
        "READER_FILE_TYPE": "json",
        "WRITER_FILE_TYPE": "yaml",
    }
)


app = App(config)
assert app.reader.read_dir == "/tmp/read"
assert app.writer.write_dir == "/tmp/write"


================================================
FILE: examples/testdebug.py
================================================
# testdebug.py

"""
Minimal example showing how to override configuration values when testing.
"""

import unittest

from everett.manager import ConfigManager, config_override


class App:
    def __init__(self):
        config = ConfigManager.basic_config()
        self.debug = config("debug", default="False", parser=bool)


class TestDebug(unittest.TestCase):
    def test_debug_on(self):
        with config_override(DEBUG="on"):
            app = App()
            self.assertTrue(app.debug)

    def test_debug_off(self):
        with config_override(DEBUG="off"):
            app = App()
            self.assertFalse(app.debug)


if __name__ == "__main__":
    unittest.main()


================================================
FILE: justfile
================================================
sphinxbuild := "../.venv/bin/sphinx-build"

@_default:
    just --list

# Build a development environment
devenv:
    uv sync --extra sphinx --extra ini --extra yaml --extra dev --refresh --upgrade

# Run tests, linting, and static typechecking
test: devenv
    uv run tox

# Format files
format: devenv
    uv run tox exec -e py310-lint -- ruff format

# Lint files
lint: devenv
    uv run tox -e py310-lint

# Wipe devenv and build artifacts
clean:
    rm -rf .venv uv.lock
    rm -rf build dist src/everett.egg-info .tox .pytest_cache .mypy_cache
    rm -rf docs/_build/*
    find src/ tests/ -name __pycache__ | xargs rm -rf
    find src/ tests/ -name '*.pyc' | xargs rm -rf

# Runs cog and builds Sphinx docs
docs: devenv
    uv run python -m cogapp -r README.rst
    uv run python -m cogapp -r docs/components.rst
    uv run python -m cogapp -r docs/configmanager.rst
    uv run python -m cogapp -r docs/configuration.rst
    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ clean
    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ doctest
    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ html

# Build files for relase
build: devenv
    rm -rf build/ dist/
    uv run python -m build
    uv run twine check dist/*


================================================
FILE: pyproject.toml
================================================
[project]
name = "everett"
description = "Configuration library for Python applications"
version = "3.5.0"
readme = "README.rst"
keywords = ["conf", "config", "configuration", "ini", "env", "yaml"]
authors = [{name = "Will Kahn-Greene"}]
license = {text = "MPLv2"}
requires-python = ">=3.10"
dependencies = []
classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
    "Natural Language :: English",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Programming Language :: Python :: Implementation :: CPython",
    "Topic :: Software Development :: Libraries :: Python Modules",
]
urls.Homepage = "https://everett.readthedocs.io/"
urls.Source = "https://github.com/willkg/everett/"
urls.Issues = "https://github.com/willkg/everett/issues"

[project.optional-dependencies]
sphinx = [
    "sphinx",
]
ini = [
    "configobj",
]
yaml = [
    "PyYAML",
]
dev = [
    "build",
    "cogapp",
    "mypy",
    "pytest",
    "ruff",
    "tox",
    "tox-gh-actions",
    "tox-uv",
    "twine",
    "types-PyYAML",
    "Sphinx==7.2.6",
    "sphinx_rtd_theme",
]


[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"


[tool.ruff]
target-version = "py310"
src = ["src"]
line-length = 88

[tool.ruff.lint]
# Enable pycodestyle (E), pyflakes (F), and bugbear (B) rules
select = ["E", "F", "B"]

# Ignore line length violations; ruff format does its best and we can rely on
# that
ignore = ["E501"]

[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"


[tool.mypy]
python_version = "3.10"
disallow_untyped_defs = true

[[tool.mypy.overrides]]
module = "configobj.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "docutils.*"
ignore_missing_imports = true


[tool.pytest.ini_options]
filterwarnings = [
    "error",
    "ignore:::babel[.*]",
    "ignore:::jinja2[.*]",
    "ignore:::yaml[.*]",
    # Sphinx 4.2.0 uses distutils and it's deprecated in 3.10
    "ignore::DeprecationWarning:sphinx",
]


[tool.tox]
legacy_tox_ini = """
[tox]
envlist =
    py310
    py311
    py312
    py313
    py314
    py310-doctest
    py310-lint
    py310-typecheck
uv_python_preference = only-managed

[gh-actions]
python =
    3.10: py310
    3.11: py311
    3.12: py312
    3.13: py313
    3.14: py314

[testenv]
extras = dev,ini,sphinx,yaml
commands = pytest {posargs} tests/

[testenv:py310-doctest]
basepython = python3.10
commands = pytest --doctest-modules src/

[testenv:py310-lint]
allowlist_externals = ruff
basepython = python3.10
changedir = {toxinidir}
commands =
    ruff format --check tests docs examples
    ruff check src tests docs examples

[testenv:py310-typecheck]
basepython = python3.10
changedir = {toxinidir}
commands =
    mypy src/everett/
"""


================================================
FILE: src/everett/__init__.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.


"""Everett is a Python library for configuration."""

from importlib.metadata import (
    version as importlib_version,
    PackageNotFoundError,
)
from typing import Callable, Union


try:
    __version__ = importlib_version("everett")
except PackageNotFoundError:
    __version__ = "unknown"


__all__ = [
    "NO_VALUE",
    "ConfigurationError",
    "DetailedConfigurationError",
    "InvalidKeyError",
    "InvalidValueError",
    "ConfigurationMissingError",
]


# NoValue instances are always false
class NoValue:
    def __nonzero__(self) -> bool:
        return False

    def __bool__(self) -> bool:
        return False

    def __repr__(self) -> str:
        return "NO_VALUE"


#: Singleton indicating a non-value.
NO_VALUE = NoValue()


class ConfigurationError(Exception):
    """Configuration error base class."""

    pass


class InvalidKeyError(ConfigurationError):
    """Error that indicates the key is not valid for this component."""

    pass


class DetailedConfigurationError(ConfigurationError):
    """Base class for configuration errors that have a msg, namespace, key, and parser."""

    def __init__(
        self, msg: str, namespace: Union[list[str], None], key: str, parser: Callable
    ):
        self.msg = msg
        self.namespace = namespace
        self.key = key
        self.parser = parser
        super().__init__(msg, namespace, key, parser)

    def __str__(self) -> str:
        return self.msg


class ConfigurationMissingError(DetailedConfigurationError):
    """Error that indicates that required configuration is missing."""

    pass


class InvalidValueError(DetailedConfigurationError):
    """Error that indicates that the value is not valid.

    .. Note::

       Parsers should not raise this exception. Parsers should raise ``ValueError``
       when the value is not a valid value.

    """

    pass


================================================
FILE: src/everett/ext/__init__.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Holds env files that have other requirements."""


================================================
FILE: src/everett/ext/inifile.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Holds the ConfigIniEnv environment.

To use this, you must install the optional requirements::

    $ pip install 'everett[ini]'

"""

import logging
import os
from typing import Optional, Union

from configobj import ConfigObj

from everett import NO_VALUE, NoValue
from everett.manager import generate_uppercase_key, get_key_from_envs, listify


logger = logging.getLogger("everett")


class ConfigIniEnv:
    """Source for pulling configuration from INI files.

    This requires optional dependencies. You can install them with::

        $ pip install 'everett[ini]'

    Takes a path or list of possible paths to look for a INI file. It uses
    the first INI file it can find.

    If it finds no INI files in the possible paths, then this configuration
    source will be a no-op.

    This will expand ``~`` as well as work relative to the current working
    directory.

    This example looks just for the INI file specified in the environment::

        from everett.manager import ConfigManager
        from everett.ext.inifile import ConfigIniEnv

        config = ConfigManager([
            ConfigIniEnv(possible_paths=os.environ.get("FOO_INI"))
        ])


    If there's no ``FOO_INI`` in the environment, then the path will be
    ignored.

    Here's an example that looks for the INI file specified in the environment
    variable ``FOO_INI`` and failing that will look for ``.antenna.ini`` in the
    user's home directory::

        from everett.manager import ConfigManager
        from everett.ext.inifile import ConfigIniEnv

        config = ConfigManager([
            ConfigIniEnv(
                possible_paths=[
                    os.environ.get("FOO_INI"),
                    "~/.antenna.ini"
                ]
            )
        ])


    This example looks for a ``config/local.ini`` file which overrides values
    in a ``config/base.ini`` file both are relative to the current working
    directory::

        from everett.manager import ConfigManager
        from everett.ext.inifile import ConfigIniEnv

        config = ConfigManager([
            ConfigIniEnv(possible_paths="config/local.ini"),
            ConfigIniEnv(possible_paths="config/base.ini")
        ])


    Note how you can have multiple ``ConfigIniEnv`` files and this is how you
    can set Everett up to have values in one INI file override values in
    another INI file.

    INI files must have a "main" section. This is where keys that aren't in a
    namespace are placed.

    Minimal INI file::

        [main]


    In the INI file, namespace is a section. So key "user" in namespace "foo"
    is::

        [foo]
        user=someval


    Everett uses configobj, so it supports nested sections like this::

        [main]
        foo=bar

        [namespace]
        foo2=bar2

          [[namespace2]]
          foo3=bar3


    Which gives you these:

    * ``FOO``
    * ``NAMESPACE_FOO2``
    * ``NAMESPACE_NAMESPACE2_FOO3``

    See more details here:
    http://configobj.readthedocs.io/en/latest/configobj.html#the-config-file-format

    """

    def __init__(self, possible_paths: Union[str, list[str]]) -> None:
        """
        :param possible_paths: either a single string with a file path (e.g.
            ``"/etc/project.ini"`` or a list of strings with file paths

        """
        self.cfg = {}
        self.path = None
        possible_paths = listify(possible_paths)

        for path in possible_paths:
            if not path:
                continue

            path = os.path.abspath(os.path.expanduser(path.strip()))
            if path and os.path.isfile(path):
                self.path = path
                self.cfg.update(self.parse_ini_file(path))
                break

        if not self.path:
            logger.debug("No INI file found: %s", possible_paths)

    def parse_ini_file(self, path: str) -> dict:
        """Parse ini file at ``path`` and return dict."""
        cfgobj = ConfigObj(path, list_values=False)

        def extract_section(namespace: list[str], d: dict) -> dict:
            cfg = {}
            for key, val in d.items():
                if isinstance(d[key], dict):
                    cfg.update(extract_section(namespace + [key], d[key]))
                else:
                    cfg["_".join(namespace + [key]).upper()] = val

            return cfg

        return extract_section([], cfgobj.dict())

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        if not self.path:
            return NO_VALUE

        # NOTE(willkg): The "main" section is considered the root mainspace.
        namespace = namespace or ["main"]
        logger.debug("Searching %r for key: %s, namespace: %s", self, key, namespace)
        full_key = generate_uppercase_key(key, namespace)
        return get_key_from_envs(self.cfg, full_key)

    def __repr__(self) -> str:
        return "<ConfigIniEnv: %s>" % self.path


================================================
FILE: src/everett/ext/yamlfile.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Holds the ConfigYamlEnv environment.

To use this, you must install the optional requirements::

    $ pip install 'everett[yaml]'

"""

import logging
import os
from typing import Optional, Union

import yaml

from everett import ConfigurationError, NO_VALUE, NoValue
from everett.manager import generate_uppercase_key, get_key_from_envs, listify


logger = logging.getLogger("everett")


class ConfigYamlEnv:
    """Source for pulling configuration from YAML files.

    This requires optional dependencies. You can install them with::

        $ pip install 'everett[yaml]'


    Takes a path or list of possible paths to look for a YAML file. It uses
    the first YAML file it can find.

    If it finds no YAML files in the possible paths, then this configuration
    source will be a no-op.

    This will expand ``~`` as well as work relative to the current working
    directory.

    This example looks just for the YAML file specified in the environment::

        from everett.manager import ConfigManager
        from everett.ext.yamlfile import ConfigYamlEnv

        config = ConfigManager([
            ConfigYamlEnv(os.environ.get('FOO_YAML'))
        ])

    If there's no ``FOO_YAML`` in the environment, then the path will be
    ignored.

    Here's an example that looks for the YAML file specified in the environment
    variable ``FOO_YAML`` and failing that will look for ``.antenna.yaml`` in
    the user's home directory::

        from everett.manager import ConfigManager
        from everett.ext.yamlfile import ConfigYamlEnv

        config = ConfigManager([
            ConfigYamlEnv([
                os.environ.get('FOO_YAML'),
                '~/.antenna.yaml'
            ])
        ])

    This example looks for a ``config/local.yaml`` file which overrides values
    in a ``config/base.yaml`` file both are relative to the current working
    directory::

        from everett.manager import ConfigManager
        from everett.ext.yamlfile import ConfigYamlEnv

        config = ConfigManager([
            ConfigYamlEnv('config/local.yaml'),
            ConfigYamlEnv('config/base.yaml')
        ])


    Note how you can have multiple ``ConfigYamlEnv`` files. This is how you
    can set Everett up to have values in one YAML file override values in
    another YAML file.

    Everett looks for keys and values in YAML files. YAML files can be split
    into multiple documents, but Everett only looks at the first one.

    Keys are case-insensitive. You can do namespaces either in the key itself
    using ``_`` as a separator or as nested mappings.

    All values should be double-quoted.

    Here's an example::

        foo: "bar"
        FOO2: "bar"
        namespace_foo: "bar"
        namespace:
            namespace2:
                foo: "bar"

    Giving you these namespaced keys:

    * ``FOO``
    * ``FOO2``
    * ``NAMESPACE_FOO``
    * ``NAMESPACE_NAMEPSACE2_FOO``

    """

    def __init__(self, possible_paths: Union[str, list[str]]) -> None:
        """
        :param possible_paths: either a single string with a file path (e.g.
            ``"/etc/project.yaml"`` or a list of strings with file paths

        """
        self.cfg = {}
        self.path = None
        possible_paths = listify(possible_paths)

        for path in possible_paths:
            if not path:
                continue

            path = os.path.abspath(os.path.expanduser(path.strip()))
            if path and os.path.isfile(path):
                self.path = path
                self.cfg = self.parse_yaml_file(path)
                break

        if not self.path:
            logger.debug("No YAML file found: %s", possible_paths)

    def parse_yaml_file(self, path: str) -> dict:
        """Parse yaml file at ``path`` and return a dict."""
        with open(path) as fp:
            data = yaml.safe_load(fp)

        if not data:
            return {}

        def traverse(namespace: list[str], d: dict) -> dict:
            cfg = {}
            for key, val in d.items():
                if isinstance(val, dict):
                    cfg.update(traverse(namespace + [key], val))
                elif isinstance(val, str):
                    cfg["_".join(namespace + [key]).upper()] = val
                else:
                    # All values should be double-quoted strings so they
                    # parse as strings; anything else is a configuration
                    # error at parse-time
                    raise ConfigurationError(
                        "Invalid value %r in file %s: values must be double-quoted strings"
                        % (val, path)
                    )

            return cfg

        return traverse([], data)

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        if not self.path:
            return NO_VALUE

        logger.debug("Searching %r for key: %s, namepsace: %s", self, key, namespace)
        full_key = generate_uppercase_key(key, namespace)
        return get_key_from_envs(self.cfg, full_key)

    def __repr__(self) -> str:
        return "<ConfigYamlEnv: %s>" % self.path


================================================
FILE: src/everett/manager.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Contains configuration infrastructure.

This module contains the configuration classes and functions for deriving
configuration values from specified sources in the order specified.

"""

from functools import wraps
import importlib
import inspect
import logging
import os
import re
import sys
from types import TracebackType
from typing import (
    Any,
    Callable,
    Optional,
    Union,
)
from collections.abc import Iterable, Mapping

from everett import (
    ConfigurationError,
    ConfigurationMissingError,
    InvalidValueError,
    InvalidKeyError,
    NO_VALUE,
    NoValue,
)


__all__ = [
    "ChoiceOf",
    "ConfigDictEnv",
    "ConfigEnvFileEnv",
    "ConfigManager",
    "ConfigObjEnv",
    "ConfigOSEnv",
    "config_override",
    "get_config_for_class",
    "get_runtime_config",
    "ListOf",
    "Option",
    "parse_bool",
    "parse_class",
    "parse_data_size",
    "parse_env_file",
    "parse_time_period",
]


# Regex for valid keys in an env file
ENV_KEY_RE = re.compile(r"^[a-z_][a-z0-9_]*$", flags=re.IGNORECASE)

logger = logging.getLogger("everett")


def qualname(thing: Any) -> str:
    """Return the Python dotted name for a given thing.

    >>> import everett.manager
    >>> qualname(str)
    'str'
    >>> qualname(everett.manager.parse_class)
    'everett.manager.parse_class'
    >>> qualname(everett.manager)
    'everett.manager'

    :param thing: the thing to get the qualname from

    :returns: the Python dotted name

    """
    parts = []

    # Add the module, unless it's a builtin
    mod = inspect.getmodule(thing)
    if mod and mod.__name__ not in ("__main__", "__builtin__", "builtins"):
        parts.append(mod.__name__)

    if hasattr(thing, "__qualname__"):
        parts.append(thing.__qualname__)
        return ".".join(parts)

    # If it's a module
    if inspect.ismodule(thing):
        return ".".join(parts)

    # It's an instance, so ... let's call repr on it
    return repr(thing)


def build_msg(
    namespace: Optional[list[str]],
    key: Optional[str],
    parser: Optional[Callable],
    msg: str = "",
    option_doc: str = "",
    config_doc: str = "",
) -> str:
    """Builds a message for a configuration error exception

    :param namespace: list of strings that represent the configuration variable namespace or ``None``
    :param key: the configuration variable key or ``None``
    :param parser: the parser that will be used to parse the value for this configuration variable or ``None``
    :param msg: the error message
    :param option_doc: the configuration option documentation
    :param config_doc: the ConfigManager documentation

    :returns: the error message string

    """
    text = [msg]
    if key and parser:
        full_key = generate_uppercase_key(key, namespace)
        text.append(f"{full_key} requires a value parseable by {qualname(parser)}")
    else:
        full_key = None
    if option_doc and full_key:
        text.append(f"{full_key} docs: {option_doc}")
    if config_doc:
        text.append(f"Project docs: {config_doc}")

    return "\n".join([line for line in text if line])


# FIXME(willkg): we can rewrite this as a dataclass as soon as we can drop
# Python 3.6 support
class Option:
    """Settings for a single configuration option.

    Use this when creating Everett configuration components.

    Example::

        from everett.manager import Option

        class MyService:
            # Note: The Config class has to be called "Config".
            class Config:
                host = Option(default="localhost")
                port = Option(default="8000", parser=int)

    """

    def __init__(
        self,
        default: Union[str, NoValue] = NO_VALUE,
        alternate_keys: Optional[list[str]] = None,
        doc: str = "",
        parser: Callable = str,
        meta: Any = None,
    ):
        """
        :param default: the default value (if any); this must be a string that is
            parseable by the specified parser; if no default is provided, this
            will raise an error or return ``everett.NO_VALUE`` depending on
            the value of ``raise_error``

        :param alternate_keys: the list of alternate keys to look up;
            supports a ``root:`` key prefix which will cause this to look at
            the configuration root rather than the current namespace

            .. versionadded:: 0.3

        :param doc: documentation for this config option

            .. versionadded:: 0.6

        :param parser: the parser for converting this value to a Python object

        :param meta: any meta information that's tied to this option; useful
            for noting which options are related in some way or which are secrets
            that shouldn't be logged

        """
        self.default = default
        self.alternate_keys = alternate_keys
        self.doc = doc
        self.parser = parser
        self.meta = meta or {}

    def __eq__(self, obj: Any) -> bool:
        return (
            isinstance(obj, Option)
            and obj.default == self.default
            and obj.alternate_keys == self.alternate_keys
            and obj.doc == self.doc
            and obj.parser == self.parser
            and obj.meta == self.meta
        )


def get_config_for_class(cls: type) -> dict[str, tuple[Option, type]]:
    """Roll up configuration options for this class and parent classes.

    This handles subclasses overriding configuration options in parent classes.

    :param cls: the component class to return configuration options for

    :returns: final dict of configuration options for this class in
        ``key -> (option, cls)`` form

    """
    options = {}
    for subcls in reversed(cls.__mro__):
        if not hasattr(subcls, "Config"):
            continue

        subcls_config = subcls.Config
        for attr in subcls_config.__dict__.keys():
            if attr.startswith("__"):
                continue

            val = getattr(subcls_config, attr)
            if isinstance(val, Option):
                options[attr] = (val, subcls)
    return options


def traverse_tree(
    instance: Any, namespace: Optional[list[str]] = None
) -> Iterable[tuple[list[str], str, Option, Any]]:
    """Traverses a tree of objects and computes the configuration for it

    Note: This expects the tree not to have any loops or repeated nodes.

    :param instance: the component to traverse
    :param namespace: the list of strings forming the namespace or None

    :returns: list of ``(namespace, key, value, option, component)``

    """
    namespace = namespace or []

    # Check to see if this class has options; if it does, capture those and
    # traverse the tree
    this_options = get_config_for_class(instance.__class__)
    if not this_options:
        return []

    options = [
        (namespace, key, option, instance)
        for key, (option, cls) in this_options.items()
    ]

    # Now go through attributes for other options classes
    for attr in dir(instance):
        if attr.startswith("__"):
            continue
        # NOTE(willkg): we skip slots; maybe they could be component classes,
        # but that seems bizarre and I'd like to see a reasonable example
        # before supporting it
        val = getattr(instance, attr, None)
        if not val or isinstance(val, Option):
            continue

        options.extend(traverse_tree(val, namespace + [attr]))

    return options


def parse_env_file(envfile: Iterable[str]) -> dict:
    """Parse the content of an iterable of lines as ``.env``.

    Return a dict of config variables.

    >>> from everett.manager import parse_env_file
    >>> parse_env_file(["DUDE=Abides"])
    {'DUDE': 'Abides'}

    """
    data = {}
    for line_no, line in enumerate(envfile):
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" not in line:
            raise ConfigurationError(
                f"Env file line missing = operator (line {line_no + 1})"
            )
        k, v = line.split("=", 1)
        k = k.strip()
        if not ENV_KEY_RE.match(k):
            raise ConfigurationError(
                f"Invalid variable name {k!r} in env file (line {line_no + 1})"
            )

        v = v.strip()

        # Need to strip matching ' and " from beginning and end--but only one
        # round
        for quote in "'\"":
            if v.startswith(quote) and v.endswith(quote):
                v = v[1:-1]
                break

        data[k] = v

    return data


def parse_bool(val: str) -> bool:
    """Parse a bool value.

    Handles a series of values, but you should probably standardize on
    "true" and "false".

    >>> from everett.manager import parse_bool
    >>> parse_bool("y")
    True
    >>> parse_bool("FALSE")
    False

    """
    true_vals = ("t", "true", "yes", "y", "1", "on")
    false_vals = ("f", "false", "no", "n", "0", "off")

    val = val.lower()
    if val in true_vals:
        return True
    if val in false_vals:
        return False

    raise ValueError(f"{val!r} is not a valid bool value")


def parse_class(val: str) -> Any:
    """Parse a string, imports the module and returns the class.

    >>> from everett.manager import parse_class
    >>> parse_class("everett.manager.Option")
    <class 'everett.manager.Option'>

    """
    if "." not in val:
        raise ValueError(f"{val!r} is not a valid Python dotted-path")

    module_name, class_name = val.rsplit(".", 1)
    module = importlib.import_module(module_name)
    try:
        return getattr(module, class_name)
    except AttributeError as exc:
        raise ValueError(
            f"{class_name!r} is not a valid member of {qualname(module)}"
        ) from exc


_DATA_SIZE_METRIC_TO_MULTIPLIER = {
    "": 1,
    "b": 1,
    "kb": 1_000,
    "mb": pow(1_000, 2),
    "gb": pow(1_000, 3),
    "tb": pow(1_000, 4),
    "kib": 1_024,
    "mib": pow(1_024, 2),
    "gib": pow(1_024, 3),
    "tib": pow(1_024, 4),
}
_DATA_SIZE_RE = re.compile(
    r"^([0-9_]+)(" + "|".join(_DATA_SIZE_METRIC_TO_MULTIPLIER.keys()) + ")?$"
)


def parse_data_size(val: str) -> Any:
    """Parse a string denoting a data size into an int of bytes.

    This allows you to parse data sizes with a number and then the metric. Examples:

    * 10b - 10 bytes
    * 100kb - 100 kilobytes = 100 * 1000
    * 40gb - 40 gigabytes = 40 * 1000^3
    * 23gib - 40 gibibytes = 23 * 1024^3

    Supported metrics:

    * b - bytes
    * decimal:

      * kb - kilobytes
      * mb - megabytes
      * gb - gigabytes
      * tb - terabytes
      * pb - petabytes

    * binary:

      * kib - kibibytes
      * mib - mebibytes
      * gib - gibibytes
      * tib - tebibytes
      * pib - pebibytes

    The metrics are not case sensitive--it supports upper, lower, and mixed case.

    >>> from everett.manager import parse_data_size
    >>> parse_data_size("40_000_000")
    40000000
    >>> parse_data_size("40gb")
    40000000000
    >>> parse_data_size("20KiB")
    20480

    """
    fixed_val = val.lower().strip()
    try:
        return int(fixed_val)
    except ValueError:
        pass

    match = _DATA_SIZE_RE.match(fixed_val)
    if not match:
        raise ValueError(f"{val!r} is not a valid data size")
    amount, metric = match.groups()
    return int(amount) * _DATA_SIZE_METRIC_TO_MULTIPLIER[metric]


_TIME_UNIT_TO_MULTIPLIER = {
    "w": 7 * 24 * 60 * 60,
    "d": 24 * 60 * 60,
    "h": 60 * 60,
    "m": 60,
    "s": 1,
}
_TIME_RE = re.compile(r"([0-9_]+)([" + "".join(_TIME_UNIT_TO_MULTIPLIER.keys()) + r"])")


def parse_time_period(val: str) -> Any:
    """Parse a string denoting a time period into a number of seconds.

    Units:

    * w - week
    * d - day
    * h - hour
    * m - minute
    * s - second

    >>> from everett.manager import parse_time_period
    >>> parse_time_period("103")
    103
    >>> parse_time_period("1_000m")
    60000
    >>> parse_time_period("15m4s")
    904

    """
    fixed_val = val.lower().strip()
    try:
        return int(fixed_val)
    except ValueError:
        pass

    parts = _TIME_RE.findall(fixed_val)
    if not parts:
        raise ValueError(f"{val!r} is not a valid time period")

    total = 0
    for part in parts:
        amount, unit = part
        total = total + (int(amount) * _TIME_UNIT_TO_MULTIPLIER[unit])
    return total


def get_parser(parser: Callable) -> Callable:
    """Return a parsing function for a given parser."""
    # Special case bool so that we can explicitly give bool values otherwise
    # all values would be True since they're non-empty strings.
    if parser is bool:
        return parse_bool
    return parser


def listify(thing: Any) -> list[Any]:
    """Convert thing to a list.

    If thing is a string, then returns a list of thing. Otherwise
    returns thing.

    :param thing: string or list of things

    :returns: list

    """
    if thing is None:
        return []
    if isinstance(thing, str):
        return [thing]
    return thing


def generate_uppercase_key(key: str, namespace: Optional[list[str]] = None) -> str:
    """Given a key and a namespace, generates a final uppercase key.

    >>> generate_uppercase_key("foo")
    'FOO'
    >>> generate_uppercase_key("foo", ["namespace"])
    'NAMESPACE_FOO'
    >>> generate_uppercase_key("foo", ["namespace", "subnamespace"])
    'NAMESPACE_SUBNAMESPACE_FOO'

    """
    if namespace:
        namespace = [part for part in listify(namespace) if part]
        key = "_".join(namespace + [key])

    key = key.upper()
    return key


def get_key_from_envs(envs: Iterable[Any], key: str) -> Union[str, NoValue]:
    """Return the value of a key from the given dict respecting namespaces.

    Data can also be a list of data dicts.

    """
    # if it barks like a dict, make it a list have to use `get` since dicts and
    # lists both have __getitem__
    if hasattr(envs, "get"):
        envs = [envs]

    for env in envs:
        if key in env:
            return env[key]

    return NO_VALUE


class ListOf:
    """Parse a delimiter-separated list of things.

    After delimiting items, this strips the whitespace at the beginning and end
    of each string. Then it passes each string into the parser to get the final
    value.

    >>> from everett.manager import ListOf
    >>> ListOf(str)('')
    []
    >>> ListOf(str)('a,b,c,d')
    ['a', 'b', 'c', 'd']
    >>> ListOf(int)('1,2,3,4')
    [1, 2, 3, 4]
    >>> ListOf(str)('1, 2  ,3,4')
    ['1', '2', '3', '4']

    ``ListOf`` defaults to using a comma as a delimiter, but supports other
    delimiters:

    >>> ListOf(str, delimiter=":")("/path/a/:/path/b/")
    ['/path/a/', '/path/b/']

    ``ListOf`` supports raising a configuration error when one of the values
    is an empty string:

    >>> ListOf(str, allow_empty=False)("a,,b")
    Traceback (most recent call last):
      ...
    ValueError: 'a,,b' can not have empty values

    The user will get a configuration error like this::

        ValueError: 'a,,b' can not have empty values
        NAMES requires a value parseable by <ListOf(str, delimiter=',', allow_empty=False)>

    Note: This doesn't handle quotes or backslashes or any complicated string
    parsing.

    For example:

    >>> ListOf(str)('"a,b",c,d')
    ['"a', 'b"', 'c', 'd']

    """

    def __init__(
        self, parser: Callable, delimiter: str = ",", allow_empty: bool = True
    ):
        self.sub_parser = parser
        self.delimiter = delimiter
        self.allow_empty = allow_empty

    def __call__(self, value: str) -> list[Any]:
        parser = get_parser(self.sub_parser)
        if value:
            parsed_values = []
            for token in value.split(self.delimiter):
                token = token.strip()
                if not token and not self.allow_empty:
                    raise ValueError(f"{value!r} can not have empty values")
                parsed_values.append(parser(token))
            return parsed_values
        else:
            return []

    def __repr__(self) -> str:
        return (
            f"<ListOf({qualname(self.sub_parser)}, "
            + f"delimiter={self.delimiter!r}, "
            + f"allow_empty={self.allow_empty!r})>"
        )


class ChoiceOf:
    """Parser that enforces values are in a specified value domain.

    Choices can be a list of string values that are parseable by the sub
    parser. For example, say you only supported two cloud providers and need
    the configuration value to be one of "aws" or "gcp":

    >>> from everett.manager import ChoiceOf
    >>> ChoiceOf(str, choices=["aws", "gcp"])("aws")
    'aws'

    Choices works with the int sub-parser:

    >>> from everett.manager import ChoiceOf
    >>> ChoiceOf(int, choices=["1", "2", "3"])("1")
    1

    Choices works with any sub-parser:

    >>> from everett.manager import ChoiceOf, parse_data_size
    >>> ChoiceOf(parse_data_size, choices=["1kb", "1mb", "1gb"])("1mb")
    1000000

    Note: The choices list is a list of strings--these are values before being
    parsed. This makes it easier for people who are doing configuration to know
    what the values they put in their configuration files need to look like.

    """

    def __init__(self, parser: Callable, choices: list[str]):
        self.sub_parser = parser
        if not choices or not all(isinstance(choice, str) for choice in choices):
            raise ValueError(f"choices {choices!r} must be a non-empty list of strings")

        self.choices = choices

    def __call__(self, value: str) -> Any:
        parser = get_parser(self.sub_parser)
        if value and value in self.choices:
            return parser(value)
        raise ValueError(f"{value!r} is not a valid choice")

    def __repr__(self) -> str:
        return f"<ChoiceOf({qualname(self.sub_parser)}, {self.choices})>"


class ConfigOverrideEnv:
    """Override configuration layer for testing."""

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        global _CONFIG_OVERRIDE

        # Short-circuit to reduce overhead.
        if not _CONFIG_OVERRIDE:
            return NO_VALUE
        full_key = generate_uppercase_key(key, namespace)
        logger.debug(f"Searching {self!r} for {full_key}")
        return get_key_from_envs(reversed(_CONFIG_OVERRIDE), full_key)

    def __repr__(self) -> str:
        return "<ConfigOverrideEnv>"


class ConfigObjEnv:
    """Source for pulling configuration values out of a Python object.

    This is handy for a few weird situations. For example, you can use this to
    "bridge" Everett configuration with command line arguments. The argparse
    Namespace works fine here.

    Namespace (the Everett one--not the argparse one) is prefixed. So key "foo"
    in namespace "bar" is "foo_bar".

    For example::

        import argparse

        from everett.manager import ConfigObjEnv, ConfigManager

        parser = argparse.ArgumentParser()
        parser.add_argument(
            "--debug", help="to debug or not to debug"
        )
        parsed_vals = parser.parse_known_args()[0]

        config = ConfigManager([
            ConfigObjEnv(parsed_vals)
        ])

        print config("debug", parser=bool)


    Keys are not case-sensitive--everything is converted to lowercase before
    pulling it from the object.


    .. Note::

       ConfigObjEnv has nothing to do with the library configobj.

    .. versionadded:: 0.6

    """

    def __init__(self, obj: Any, force_lower: bool = True):
        self.obj = obj

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        full_key = generate_uppercase_key(key, namespace)
        full_key = full_key.lower()

        logger.debug(f"Searching {self!r} for {full_key}")

        # Build a map of lowercase -> actual key
        obj_keys = {
            item.lower(): item for item in dir(self.obj) if not item.startswith("__")
        }

        if full_key in obj_keys:
            val = getattr(self.obj, obj_keys[full_key])

            # If the value is None, then we're going to treat it as a non-valid
            # value.
            if val is not None:
                # This is goofy, but this allows people to specify arg parser
                # defaults, but do the right thing in Everett where everything
                # is a string until it's parsed.
                return str(val)

        return NO_VALUE

    def __repr__(self) -> str:
        return "<ConfigObjEnv>"


class ConfigDictEnv:
    """Source for pulling configuration out of a dict.

    This is handy for testing. You might also use it if you wanted to move all
    your defaults values into one centralized place.

    Keys are prefixed by namespaces and the whole thing is uppercased.

    For example, namespace "bar" for key "foo" becomes ``BAR_FOO`` in the
    dict.

    For example::

        from everett.manager import ConfigDictEnv, ConfigManager

        config = ConfigManager([
            ConfigDictEnv({
                "FOO_BAR": "someval",
                "BAT": "1",
            })
        ])

    Keys are not case sensitive. This also works::

        from everett.manager import ConfigDictEnv, ConfigManager

        config = ConfigManager([
            ConfigDictEnv({
                "foo_bar": "someval",
                "bat": "1",
            })
        ])

        print config("foo_bar")
        print config("FOO_BAR")
        print config.with_namespace("foo")("bar")


    Also, ``ConfigManager`` has a convenience classmethod for creating a
    ``ConfigManager`` with just a dict environment::

        from everett.manager import ConfigManager

        config = ConfigManager.from_dict({
            "FOO_BAR": "bat"
        })


    .. versionchanged:: 0.3
       Keys are no longer case-sensitive.

    """

    def __init__(self, cfg: dict):
        self.cfg = {key.upper(): val for key, val in cfg.items()}

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        full_key = generate_uppercase_key(key, namespace)
        logger.debug(f"Searching {self!r} for {full_key}")
        return get_key_from_envs(self.cfg, full_key)

    def __repr__(self) -> str:
        return f"<ConfigDictEnv: {self.cfg!r}>"


class ConfigEnvFileEnv:
    """Source for pulling configuration out of ``.env`` files.

    This source lets you specify configuration in an .env file. This
    is useful for local development when in production you use values
    in environment variables.

    Keys are prefixed by namespaces and the whole thing is uppercased.

    For example, key "foo" will be ``FOO`` in the file.

    For example, namespace "bar" for key "foo" becomes ``BAR_FOO`` in the
    file.

    Key and namespace can consist of alphanumeric characters and ``_``.

    To use, instantiate and toss in the source list::

        from everett.manager import ConfigEnvFileEnv, ConfigManager

        config = ConfigManager([
            ConfigEnvFileEnv('.env')
        ])


    For multiple paths::

        from everett.manager import ConfigEnvFileEnv, ConfigManager

        config = ConfigManager([
            ConfigEnvFileEnv([
                '.env',
                'config/prod.env'
            ])
        ])


    Here's an example .env file::

        DEBUG=true

        # secrets
        SECRET_KEY=ou812

        # database setup
        DB_HOST=localhost
        DB_PORT=5432

        # CSP reporting
        CSP_SCRIPT_SRC="'self' www.googletagmanager.com"

    """

    def __init__(self, possible_paths: Union[str, list[str]]):
        self.data = {}
        self.path = None

        possible_paths = listify(possible_paths)
        for path in possible_paths:
            if not path:
                continue

            path = os.path.abspath(os.path.expanduser(path.strip()))
            if path and os.path.isfile(path):
                self.path = path
                with open(path) as envfile:
                    self.data = parse_env_file(envfile)
                    break

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        full_key = generate_uppercase_key(key, namespace)
        logger.debug(f"Searching {self!r} for {full_key}")
        return get_key_from_envs(self.data, full_key)

    def __repr__(self) -> str:
        return f"<ConfigEnvFileEnv: {self.path!r}>"


class ConfigOSEnv:
    """Source for pulling configuration out of the environment.

    This source lets you specify configuration in the environment. This is
    useful for infrastructure related configuration like usernames and ports
    and secret configuration like passwords.

    Keys are prefixed by namespaces and the whole thing is uppercased.

    For example, key "foo" will be ``FOO`` in the environment.

    For example, namespace "bar" for key "foo" becomes ``BAR_FOO`` in the
    environment.

    Key and namespace can consist of alphanumeric characters and ``_``.

    .. Note::

       Unlike other config environments, this one is case sensitive in that
       keys defined in the environment **must** be all uppercase.

       For example, these are good::

           FOO=bar
           FOO_BAR=bar
           FOO_BAR1=bar


       This is bad::

           foo=bar


    To use, instantiate and toss in the source list::

        from everett.manager import ConfigOSEnv, ConfigManager

        config = ConfigManager([
            ConfigOSEnv()
        ])

    """

    def get(
        self, key: str, namespace: Optional[list[str]] = None
    ) -> Union[str, NoValue]:
        """Retrieve value for key."""
        full_key = generate_uppercase_key(key, namespace)
        logger.debug(f"Searching {self!r} for {full_key}")
        return get_key_from_envs(os.environ, full_key)

    def __repr__(self) -> str:
        return "<ConfigOSEnv>"


def _get_component_name(component: Any) -> str:
    if not inspect.isclass(component):
        cls = component.__class__
    else:
        cls = component
    return cls.__module__ + "." + cls.__name__


def get_runtime_config(
    config: "ConfigManager",
    component: Any,
    traverse: Callable = traverse_tree,
) -> list[tuple[list[str], str, Any, Option]]:
    """Returns configuration specification and values for a component tree

    For example, if you had a tree of components instantiated, you could
    traverse the tree and log the configuration::

        from everett.manager import (
            ConfigManager,
            generate_uppercase_key,
            get_runtime_config,
            Option,
            parse_class,
        )

        class App:
            class Config:
                debug = Option(default="False", parser=bool)
                reader = Option(parser=parse_class)
                writer = Option(parser=parse_class)

            def __init__(self, config):
                self.config = config.with_options(self)

                # App has a reader and a writer each of which has configuration
                # options
                self.reader = self.config("reader")(config.with_namespace("reader"))
                self.writer = self.config("writer")(config.with_namespace("writer"))

        class Reader:
            class Config:
                input_file = Option()

            def __init__(self, config):
                self.config = config.with_options(self)

        class Writer:
            class Config:
                output_file = Option()

            def __init__(self, config):
                self.config = config.with_options(self)

        cm = ConfigManager.from_dict(
            {
                # This specifies which reader component to use. Because we
                # specified this one, we need to define a READER_INPUT_FILE
                # value.
                "READER": "__main__.Reader",
                "READER_INPUT_FILE": "input.txt",

                # Same thing for the writer component.
                "WRITER": "__main__.Writer",
                "WRITER_OUTPUT_FILE": "output.txt",
            }
        )

        my_app = App(cm)

        # This traverses the component tree starting with my_app and then
        # traversing .reader and .writer attributes.
        for namespace, key, value, option in get_runtime_config(cm, my_app):
            full_key = generate_uppercase_key(key, namespace)
            print(f"{full_key.upper()}={value or ''}")

        # This should print out:
        # DEBUG=False
        # READER=__main__.Reader
        # READER_INPUT_FILE=input.txt
        # WRITER=__main__.Writer
        # WRITER_OUTPUT_FILE=output.txt

    :param config: a configuration manager instance
    :param component: a component or tree of components
    :param traverse: the function for traversing the component tree; see
        :py:func:`everett.manager.traverse_tree` for signature

    :returns: a list of (namespace, key, value, option) tuples

    """
    runtime_config = []
    for namespace, key, option, obj in traverse(component):
        runtime_config.append(
            (
                namespace,
                key,
                config.with_namespace(namespace).with_options(obj)(
                    key, raise_error=False, raw_value=True
                ),
                option,
            )
        )
    return runtime_config


class ConfigManager:
    """Manage multiple configuration environment layers."""

    def __init__(
        self,
        environments: list[Any],
        doc: str = "",
        msg_builder: Callable = build_msg,
        with_override: bool = True,
    ):
        """Instantiate a ConfigManager.

        :param environments: list of configuration sources to look through in
            the order they should be looked through

        :param doc: help text printed to users when they encounter configuration
            errors

            .. versionadded:: 0.6

        :param msg_builder: function that takes arguments and builds an exception
            message intended to be printed or conveyed to the user

            For example::

                def build_msg(namespace, key, parser, msg="", option_doc="", config_doc=""):
                    full_key = namespace or []
                    full_key = "_".join(full_key + [key]).upper()

                    return (
                        f"{full_key} requires a value parseable by {qualname(parser)}\\n"
                        + option_doc + "\\n"
                        + config_doc + "\\n"
                    )

        :param with_override: whether or not to insert the special override
            environment used for testing as the first environment in the list
            of sources

        """
        self.with_override = with_override
        if with_override:
            # Add ConfigOverrideEnv if it's not in the environments list already
            override = [
                env for env in environments if isinstance(env, ConfigOverrideEnv)
            ]
            if not override:
                environments.insert(0, ConfigOverrideEnv())

        self.envs = environments
        self.doc = doc
        self.msg_builder = msg_builder

        self.namespace: list[str] = []

        self.bound_component: Any = None
        self.bound_component_prefix: list[str] = []
        self.bound_component_options: Mapping[str, Any] = {}

        self.original_manager = self

    @classmethod
    def basic_config(cls, env_file: str = ".env", doc: str = "") -> "ConfigManager":
        """Return a basic ConfigManager.

        This sets up a ConfigManager that will look for configuration in
        this order:

        1. environment
        2. specified ``env_file`` defaulting to ``.env``

        This is for a fast one-line opinionated setup.

        Example::

            from everett.manager import ConfigManager

            config = ConfigManager.basic_config()


        This is shorthand for::

            config = ConfigManager(
                environments=[
                    ConfigOSEnv(),
                    ConfigEnvFileEnv(['.env'])
                ]
            )


        :param env_file: the name of the env file to use
        :param doc: help text printed to users when they encounter configuration
            errors

        :returns: a :py:class:`everett.manager.ConfigManager`

        """
        return cls(environments=[ConfigOSEnv(), ConfigEnvFileEnv([env_file])], doc=doc)

    @classmethod
    def from_dict(cls, dict_config: dict) -> "ConfigManager":
        """Create a ConfigManager with specified configuration as a Python dict.

        This is shorthand for::

            config = ConfigManager([ConfigDictEnv(dict_config)])


        This is handy for writing tests for the app you're using Everett in.

        :param dict_config: Python dict holding the configuration for this
            manager

        :returns: ConfigManager with specified configuration

        .. versionadded:: 0.3

        """
        return cls([ConfigDictEnv(dict_config)])

    def get_bound_component(self) -> Any:
        """Retrieve the bound component for this config object.

        :returns: component or None

        """
        return self.bound_component

    def get_namespace(self) -> list[str]:
        """Retrieve the complete namespace for this config object.

        :returns: namespace as a list of strings

        """
        return self.namespace

    def _get_base_config(self) -> "ConfigManager":
        return self.original_manager

    def clone(self) -> "ConfigManager":
        my_clone = ConfigManager(
            environments=list(self.envs),
            doc=self.doc,
            msg_builder=self.msg_builder,
            with_override=self.with_override,
        )
        my_clone.namespace = list(self.namespace)
        my_clone.bound_component = self.bound_component
        my_clone.bound_component_prefix = []
        my_clone.bound_component_options = self.bound_component_options

        my_clone.original_manager = self.original_manager

        return my_clone

    def with_namespace(self, namespace: Union[list[str], str]) -> "ConfigManager":
        """Apply a namespace to this configuration.

        Namespaces accumulate as you add them.

        :param namepace: namespace as a string or list of strings

        :returns: a clone of the ConfigManager instance with the namespace applied

        """
        namespace = listify(namespace)
        if not namespace:
            return self

        my_clone = self.clone()
        if my_clone.bound_component:
            my_clone.bound_component_prefix.extend(namespace)
        else:
            my_clone.namespace.extend(namespace)
        return my_clone

    def with_options(self, component: Any) -> "ConfigManager":
        """Apply options component options to this configuration.

        :param component: the instance or class with a Config to bind this
            ConfigManager to

        :returns: a clone of the ConfigManager instance bound to specified
            component

        """
        # If this is an instance, get the class
        if not inspect.isclass(component):
            component = component.__class__

        options = get_config_for_class(component)
        # NOTE(willkg): if the component has no options, then there's nothing
        # to bind to
        if not options:
            return self

        my_clone = self.clone()
        my_clone.bound_component = component
        my_clone.bound_component_prefix = []
        my_clone.bound_component_options = options

        # IF there's a bound component with a prefix, then it means someone is doing
        # something like:
        #
        # config = config.with_options(Comp).with_namespace("foo").with_options(SubComp)
        #
        # In that case, we want the namespace "foo" to be part of the namespace
        # and not part of the key prefix for SubComp.
        if self.bound_component_prefix:
            my_clone.namespace.extend(self.bound_component_prefix)

        return my_clone

    def __call__(
        self,
        key: str,
        namespace: Union[list[str], str, None] = None,
        default: Union[str, NoValue] = NO_VALUE,
        default_if_empty: bool = True,
        alternate_keys: Optional[list[str]] = None,
        doc: str = "",
        parser: Callable = str,
        raise_error: bool = True,
        raw_value: bool = False,
    ) -> Any:
        """Return a parsed value from the environment.

        :param key: the key to look up

        :param namespace: the namespace for the key--different environments
            use this differently

        :param default: the default value (if any); this must be a string that is
            parseable by the specified parser; if no default is provided, this
            will raise an error or return ``everett.NO_VALUE`` depending on
            the value of ``raise_error``

            If this ConfigManager is bound to a component, the default will be
            the default of the option in the bound component configuration.

        :param default_if_empty: if True, treat empty string values as a
            non-value and return the specified default

        :param alternate_keys: the list of alternate keys to look up;
            supports a ``root:`` key prefix which will cause this to look at
            the configuration root rather than the current namespace

            If this ConfigManager is bound to a component, the alternate_keys
            will be the alternate_keys of the option in the bound component
            configuration.

            .. versionadded:: 0.3

        :param doc: documentation for this config option

            If this ConfigManager is bound to a component, the doc will be the
            doc of the option in the bound component configuration.

            .. versionadded:: 0.6

        :param parser: the parser for converting this value to a Python object

            If this ConfigManager is bound to a component, the parser will be
            the parser of the option in the bound component configuration.

        :param raise_error: True if you want a lack of value to raise a
            ``everett.ConfigurationError``

        :param raw_value: True if you want the raw unparsed value, False otherwise

        :raises everett.ConfigurationMissingError: if the required bit of configuration
            is missing from all the environments

        :raises everett.InvalidKeyError: if the configuration key doesn't exist for
            that component

        :raises everett.InvalidValueError: if the configuration value is
            invalid in some way (not an integer, not a bool, etc)

        .. Note::

           The default value should **always** be a string that is parseable by
           the parser. This simplifies thinking about values since **all**
           values are strings that are parsed by the parser rather than default
           values do one thing and non-default values doa nother. Further, it
           simplifies documentation for the user since the default value is an
           example value.

           The parser can be any callable that takes a string value and returns
           a parsed value.

        """
        if not (default is NO_VALUE or isinstance(default, str)):
            raise ConfigurationError(f"default value {default!r} is not a string")

        # If we have a bound component, then the "namespace" is a key prefix,
        # so do that. Otherwise it's a namespace.
        if self.bound_component:
            key = "_".join(
                listify(self.bound_component_prefix) + listify(namespace) + [key]
            )
            namespace = self.namespace

        else:
            namespace = self.namespace + listify(namespace)

        # If this is a bound config, then apply everything to that
        if self.bound_component:
            try:
                option, cls = self.bound_component_options[key]
            except KeyError as exc:
                if raise_error:
                    raise InvalidKeyError(
                        f"{key!r} is not a valid key for this component"
                    ) from exc
                return None

            default = option.default
            alternate_keys = option.alternate_keys
            doc = option.doc
            parser = option.parser

        if raw_value:
            # If we're returning raw values, then we can just use str which is
            # a no-op.
            parser = str
        else:
            parser = get_parser(parser)

        # Go through all possible keys
        all_keys = [key]
        if alternate_keys:
            all_keys = all_keys + alternate_keys

        for possible_key in all_keys:
            if possible_key.startswith("root:"):
                # If this is a root-anchored key, we drop the namespace.
                possible_key = possible_key[5:]
                use_namespace = None
            else:
                use_namespace = namespace

            logger.debug(f"Looking up key: {possible_key}, namespace: {use_namespace}")

            # Go through environments in reverse order
            for env in self.envs:
                val = env.get(possible_key, use_namespace)

                # If the value is the empty string and default_if_empty is
                # True, treat it as a non-value
                if val == "" and default_if_empty:
                    val = NO_VALUE

                if val is not NO_VALUE:
                    try:
                        parsed_val = parser(val)
                        logger.debug(f"Returning raw: {val!r}, parsed: {parsed_val!r}")
                        return parsed_val
                    except ConfigurationError:
                        # Re-raise ConfigurationError and friends since that's
                        # what we want to be raising.
                        raise
                    except Exception as exc:
                        exc_type, exc_value, exc_traceback = sys.exc_info()
                        exc_type_name = exc_type.__name__ if exc_type else "None"

                        msg = self.msg_builder(
                            namespace=use_namespace,
                            key=key,
                            parser=parser,
                            msg=f"{exc_type_name}: {exc_value}",
                            option_doc=doc,
                            config_doc=self.doc,
                        )

                        raise InvalidValueError(msg, namespace, key, parser) from exc

        # Return the default if there is one
        if default is not NO_VALUE:
            try:
                parsed_val = parser(default)
                logger.debug(
                    f"Returning default raw: {default!r}, parsed: {parsed_val!r}"
                )
                return parsed_val
            except ConfigurationError:
                # Re-raise ConfigurationError and friends since that's
                # what we want to be raising.
                raise
            except Exception as exc:
                # FIXME(willkg): This is a programmer error--not a user
                # configuration error. We might want to denote that better.
                exc_type, exc_value, exc_traceback = sys.exc_info()
                exc_type_name = exc_type.__name__ if exc_type else "None"

                msg = self.msg_builder(
                    namespace=use_namespace,
                    key=key,
                    parser=parser,
                    msg=f"{exc_type_name}: {exc_value} (default value)",
                    option_doc=doc,
                    config_doc=self.doc,
                )

                raise InvalidValueError(msg, namespace, key, parser) from exc

        # No value specified and no default, so raise an error to the user
        if raise_error:
            msg = self.msg_builder(
                namespace=use_namespace,
                key=key,
                parser=parser,
                option_doc=doc,
                config_doc=self.doc,
            )

            raise ConfigurationMissingError(msg, namespace, key, parser)

        logger.debug("Found nothing--returning NO_VALUE")
        # Otherwise return NO_VALUE
        return NO_VALUE

    def raise_configuration_error(self, msg: str) -> None:
        """Convenience function for raising configuration errors.

        This is helpful for situations where you need to do additional checking
        of configuration values and need to raise a configuration error for the
        user that includes the configuration documentation.

        For example::

            from everett.manager import ConfigManager

            config = ConfigManager.basic_config()
            host = config("host")
            port = config("port")

            if host is None or port is None:
                config.raise_configuration_error(
                    "Both HOST and PORT must be specified."
                )

        :param msg: the configuration error message

        """

        msg = self.msg_builder(
            namespace=None,
            key=None,
            parser=None,
            msg=msg,
            option_doc=None,
            config_doc=self.doc,
        )
        raise ConfigurationError(msg)

    def __repr__(self) -> str:
        if self.bound_component:
            name = _get_component_name(self.bound_component)
            return f"<ConfigManager({name}): namespace:{self.get_namespace()}>"
        else:
            return f"<ConfigManager: namespace:{self.get_namespace()}>"


# This is a stack of overrides to be examined in reverse order
_CONFIG_OVERRIDE = []


class ConfigOverride:
    """Handle contexts and decoration for overriding config in testing."""

    def __init__(self, **cfg: str):
        self._cfg = cfg

    def push_config(self) -> None:
        """Push ``self._cfg`` as a config layer onto the stack."""
        _CONFIG_OVERRIDE.append(self._cfg)

    def pop_config(self) -> None:
        """Pop a config layer off.

        :raises IndexError: If there are no layers to pop off

        """
        _CONFIG_OVERRIDE.pop()

    def __enter__(self) -> None:
        self.push_config()

    def __exit__(
        self,
        exc_type: Optional[type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        self.pop_config()

    def decorate(self, fun: Callable) -> Callable:
        """Decorate a function for overriding configuration."""

        @wraps(fun)
        def _decorated(*args: Any, **kwargs: Any) -> Any:
            # Push the config, run the function and pop it afterwards.
            self.push_config()
            try:
                return fun(*args, **kwargs)
            finally:
                self.pop_config()

        return _decorated

    def __call__(self, class_or_fun: Callable) -> Callable:
        if inspect.isclass(class_or_fun):
            # If class_or_fun is a class, decorate all of its methods
            # that start with 'test'.
            for attr in class_or_fun.__dict__.keys():
                prop = getattr(class_or_fun, attr)
                if attr.startswith("test") and callable(prop):
                    setattr(class_or_fun, attr, self.decorate(prop))
            return class_or_fun

        else:
            return self.decorate(class_or_fun)


def config_override(**cfg: str) -> ConfigOverride:
    """Allow you to override config for writing tests.

    This can be used as a class decorator::

        @config_override(FOO="bar", BAZ="bat")
        class FooTestClass(object):
            ...


    This can be used as a function decorator::

        @config_override(FOO="bar")
        def test_foo():
            ...


    This can also be used as a context manager::

        def test_foo():
            with config_override(FOO="bar"):
                ...

    """
    return ConfigOverride(**cfg)


================================================
FILE: src/everett/sphinxext.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""Sphinx extension for auto-documenting components with configuration.

To use this, you must install the optional requirements::

    $ pip install 'everett[sphinx]'

"""

import ast
from importlib import import_module
import re
import textwrap
from typing import (
    TYPE_CHECKING,
    Any,
    Optional,
    Union,
)
from collections.abc import Generator

from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import ViewList, StringList
from sphinx import addnodes
from sphinx.addnodes import desc_signature, pending_xref
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.locale import _ as gettext
from sphinx.roles import XRefRole
from sphinx.util import ws_re
from sphinx.util import logging
from sphinx.util.docfields import Field
from sphinx.util.docstrings import prepare_docstring
from sphinx.util.nodes import make_refnode

from everett import NO_VALUE, __version__
from everett.manager import qualname, get_config_for_class


if TYPE_CHECKING:
    from sphinx.builders import Builder
    from sphinx.environment import BuildEnvironment


LOGGER = logging.getLogger(__name__)


def split_clspath(clspath: str) -> list[str]:
    """Split clspath into module and class names.

    Note: This is a really simplistic implementation.

    """
    return clspath.rsplit(".", 1)


def get_module_and_objpath(path: str) -> Any:
    """Given a path, imports the module part of the path and returns the module
    and the rest of the path.

    :arg clspath: a "a.b.c.Class" style path

    :returns: "a.b.c" module and "Class"

    """
    module = None
    parts = path.split(".")

    # Figure out the module
    for i in range(len(parts)):
        modpath = parts[:i]
        objpath = parts[i:]
        if not modpath:
            continue

        try:
            module = import_module(".".join(modpath))
        except ImportError:
            break

    return module, ".".join(objpath)


def import_class(clspath: str) -> Any:
    """Given a clspath, returns the class.

    Note: This is a really simplistic implementation.

    :arg clspath: a "a.b.c.Class" style path

    :returns: the Class

    """
    module, objpath = get_module_and_objpath(clspath)
    if module is None:
        raise ValueError(f"{clspath!r} does not point to a valid thing")

    obj = module
    for part in objpath.split(","):
        obj = getattr(obj, part)

    return obj


def upper_lower_none(arg: Optional[str]) -> Union[str, None]:
    """Validate arg value as "upper", "lower", or None."""
    if not arg:
        return arg

    arg = arg.strip().lower()
    if arg in ["upper", "lower"]:
        return arg

    raise ValueError('argument must be "upper", "lower" or None')


class EverettOption(ObjectDescription):
    """An Everett config option."""

    indextemplate = "everett option; %s"

    option_spec = {
        # This is the parser for the option
        "parser": directives.unchanged_required,
        # The default for this option; no value (not NO_VALUE--that's different) is
        # treated as an empty string
        "default": directives.unchanged_required,
        # Whether or not this option is required
        "required": directives.flag,
    }

    def handle_signature(self, sig: str, signode: desc_signature) -> str:
        signode.clear()
        signode += addnodes.desc_name(sig, sig)
        name = ws_re.sub(" ", sig)
        return name

    def add_target_and_index(
        self, name: str, sig: str, signode: desc_signature
    ) -> None:
        ref = self.env.ref_context.get("everett:component")
        if ref:
            targetname = f"{self.objtype}-{ref}.{name}"
            # If this is in a component, we change the name to include the
            # component name
            name = f"{ref}.{name}"
        else:
            targetname = f"{self.objtype}-{name}"

        if targetname not in self.state.document.ids:
            signode["names"].append(targetname)
            signode["ids"].append(targetname)
            signode["first"] = not self.names
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata["everett"]["objects"]
            key = (self.objtype, name)
            if key in objects:
                self.state_machine.reporter.warning(
                    f"duplicate description of {self.objtype} {name!r}, "
                    + f"other instance in {self.env.doc2path(objects[key][0])}",
                    line=self.lineno,
                )

            objects[key] = (self.env.docname, targetname)

        indextext = gettext("%s (component)") % name
        if self.indexnode is not None:
            self.indexnode["entries"].append(
                ("single", indextext, targetname, "", None)
            )

    def transform_content(self, contentnode: addnodes.desc_content) -> None:
        # We want to insert some stuff before the content

        lines = StringList()

        sourcename = "everett option"

        parser = self.options.get("parser", "str")
        default = self.options.get("default")
        is_required = ("required" in self.options) or (default is None)
        required = "Yes" if is_required else "No"

        lines.append(f":Parser: *{parser}*", sourcename)

        if default is not None:
            lines.append(f":Default: {default}", sourcename)

        lines.append(f":Required: {required}", sourcename)
        lines.append("", sourcename)

        node = nodes.paragraph()
        node.document = self.state.document
        self.state.nested_parse(lines, 0, node)

        # Insert  our new nodes before the rest of the content
        contentnode.children = node.children + contentnode.children


class EverettComponent(ObjectDescription):
    """Description of an Everett component."""

    doc_field_types = [
        Field(
            "options",
            names=("option",),
            label=gettext("Options"),
            rolename="option",
        )
    ]

    allow_nesting = False

    # FIXME(willkg): What's the signode here?
    def handle_signature(self, sig: str, signode: Any) -> str:
        """Create a signature for this thing."""
        if sig != "Configuration":
            signode.clear()

            # Add "component" which is the type of this thing
            signode += addnodes.desc_annotation("component ", "component ")

            if "." in sig:
                modname, clsname = sig.rsplit(".", 1)
            else:
                modname, clsname = "", sig

            # If there's a module name, then we add the module
            if modname:
                signode += addnodes.desc_addname(modname + ".", modname + ".")

            # Add the class name
            signode += addnodes.desc_name(clsname, clsname)
        else:
            # Add just "Configuration"
            signode += addnodes.desc_name(sig, sig)

        return sig

    def add_target_and_index(
        self, name: str, sig: str, signode: desc_signature
    ) -> None:
        """Add a target and index for this thing."""
        targetname = f"{self.objtype}-{name}"

        if targetname not in self.state.document.ids:
            signode["names"].append(targetname)
            signode["ids"].append(targetname)
            signode["first"] = not self.names
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata["everett"]["objects"]
            key = (self.objtype, name)
            if key in objects:
                self.state_machine.reporter.warning(
                    f"duplicate description of {self.objtype} {name!r}, "
                    + f"other instance in {self.env.doc2path(objects[key][0])}",
                    line=self.lineno,
                )
            objects[key] = (self.env.docname, targetname)

        indextext = gettext("%s (component)") % name
        if self.indexnode is not None:
            self.indexnode["entries"].append(
                ("single", indextext, targetname, "", None)
            )

    def before_content(self) -> None:
        if self.names:
            self.env.ref_context["everett:component"] = self.names[-1]

    def after_content(self) -> None:
        self.env.ref_context["everett:component"] = None


class EverettDomain(Domain):
    """Everett domain for component configuration."""

    name = "everett"
    label = "Everett"

    object_types = {
        "component": ObjType(gettext("component"), "component"),
        "option": ObjType(gettext("option"), "option"),
    }
    directives = {
        "component": EverettComponent,
        "option": EverettOption,
    }
    roles = {
        "component": XRefRole(),
        "option": XRefRole(),
    }
    initial_data: dict[str, dict] = {
        # (typ, clspath) -> sphinx document name
        "objects": {}
    }

    @property
    def objects(self) -> dict[tuple[str, str], tuple[str, str]]:
        return self.data.setdefault("objects", {})

    def clear_doc(self, docname: str) -> None:
        key: Any = None
        for key, val in list(self.objects.items()):
            if val[0] == docname:
                del self.objects[key]

    # FIXME(willkg): What's the value in otherdata dict?
    def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
        for key, val in otherdata["objects"].items():
            if val[0] in docnames:
                self.objects[key] = val

    def resolve_xref(
        self,
        env: "BuildEnvironment",
        fromdocname: str,
        builder: "Builder",
        typ: str,
        target: str,
        node: pending_xref,
        contnode: nodes.Element,
    ) -> Optional[nodes.Element]:
        objtypes = self.objtypes_for_role(typ) or []
        for objtype in objtypes:
            if (objtype, target) in self.objects:
                docname, labelid = self.objects[objtype, target]
                break

        else:
            docname, labelid = "", ""

        if docname:
            return make_refnode(builder, fromdocname, docname, labelid, contnode)

        return None


class ConfigDirective(Directive):
    """Base class for generating configuration"""

    def add_line(self, line: str, source: str, *lineno: int) -> None:
        """Add a line to the result"""
        self.result.append(line, source, *lineno)
        # NOTE(willkg): This makes figuring out issues easier. Leaving it here
        # for future me.
        # if line.strip():
        #     print(f">>> {line} [{source} {lineno}]")
        # else:
        #     print(">>> ")

    def generate_docs(
        self,
        component_name: str,
        component_index: str,
        docstring: str,
        sourcename: str,
        option_data: list[dict],
        more_content: Any,
    ) -> None:
        indent = "   "

        # Add the classname or 'Configuration'
        self.add_line(".. everett:component:: %s" % component_name, sourcename)
        self.add_line("", sourcename)

        # Add the docstring if there is one and if show-docstring
        if "show-docstring" in self.options and docstring:
            docstringlines = prepare_docstring(docstring)
            for i, line in enumerate(docstringlines):
                self.add_line(indent + line, sourcename, i)
            self.add_line("", "")

        # Add content from the directive if there was any
        if more_content:
            for line, src in zip(more_content.data, more_content.items, strict=True):
                self.add_line(indent + line, src[0], src[1])
            self.add_line("", "")

        if "show-table" in self.options and option_data:
            self.add_line(indent + "Configuration summary:", sourcename)
            self.add_line("", sourcename)

            # Build a table of metric items
            table: list[list[str]] = []
            table.append(["Setting", "Parser", "Required?"])
            for option_item in option_data:
                ref = f"{component_name}.{option_item['key']}"
                table.append(
                    [
                        f":everett:option:`{option_item['key']} <{ref}>`",
                        f"*{option_item['parser']}*",
                        "Yes" if option_item["default"] is NO_VALUE else "",
                    ]
                )

            for line in build_table(table):
                self.add_line(indent + line, sourcename)

            self.add_line("", sourcename)

            self.add_line(indent + "Configuration options:", sourcename)
            self.add_line("", sourcename)

        sourcename = "class definition"
        if option_data:
            # List the options and details
            for option_item in option_data:
                key = option_item["key"]
                self.add_line(f"{indent}.. everett:option:: {key}", sourcename)

                self.add_line(
                    f"{indent}   :parser: {option_item['parser']}", sourcename
                )
                if option_item["default"] is not NO_VALUE:
                    self.add_line(
                        f'{indent}   :default: "{option_item["default"]}"', sourcename
                    )
                else:
                    self.add_line(f"{indent}   :required:", sourcename)
                self.add_line("", sourcename)

                doc = option_item["doc"]
                for doc_line in doc.splitlines():
                    self.add_line(f"{indent}   {doc_line}", sourcename)

                self.add_line("", sourcename)
        else:
            # There are no options
            self.add_line(f"{indent}No configuration options.", sourcename)

        self.add_line("", sourcename)


class AutoComponentConfigDirective(ConfigDirective):
    """Directive for documenting configuration for an Everett component."""

    has_content = True
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False

    option_spec = {
        # Whether or not to show the class docstring--if None, don't show the
        # docstring, if empty string use __doc__, otherwise use the value of
        # the attribute on the class
        "show-docstring": directives.unchanged,
        # Whether or not to hide the class name
        "hide-name": directives.flag,
        # Prepend a specified namespace
        "namespace": directives.unchanged,
        # Render keys in specified case
        "case": upper_lower_none,
        # Whether or not to show a table
        "show-table": directives.flag,
    }

    def extract_configuration(
        self,
        obj: Any,
        namespace: Optional[str] = None,
        case: Optional[str] = None,
    ) -> list[dict]:
        """Extracts configuration values from list of Everett configuration options

        :param obj: object/class to extract configuration from
        :param namespace: namespace if any that these options are in
        :param case: None, "upper", or "lower" for converting the name

        :returns: list of dicts each representing an option

        """
        config = get_config_for_class(obj)
        options: list[dict] = []

        # Go through options and figure out relevant information
        for key, (option, _) in config.items():
            if namespace:
                namespaced_key = namespace + "_" + key
            else:
                namespaced_key = key

            if case == "upper":
                namespaced_key = namespaced_key.upper()
            elif case == "lower":
                namespaced_key = namespaced_key.lower()

            options.append(
                {
                    "key": namespaced_key,
                    "default": option.default,
                    "parser": qualname(option.parser),
                    "doc": option.doc,
                    "meta": {},
                }
            )
        return options

    def run(self) -> list[nodes.Node]:
        self.reporter = self.state.document.reporter
        self.result = ViewList()

        clspath = self.arguments[0]

        obj = import_class(clspath)
        sourcename = "configuration of %s" % clspath

        option_data = self.extract_configuration(
            obj=obj,
            namespace=self.options.get("namespace"),
            case=self.options.get("case"),
        )

        if "hide-name" not in self.options:
            modname, clsname = split_clspath(clspath)
            component_name = clspath
            component_index = clsname
        else:
            component_name = "Configuration"
            component_index = "Configuration"

        # Add the docstring if there is one and if show-docstring
        if "show-docstring" in self.options:
            docstring_attr = self.options["show-docstring"] or "__doc__"
            docstring = getattr(obj, docstring_attr, "")
        else:
            docstring = ""

        self.generate_docs(
            component_name=component_name,
            component_index=component_index,
            docstring=docstring,
            sourcename=sourcename,
            option_data=option_data,
            more_content=self.content,
        )

        if not self.result:
            return []

        node = nodes.paragraph()
        node.document = self.state.document
        self.state.nested_parse(self.result, 0, node)
        return node.children


SETTING_RE = re.compile(r"^[A-Z_]+$")


def build_table(table: list[list[str]]) -> list[str]:
    """Generates reST for a table.

    :param table: a 2d array of rows and columns

    :returns: list of strings

    """
    output: list[str] = []

    col_size = [0] * len(table[0])
    for row in table:
        for i, col in enumerate(row):
            col_size[i] = max(col_size[i], len(col))

    col_size = [width + 2 for width in col_size]

    # Build header
    output.append("  ".join("=" * width for width in col_size))
    output.append(
        "  ".join(
            header + (" " * (width - len(header)))
            for header, width in zip(table[0], col_size, strict=True)
        )
    )
    output.append("  ".join("=" * width for width in col_size))

    # Iterate through rows
    for row in table[1:]:
        output.append(
            "  ".join(
                col + (" " * (width - len(col)))
                for col, width in zip(row, col_size, strict=True)
            )
        )
    output.append("  ".join("=" * width for width in col_size))
    return output


class AutoModuleConfigDirective(ConfigDirective):
    """Directive for documenting configuration for a module."""

    has_content = True
    # path/to/module.py variablename
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = False

    option_spec = {
        # Whether or not to show the class docstring--if None, don't show the
        # docstring, if empty string use __doc__, otherwise use the value of
        # the attribute on the class
        "show-docstring": directives.unchanged,
        # Whether or not to hide the name
        "hide-name": directives.flag,
        # Prepend a specified namespace
        "namespace": directives.unchanged,
        # Render keys in specified case
        "case": upper_lower_none,
        # Whether or not to show a table
        "show-table": directives.flag,
    }

    def _walk_ast(self, tree: ast.AST) -> Generator[ast.AST, None, None]:
        """Walks an AST returning Assign nodes

        :param tree: the tree to walk

        :returns: generator of Assign nodes

        """
        for node in ast.walk(tree):
            if isinstance(node, (ast.Assign, ast.Dict)):
                yield node

    def extract_configuration(
        self,
        filepath: str,
        variable_name: str,
        namespace: Optional[str] = None,
        case: Optional[str] = None,
    ) -> list[dict]:
        """Extracts configuration values from a module at filepath

        :param filepath: the filepath to parse configuration from
        :param variable_name: the ConfigurationManager variable name
        :param namespace: namespace if any that these options are in
        :param case: None, "upper", or "lower" for converting the name

        :returns: list of dicts each representing an option

        """
        with open(filepath) as fp:
            source = fp.read()

        tree = ast.parse(source=source, filename=filepath, mode="exec")
        config_nodes = []

        for node in self._walk_ast(tree):
            if isinstance(node, ast.Assign):
                # Covers:
                #
                # SOMESETTING = _config("option", default="foo", ...)
                if (
                    len(node.targets) == 1
                    and isinstance(node.targets[0], ast.Name)
                    and SETTING_RE.match(node.targets[0].id)
                    and isinstance(node.value, ast.Call)
                    and isinstance(node.value.func, ast.Name)
                    and node.value.func.id == variable_name
                ):
                    config_nodes.append((node.targets[0].id, node.value))

            elif isinstance(node, ast.Dict):
                # Covers:
                #
                # SOMESETTING = {
                #     "NAME": _config("option", default="foo", ...),
                #     "NAME2": _config("option2", default="foo", ...),
                # }
                for key, val in zip(node.keys, node.values, strict=True):
                    if (
                        isinstance(key, ast.Constant)
                        and isinstance(val, ast.Call)
                        and isinstance(val.func, ast.Name)
                        and val.func.id == variable_name
                    ):
                        config_nodes.append((str(key.value), val))

        CONFIG_ARGS = [
            "key",
            "default",
            "parser",
            "doc",
            "meta",
        ]

        def extract_value(source: str, val: ast.AST) -> tuple[str, str]:
            """Returns (category, value)"""
            if isinstance(val, ast.Constant):
                return "constant", str(val.value)
            if isinstance(val, ast.Name):
                return "name", val.id
            if isinstance(val, ast.BinOp) and isinstance(val.op, ast.Add):
                _, left = extract_value(source, val.left)
                _, right = extract_value(source, val.right)
                return "binop", left + right
            return "unknown", ast.get_source_segment(source, val) or "?"

        # Using a dict here avoids the case where configuration options are
        # defined multiple times
        configuration = {}

        for name, node in config_nodes:
            args: dict[str, Any] = {
                "key": name,
                "default": NO_VALUE,
                "parser": "str",
                "doc": "",
                "meta": {},
            }
            for i, arg in enumerate(node.args):
                cat, value = extract_value(source, arg)

                # NOTE(willkg): we're dropping the cat here; but we might want
                # to do something with the category in the future, so I'm
                # leaving the figuring in for now
                args[CONFIG_ARGS[i]] = value

            for keyword in node.keywords:
                # NOTE(willkg): mypy thinks this can be None for some reason,
                # but I'm not sure why. If it is None, we should skip it.
                if keyword.arg is None:
                    continue

                cat, value = extract_value(source, keyword.value)
                if keyword.arg == "doc":
                    value = textwrap.dedent(value)

                # NOTE(willkg): we're dropping the cat here; but we might want
                # to do something with the category in the future, so I'm
                # leaving the figuring in for now
                args[keyword.arg] = value

            key = args["key"]
            if namespace:
                namespaced_key = f"{namespace}_{key}"
            else:
                namespaced_key = str(key)

            if case == "upper":
                namespaced_key = namespaced_key.upper()
            elif case == "lower":
                namespaced_key = namespaced_key.lower()

            args["key"] = namespaced_key
            configuration[name] = args

        return list(configuration.values())

    def run(self) -> list[nodes.Node]:
        self.reporter = self.state.document.reporter
        self.result = ViewList()

        clspath = self.arguments[0]

        module, objpath = get_module_and_objpath(clspath)
        if module is None:
            raise ValueError(f"{clspath!r} does not point to a valid thing")

        filepath = module.__file__
        variable_name = objpath

        if not variable_name:
            raise ValueError("Variable in module is unknown")

        sourcename = "configuration of %s" % clspath

        option_data = self.extract_configuration(
            filepath=filepath,
            variable_name=variable_name,
            namespace=self.options.get("namespace"),
            case=self.options.get("case"),
        )

        if "hide-name" not in self.options:
            modname, clsname = split_clspath(clspath)
            component_name = clspath
            component_index = clsname
        else:
            component_name = "Configuration"
            component_index = "Configuration"

        # Add the docstring if there is one and if show-docstring
        if "show-docstring" in self.options:
            obj = module
            docstring_attr = self.options["show-docstring"] or "__doc__"
            docstring = getattr(obj, docstring_attr, "")
        else:
            docstring = ""

        self.generate_docs(
            component_name=component_name,
            component_index=component_index,
            docstring=docstring,
            sourcename=sourcename,
            option_data=option_data,
            more_content=self.content,
        )

        if not self.result:
            return []

        node = nodes.paragraph()
        node.document = self.state.document
        self.state.nested_parse(self.result, 0, node)
        return node.children


# FIXME(willkg): this takes a Sphinx app
def setup(app: Any) -> dict[str, Any]:
    """Register domain and directive in Sphinx."""
    app.add_domain(EverettDomain)
    app.add_directive("autocomponentconfig", AutoComponentConfigDirective)
    app.add_directive("automoduleconfig", AutoModuleConfigDirective)

    return {
        "version": __version__,
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }


================================================
FILE: tests/basic_component_config.py
================================================
"""Basic component config."""

from everett.manager import ListOf, Option, parse_class


class ComponentBasic:
    """Basic component.

    Multiple lines.

    """

    HELP = "Help attribute value."

    class Config:
        user = Option()


class ComponentNoOptions:
    """Basic component with no options."""

    class Config:
        pass


class ComponentSubclass(ComponentBasic):
    """A different docstring."""


class ComponentOptionDefault:
    class Config:
        user = Option(default="ou812")


class ComponentOptionDoc:
    class Config:
        user = Option(doc="ou812")


class ComponentOptionDocMultiline:
    class Config:
        user = Option(doc="ou812")
        password = Option(doc="First ``paragraph``.\n\nSecond paragraph.")


class ComponentOptionDocDefault:
    class Config:
        user = Option(doc="This is some docs.", default="ou812")


class Foo:
    @classmethod
    def parse_foo_class(cls, value):
        pass

    def parse_foo_instance(self, value):
        pass


class ComponentOptionParser:
    class Config:
        user_builtin = Option(parser=int)
        user_parse_class = Option(parser=parse_class)
        user_listof = Option(parser=ListOf(str))
        user_class_method = Option(parser=Foo.parse_foo_class)
        user_instance_method = Option(parser=Foo().parse_foo_instance)


class ComponentWithDocstring:
    """This component is the best.

    The best!

    """

    class Config:
        user = Option()


class ComponentDocstringOtherAttribute:
    """Programming-focused help"""

    __everett_help__ = """
        User-focused help
    """

    class Config:
        user = Option()


================================================
FILE: tests/basic_module_config.py
================================================
"""Basic module config."""

from everett.manager import ConfigManager


_config = ConfigManager.from_dict(
    {"debug": "False", "logging_level": "INFO", "password": "pwd", "fun": "0.0"}
)


def parse_logging_level(s: str) -> str:
    if s not in ("CRITICAL", "WARNING", "INFO", "ERROR"):
        raise ValueError("invalid logging level value")
    return s


DEBUG = _config(key="debug", parser=bool, default="False", doc="Debug mode.")

LOGGING_LEVEL = _config(key="logging_level", parser=parse_logging_level, doc="Level.")

PASSWORD = _config(key="password", doc="Password field.\n\nMust be provided.")

FUN = _config(key="fun", parser=(int if 0 else float), doc="Woah.")

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": _config(
            "cache_location", default="127.0.0.1:11211", doc="The location"
        ),
    }
}

LONG_DESC = _config(
    key="long_description",
    default="",
    doc=(
        "This configuration item has a really long description that spans "
        + "several lines so we can test runtime string concatenation.\n\n"
        + "Multiple lines should work, too."
    ),
)


================================================
FILE: tests/conftest.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import pytest


@pytest.fixture
def datadir():
    return os.path.join(os.path.dirname(__file__), "data")


================================================
FILE: tests/data/config_test.ini
================================================
[main]
foo = bar
bar = test1,test2

[nsbaz]
foo = bat

  [[nsbaz2]]
  foo = bat2


================================================
FILE: tests/data/config_test_original.ini
================================================
[main]
foo_original = original


================================================
FILE: tests/ext/test_inifile.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os

from everett import NO_VALUE
from everett.ext.inifile import ConfigIniEnv


class TestConfigIniEnv:
    def test_basic_usage(self, datadir):
        ini_filename = os.path.join(datadir, "config_test.ini")
        cie = ConfigIniEnv([ini_filename])
        assert cie.get("foo") == "bar"
        assert cie.get("FOO") == "bar"
        assert cie.get("foo", namespace="nsbaz") == "bat"
        assert cie.get("foo", namespace=["nsbaz"]) == "bat"
        assert cie.get("foo", namespace=["nsbaz", "nsbaz2"]) == "bat2"

        cie = ConfigIniEnv(["/a/b/c/bogus/filename"])
        assert cie.get("foo") == NO_VALUE

    def test_multiple_files(self, datadir):
        ini_filename = os.path.join(datadir, "config_test.ini")
        ini_filename_original = os.path.join(datadir, "config_test_original.ini")
        cie = ConfigIniEnv([ini_filename, ini_filename_original])
        # Only the first found file is loaded, so foo_original does not exist
        assert cie.get("foo_original") == NO_VALUE
        cie = ConfigIniEnv([ini_filename_original])
        # ... but it is there if only the original is loaded (safety check)
        assert cie.get("foo_original") == "original"

    def test_does_not_parse_lists(self, datadir):
        ini_filename = os.path.join(datadir, "config_test.ini")
        cie = ConfigIniEnv([ini_filename])
        assert cie.get("bar") == "test1,test2"


================================================
FILE: tests/ext/test_yamlfile.py
================================================
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

im
Download .txt
gitextract_fnd3ql0f/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       └── main.yml
├── .gitignore
├── .readthedocs.yaml
├── CODEOWNERS
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs/
│   ├── Makefile
│   ├── api.rst
│   ├── components.rst
│   ├── conf.py
│   ├── configmanager.rst
│   ├── configuration.rst
│   ├── dev.rst
│   ├── documenting.rst
│   ├── environments.rst
│   ├── history.rst
│   ├── index.rst
│   ├── parsers.rst
│   ├── recipes.rst
│   ├── test_code.py
│   └── testing.rst
├── examples/
│   ├── component_appconfig.py
│   ├── componentapp.py
│   ├── components_subclass.py
│   ├── environments.py
│   ├── handling_exceptions.py
│   ├── msg_builder.py
│   ├── myserver.py
│   ├── myserver_with_environments.py
│   ├── namespaces.py
│   ├── namespaces2.py
│   ├── parser_examples.py
│   ├── recipes_alternate_keys.py
│   ├── recipes_appconfig.py
│   ├── recipes_djangosettings.py
│   ├── recipes_shared.py
│   └── testdebug.py
├── justfile
├── pyproject.toml
├── src/
│   └── everett/
│       ├── __init__.py
│       ├── ext/
│       │   ├── __init__.py
│       │   ├── inifile.py
│       │   └── yamlfile.py
│       ├── manager.py
│       └── sphinxext.py
└── tests/
    ├── basic_component_config.py
    ├── basic_module_config.py
    ├── conftest.py
    ├── data/
    │   ├── config_test.ini
    │   └── config_test_original.ini
    ├── ext/
    │   ├── test_inifile.py
    │   └── test_yamlfile.py
    ├── simple_module_config.py
    ├── test_manager.py
    └── test_sphinxext.py
Download .txt
SYMBOL INDEX (299 symbols across 25 files)

FILE: docs/test_code.py
  function main (line 14) | def main():

FILE: examples/component_appconfig.py
  class AppConfig (line 7) | class AppConfig:
    class Config (line 8) | class Config:
  function get_config (line 21) | def get_config():

FILE: examples/componentapp.py
  class S3Bucket (line 6) | class S3Bucket:
    class Config (line 7) | class Config:
    method __init__ (line 11) | def __init__(self, config):
    method repr (line 19) | def repr(self):

FILE: examples/components_subclass.py
  class ComponentA (line 6) | class ComponentA:
    class Config (line 7) | class Config:
  class ComponentB (line 12) | class ComponentB(ComponentA):
    class Config (line 13) | class Config:
    method __init__ (line 16) | def __init__(self, config):

FILE: examples/environments.py
  class NoOpEnv (line 7) | class NoOpEnv(object):
    method get (line 8) | def get(self, key, namespace=None):

FILE: examples/msg_builder.py
  function build_msg_for_ini (line 6) | def build_msg_for_ini(namespace, key, parser, msg="", option_doc="", con...

FILE: examples/namespaces.py
  function open_connection (line 6) | def open_connection(config):

FILE: examples/namespaces2.py
  function open_connection (line 6) | def open_connection(config):

FILE: examples/parser_examples.py
  function parse_ynm (line 6) | def parse_ynm(val):
  class Pairs (line 25) | class Pairs(object):
    method __init__ (line 26) | def __init__(self, val_parser):
    method __call__ (line 29) | def __call__(self, val):

FILE: examples/recipes_alternate_keys.py
  class DatabaseReader (line 6) | class DatabaseReader:
    class Config (line 7) | class Config:
    method __init__ (line 11) | def __init__(self, config):
  class DatabaseWriter (line 15) | class DatabaseWriter:
    class Config (line 16) | class Config:
    method __init__ (line 20) | def __init__(self, config):

FILE: examples/recipes_appconfig.py
  function parse_loglevel (line 17) | def parse_loglevel(value):
  class AppConfig (line 27) | class AppConfig:
    class Config (line 28) | class Config:
  function init_app (line 43) | def init_app():

FILE: examples/recipes_shared.py
  class App (line 8) | class App:
    class Config (line 9) | class Config:
    method __init__ (line 14) | def __init__(self, config):
  class FilesystemReader (line 22) | class FilesystemReader:
    class Config (line 23) | class Config:
    method __init__ (line 26) | def __init__(self, config, basedir):
  class FilesystemWriter (line 31) | class FilesystemWriter:
    class Config (line 32) | class Config:
    method __init__ (line 35) | def __init__(self, config, basedir):

FILE: examples/testdebug.py
  class App (line 12) | class App:
    method __init__ (line 13) | def __init__(self):
  class TestDebug (line 18) | class TestDebug(unittest.TestCase):
    method test_debug_on (line 19) | def test_debug_on(self):
    method test_debug_off (line 24) | def test_debug_off(self):

FILE: src/everett/__init__.py
  class NoValue (line 32) | class NoValue:
    method __nonzero__ (line 33) | def __nonzero__(self) -> bool:
    method __bool__ (line 36) | def __bool__(self) -> bool:
    method __repr__ (line 39) | def __repr__(self) -> str:
  class ConfigurationError (line 47) | class ConfigurationError(Exception):
  class InvalidKeyError (line 53) | class InvalidKeyError(ConfigurationError):
  class DetailedConfigurationError (line 59) | class DetailedConfigurationError(ConfigurationError):
    method __init__ (line 62) | def __init__(
    method __str__ (line 71) | def __str__(self) -> str:
  class ConfigurationMissingError (line 75) | class ConfigurationMissingError(DetailedConfigurationError):
  class InvalidValueError (line 81) | class InvalidValueError(DetailedConfigurationError):

FILE: src/everett/ext/inifile.py
  class ConfigIniEnv (line 26) | class ConfigIniEnv:
    method __init__ (line 127) | def __init__(self, possible_paths: Union[str, list[str]]) -> None:
    method parse_ini_file (line 150) | def parse_ini_file(self, path: str) -> dict:
    method get (line 166) | def get(
    method __repr__ (line 179) | def __repr__(self) -> str:

FILE: src/everett/ext/yamlfile.py
  class ConfigYamlEnv (line 26) | class ConfigYamlEnv:
    method __init__ (line 112) | def __init__(self, possible_paths: Union[str, list[str]]) -> None:
    method parse_yaml_file (line 135) | def parse_yaml_file(self, path: str) -> dict:
    method get (line 163) | def get(
    method __repr__ (line 174) | def __repr__(self) -> str:

FILE: src/everett/manager.py
  function qualname (line 64) | def qualname(thing: Any) -> str:
  function build_msg (line 99) | def build_msg(
  class Option (line 135) | class Option:
    method __init__ (line 152) | def __init__(
    method __eq__ (line 189) | def __eq__(self, obj: Any) -> bool:
  function get_config_for_class (line 200) | def get_config_for_class(cls: type) -> dict[str, tuple[Option, type]]:
  function traverse_tree (line 227) | def traverse_tree(
  function parse_env_file (line 269) | def parse_env_file(envfile: Iterable[str]) -> dict:
  function parse_bool (line 309) | def parse_bool(val: str) -> bool:
  function parse_class (line 334) | def parse_class(val: str) -> Any:
  function parse_data_size (line 372) | def parse_data_size(val: str) -> Any:
  function parse_time_period (line 435) | def parse_time_period(val: str) -> Any:
  function get_parser (line 472) | def get_parser(parser: Callable) -> Callable:
  function listify (line 481) | def listify(thing: Any) -> list[Any]:
  function generate_uppercase_key (line 499) | def generate_uppercase_key(key: str, namespace: Optional[list[str]] = No...
  function get_key_from_envs (line 518) | def get_key_from_envs(envs: Iterable[Any], key: str) -> Union[str, NoVal...
  class ListOf (line 536) | class ListOf:
    method __init__ (line 582) | def __init__(
    method __call__ (line 589) | def __call__(self, value: str) -> list[Any]:
    method __repr__ (line 602) | def __repr__(self) -> str:
  class ChoiceOf (line 610) | class ChoiceOf:
    method __init__ (line 639) | def __init__(self, parser: Callable, choices: list[str]):
    method __call__ (line 646) | def __call__(self, value: str) -> Any:
    method __repr__ (line 652) | def __repr__(self) -> str:
  class ConfigOverrideEnv (line 656) | class ConfigOverrideEnv:
    method get (line 659) | def get(
    method __repr__ (line 672) | def __repr__(self) -> str:
  class ConfigObjEnv (line 676) | class ConfigObjEnv:
    method __init__ (line 717) | def __init__(self, obj: Any, force_lower: bool = True):
    method get (line 720) | def get(
    method __repr__ (line 747) | def __repr__(self) -> str:
  class ConfigDictEnv (line 751) | class ConfigDictEnv:
    method __init__ (line 804) | def __init__(self, cfg: dict):
    method get (line 807) | def get(
    method __repr__ (line 815) | def __repr__(self) -> str:
  class ConfigEnvFileEnv (line 819) | class ConfigEnvFileEnv:
    method __init__ (line 872) | def __init__(self, possible_paths: Union[str, list[str]]):
    method get (line 888) | def get(
    method __repr__ (line 896) | def __repr__(self) -> str:
  class ConfigOSEnv (line 900) | class ConfigOSEnv:
    method get (line 943) | def get(
    method __repr__ (line 951) | def __repr__(self) -> str:
  function _get_component_name (line 955) | def _get_component_name(component: Any) -> str:
  function get_runtime_config (line 963) | def get_runtime_config(
  class ConfigManager (line 1061) | class ConfigManager:
    method __init__ (line 1064) | def __init__(
    method basic_config (line 1123) | def basic_config(cls, env_file: str = ".env", doc: str = "") -> "Confi...
    method from_dict (line 1161) | def from_dict(cls, dict_config: dict) -> "ConfigManager":
    method get_bound_component (line 1181) | def get_bound_component(self) -> Any:
    method get_namespace (line 1189) | def get_namespace(self) -> list[str]:
    method _get_base_config (line 1197) | def _get_base_config(self) -> "ConfigManager":
    method clone (line 1200) | def clone(self) -> "ConfigManager":
    method with_namespace (line 1216) | def with_namespace(self, namespace: Union[list[str], str]) -> "ConfigM...
    method with_options (line 1237) | def with_options(self, component: Any) -> "ConfigManager":
    method __call__ (line 1274) | def __call__(
    method raise_configuration_error (line 1483) | def raise_configuration_error(self, msg: str) -> None:
    method __repr__ (line 1517) | def __repr__(self) -> str:
  class ConfigOverride (line 1529) | class ConfigOverride:
    method __init__ (line 1532) | def __init__(self, **cfg: str):
    method push_config (line 1535) | def push_config(self) -> None:
    method pop_config (line 1539) | def pop_config(self) -> None:
    method __enter__ (line 1547) | def __enter__(self) -> None:
    method __exit__ (line 1550) | def __exit__(
    method decorate (line 1558) | def decorate(self, fun: Callable) -> Callable:
    method __call__ (line 1572) | def __call__(self, class_or_fun: Callable) -> Callable:
  function config_override (line 1586) | def config_override(**cfg: str) -> ConfigOverride:

FILE: src/everett/sphinxext.py
  function split_clspath (line 52) | def split_clspath(clspath: str) -> list[str]:
  function get_module_and_objpath (line 61) | def get_module_and_objpath(path: str) -> Any:
  function import_class (line 88) | def import_class(clspath: str) -> Any:
  function upper_lower_none (line 109) | def upper_lower_none(arg: Optional[str]) -> Union[str, None]:
  class EverettOption (line 121) | class EverettOption(ObjectDescription):
    method handle_signature (line 136) | def handle_signature(self, sig: str, signode: desc_signature) -> str:
    method add_target_and_index (line 142) | def add_target_and_index(
    method transform_content (line 177) | def transform_content(self, contentnode: addnodes.desc_content) -> None:
  class EverettComponent (line 205) | class EverettComponent(ObjectDescription):
    method handle_signature (line 220) | def handle_signature(self, sig: str, signode: Any) -> str:
    method add_target_and_index (line 245) | def add_target_and_index(
    method before_content (line 273) | def before_content(self) -> None:
    method after_content (line 277) | def after_content(self) -> None:
  class EverettDomain (line 281) | class EverettDomain(Domain):
    method objects (line 305) | def objects(self) -> dict[tuple[str, str], tuple[str, str]]:
    method clear_doc (line 308) | def clear_doc(self, docname: str) -> None:
    method merge_domaindata (line 315) | def merge_domaindata(self, docnames: list[str], otherdata: dict[str, A...
    method resolve_xref (line 320) | def resolve_xref(
  class ConfigDirective (line 345) | class ConfigDirective(Directive):
    method add_line (line 348) | def add_line(self, line: str, source: str, *lineno: int) -> None:
    method generate_docs (line 358) | def generate_docs(
  class AutoComponentConfigDirective (line 441) | class AutoComponentConfigDirective(ConfigDirective):
    method extract_configuration (line 464) | def extract_configuration(
    method run (line 505) | def run(self) -> list[nodes.Node]:
  function build_table (line 556) | def build_table(table: list[list[str]]) -> list[str]:
  class AutoModuleConfigDirective (line 595) | class AutoModuleConfigDirective(ConfigDirective):
    method _walk_ast (line 619) | def _walk_ast(self, tree: ast.AST) -> Generator[ast.AST, None, None]:
    method extract_configuration (line 631) | def extract_configuration(
    method run (line 756) | def run(self) -> list[nodes.Node]:
  function setup (line 816) | def setup(app: Any) -> dict[str, Any]:

FILE: tests/basic_component_config.py
  class ComponentBasic (line 6) | class ComponentBasic:
    class Config (line 15) | class Config:
  class ComponentNoOptions (line 19) | class ComponentNoOptions:
    class Config (line 22) | class Config:
  class ComponentSubclass (line 26) | class ComponentSubclass(ComponentBasic):
  class ComponentOptionDefault (line 30) | class ComponentOptionDefault:
    class Config (line 31) | class Config:
  class ComponentOptionDoc (line 35) | class ComponentOptionDoc:
    class Config (line 36) | class Config:
  class ComponentOptionDocMultiline (line 40) | class ComponentOptionDocMultiline:
    class Config (line 41) | class Config:
  class ComponentOptionDocDefault (line 46) | class ComponentOptionDocDefault:
    class Config (line 47) | class Config:
  class Foo (line 51) | class Foo:
    method parse_foo_class (line 53) | def parse_foo_class(cls, value):
    method parse_foo_instance (line 56) | def parse_foo_instance(self, value):
  class ComponentOptionParser (line 60) | class ComponentOptionParser:
    class Config (line 61) | class Config:
  class ComponentWithDocstring (line 69) | class ComponentWithDocstring:
    class Config (line 76) | class Config:
  class ComponentDocstringOtherAttribute (line 80) | class ComponentDocstringOtherAttribute:
    class Config (line 87) | class Config:

FILE: tests/basic_module_config.py
  function parse_logging_level (line 11) | def parse_logging_level(s: str) -> str:

FILE: tests/conftest.py
  function datadir (line 10) | def datadir():

FILE: tests/ext/test_inifile.py
  class TestConfigIniEnv (line 11) | class TestConfigIniEnv:
    method test_basic_usage (line 12) | def test_basic_usage(self, datadir):
    method test_multiple_files (line 24) | def test_multiple_files(self, datadir):
    method test_does_not_parse_lists (line 34) | def test_does_not_parse_lists(self, datadir):

FILE: tests/ext/test_yamlfile.py
  class TestConfigYamlEnv (line 41) | class TestConfigYamlEnv:
    method test_missing_file (line 42) | def test_missing_file(self):
    method test_flat (line 46) | def test_flat(self, tmpdir):
    method test_flat_caps (line 57) | def test_flat_caps(self, tmpdir):
    method test_hierarchical (line 70) | def test_hierarchical(self, tmpdir):
    method test_multiple_files (line 81) | def test_multiple_files(self, tmpdir):
    method test_non_string_values (line 97) | def test_non_string_values(self, tmpdir):

FILE: tests/test_manager.py
  function test_qualname (line 63) | def test_qualname(thing, expected):
  function test_get_config_for_class (line 67) | def test_get_config_for_class():
  function test_get_config_for_class_complex_mro (line 78) | def test_get_config_for_class_complex_mro():
  function test_no_value (line 115) | def test_no_value():
  function test_parse_bool_error (line 121) | def test_parse_bool_error():
  function test_listify (line 137) | def test_listify(data, expected):
  function test_parse_bool_true (line 144) | def test_parse_bool_true(data):
  function test_parse_bool_false (line 152) | def test_parse_bool_false(data):
  function test_parse_bool_with_config (line 156) | def test_parse_bool_with_config():
  function test_parse_missing_class (line 176) | def test_parse_missing_class():
  function test_parse_class (line 184) | def test_parse_class():
  function test_parse_data_size (line 211) | def test_parse_data_size(text, expected):
  function test_parse_data_size_bad_values (line 216) | def test_parse_data_size_bad_values(text):
  function test_parse_time_period (line 235) | def test_parse_time_period(text, expected):
  function test_parse_time_period_bad_values (line 240) | def test_parse_time_period_bad_values(text):
  function test_parse_class_config (line 245) | def test_parse_class_config():
  function test_get_parser (line 265) | def test_get_parser():
  function test_ListOf (line 275) | def test_ListOf():
  function test_ListOf_error (line 285) | def test_ListOf_error():
  function test_ListOf_allow_empty_error (line 297) | def test_ListOf_allow_empty_error():
  function test_ChoiceOf (line 309) | def test_ChoiceOf():
  function test_ChoiceOf_bad_choices (line 319) | def test_ChoiceOf_bad_choices():
  function test_ChoiceOf_error (line 333) | def test_ChoiceOf_error():
  class TestConfigObjEnv (line 349) | class TestConfigObjEnv:
    method test_basic (line 350) | def test_basic(self):
    method test_with_argparse (line 363) | def test_with_argparse(self):
    method test_with_argparse_actions (line 378) | def test_with_argparse_actions(self):
  function test_ConfigDictEnv (line 400) | def test_ConfigDictEnv():
  function test_ConfigOSEnv (line 416) | def test_ConfigOSEnv():
  function test_ConfigEnvFileEnv (line 426) | def test_ConfigEnvFileEnv(datadir):
  function test_parse_env_file_line (line 468) | def test_parse_env_file_line(line, expected):
  function test_parse_env_file_errors (line 472) | def test_parse_env_file_errors():
  function test_generate_uppercase_key (line 498) | def test_generate_uppercase_key(key, ns, expected):
  function test_get_key_from_envs (line 503) | def test_get_key_from_envs():
  function test_config (line 518) | def test_config():
  function test_raise_configuration_error (line 546) | def test_raise_configuration_error():
  function test_invalidvalueerror (line 555) | def test_invalidvalueerror():
  function test_configurationmissingerror (line 564) | def test_configurationmissingerror():
  function test_config_from_dict (line 581) | def test_config_from_dict():
  function test_basic_config (line 591) | def test_basic_config(datadir):
  function test_basic_config_with_docs (line 606) | def test_basic_config_with_docs(datadir):
  function test_config_manager_doc (line 612) | def test_config_manager_doc():
  function test_config_override (line 637) | def test_config_override():
  function test_default_must_be_string (line 653) | def test_default_must_be_string():
  function test_default_if_empty (line 660) | def test_default_if_empty():
  function test_with_namespace (line 669) | def test_with_namespace():
  function test_get_namespace (line 688) | def test_get_namespace():
  function test_alternate_keys (line 711) | def test_alternate_keys(key, alternate_keys, expected):
  function test_alternate_keys_with_namespace (line 729) | def test_alternate_keys_with_namespace(key, alternate_keys, expected):
  function test_raw_value (line 744) | def test_raw_value():
  function test_with_options (line 756) | def test_with_options():
  function test_nested_options (line 784) | def test_nested_options():
  function test_namespace_and_options (line 804) | def test_namespace_and_options():
  function test_options_and_namespace (line 817) | def test_options_and_namespace():
  function test_default_comes_from_options (line 837) | def test_default_comes_from_options():
  function test_parser_comes_from_options (line 852) | def test_parser_comes_from_options():
  function test_component_get_namespace (line 867) | def test_component_get_namespace():
  function test_component_alternate_keys (line 890) | def test_component_alternate_keys():
  function test_component_doc (line 908) | def test_component_doc():
  function test_component_raw_value (line 929) | def test_component_raw_value():
  class TestGetRuntimeConfig (line 957) | class TestGetRuntimeConfig:
    method test_bound_config (line 958) | def test_bound_config(self):
    method test_tree_with_specified_namespace (line 975) | def test_tree_with_specified_namespace(self):
    method test_tree_inferred_namespace (line 1002) | def test_tree_inferred_namespace(self):
    method test_slots (line 1030) | def test_slots(self):

FILE: tests/test_sphinxext.py
  function run_sphinx (line 13) | def run_sphinx(docsdir, text, builder="text"):
  function test_infrastructure (line 43) | def test_infrastructure(tmpdir):
  function test_everett_component (line 54) | def test_everett_component(tmpdir, capsys):
  class Test_autocomponentconfig (line 93) | class Test_autocomponentconfig:
    method test_basic (line 94) | def test_basic(self, tmpdir, capsys):
    method test_no_option_data (line 118) | def test_no_option_data(self, tmpdir, capsys):
    method test_hide_name (line 136) | def test_hide_name(self, tmpdir, capsys):
    method test_namespace (line 163) | def test_namespace(self, tmpdir, capsys):
    method test_case_bad_value (line 189) | def test_case_bad_value(self, tmpdir, capsys):
    method test_case_lower (line 204) | def test_case_lower(self, tmpdir, capsys):
    method test_case_upper (line 230) | def test_case_upper(self, tmpdir, capsys):
    method test_show_docstring_class_has_no_docstring (line 256) | def test_show_docstring_class_has_no_docstring(self, tmpdir, capsys):
    method test_show_docstring (line 287) | def test_show_docstring(self, tmpdir, capsys):
    method test_show_docstring_other_attribute (line 317) | def test_show_docstring_other_attribute(self, tmpdir, capsys):
    method test_show_docstring_subclass (line 345) | def test_show_docstring_subclass(self, tmpdir, capsys):
    method test_option_default (line 373) | def test_option_default(self, tmpdir, capsys):
    method test_option_doc (line 400) | def test_option_doc(self, tmpdir, capsys):
    method test_option_doc_multiline (line 426) | def test_option_doc_multiline(self, tmpdir, capsys):
    method test_option_doc_default (line 464) | def test_option_doc_default(self, tmpdir, capsys):
    method test_option_parser (line 493) | def test_option_parser(self, tmpdir, capsys):
  class Test_automoduleconfig (line 550) | class Test_automoduleconfig:
    method test_basic (line 551) | def test_basic(self, tmpdir, capsys):
    method test_hide_name (line 643) | def test_hide_name(self, tmpdir, capsys):
    method test_namespace (line 670) | def test_namespace(self, tmpdir, capsys):
    method test_case_upper (line 697) | def test_case_upper(self, tmpdir, capsys):
    method test_case_lower (line 724) | def test_case_lower(self, tmpdir, capsys):
    method test_show_table (line 751) | def test_show_table(self, tmpdir, capsys):
    method test_show_docstring (line 788) | def test_show_docstring(self, tmpdir, capsys):
    method test_show_docstring_by_attribute (line 817) | def test_show_docstring_by_attribute(self, tmpdir, capsys):
    method test_import_error (line 846) | def test_import_error(self, tmpdir, capsys):
  class Test_everett_option (line 862) | class Test_everett_option:
    method test_basic (line 863) | def test_basic(self, tmpdir, capsys):
    method test_thorough (line 885) | def test_thorough(self, tmpdir, capsys):
    method test_no_default_means_required (line 912) | def test_no_default_means_required(self, tmpdir, capsys):
  class Test_everett_component (line 936) | class Test_everett_component:
    method test_basic (line 937) | def test_basic(self, tmpdir, capsys):
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (268K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 155,
    "preview": "---\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthl"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 656,
    "preview": "---\nname: CI\n\non:\n  push:\n    branches:\n      - 'main'\n  pull_request:\n    branches:\n      - 'main'\n\njobs:\n  build:\n    "
  },
  {
    "path": ".gitignore",
    "chars": 720,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".readthedocs.yaml",
    "chars": 560,
    "preview": "---\n# Read the Docs configuration file\n\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# R"
  },
  {
    "path": "CODEOWNERS",
    "chars": 10,
    "preview": "* @willkg\n"
  },
  {
    "path": "HISTORY.rst",
    "chars": 13703,
    "preview": "History\n=======\n\n3.5.0 (October 15th, 2025)\n--------------------------\n\nBackwards incompatibel changes:\n\n* Drop support "
  },
  {
    "path": "LICENSE",
    "chars": 15885,
    "preview": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n     means each individual or legal entity that"
  },
  {
    "path": "MANIFEST.in",
    "chars": 340,
    "preview": "include *.rst\ninclude pyproject.toml\ninclude justfile\ninclude LICENSE\ninclude .readthedocs.yaml\nrecursive-include docs *"
  },
  {
    "path": "README.rst",
    "chars": 7711,
    "preview": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n=======\nEvere"
  },
  {
    "path": "docs/Makefile",
    "chars": 674,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHI"
  },
  {
    "path": "docs/api.rst",
    "chars": 1392,
    "preview": "===\nAPI\n===\n\nThis is the API of functions and classes in Everett.\n\nConfiguration things:\n\n* :py:class:`everett.manager.C"
  },
  {
    "path": "docs/components.rst",
    "chars": 5435,
    "preview": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n==========\nCo"
  },
  {
    "path": "docs/conf.py",
    "chars": 3055,
    "preview": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common op"
  },
  {
    "path": "docs/configmanager.rst",
    "chars": 3371,
    "preview": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n============="
  },
  {
    "path": "docs/configuration.rst",
    "chars": 7763,
    "preview": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n============="
  },
  {
    "path": "docs/dev.rst",
    "chars": 433,
    "preview": "==================\nDeveloping Everett\n==================\n\nInstall for development\n=======================\n\nRequirements:"
  },
  {
    "path": "docs/documenting.rst",
    "chars": 8792,
    "preview": "=========================\nDocumenting configuration\n=========================\n\n.. contents::\n   :local:\n\nIt's hard to ke"
  },
  {
    "path": "docs/environments.rst",
    "chars": 1784,
    "preview": "==========================\nConfiguration environments\n==========================\n\n.. contents::\n   :local:\n\nDict (Config"
  },
  {
    "path": "docs/history.rst",
    "chars": 28,
    "preview": ".. include:: ../HISTORY.rst\n"
  },
  {
    "path": "docs/index.rst",
    "chars": 308,
    "preview": ".. include:: ../README.rst\n\n\nContents\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   configmanager\n   configuration\n   parse"
  },
  {
    "path": "docs/parsers.rst",
    "chars": 5371,
    "preview": "=======\nParsers\n=======\n\n.. contents::\n   :local:\n\n\nWhat's a parser?\n================\n\nAll parsers are functions that ta"
  },
  {
    "path": "docs/recipes.rst",
    "chars": 2961,
    "preview": "=======\nRecipes\n=======\n\nThis contains some ways of solving problems I've had with applications I use\nEverett in. These "
  },
  {
    "path": "docs/test_code.py",
    "chars": 651,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "docs/testing.rst",
    "chars": 272,
    "preview": "=======\nTesting\n=======\n\nYou can test your code using ``config_override`` in your tests to test various\nconfiguration va"
  },
  {
    "path": "examples/component_appconfig.py",
    "chars": 859,
    "preview": "# component_appconfig.py\n\nfrom everett.manager import ConfigManager, Option\n\n\n# Central class holding configuration info"
  },
  {
    "path": "examples/componentapp.py",
    "chars": 1057,
    "preview": "# componentapp.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass S3Bucket:\n    class Config:\n        region "
  },
  {
    "path": "examples/components_subclass.py",
    "chars": 496,
    "preview": "# components_subclass.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass ComponentA:\n    class Config:\n      "
  },
  {
    "path": "examples/environments.py",
    "chars": 512,
    "preview": "# environments.py\n\nfrom everett import NO_VALUE\nfrom everett.manager import listify\n\n\nclass NoOpEnv(object):\n    def get"
  },
  {
    "path": "examples/handling_exceptions.py",
    "chars": 384,
    "preview": "# handling_exceptions.py\n\nimport logging\n\nfrom everett import InvalidValueError\nfrom everett.manager import ConfigManage"
  },
  {
    "path": "examples/msg_builder.py",
    "chars": 466,
    "preview": "# msg_builder.py\n\nfrom everett.manager import ConfigManager, ConfigOSEnv\n\n\ndef build_msg_for_ini(namespace, key, parser,"
  },
  {
    "path": "examples/myserver.py",
    "chars": 550,
    "preview": "# myserver.py\n\n\"\"\"\nMinimal example showing how to use configuration for a web app.\n\"\"\"\n\nfrom everett.manager import Conf"
  },
  {
    "path": "examples/myserver_with_environments.py",
    "chars": 1021,
    "preview": "# myserver_with_environments.py\n\n\"\"\"\nMinimal example showing how to use configuration for a web app that pulls\nconfigura"
  },
  {
    "path": "examples/namespaces.py",
    "chars": 603,
    "preview": "# namespaces.py\n\nfrom everett.manager import ConfigManager\n\n\ndef open_connection(config):\n    username = config(\"usernam"
  },
  {
    "path": "examples/namespaces2.py",
    "chars": 832,
    "preview": "# namespaces2.py\n\nfrom everett.manager import ConfigManager\n\n\ndef open_connection(config):\n    username = config(\"userna"
  },
  {
    "path": "examples/parser_examples.py",
    "chars": 1005,
    "preview": "# parser_examples.py\n\nfrom everett.manager import ConfigManager, get_parser\n\n\ndef parse_ynm(val):\n    \"\"\"Returns True, F"
  },
  {
    "path": "examples/recipes_alternate_keys.py",
    "chars": 1480,
    "preview": "# recipes_alternate_keys.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass DatabaseReader:\n    class Config:"
  },
  {
    "path": "examples/recipes_appconfig.py",
    "chars": 1151,
    "preview": "# recipes_appconfig.py\n\nimport logging\n\nfrom everett.manager import ConfigManager, Option\n\n\nTEXT_TO_LOGGING_LEVEL = {\n  "
  },
  {
    "path": "examples/recipes_djangosettings.py",
    "chars": 797,
    "preview": "# recipes_djangosettings.py\n\nfrom everett.manager import ConfigManager\n\n\n_config = ConfigManager.basic_config()\n\n\nDEBUG "
  },
  {
    "path": "examples/recipes_shared.py",
    "chars": 1314,
    "preview": "# recipes_shared.py\n\nimport os\n\nfrom everett.manager import ConfigManager, Option, parse_class\n\n\nclass App:\n    class Co"
  },
  {
    "path": "examples/testdebug.py",
    "chars": 685,
    "preview": "# testdebug.py\n\n\"\"\"\nMinimal example showing how to override configuration values when testing.\n\"\"\"\n\nimport unittest\n\nfro"
  },
  {
    "path": "justfile",
    "chars": 1215,
    "preview": "sphinxbuild := \"../.venv/bin/sphinx-build\"\n\n@_default:\n    just --list\n\n# Build a development environment\ndevenv:\n    uv"
  },
  {
    "path": "pyproject.toml",
    "chars": 3075,
    "preview": "[project]\nname = \"everett\"\ndescription = \"Configuration library for Python applications\"\nversion = \"3.5.0\"\nreadme = \"REA"
  },
  {
    "path": "src/everett/__init__.py",
    "chars": 2066,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "src/everett/ext/__init__.py",
    "chars": 252,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "src/everett/ext/inifile.py",
    "chars": 5196,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "src/everett/ext/yamlfile.py",
    "chars": 5397,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "src/everett/manager.py",
    "chars": 48504,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "src/everett/sphinxext.py",
    "chars": 26936,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "tests/basic_component_config.py",
    "chars": 1655,
    "preview": "\"\"\"Basic component config.\"\"\"\n\nfrom everett.manager import ListOf, Option, parse_class\n\n\nclass ComponentBasic:\n    \"\"\"Ba"
  },
  {
    "path": "tests/basic_module_config.py",
    "chars": 1185,
    "preview": "\"\"\"Basic module config.\"\"\"\n\nfrom everett.manager import ConfigManager\n\n\n_config = ConfigManager.from_dict(\n    {\"debug\":"
  },
  {
    "path": "tests/conftest.py",
    "chars": 316,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "tests/data/config_test.ini",
    "chars": 81,
    "preview": "[main]\nfoo = bar\nbar = test1,test2\n\n[nsbaz]\nfoo = bat\n\n  [[nsbaz2]]\n  foo = bat2\n"
  },
  {
    "path": "tests/data/config_test_original.ini",
    "chars": 31,
    "preview": "[main]\nfoo_original = original\n"
  },
  {
    "path": "tests/ext/test_inifile.py",
    "chars": 1595,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "tests/ext/test_yamlfile.py",
    "chars": 3133,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "tests/simple_module_config.py",
    "chars": 229,
    "preview": "\"\"\"Simple module config.\"\"\"\n\nfrom everett.manager import ConfigManager\n\n\nHELP = \"Help attribute value.\"\n\n\n_config = Conf"
  },
  {
    "path": "tests/test_manager.py",
    "chars": 31674,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  },
  {
    "path": "tests/test_sphinxext.py",
    "chars": 23093,
    "preview": "# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not di"
  }
]

About this extraction

This page contains the full source code of the willkg/everett GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (245.0 KB), approximately 58.6k tokens, and a symbol index with 299 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!