[
  {
    "path": ".github/dependabot.yml",
    "content": "---\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    rebase-strategy: \"disabled\"\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "---\nname: CI\n\non:\n  push:\n    branches:\n      - 'main'\n  pull_request:\n    branches:\n      - 'main'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']\n\n    name: Python ${{ matrix.python-version}}\n    steps:\n      - uses: actions/checkout@v5.0.0\n\n      - name: Set up Python\n        uses: actions/setup-python@v6.0.0\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Update pip and install dev requirements\n        run: |\n          python -m pip install --upgrade pip\n          pip install '.[dev]'\n\n      - name: Test\n        run: tox\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\nuv.lock\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx-generated things\ndocs/_build/\n\n# PyBuilder\ntarget/\n.vscode/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "---\n# Read the Docs configuration file\n\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.10\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# Install dev requirements so that the documentation can build correctly\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - dev\n        - ini\n        - sphinx\n        - yaml\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "* @willkg\n"
  },
  {
    "path": "HISTORY.rst",
    "content": "History\n=======\n\n3.5.0 (October 15th, 2025)\n--------------------------\n\nBackwards incompatibel changes:\n\n* Drop support for Python 3.9. (#282)\n\n* Deprecate Everett.\n\n  I encourage you to switch from Everett to pydantic-settings.\n  See https://github.com/willkg/everett/issues/278\n\nFixes and features:\n\n* Add support for Python 3.14. (#283)\n\n\n3.4.0 (October 30th, 2024)\n--------------------------\n\nBackwards incompatible changes:\n\n* Drop support for Python 3.8. Thanks, Rob!\n\nFixes and features:\n\n* Add support for Python 3.13. (#260) Thanks, Rob!\n\n* Add support for underscore as first character in variable names in env files.\n  (#263)\n\n* Add ``ChoiceOf`` parser for enforcing configuration values belong in\n  specified value domain. (#253)\n\n* Fix ``autocomponentconfig`` to support components with no options. (#244)\n\n* Add ``allow_empty`` option to ``ListOf`` parser that lets you specify whether\n  empty strings are a configuration error or not. (#268)\n\n\n3.3.0 (November 6th, 2023)\n--------------------------\n\nBackwards incompatible changes:\n\n* Drop support for Python 3.7. (#220)\n\nFixes and features:\n\n* Add support for Python 3.12 (#221)\n\n* Fix env file parsing in regards to quotes. (#230)\n\n\n3.2.0 (March 21st, 2023)\n------------------------\n\nFixes and features:\n\n* Implement ``default_if_empty`` argument which will return the default value\n  (if specified) if the value is the empty string. (#205)\n\n* Implement ``parse_time_period`` parser for converting time periods like \"10m4s\"\n  into the total number of seconds that represents.\n\n  ::\n\n      >>> from everett.manager import parse_time_period\n      >>> parse_time_period(\"4m\")\n      240\n\n  (#203)\n\n* Implement ``parse_data_size`` parser for converting values like \"40gb\" into\n  the total number of bytes that represents.\n\n  ::\n\n      >>> from everett.manager import parse_data_size\n      >>> parse_time_period(\"40gb\")\n      40000000000\n\n  (#204)\n\n* Fix an ``UnboundLocalError`` when using ``automoduleconfig`` and providing a\n  Python dotted path to a thing that either kicks up an ``ImportError`` or\n  doesn't exist. Now it raises a more helpful error. (#201)\n\n\n3.1.0 (October 26th, 2022)\n--------------------------\n\nFixes and features:\n\n* Add support for Python 3.11. (#187)\n\n* Add ``raise_configuration_error`` method on ``ConfigManager``. (#185)\n\n* Improve ``automoduleconfig`` to walk the whole AST and document configuration\n  set by assign::\n\n      SOMEVAR = _config(\"somevar\")\n\n  and dict::\n     \n      SOMEGROUP = {\n          \"SOMEVAR\": _config(\"somevar\"),\n      }\n\n  (#184)\n\n* Fix options not showing up on ReadTheDocs. (#186)\n\n\n3.0.0 (January 13th, 2022)\n--------------------------\n\nBackwards incompatible changes:\n\n* Dropped support for Python 3.6. (#176)\n\n* Dropped ``autocomponent`` Sphinx directive in favor of\n  ``autocomponentconfig``.\n\nFixes and features:\n\n* Add support for Python 3.10. (#173)\n\n* Rework namespaces so that you can apply a namespace (``with_namespace()``)\n  after binding a component (``with_options()``) (#175)\n\n* Overhauled, simplified, and improved documentation. Files with example output\n  are now generated using `cog <https://pypi.org/project/cogapp/>`_.\n\n* Rewrite Sphinx extension.\n\n  This now supports manually documenting configuration using\n  ``everett:component`` and ``everett:option`` directives.\n\n  This adds ``:everett:component:`` and ``:everett:option:`` roles for linking\n  to specific configuration in the docs.\n\n  It also addsh ``autocomponentconfig`` and ``automoduleconfig`` directives for\n  automatically generating documentation.\n\n  When using these directives, items are added to the index and everything is\n  linkable making it easier to find and talk to users about specific\n  configuration items. (#172)\n\n\n2.0.1 (August, 23rd, 2021)\n--------------------------\n\nFixes:\n\n* Fix Sphinx warning about roles in Everett sphinxext. (#165)\n\n* Fix ``get_runtime_config`` to work with slots (#166)\n\n\n2.0.0 (July 27th, 2021)\n-----------------------\n\nBackwards incompatible changes:\n\n* This radically reduces the boilerplate required to define components. It also\n  improves the connections between things so it's easier to:\n\n  * determine the configuration required for a single component (taking into\n    account superclasses, overriding, etc)\n  * determine the runtime configuration for a component tree given a\n    configuration manager\n\n  Previously, components needed to subclass RequiredConfigMixin and provide a\n  \"required_config\" class attribute. Something like this::\n\n      from everett.component import RequiredConfigMixin, ConfigOptions\n\n      class SomeClass(RequiredConfigMixin):\n          required_config = ConfigOptions()\n          required_config.add_option(\n              \"some_option\",\n              default=\"42\",\n          )\n\n  That's been slimmed down and now looks like this::\n\n      from everett.manager import Option\n\n      class SomeClass:\n          class Config:\n              some_option = Option(default=\"42\")\n\n  That's much simpler and the underlying implementation code is less tangled\n  and complex, too.\n\n  If you used ``everett.component.RequiredConfigMixin`` or\n  ``everett.component.ConfigOptions``, you'll need to update your classes.\n\n  If you didn't use those things, then you don't have to make any changes.\n\n  See the documentation on components for how it all works now.\n\n* Changed the way configuration variables are referred to in configuration\n  error messages. Previously, I tried to use a general way \"namespace=something\n  key=somethingelse\" but that's confusing and won't match up with project\n  documentation.\n\n  I changed it to the convention used in the process environment and\n  env files. For example, ``FOO_BAR``.\n\n  If you use INI or YAML for configuration, you can specify a ``msg_builder``\n  argument when you build the ``ConfigManager`` and build error messages\n  tailored to your users.\n\nFixes:\n\n* Switch to ``src/`` repository layout.\n\n* Added type annotations and type checking during CI. (#155)\n\n* Standardized on f-strings across the codebase.\n\n* Switched Sphinx theme.\n\n* Update of documentation, fleshed out and simplified examples, cleaned up\n  language, reworked structure of API section (previously called Library or\n  some unhelpful thing like that), etc.\n\n\n1.0.3 (October 28th, 2020)\n--------------------------\n\nBackwards incompatible changes:\n\n* Dropped support for Python 3.4. (#96)\n\n* Dropped support for Python 3.5. (#116)\n\nFixes:\n\n* Add support for Python 3.7. (#68)\n\n* Add support for Python 3.8. (#102)\n\n* Add support for Python 3.9. (#117)\n\n* Reformatted code with Black, added Makefile, switched to GitHub Actions.\n\n* Fix ``get_runtime_config()`` to infer namespaces. (#118)\n\n* Fix ``RemovedInSphinx50Warning``. (#115)\n\n* Documentation fixes and clarifications.\n\n\n1.0.2 (February 22nd, 2019)\n---------------------------\n\nFixes:\n\n* Improve documentation.\n\n* Fix problems when there are nested ``BoundConfigs``. Now they work\n  correctly. (#90)\n\n* Add \"meta\" to options letting you declare additional data on the option\n  when you're adding it.\n\n  For example, this lets you do things like mark options as \"secrets\"\n  so that you know which ones to ``******`` out when logging your\n  configuration. (#88)\n\n\n1.0.1 (January 8th, 2019)\n-------------------------\n\nFixes:\n\n* Fix documentation issues.\n\n* Package missing ``everett.ext``. Thank you, dsblank! (#84)\n\n\n1.0.0 (January 7th, 2019)\n-------------------------\n\nBackwards incompatible changes:\n\n* Dropped support for Python 2.7. Everett no longer supports Python 2. (#73)\n\n* Dropped support for Python 3.3 and added support for Python 3.7. Thank you,\n  pjz! (#68)\n\n* Moved ``ConfigIniEnv`` to a different module. Now you need to import it\n  like this::\n\n      from everett.ext.inifile import ConfigIniEnv\n\n  (#79)\n\nFeatures:\n\n* Everett now logs configuration discovery in the ``everett`` logger at the\n  ``logging.DEBUG`` level. This is helpful for trouble-shooting some kinds of\n  issues. (#74)\n\n* Everett now has a YAML configuration environment. In order to use it, you\n  need to install its requirements::\n\n      $ pip install everett[yaml]\n\n  Then you can import it like this::\n\n      from everett.ext.yamlfile import ConfigYamlEnv\n\n  (#72)\n\nFixes:\n\n* Everett no longer requires ``configobj``--it's now optional. If you use\n  ``ConfigIniEnv``, you can install it with::\n\n      $ pip install everett[ini]\n\n  (#79)\n\n* Fixed list parsing and file discovery in ConfigIniEnv so they match the\n  docs and are more consistent with other envs. Thank you, apollo13! (#71)\n\n* Added a ``.basic_config()`` for fast opinionated setup that uses the\n  process environment and a ``.env`` file in the current working directory.\n\n* Switching to semver.\n\n\n0.9 (April 7th, 2017)\n---------------------\n\nChanged:\n\n* Rewrite Sphinx extension. The extension is now in the ``everett.sphinxext``\n  module and the directive is now ``.. autocomponent::``. It generates better\n  documentation and it now indexes Everett components and options.\n\n  This is backwards-incompatible. You will need to update your Sphinx\n  configuration and documentation.\n\n* Changed the ``HISTORY.rst`` structure.\n\n* Changed the repr for ``everett.NO_VALUE`` to ``\"NO_VALUE\"``.\n\n* ``InvalidValueError`` and ``ConfigurationMissingError`` now have\n  ``namespace``, ``key``, and ``parser`` attributes allowing you to build your\n  own messages.\n\nFixed:\n\n* Fix an example in the docs where the final key was backwards. Thank you, pjz!\n\nDocumentation fixes and updates.\n\n\n0.8 (January 24th, 2017)\n------------------------\n\nAdded:\n\n* Add ``:namespace:`` and ``:case:`` arguments to autoconfig directive. These\n  make it easier to cater your documentation to your project's needs.\n\n* Add support for Python 3.6.\n\nMinor documentation fixes and updates.\n\n\n0.7 (January 5th, 2017)\n-----------------------\n\nAdded:\n\n* Feature: You can now include documentation hints and urls for\n  ``ConfigManager`` objects and config options. This will make it easier for\n  your users to debug configuration errors they're having with your software.\n\nFixed:\n\n* Fix ``ListOf`` so it returns empty lists rather than a list with a single\n  empty string.\n\nDocumentation fixes and updates.\n\n\n0.6 (November 28th, 2016)\n-------------------------\n\nAdded:\n\n* Add ``RequiredConfigMixin.get_runtime_config()`` which returns the runtime\n  configuration for a component or tree of components. This lets you print\n  runtime configuration at startup, generate INI files, etc.\n\n* Add ``ConfigObjEnv`` which lets you use an object for configuration. This\n  works with argparse's Namespace amongst other things.\n\nChanged:\n\n* Change ``:show-docstring:`` to take an optional value which is the attribute\n  to pull docstring content from. This means you don't have to mix programming\n  documentation with user documentation--they can be in different attributes.\n\n* Improve configuration-related exceptions. With Python 3, configuration errors\n  all derive from ``ConfigurationError`` and have helpful error messages that\n  should make it clear what's wrong with the configuration value. With Python 2,\n  you can get other kinds of Exceptions thrown depending on the parser used, but\n  configuration error messages should still be helpful.\n\nDocumentation fixes and updates.\n\n\n0.5 (November 8th, 2016)\n------------------------\n\nAdded:\n\n* Add ``:show-docstring:`` flag to ``autoconfig`` directive.\n\n* Add ``:hide-classname:`` flag to ``autoconfig`` directive.\n\nChanged:\n\n* Rewrite ``ConfigIniEnv`` to use configobj which allows for nested sections in\n  INI files. This also allows you to specify multiple INI files and have later\n  ones override earlier ones.\n\nFixed:\n\n* Fix ``autoconfig`` Sphinx directive and add tests--it was all kinds of broken.\n\nDocumentation fixes and updates.\n\n\n0.4 (October 27th, 2016)\n------------------------\n\nAdded:\n\n* Add ``raw_value`` argument to config calls. This makes it easier to write code\n  that prints configuration.\n\nFixed:\n\n* Fix ``listify(None)`` to return ``[]``.\n\nDocumentation fixes and updates.\n\n\n0.3.1 (October 12th, 2016)\n--------------------------\n\nFixed:\n\n* Fix ``alternate_keys`` with components. Previously it worked for everything\n  but components. Now it works with components, too.\n\nDocumentation fixes and updates.\n\n\n0.3 (October 6th, 2016)\n-----------------------\n\nAdded:\n\n* Add ``ConfigManager.from_dict()`` shorthand for building configuration\n  instances.\n\n* Add ``.get_namespace()`` to ``ConfigManager`` and friends for getting\n  the complete namespace for a given config instance as a list of strings.\n\n* Add ``alternate_keys`` to config call. This lets you specify a list of keys in\n  order to try if the primary key doesn't find a value. This is helpful for\n  deprecating keys that you used to use in a backwards-compatible way.\n\n* Add ``root:`` prefix to keys allowing you to look outside of the current\n  namespace and at the configuration root for configuration values.\n\nChanged:\n\n* Make ``ConfigDictEnv`` case-insensitive to keys and namespaces.\n\nDocumentation fixes and updates.\n\n\n0.2 (August 16th, 2016)\n-----------------------\n\nAdded:\n\n* Add ``ConfigEnvFileEnv`` for supporting ``.env`` files. Thank you, Paul!\n\n* Add \"on\" and \"off\" as valid boolean values. This makes it easier to use config\n  for feature flippers. Thank you, Paul!\n\nChanged:\n\n* Change ``ConfigIniEnv`` to take a single path or list of paths. Thank you,\n  Paul!\n\n* Make ``NO_VALUE`` falsy.\n\nFixed:\n\n* Fix ``__call__`` returning None--it should return ``NO_VALUE``.\n\nLots of docs updates: finished the section about making your own parsers, added\na section on using dj-database-url, added a section on django-cache-url and\nexpanded on existing examples.\n\n\n0.1 (August 1st, 2016)\n----------------------\n\nInitial writing.\n"
  },
  {
    "path": "LICENSE",
    "content": "Mozilla Public License, version 2.0\n\n1. Definitions\n\n1.1. “Contributor”\n\n     means each individual or legal entity that creates, contributes to the\n     creation of, or owns Covered Software.\n\n1.2. “Contributor Version”\n\n     means the combination of the Contributions of others (if any) used by a\n     Contributor and that particular Contributor’s Contribution.\n\n1.3. “Contribution”\n\n     means Covered Software of a particular Contributor.\n\n1.4. “Covered Software”\n\n     means Source Code Form to which the initial Contributor has attached the\n     notice in Exhibit A, the Executable Form of such Source Code Form, and\n     Modifications of such Source Code Form, in each case including portions\n     thereof.\n\n1.5. “Incompatible With Secondary Licenses”\n     means\n\n     a. that the initial Contributor has attached the notice described in\n        Exhibit B to the Covered Software; or\n\n     b. that the Covered Software was made available under the terms of version\n        1.1 or earlier of the License, but not also under the terms of a\n        Secondary License.\n\n1.6. “Executable Form”\n\n     means any form of the work other than Source Code Form.\n\n1.7. “Larger Work”\n\n     means a work that combines Covered Software with other material, in a separate\n     file or files, that is not Covered Software.\n\n1.8. “License”\n\n     means this document.\n\n1.9. “Licensable”\n\n     means having the right to grant, to the maximum extent possible, whether at the\n     time of the initial grant or subsequently, any and all of the rights conveyed by\n     this License.\n\n1.10. “Modifications”\n\n     means any of the following:\n\n     a. any file in Source Code Form that results from an addition to, deletion\n        from, or modification of the contents of Covered Software; or\n\n     b. any new file in Source Code Form that contains any Covered Software.\n\n1.11. “Patent Claims” of a Contributor\n\n      means any patent claim(s), including without limitation, method, process,\n      and apparatus claims, in any patent Licensable by such Contributor that\n      would be infringed, but for the grant of the License, by the making,\n      using, selling, offering for sale, having made, import, or transfer of\n      either its Contributions or its Contributor Version.\n\n1.12. “Secondary License”\n\n      means either the GNU General Public License, Version 2.0, the GNU Lesser\n      General Public License, Version 2.1, the GNU Affero General Public\n      License, Version 3.0, or any later versions of those licenses.\n\n1.13. “Source Code Form”\n\n      means the form of the work preferred for making modifications.\n\n1.14. “You” (or “Your”)\n\n      means an individual or a legal entity exercising rights under this\n      License. For legal entities, “You” includes any entity that controls, is\n      controlled by, or is under common control with You. For purposes of this\n      definition, “control” means (a) the power, direct or indirect, to cause\n      the direction or management of such entity, whether by contract or\n      otherwise, or (b) ownership of more than fifty percent (50%) of the\n      outstanding shares or beneficial ownership of such entity.\n\n\n2. License Grants and Conditions\n\n2.1. Grants\n\n     Each Contributor hereby grants You a world-wide, royalty-free,\n     non-exclusive license:\n\n     a. under intellectual property rights (other than patent or trademark)\n        Licensable by such Contributor to use, reproduce, make available,\n        modify, display, perform, distribute, and otherwise exploit its\n        Contributions, either on an unmodified basis, with Modifications, or as\n        part of a Larger Work; and\n\n     b. under Patent Claims of such Contributor to make, use, sell, offer for\n        sale, have made, import, and otherwise transfer either its Contributions\n        or its Contributor Version.\n\n2.2. Effective Date\n\n     The licenses granted in Section 2.1 with respect to any Contribution become\n     effective for each Contribution on the date the Contributor first distributes\n     such Contribution.\n\n2.3. Limitations on Grant Scope\n\n     The licenses granted in this Section 2 are the only rights granted under this\n     License. No additional rights or licenses will be implied from the distribution\n     or licensing of Covered Software under this License. Notwithstanding Section\n     2.1(b) above, no patent license is granted by a Contributor:\n\n     a. for any code that a Contributor has removed from Covered Software; or\n\n     b. for infringements caused by: (i) Your and any other third party’s\n        modifications of Covered Software, or (ii) the combination of its\n        Contributions with other software (except as part of its Contributor\n        Version); or\n\n     c. under Patent Claims infringed by Covered Software in the absence of its\n        Contributions.\n\n     This License does not grant any rights in the trademarks, service marks, or\n     logos of any Contributor (except as may be necessary to comply with the\n     notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\n     No Contributor makes additional grants as a result of Your choice to\n     distribute the Covered Software under a subsequent version of this License\n     (see Section 10.2) or under the terms of a Secondary License (if permitted\n     under the terms of Section 3.3).\n\n2.5. Representation\n\n     Each Contributor represents that the Contributor believes its Contributions\n     are its original creation(s) or it has sufficient rights to grant the\n     rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\n     This License is not intended to limit any rights You have under applicable\n     copyright doctrines of fair use, fair dealing, or other equivalents.\n\n2.7. Conditions\n\n     Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in\n     Section 2.1.\n\n\n3. Responsibilities\n\n3.1. Distribution of Source Form\n\n     All distribution of Covered Software in Source Code Form, including any\n     Modifications that You create or to which You contribute, must be under the\n     terms of this License. You must inform recipients that the Source Code Form\n     of the Covered Software is governed by the terms of this License, and how\n     they can obtain a copy of this License. You may not attempt to alter or\n     restrict the recipients’ rights in the Source Code Form.\n\n3.2. Distribution of Executable Form\n\n     If You distribute Covered Software in Executable Form then:\n\n     a. such Covered Software must also be made available in Source Code Form,\n        as described in Section 3.1, and You must inform recipients of the\n        Executable Form how they can obtain a copy of such Source Code Form by\n        reasonable means in a timely manner, at a charge no more than the cost\n        of distribution to the recipient; and\n\n     b. You may distribute such Executable Form under the terms of this License,\n        or sublicense it under different terms, provided that the license for\n        the Executable Form does not attempt to limit or alter the recipients’\n        rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\n     You may create and distribute a Larger Work under terms of Your choice,\n     provided that You also comply with the requirements of this License for the\n     Covered Software. If the Larger Work is a combination of Covered Software\n     with a work governed by one or more Secondary Licenses, and the Covered\n     Software is not Incompatible With Secondary Licenses, this License permits\n     You to additionally distribute such Covered Software under the terms of\n     such Secondary License(s), so that the recipient of the Larger Work may, at\n     their option, further distribute the Covered Software under the terms of\n     either this License or such Secondary License(s).\n\n3.4. Notices\n\n     You may not remove or alter the substance of any license notices (including\n     copyright notices, patent notices, disclaimers of warranty, or limitations\n     of liability) contained within the Source Code Form of the Covered\n     Software, except that You may alter any license notices to the extent\n     required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\n     You may choose to offer, and to charge a fee for, warranty, support,\n     indemnity or liability obligations to one or more recipients of Covered\n     Software. However, You may do so only on Your own behalf, and not on behalf\n     of any Contributor. You must make it absolutely clear that any such\n     warranty, support, indemnity, or liability obligation is offered by You\n     alone, and You hereby agree to indemnify every Contributor for any\n     liability incurred by such Contributor as a result of warranty, support,\n     indemnity or liability terms You offer. You may include additional\n     disclaimers of warranty and limitations of liability specific to any\n     jurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n\n   If it is impossible for You to comply with any of the terms of this License\n   with respect to some or all of the Covered Software due to statute, judicial\n   order, or regulation then You must: (a) comply with the terms of this License\n   to the maximum extent possible; and (b) describe the limitations and the code\n   they affect. Such description must be placed in a text file included with all\n   distributions of the Covered Software under this License. Except to the\n   extent prohibited by statute or regulation, such description must be\n   sufficiently detailed for a recipient of ordinary skill to be able to\n   understand it.\n\n5. Termination\n\n5.1. The rights granted under this License will terminate automatically if You\n     fail to comply with any of its terms. However, if You become compliant,\n     then the rights granted under this License from a particular Contributor\n     are reinstated (a) provisionally, unless and until such Contributor\n     explicitly and finally terminates Your grants, and (b) on an ongoing basis,\n     if such Contributor fails to notify You of the non-compliance by some\n     reasonable means prior to 60 days after You have come back into compliance.\n     Moreover, Your grants from a particular Contributor are reinstated on an\n     ongoing basis if such Contributor notifies You of the non-compliance by\n     some reasonable means, this is the first time You have received notice of\n     non-compliance with this License from such Contributor, and You become\n     compliant prior to 30 days after Your receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\n     infringement claim (excluding declaratory judgment actions, counter-claims,\n     and cross-claims) alleging that a Contributor Version directly or\n     indirectly infringes any patent, then the rights granted to You by any and\n     all Contributors for the Covered Software under Section 2.1 of this License\n     shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user\n     license agreements (excluding distributors and resellers) which have been\n     validly granted by You or Your distributors under this License prior to\n     termination shall survive termination.\n\n6. Disclaimer of Warranty\n\n   Covered Software is provided under this License on an “as is” basis, without\n   warranty of any kind, either expressed, implied, or statutory, including,\n   without limitation, warranties that the Covered Software is free of defects,\n   merchantable, fit for a particular purpose or non-infringing. The entire\n   risk as to the quality and performance of the Covered Software is with You.\n   Should any Covered Software prove defective in any respect, You (not any\n   Contributor) assume the cost of any necessary servicing, repair, or\n   correction. This disclaimer of warranty constitutes an essential part of this\n   License. No use of  any Covered Software is authorized under this License\n   except under this disclaimer.\n\n7. Limitation of Liability\n\n   Under no circumstances and under no legal theory, whether tort (including\n   negligence), contract, or otherwise, shall any Contributor, or anyone who\n   distributes Covered Software as permitted above, be liable to You for any\n   direct, indirect, special, incidental, or consequential damages of any\n   character including, without limitation, damages for lost profits, loss of\n   goodwill, work stoppage, computer failure or malfunction, or any and all\n   other commercial damages or losses, even if such party shall have been\n   informed of the possibility of such damages. This limitation of liability\n   shall not apply to liability for death or personal injury resulting from such\n   party’s negligence to the extent applicable law prohibits such limitation.\n   Some jurisdictions do not allow the exclusion or limitation of incidental or\n   consequential damages, so this exclusion and limitation may not apply to You.\n\n8. Litigation\n\n   Any litigation relating to this License may be brought only in the courts of\n   a jurisdiction where the defendant maintains its principal place of business\n   and such litigation shall be governed by laws of that jurisdiction, without\n   reference to its conflict-of-law provisions. Nothing in this Section shall\n   prevent a party’s ability to bring cross-claims or counter-claims.\n\n9. Miscellaneous\n\n   This License represents the complete agreement concerning the subject matter\n   hereof. If any provision of this License is held to be unenforceable, such\n   provision shall be reformed only to the extent necessary to make it\n   enforceable. Any law or regulation which provides that the language of a\n   contract shall be construed against the drafter shall not be used to construe\n   this License against a Contributor.\n\n\n10. Versions of the License\n\n10.1. New Versions\n\n      Mozilla Foundation is the license steward. Except as provided in Section\n      10.3, no one other than the license steward has the right to modify or\n      publish new versions of this License. Each version will be given a\n      distinguishing version number.\n\n10.2. Effect of New Versions\n\n      You may distribute the Covered Software under the terms of the version of\n      the License under which You originally received the Covered Software, or\n      under the terms of any subsequent version published by the license\n      steward.\n\n10.3. Modified Versions\n\n      If you create software not governed by this License, and you want to\n      create a new license for such software, you may create and use a modified\n      version of this License if you rename the license and remove any\n      references to the name of the license steward (except to note that such\n      modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses\n      If You choose to distribute Source Code Form that is Incompatible With\n      Secondary Licenses under the terms of this version of the License, the\n      notice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n\n      This Source Code Form is subject to the\n      terms of the Mozilla Public License, v.\n      2.0. If a copy of the MPL was not\n      distributed with this file, You can\n      obtain one at\n      http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular file, then\nYou may include the notice in a location (such as a LICENSE file in a relevant\ndirectory) where a recipient would be likely to look for such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - “Incompatible With Secondary Licenses” Notice\n\n      This Source Code Form is “Incompatible\n      With Secondary Licenses”, as defined by\n      the Mozilla Public License, v. 2.0.\n\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include *.rst\ninclude pyproject.toml\ninclude justfile\ninclude LICENSE\ninclude .readthedocs.yaml\nrecursive-include docs *.rst\nrecursive-include docs *.py\nrecursive-include docs Makefile\nrecursive-include docs_tmpl *.rst\nrecursive-include examples *.py\nrecursive-include tests *.env\nrecursive-include tests *.ini\nrecursive-include tests *.py\n"
  },
  {
    "path": "README.rst",
    "content": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n=======\nEverett\n=======\n\n**Status 2025-10-15: This project is deprecated.**\n\nEverett is a Python configuration library for your app.\n\n:Code:          https://github.com/willkg/everett\n:Issues:        https://github.com/willkg/everett/issues\n:License:       MPL v2\n:Documentation: https://everett.readthedocs.io/\n\n\nGoals\n=====\n\nGoals of Everett:\n\n1. flexible configuration from multiple configured environments\n2. easy testing with configuration\n3. easy automated documentation of configuration for users\n\nFrom that, Everett has the following features:\n\n* is flexible for your configuration environment needs and supports\n  process environment, env files, dicts, INI files, YAML files,\n  and writing your own configuration environments\n* facilitates helpful error messages for users trying to configure your\n  software\n* has a Sphinx extension for documenting configuration including\n  ``autocomponentconfig`` and ``automoduleconfig`` directives for\n  automatically generating configuration documentation\n* facilitates testing of configuration values\n* supports parsing values of a variety of types like bool, int, lists of\n  things, classes, and others and lets you write your own parsers\n* supports key namespaces\n* supports component architectures\n* works with whatever you're writing--command line tools, web sites, system\n  daemons, etc\n\nEverett is inspired by\n`python-decouple <https://github.com/henriquebastos/python-decouple>`__\nand `configman <https://configman.readthedocs.io/en/latest/>`__.\n\n\nInstall\n=======\n\nRun::\n\n    $ pip install everett\n\nSome configuration environments require additional dependencies::\n\n\n    # For INI support\n    $ pip install 'everett[ini]'\n\n    # for YAML support\n    $ pip install 'everett[yaml]'\n\n\nQuick start\n===========\n\nExample:\n\n.. [[[cog\n   import cog\n   with open(\"examples/myserver.py\", \"r\") as fp:\n       cog.outl(\"\\n::\\n\")\n       for line in fp.readlines():\n           if line.strip():\n               cog.out(f\"   {line}\")\n           else:\n               cog.outl()\n   cog.outl()\n   ]]]\n\n::\n\n   # myserver.py\n\n   \"\"\"\n   Minimal example showing how to use configuration for a web app.\n   \"\"\"\n\n   from everett.manager import ConfigManager\n\n   config = ConfigManager.basic_config(\n       doc=\"Check https://example.com/configuration for documentation.\"\n   )\n\n   host = config(\"host\", default=\"localhost\")\n   port = config(\"port\", default=\"8000\", parser=int)\n   debug_mode = config(\n       \"debug\",\n       default=\"False\",\n       parser=bool,\n       doc=\"Set to True for debugmode; False for regular mode\",\n   )\n\n   print(f\"host: {host}\")\n   print(f\"port: {port}\")\n   print(f\"debug_mode: {debug_mode}\")\n\n.. [[[end]]]\n\nThen you can run it:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\") \n   cog.outl(\"   $ python myserver.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python myserver.py\n   host: localhost\n   port: 8000\n   debug_mode: False\n\n.. [[[end]]]\n\nYou can set environment variables to affect configuration:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   os.environ[\"PORT\"] = \"7000\"\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ PORT=7000 python myserver.py\")\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   del os.environ[\"PORT\"]\n   ]]]\n\n::\n\n   $ PORT=7000 python myserver.py\n   host: localhost\n   port: 7000\n   debug_mode: False\n\n.. [[[end]]]\n\nIt checks a ``.env`` file in the current directory:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   with open(\".env\", \"w\") as fp:\n       fp.write(\"HOST=127.0.0.1\")\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ echo \\\"HOST=127.0.0.1\\\" > .env\")\n   cog.outl(\"   $ python myserver.py\")\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ echo \"HOST=127.0.0.1\" > .env\n   $ python myserver.py\n   host: 127.0.0.1\n   port: 8000\n   debug_mode: False\n\n.. [[[end]]]\n\nIt spits out useful error information if configuration is wrong:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   os.environ[\"DEBUG\"] = \"foo\"\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   stderr = ret.stderr.decode(\"utf-8\").strip()\n   stderr = stderr[stderr.find(\"everett.InvalidValueError\"):]\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ DEBUG=foo python myserver.py\")\n   cog.outl(\"   <traceback>\")\n   for line in stderr.splitlines():\n      cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ DEBUG=foo python myserver.py\n   <traceback>\n   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value\n   DEBUG requires a value parseable by everett.manager.parse_bool\n   DEBUG docs: Set to True for debugmode; False for regular mode\n   Project docs: Check https://example.com/configuration for documentation.\n\n.. [[[end]]]\n\nYou can test your code using ``config_override`` in your tests to test various\nconfiguration values:\n\n.. [[[cog\n   import cog\n   cog.outl(\"\\n::\\n\")\n   with open(\"examples/testdebug.py\", \"r\") as fp:\n       for line in fp.readlines():\n           cog.out(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   # testdebug.py\n   \n   \"\"\"\n   Minimal example showing how to override configuration values when testing.\n   \"\"\"\n   \n   import unittest\n   \n   from everett.manager import ConfigManager, config_override\n   \n   \n   class App:\n       def __init__(self):\n           config = ConfigManager.basic_config()\n           self.debug = config(\"debug\", default=\"False\", parser=bool)\n   \n   \n   class TestDebug(unittest.TestCase):\n       def test_debug_on(self):\n           with config_override(DEBUG=\"on\"):\n               app = App()\n               self.assertTrue(app.debug)\n   \n       def test_debug_off(self):\n           with config_override(DEBUG=\"off\"):\n               app = App()\n               self.assertFalse(app.debug)\n   \n   \n   if __name__ == \"__main__\":\n       unittest.main()\n\n.. [[[end]]]\n\nRun that:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   ret = subprocess.run([\"python\", \"examples/testdebug.py\"], capture_output=True)\n   stderr = ret.stderr.decode(\"utf-8\").strip()\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python testdebug.py\")\n   for line in stderr.splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python testdebug.py\n   ..\n   ----------------------------------------------------------------------\n   Ran 2 tests in 0.000s\n   \n   OK\n\n.. [[[end]]]\n\nThat's perfectly fine for a `12-Factor <https://12factor.net/>`__ app.\n\nWhen you outgrow that or need different variations of it, you can switch to\ncreating a ``ConfigManager`` instance that meets your needs.\n\n\nWhy not other libs?\n===================\n\nMost other libraries I looked at had one or more of the following issues:\n\n* were tied to a specific web app framework\n* didn't allow you to specify configuration sources\n* provided poor error messages when users configure things wrong\n* had a global configuration object\n* made it really hard to override specific configuration when writing tests\n* had no facilities for autogenerating configuration documentation\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = Everett\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile view\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\twhich $(SPHINXBUILD)\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\nview:\n\tgnome-open _build/html/index.html\n"
  },
  {
    "path": "docs/api.rst",
    "content": "===\nAPI\n===\n\nThis is the API of functions and classes in Everett.\n\nConfiguration things:\n\n* :py:class:`everett.manager.ConfigManager`\n* :py:class:`everett.manager.Option`\n\nUtility functions:\n\n* :py:class:`everett.manager.get_config_for_class`\n* :py:class:`everett.manager.get_runtime_config`\n\nTesting utility functions:\n\n* :py:class:`everett.manager.config_override`\n\nConfiguration environments:\n\n* :py:class:`everett.manager.ConfigObjEnv`\n* :py:class:`everett.manager.ConfigDictEnv`\n* :py:class:`everett.manager.ConfigEnvFileEnv`\n* :py:class:`everett.manager.ConfigOSEnv`\n* (INI) :py:class:`everett.ext.inifile.ConfigIniEnv`\n* (YAML) :py:class:`everett.ext.yamlfile.ConfigYamlEnv`\n\nErrors:\n\n* :py:class:`everett.ConfigurationError`\n* :py:class:`everett.InvalidKeyError`\n* :py:class:`everett.ConfigurationMissingError`\n* :py:class:`everett.InvalidValueError`\n\nParsers:\n\n* :py:func:`everett.manager.parse_bool`\n* :py:func:`everett.manager.parse_class`\n* :py:func:`everett.manager.parse_data_size`\n* :py:func:`everett.manager.ListOf`\n\n\neverett\n=======\n\n.. automodule:: everett\n   :members:\n\neverett.manager\n===============\n\n.. automodule:: everett.manager\n   :members:\n   :special-members: __init__, __call__\n\neverett.ext.inifile\n===================\n\n.. automodule:: everett.ext.inifile\n   :members:\n\neverett.ext.yamlfile\n====================\n\n.. automodule:: everett.ext.yamlfile\n   :members:\n"
  },
  {
    "path": "docs/components.rst",
    "content": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n==========\nComponents\n==========\n\n.. contents::\n   :local:\n\n\n.. versionchanged:: 2.0\n\n   This is redone for v2.0.0 and simplified.\n\n\nConfiguration components\n========================\n\nEverett supports configuration components.\n\nThere are two big use cases for this:\n\n1. Centralizing configuration specification for your application into a single\n   class.\n\n2. Component architectures.\n\n\nCentralizing configuration\n--------------------------\n\nInstead of having configuration-related bits defined across your codebase, you\ncan define it in a class.\n\nHere's an example with an ``AppConfig``:\n\n.. literalinclude:: ../examples/component_appconfig.py\n   :language: python\n\nLet's run it with the defaults:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/component_appconfig.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python component_appconfig.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python component_appconfig.py\n   debug: False\n\n.. [[[end]]]\n\nNow with ``DEBUG=true``:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   os.environ[\"DEBUG\"] = \"true\"\n   ret = subprocess.run([\"python\", \"examples/component_appconfig.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ DEBUG=true python component_appconfig.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   del os.environ[\"DEBUG\"]\n   ]]]\n\n::\n\n   $ DEBUG=true python component_appconfig.py\n   debug: True\n\n.. [[[end]]]\n\nLet's run a Python shell and do some other things with it:\n\n.. doctest::\n\n   >>> import component_appconfig\n   debug: False\n   >>> config = component_appconfig.get_config()\n   >>> config(\"badkey\")\n   Traceback (most recent call last):\n       ...\n   everett.InvalidKeyError: 'badkey' is not a valid key for this component\n\nNotice how you can't use configuration keys that aren't specified in the bound\ncomponent.\n\nCentrally defining configuration like this helps in a few ways:\n\n1. You can reduce some bugs that occur as your application evolves over time.\n   Every time you use configuration, the ``ConfigManager`` will enforce that\n   the key is a valid option.\n\n2. Your application configuration is centralized in one place instead\n   of spread out across your code base.\n\n3. You can automatically document your configuration using the\n   ``everett.sphinxext`` Sphinx extension and ``autocomponentconfig`` directive::\n\n       .. autocomponentconfig:: path.to.AppConfig\n\n   Because it's automatically documented, your documentation is always\n   up-to-date.\n\n\nComponent architectures\n-----------------------\n\nEverett configuration supports component architectures. Say your app needs to\nconnect to RabbitMQ. With Everett, you can define the component's configuration\nneeds in the component class.\n\nHere's an example:\n\n.. literalinclude:: ../examples/componentapp.py\n   :language: python\n\n\nThat's not wildly exciting, but if the component was in a library of\ncomponents, then you can string them together using configuraiton.\n\nFor example, what if the destination wasn't a single bucket, but rather\na set of buckets?\n\n::\n\n    dest_config = config(\"pipeline\", default=\"dest\", parser=ListOf(str))\n\n    dest_buckets = []\n    for name in dest_config:\n        dest_buckets.append(S3Bucket(s3_config.with_namespace(name)))\n\nYou can autogenerate configuration documentation for this component in your\nSphinx docs by including the ``everett.sphinxext`` Sphinx extension and\nusing the ``autocomponentconfig`` directive::\n\n    .. autocomponentconfig:: myapp.S3Bucket\n\n\nSubclassing\n===========\n\nYou can subclass components and override configuration options.\n\nFor example:\n\n.. literalinclude:: ../examples/components_subclass.py\n   :language: python\n\nThat prints:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/components_subclass.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python components_subclass.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python components_subclass.py\n   foo_from_b\n   bar_from_a\n\n.. [[[end]]]\n\n\nGetting configuration information for components\n================================================\n\nYou can get the configuration options for a component class using\n:py:func:`everett.manager.get_config_for_class`. This returns a dict of\n``configuration key -> (option, class)``. This helps with debugging which\noption came from which class.\n\n.. autofunction:: everett.manager.get_config_for_class\n   :noindex:\n\n\nYou can get the runtime configuration for a component or tree of components\nusing :py:func:`everett.manager.get_runtime_config`. This returns a list of\n``(namespace, key, value, option, class)`` tuples. The value is the computed\nruntime value taking into account the environments specified in the\n``ConfigManager`` and class hierarchies.\n\nIt'll traverse any instance attributes that are components with options.\n\n.. autofunction:: everett.manager.get_runtime_config\n   :noindex:\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nimport os\nimport sys\n\ncwd = os.getcwd()\n\n# Add ../src/ directory so we can pull in Everett things using autodoc\nproject_root = os.path.dirname(cwd)\nsrc_root = os.path.join(project_root, \"src\")\nsys.path.insert(0, src_root)\n\n# Add ../examples/ directory so we can use autocomponentconfig with a recipe\nsys.path.insert(0, os.path.join(project_root, \"examples\"))\n\nimport everett  # noqa\n\n\n# -- Project information -----------------------------------------------------\n\nproject = \"Everett\"\ncopyright = \"2016-2022, Will Kahn-Greene\"\nauthor = \"Will Kahn-Greene\"\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.doctest\",\n    \"everett.sphinxext\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = everett.__version__\n# The full version with the release date.\nrelease = everett.__version__\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\nautodoc_typehints = \"description\"\nautoclass_content = \"both\"\nautodoc_default_options = {\n    \"class-doc-from\": \"both\",\n    \"member-order\": \"bysource\",\n    \"inheireted-members\": True,\n}\n\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"sphinx_rtd_theme\"\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Custom sidebar templates, maps document names to template names.\n#\nhtml_sidebars = {\n    \"**\": [\n        \"about.html\",\n        \"navigation.html\",\n        \"relations.html\",\n        \"searchbox.html\",\n    ]\n}\n"
  },
  {
    "path": "docs/configmanager.rst",
    "content": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n======================\nConfiguration Managers\n======================\n\n.. contents::\n   :local:\n\n\nCreating a ConfigManager and specifying environments\n====================================================\n\nFirst, you define a :py:class:`everett.manager.ConfigManager` which specifies\nthe environments you want to pull configuration from.\n\nThen you can use the :py:class:`everett.manager.ConfigManager` to look up\nconfiguration keys. The :py:class:`everett.manager.ConfigManager` will go\nthrough the environments in order to find a value for the configuration key.\nOnce it finds a value, it runs it through the parser and returns the parsed\nvalue.\n\nThere are a few ways to create a :py:class:`everett.manager.ConfigManager`.\n\nThe easiest is to use :py:func:`everett.manager.ConfigManager.basic_config`.\n\nFor example:\n\n.. literalinclude:: ../examples/myserver.py\n   :language: python\n\nThat creates a :py:class:`everett.manager.ConfigManager` that looks up\nconfiguration keys in these environments:\n\n1. the process environment\n2. the specified env file which defaults to ``.env``\n\nThat works for most cases.\n\nYou can create your own :py:class:`everett.manager.ConfigManager` and specify\nenvironments specific to your needs.\n\nFor example:\n\n.. literalinclude:: ../examples/myserver_with_environments.py\n   :language: python\n\n\nSpecifying configuration documentation\n======================================\n\nWhen building a :py:class:`everett.manager.ConfigManager`, you can specify\ndocumentation for configuration. It will get printed when there are\nconfiguration errors. This is a great place to put a link to configuration\ndocumentation.\n\nFor example:\n\n.. literalinclude:: ../examples/myserver.py\n   :language: python\n\nLet's run that:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python myserver.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python myserver.py\n   host: localhost\n   port: 8000\n   debug_mode: False\n\n.. [[[end]]]\n\nLet's set ``DEBUG`` wrong and see what it tells us:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   os.environ[\"DEBUG\"] = \"foo\"\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   stderr = ret.stderr.decode(\"utf-8\").strip()\n   stderr = stderr[stderr.find(\"everett.InvalidValueError\"):]\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ DEBUG=foo python myserver.py\")\n   cog.outl(\"   <traceback>\")\n   for line in stderr.splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ DEBUG=foo python myserver.py\n   <traceback>\n   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value\n   DEBUG requires a value parseable by everett.manager.parse_bool\n   DEBUG docs: Set to True for debugmode; False for regular mode\n   Project docs: Check https://example.com/configuration for documentation.\n\n.. [[[end]]]\n\nHere, we see the documentation for the ``DEBUG`` option, the documentation from\nthe ``ConfigManager``, and the specific Python exception information.\n"
  },
  {
    "path": "docs/configuration.rst",
    "content": ".. NOTE: Make sure to edit the template for this file in docs_tmpl/ and\n.. not the cog-generated version.\n\n=============\nConfiguration\n=============\n\n.. contents::\n   :local:\n\n\nExtracting values\n=================\n\nOnce you have a configuration manager set up with sources, you can pull\nconfiguration values from it.\n\nConfiguration must have a key. Everything else is optional.\n\nExamples:\n\n::\n\n    config(\"password\")\n\nThe key is \"password\".\n\nThe value is parsed as a string.\n\nThere is no default value provided so if \"password\" isn't provided in any of\nthe configuration sources, then this will raise a\n:py:class:`everett.ConfigurationError`.\n\nThis is what you want to do to require that a configuration value exist.\n\n::\n\n    config(\"name\", raise_error=False)\n\nThe key is \"name\".\n\nThe value is parsed as a string.\n\nThere is no default value provided and raise_error is set to False, so if\nthis configuration variable isn't set anywhere, the result of this will be\n``everett.NO_VALUE``.\n\n.. Note::\n\n   :py:data:`everett.NO_VALUE` is a falsy value so you can use it in\n   comparative contexts::\n\n       debug = config(\"DEBUG\", parser=bool, raise_error=False)\n       if not debug:\n           pass\n\n::\n\n    config(\"port\", parser=int, default=\"5432\")\n\nThe key is \"port\".\n\nThe value is parsed using int.\n\nThere is a default provided, so if this configuration variable isn't set in\nthe specified sources, the default will be false.\n\nNote that the default value is always a string that's parseable by the\nparser.\n\n::\n\n    config(\"username\", namespace=\"db\")\n\nThe key is \"username\".\n\nThe namespace is \"db\".\n\nThere's no default, so if there's no \"username\" in namespace \"db\"\nconfiguration variable set in the sources, this will raise a\n:py:class:`everett.ConfigurationError`.\n\nIf you're looking up values in the process environment, then the full\nkey would be ``DB_USERNAME``.\n\n::\n\n    config(\"password\", namespace=\"postgres\", alternate_keys=[\"db_password\", \"root:postgres_password\"])\n\nThe key is \"password\".\n\nThe namespace is \"postgres\".\n\nIf there is no key \"password\" in namespace \"postgres\", then it looks for\n\"db_password\" in namespace \"postgres\". This makes it possible to deprecate\nold key names, but still support them.\n\nIf there is no key \"password\" or \"db_password\" in namespace \"postgres\", then\nit looks at \"postgres_password\" in the root namespace. This allows you to\nhave multiple components that share configuration like credentials and\nhostnames.\n\n::\n\n    config(\n        \"debug\", default=\"false\", parser=bool,\n        doc=\"Set to True for debugmode; False for regular mode\",\n    )\n\nYou can provide a ``doc`` argument which will give users users who are trying to\nconfigure your software a more helpful error message when they hit a configuration\nerror.\n\nExample of error message for an option that specifies ``doc`` when trying to\nset ``DEBUG`` to ``foo``:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   os.environ[\"DEBUG\"] = \"foo\"\n   ret = subprocess.run([\"python\", \"examples/myserver.py\"], capture_output=True)\n   stderr = ret.stderr.decode(\"utf-8\").strip()\n   stderr = stderr[stderr.find(\"everett.InvalidValueError\"):]\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python example.py\")\n   cog.outl(\"   <traceback>\")\n   for line in stderr.splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python example.py\n   <traceback>\n   everett.InvalidValueError: ValueError: 'foo' is not a valid bool value\n   DEBUG requires a value parseable by everett.manager.parse_bool\n   DEBUG docs: Set to True for debugmode; False for regular mode\n   Project docs: Check https://example.com/configuration for documentation.\n\n.. [[[end]]]\n\nThat last line comes directly from the ``doc`` argument you provide.\n\n\n.. automethod:: everett.manager.ConfigManager.__call__\n   :noindex:\n\n.. autoclass:: everett.ConfigurationError\n   :noindex:\n\n.. autoclass:: everett.InvalidValueError\n   :noindex:\n\n.. autoclass:: everett.ConfigurationMissingError\n   :noindex:\n\n.. autoclass:: everett.InvalidKeyError\n   :noindex:\n\n\nNamespaces\n==========\n\nEverett has namespaces for grouping related configuration values.\n\nFor example, this uses \"username\", \"password\", and \"port\" configuration keys\nin the \"db\" namespace:\n\n.. literalinclude:: ../examples/namespaces.py\n   :language: python\n\nThese variables in the environment would be ``DB_USERNAME``, ``DB_PASSWORD``\nand ``DB_PORT``.\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/namespaces.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python namespaces.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python namespaces.py\n   Opened database with admin/ou812 on port 5432\n\n.. [[[end]]]\n\n\nThis is helpful when you need to create two of the same thing, but using\nseparate configuration.\n\nWhat if we had source and destination databases and needed to have the\nconfiguration keys separated?\n\n.. literalinclude:: ../examples/namespaces2.py\n   :language: python\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/namespaces2.py\"], capture_output=True)\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ python namespaces2.py\")\n   for line in ret.stdout.decode(\"utf-8\").splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   ]]]\n\n::\n\n   $ python namespaces2.py\n   Opened database with admin/ou812 on port 5432\n   Opened database with admin/P9rwvnnj8CidECMb on port 5432\n\n.. [[[end]]]\n\n\nHandling exceptions when extracting values\n==========================================\n\nWhen the namespaced key isn't found in any of the sources, then Everett will\nraise an exception that is a subclass of\n:py:class:`everett.ConfigurationError`. This makes it easier to\nprogrammatically figure out what happened.\n\nIf you don't like what Everett prints by default, you can catch\nthe errors and print something different.\n\nFor example:\n\n.. literalinclude:: ../examples/handling_exceptions.py\n   :language: python\n\n\nAlso, you can change the structure of the error message by passing in a ``msg_builder``\nargument to the :py:class:`everett.manager.ConfigManager`.\n\nFor example, say your project is entirely done with INI configuration. Then you'd\nwant to tailor the message accordingly.\n\n.. literalinclude:: ../examples/msg_builder.py\n   :language: python\n\nThat prints this:\n\n.. [[[cog\n   import cog\n   import os\n   import subprocess\n   os.environ[\"DEBUG\"] = \"lizard\"\n   if os.path.exists(\".env\"):\n       os.remove(\".env\")\n   ret = subprocess.run([\"python\", \"examples/msg_builder.py\"], capture_output=True)\n   stderr = ret.stderr.decode(\"utf-8\").strip()\n   stderr = stderr[stderr.find(\"everett.InvalidValueError\"):]\n   cog.outl(\"\\n::\\n\")\n   cog.outl(\"   $ DEBUG=lizard python msg_builder.py\")\n   cog.outl(\"   <traceback>\")\n   for line in stderr.splitlines():\n       cog.outl(f\"   {line}\")\n   cog.outl()\n   del os.environ[\"DEBUG\"]\n   ]]]\n\n::\n\n   $ DEBUG=lizard python msg_builder.py\n   <traceback>\n   everett.InvalidValueError: Dear user. debug in section [main] is not correct. Please fix it.\n\n.. [[[end]]]\n\n\nTrouble-shooting and logging what happened\n=============================================\n\nIf you have a non-trivial Everett configuration, it might be difficult to\nfigure out exactly why a key lookup failed.\n\nEverett logs to the ``everett`` logger at the ``logging.DEBUG`` level. You\ncan enable this logging and get a clearer idea of what's going on.\n\nSee `Python logging documentation\n<https://docs.python.org/3/library/logging.html>`_ for details on enabling\nlogging.\n"
  },
  {
    "path": "docs/dev.rst",
    "content": "==================\nDeveloping Everett\n==================\n\nInstall for development\n=======================\n\nRequirements:\n\n* `uv <https://docs.astral.sh/uv/>`__\n* `just <https://just.systems/>`__\n* `git <https://git-scm.com/>`__\n\nClone the repository::\n\n    $ git clone https://github.com/willkg/everett\n\nCreate a dev environment::\n\n    $ just devenv\n\nDevelopment recipes are in ``justfile``. You can get a list with\n``just --list``.\n"
  },
  {
    "path": "docs/documenting.rst",
    "content": "=========================\nDocumenting configuration\n=========================\n\n.. contents::\n   :local:\n\nIt's hard to keep configuration documentation up-to-date as projects change\nover time.\n\nEverett comes with a `Sphinx <https://http://www.sphinx-doc.org/en/stable/>`_\nextension for documenting configuration. It has ``autocomponentconfig`` and\n``automoduleconfig`` directives for automatically generating documentation. It\nalso has ``everett:component`` and ``everett:option`` directives for manually\ndocumenting configuration. It also comes with ``:everett:option:`` and\n``:everett:component:`` roles letting you create links to specific\nconfiguration things in your documentation.\n\nConfiguration options are added to the index and have unique links making it\neasier to find and point people to specific configuration documentation.\n\n.. versionchanged:: 3.0.0\n   Complete rewrite of Sphinx directives.\n\n\nDirectives\n==========\n\n.. rst:directive:: automoduleconfig\n\n   **Requires Python 3.8 or higher.**\n\n   Automatically documents the configuration options set in a Python module\n   using the specified :py:class:`everett.manager.ConfigManager`.\n\n   The argument is the Python dotted path to the\n   :py:class:`everett.manager.ConfigManager` instance.\n\n   .. Note::\n\n      The automoduleconfig directive works by parsing the Python module as an\n      AST and then traverses the AST.\n\n      It does not execute the module, so it doesn't evaluate any values.\n\n   .. Note::\n\n      ``automoduleconfig`` requires Python 3.8 or higher.\n\n      If you're using ReadTheDocs, it defaults to Python 3.7. You'll need to\n      configure the version of Python to use by adding a configuration file.\n\n      See `ReadTheDocs configuration file documentation\n      <https://docs.readthedocs.io/en/stable/config-file/v2.html>`_ for more\n      details.\n\n   .. rubric:: Options\n\n   .. rst:directive:option:: show-table\n      :type: no value\n\n      If set, will create a table summarizing the options in this module with\n      links to the option details.\n\n   .. rst:directive:option:: hide-name\n      :type: no value\n\n      If set, this will hide the name derived from the Python dotted path\n      and use \"Configuration\" instead.\n\n      This affects how the options are indexed. If you're documenting multiple\n      modules this way, options that exist in multiple modules will create a\n      conflict.\n\n   .. rst:directive:option:: show-docstring\n      :type: str, empty str, or omitted\n\n      If omitted, this does nothing.\n\n      If set, but with no value, this will include the module docstring in\n      the documentation.\n\n      If set with a value of the name of an attribute in the module, this will\n      include the value of that attribute in the documentation.\n\n      Example to include the module ``__doc__``:\n\n      ::\n\n          .. automoduleconfig:: myproject.settings._config\n             :show-docstring:\n    \n      Example to include the value of the value of the ``HELP`` attribute:\n\n      ::\n\n          .. automoduleconfig:: myproject.settings._config\n             :show-docstring: HELP\n\n   .. rst:directive:option:: namespace\n      :type: str\n\n      If set, this prefixes all the option keys with the specified namespace.\n      \n      For example, if you set namespace to ``source_db``, then key ``host``\n      would result in ``source_db_host`` being documented. (Case is dependent\n      on the \"case\" directive option.)\n\n   .. rst:directive:option:: case\n      :type: \"upper\", \"lower\", or omitted\n\n      Specifies whether to convert the full namespaced key to all uppercase,\n      all lowercase, or leave it as is.\n\n\n.. rst:directive:: autocomponentconfig\n\n   Automatically documents the configuration options for the specified class\n   and its superclasses.\n\n   The argument is the Python dotted path to the class.\n\n   .. Warning::\n\n      ``autocomponentconfig`` **imports** the code to be documented. If any of\n      the imported modules have side-effects at import, they will be executed\n      when building the documentation.\n\n   .. rubric:: Options\n\n   .. rst:directive:option:: show-table\n      :type: no value\n\n      If set, will create a table summarizing the options in this component\n      with links to the option details.\n\n   .. rst:directive:option:: hide-name\n      :type: no value\n\n      If set, this will hide the name of the class and use \"Configuration\"\n      instead.\n\n      This affects how the options are indexed. If you're documenting multiple\n      classes this way, options that exist in multiple classes will create a\n      conflict.\n\n   .. rst:directive:option:: show-docstring\n      :type: str, empty str, or omitted\n\n      If omitted, this does nothing.\n\n      If set, but with no value, this will include the class docstring in the\n      documentation.\n\n      If set with a value of the name of an attribute of the class, this will\n      include the value of that attribute in the documentation.\n\n      Example to include the class docstring:\n\n      ::\n\n          .. automoduleconfig:: myproject.MyClass\n             :show-docstring:\n    \n      Example to include the value of the value of the ``HELP`` attribute:\n\n      ::\n\n          .. automoduleconfig:: myproject.MyClass\n             :show-docstring: HELP\n\n   .. rst:directive:option:: namespace\n      :type: str\n\n      If set, this prefixes all the option keys with the specified namespace.\n      \n      For example, if you set namespace to ``source_db``, then key ``host``\n      would result in ``source_db_host`` being documented. (Case is dependent\n      on the \"case\" directive option.)\n\n   .. rst:directive:option:: case\n      :type: \"upper\", \"lower\", or omitted\n\n      Specifies whether to convert the full namespaced key to all uppercase,\n      all lowercase, or leave it as is.\n\n\n.. rst:directive:: everett:component\n\n   Defines an Everett component which is any Python class that has an inner\n   class named ``Config`` which defines configuration options.\n\n   The argument is the Python dotted path to the class.\n\n   Add ``everett:option`` as part of the description.\n\n\n.. rst:directive:: everett:option\n\n   Defines an Everett configuration option.\n\n   The argument is the option key.\n\n   .. rubric:: Options\n\n   .. rst:directive:option:: parser\n      :type: str\n\n      The name of the parser for this option.\n\n   .. rst:directive:option:: default\n      :type: str\n\n      If not set, the default is ``NO_VALUE`` which means that this option has\n      no default value.\n\n      If set, this is the default value. Enclose the value in double-quotes\n      because all default values must be strings.\n\n   .. rst:directive:option:: required\n      :type: no value\n\n      If set, this option is required.\n\n      If not set and the option has a default, then this option is not\n      required.\n\n      If not set and the option has no default, then this option is required.\n\n      This option is not required::\n\n          .. everett:option:: HOST\n             :default: localhost\n\n      These two options are required::\n\n          .. everett:option:: USERNAME\n             \n          .. everett:option:: PASSWORD\n             :required:\n\n\nExamples\n========\n\nDocumenting component configuration\n-----------------------------------\n\nHere's an example Everett component:\n\n.. literalinclude:: ../examples/recipes_appconfig.py\n\n\nYou can use the ``autocomponentconfig`` directive to extract the configuration\ninformation from the ``AppConfig`` class and document it::\n\n    .. autocomponentconfig:: recipes_appconfig.AppConfig\n       :case: upper\n       :show-table:\n\n\nThat gives you something that looks like this:\n\n.. autocomponentconfig:: recipes_appconfig.AppConfig\n   :case: upper\n   :show-table:\n\n\nYou can link to components with the ``:everett:component:`` role and options\nusing the ``:everett:option:`` role.\n\nExample component link::\n\n    Component link: :everett:component:`recipes_appconfig.AppConfig`\n\n\nComponent link: :everett:component:`recipes_appconfig.AppConfig`\n\nExample option link::\n\n    Option link: :everett:option:`recipes_appconfig.AppConfig.DEBUG`\n\n\nOption link: :everett:option:`recipes_appconfig.AppConfig.DEBUG`\n\n\n\nDocumenting module configuration\n--------------------------------\n\nYou can use ``automoduleconfig`` to document configuration that's set at module\nimport. This is helpful for Django settings modules.\n\nExample configuration code that sets up a\n:py:class:`everett.manager.ConfigManager` and calls it ``_config``:\n\n.. literalinclude:: ../examples/recipes_djangosettings.py\n   :language: python\n\nExample documentation directive::\n\n    .. automoduleconfig:: recipes_djangosettings._config\n       :hide-name:\n       :case: upper\n       :show-table:\n\nThat gives you this:\n\n.. automoduleconfig:: recipes_djangosettings._config\n   :hide-name:\n   :case: upper\n   :show-table:\n"
  },
  {
    "path": "docs/environments.rst",
    "content": "==========================\nConfiguration environments\n==========================\n\n.. contents::\n   :local:\n\nDict (ConfigDictEnv)\n====================\n\n.. autoclass:: everett.manager.ConfigDictEnv\n   :noindex:\n\n\nProcess environment (ConfigOSEnv)\n=================================\n\n.. autoclass:: everett.manager.ConfigOSEnv\n   :noindex:\n\n\nENV files (ConfigEnvFileEnv)\n============================\n\n.. autoclass:: everett.manager.ConfigEnvFileEnv\n   :noindex:\n\n\nPython objects (ConfigObjEnv)\n=============================\n\n.. autoclass:: everett.manager.ConfigObjEnv\n   :noindex:\n\n\nINI files (ConfigIniEnv)\n========================\n\n.. autoclass:: everett.ext.inifile.ConfigIniEnv\n   :noindex:\n\n\nYAML files (ConfigYamlEnv)\n==========================\n\n.. autoclass:: everett.ext.yamlfile.ConfigYamlEnv\n   :noindex:\n\n\nImplementing your own configuration environments\n================================================\n\nYou can implement your own configuration environments. For example, maybe you\nwant to pull configuration from a database or Redis or a post-it note on the\nrefrigerator.\n\nThey just need to implement the ``.get()`` method. A no-op implementation is\nthis:\n\n.. literalinclude:: ../examples/environments.py\n   :language: python\n\n\nGenerally, environments should return a value if the key exists in that\nenvironment and should return ``NO_VALUE`` if and only if the key does not\nexist in that environment.\n\nFor exceptions, it depends on what you want to have happen. It's ok to let\nexceptions go unhandled--Everett will wrap them in a :py:class:`everett.ConfigurationError`.\nIf your environment promises never to throw an exception, then you should\nhandle them all and return ``NO_VALUE`` since with that promise all exceptions\nwould indicate the key is not in the environment.\n"
  },
  {
    "path": "docs/history.rst",
    "content": ".. include:: ../HISTORY.rst\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. include:: ../README.rst\n\n\nContents\n========\n\n.. toctree::\n   :maxdepth: 2\n\n   configmanager\n   configuration\n   parsers\n   environments\n   components\n   documenting\n   testing\n   recipes\n   api\n   history\n   dev\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/parsers.rst",
    "content": "=======\nParsers\n=======\n\n.. contents::\n   :local:\n\n\nWhat's a parser?\n================\n\nAll parsers are functions that take a string value and return a parsed\ninstance.\n\nFor example:\n\n* ``int`` takes a string value and returns an int.\n* ``parse_class`` takes a string value that's a dotted Python path and returns\n  the class object\n* ``ListOf(str)`` takes a string value that uses a comma delimiter and returns\n  a list of strings\n\n\n.. Note::\n\n   When specifying configuration options, the default value must always be a\n   string. When Everett can't find a value for a requested key, it will take\n   the default value and pass it through the parser. Because parsers always\n   take a string as input, the default value must always be a string.\n\n   Good::\n\n       debug = config(\"debug\", parser=bool, default=\"false\")\n                                                    ^^^^^^^\n\n   Bad::\n\n       debug = config(\"debug\", parser=bool, default=False)\n                                                    ^^^^^ Not a string\n\n\nAvailable parsers\n=================\n\nPython types like str, int, float, pathlib.Path\n-----------------------------------------------\n\nPython types can convert strings to Python values. You can use these as\nparsers:\n\n* ``str``\n* ``int``\n* ``float``\n* ``decimal``\n* ``pathlib.Path``\n\n\nbools\n-----\n\nEverett provides a special bool parser that handles more descriptive values for\n\"true\" and \"false\":\n\n* true: t, true, yes, y, on, 1 (and uppercase versions)\n* false: f, false, no, n, off, 0 (and uppercase versions)\n\n.. autofunction:: everett.manager.parse_bool\n   :noindex:\n\n\nclasses\n-------\n\nEverett provides a ``everett.manager.parse_class`` that takes a string\nspecifying a module and class and returns the class.\n\n.. autofunction:: everett.manager.parse_class\n   :noindex:\n\n\ndata size\n---------\n\nEverett provides a ``everett.manager.parse_data_size`` that takes a string\nspecifying an amount and a data size metric (e.g. kb, kib, tb, etc) and returns\nthe amount of bytes that represents.\n\n.. autofunction:: everett.manager.parse_data_size\n   :noindex:\n\n\ntime period\n-----------\n\nEverett provides a ``everett.manager.parse_time_period`` that takes a string\nspecifying a period of time and returns the total number of seconds represented\nby that period.\n\n.. autofunction:: everett.manager.parse_data_size\n   :noindex:\n\n\nListOf(parser)\n--------------\n\nEverett provides a special ``everett.manager.ListOf`` parser which\nparses a list of some other type. For example::\n\n    ListOf(str)  # comma-delimited list of strings\n    ListOf(int)  # comma-delimited list of ints\n\n.. autofunction:: everett.manager.ListOf\n   :noindex:\n\n\nChoiceOf(parser, list-of-choices)\n---------------------------------\n\nEverett provides a ``everett.manager.ChoiceOf`` parser which can enforce that\nconfiguration values belong to a specificed value domain.\n\n.. autofunction:: everett.manager.ChoiceOf\n   :noindex:\n\n\ndj_database_url\n---------------\n\nEverett works with `dj-database-url\n<https://pypi.org/project/dj-database-url/>`_. The ``dj_database_url.parse``\nfunction takes a string and returns a Django database connection value.\n\nFor example::\n\n    import dj_database_url\n    from everett.manager import ConfigManager\n\n    config = ConfigManager.basic_config()\n    DATABASES = {\n        \"default\": config(\"DATABASE_URL\", parser=dj_database_url.parse)\n    }\n\n\nThat'll pull the ``DATABASE_URL`` value from the environment (it throws an\nerror if it's not there) and runs it through ``dj_database_url`` which parses\nit and returns what Django needs.\n\nWith a default::\n\n    import dj_database_url\n    from everett.manager import ConfigManager\n\n    config = ConfigManager.basic_config()\n    DATABASES = {\n        \"default\": config(\n            \"DATABASE_URL\", default=\"sqlite:///my.db\", parser=dj_database_url.parse\n        )\n    }\n\n\n.. Note::\n\n   To use dj-database-url, you'll need to install it separately. Everett doesn't\n   depend on it or require it to be installed.\n\n\ndjango-cache-url\n----------------\n\nEverett works with `django-cache-url <https://pypi.org/project/django-cache-url/>`_.\n\nFor example::\n\n    import django_cache_url\n    from everett.manager import ConfigManager\n\n    config = ConfigManager.basic_config()\n    CACHES = {\n        \"default\": config(\"CACHE_URL\", parser=django_cache_url.parse)\n    }\n\n\nThat'll pull the ``CACHE_URL`` value from the environment (it throws an error if\nit's not there) and runs it through ``django_cache_url`` which parses it and\nreturns what Django needs.\n\nWith a default::\n\n    import django_cache_url\n    from everett.manager import ConfigManager\n\n    config = ConfigManager.basic_config()\n    CACHES = {\n        \"default\": config(\n            \"CACHE_URL\", default=\"locmem://myapp\", parser=django_cache_url.parse\n        )\n    }\n\n\n.. Note::\n\n   To use django-cache-url, you'll need to install it separately. Everett\n   doesn't require it to be installed.\n\n\nImplementing your own parsers\n=============================\n\nImplementing your own parser should be straight-forward. Parsing functions\nalways take a string and return the Python value you need.\n\nIf the value is not parseable, the parsing function should raise a ``ValueError``.\n\nFor example, say we wanted to implement a parser that returned yes/no/no-answer\nor a parser class that's line delimited:\n\n.. literalinclude:: ../examples/parser_examples.py\n   :language: python\n"
  },
  {
    "path": "docs/recipes.rst",
    "content": "=======\nRecipes\n=======\n\nThis contains some ways of solving problems I've had with applications I use\nEverett in. These use cases help me to shape the Everett architecture such that\nit's convenient and flexible, but not big and overbearing.\n\nHopefully they help you, too.\n\nIf there are things you're trying to solve and you're using Everett that aren't\ncovered here, add an item to the `issue tracker\n<https://github.com/willkg/everett/issues>`_.\n\n\n.. contents::\n   :local:\n\n\nCentralizing configuration specification\n========================================\n\nIt's easy to set up a :py:class:`everett.manager.ConfigManager` and then call it\nfor configuration. However, with any non-trivial application, it's likely you're\ngoing to refer to configuration options multiple times in different parts of the\ncode.\n\nOne way to do this is to pull out the configuration value and store it in a\nglobal constant or an attribute somewhere and pass that around.\n\nAnother way to do this is to create a configuration component, define all the\nconfiguration options there and then pass that component around.\n\nFor example, this creates an ``AppConfig`` component which has configuration\nfor the application:\n\n.. literalinclude:: ../examples/recipes_appconfig.py\n   :language: python\n\n\nCouple of nice things here. First, is that if you do Sphinx documentation, you\ncan use ``autocomponentconfig`` to automatically document your configuration\nbased on the code. Second, you can use\n:py:func:`everett.manager.get_runtime_config` to print out the runtime\nconfiguration at startup.\n\n\nUsing components that share configuration by passing arguments\n==============================================================\n\nSay we have multiple components that share some configuration value that's\nprobably managed by another component.\n\nFor example, a \"basedir\" configuration value that defines the root directory for\nall the things this application does things with.\n\nLet's create an app component which creates two file system components passing\nthem a basedir:\n\n.. literalinclude:: ../examples/recipes_shared.py\n   :language: python\n\n\nWhy do it this way?\n\nIn this scenario, the ``basedir`` is defined at the app-scope and is passed to\nthe reader and writer components when they're created. In this way, ``basedir``\nis app configuration, but not reader/writer configuration.\n\n\nUsing components that share configuration using alternate keys\n==============================================================\n\nSay we have two components that share a set of credentials. We don't want to\nhave to specify the same set of credentials twice, so instead, we use alternate\nkeys which let you specify other keys to look at for a configuration value.\nThis lets us have both components look at the same keys for their credentials\nand then we only have to define them once.\n\nLet's create a db reader and a db writer component:\n\n.. literalinclude:: ../examples/recipes_alternate_keys.py\n   :language: python\n"
  },
  {
    "path": "docs/test_code.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"\nTests the code in the ../examples/ directory before including it in the docs.\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\n\n\ndef main():\n    # FIXME(willkg): This is written to run on my machine.\n    for fn in os.listdir(\"../examples/\"):\n        if not fn.endswith(\".py\"):\n            continue\n\n        print(\"Running %s...\" % fn)\n        subprocess.check_output([\"python\", \"../examples/%s\" % fn])\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "docs/testing.rst",
    "content": "=======\nTesting\n=======\n\nYou can test your code using ``config_override`` in your tests to test various\nconfiguration values.\n\nFor example:\n\n.. literalinclude:: ../examples/testdebug.py\n   :language: python\n\n\n.. autofunction:: everett.manager.config_override\n   :noindex:\n"
  },
  {
    "path": "examples/component_appconfig.py",
    "content": "# component_appconfig.py\n\nfrom everett.manager import ConfigManager, Option\n\n\n# Central class holding configuration information\nclass AppConfig:\n    class Config:\n        debug = Option(\n            parser=bool,\n            default=\"false\",\n            doc=\"Switch debug mode on and off.\",\n        )\n\n\n# Build a ConfigManger to look at the process environment for\n# configuration and bound to the configuration options specified in\n# AppConfig\n\n\ndef get_config():\n    manager = ConfigManager.basic_config(\n        doc=\"Check https://example.com/configuration for docs.\"\n    )\n\n    # Bind the configuration manager to the AppConfig component so that\n    # it handles option properties like defaults, parsers, documentation,\n    # and so on.\n    return manager.with_options(AppConfig())\n\n\nconfig = get_config()\n\ndebug = config(\"debug\")\nprint(f\"debug: {debug}\")\n"
  },
  {
    "path": "examples/componentapp.py",
    "content": "# componentapp.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass S3Bucket:\n    class Config:\n        region = Option(doc=\"AWS S3 region\")\n        bucket_name = Option(doc=\"AWS S3 bucket name\")\n\n    def __init__(self, config):\n        # Bind the configuration to just the configuration this component\n        # requires such that this component is self-contained\n        self.config = config.with_options(self)\n\n        self.region = self.config(\"region\")\n        self.bucket_name = self.config(\"bucket_name\")\n\n    def repr(self):\n        return f\"<S3Bucket {self.region} {self.bucket_name}>\"\n\n\nconfig = ConfigManager.from_dict(\n    {\n        \"S3_SOURCE_REGION\": \"us-east-1\",\n        \"S3_SOURCE_BUCKET_NAME\": \"mycompany_oldbucket\",\n        \"S3_DEST_REGION\": \"us-east-1\",\n        \"S3_DEST_BUCKET_NAME\": \"mycompany_newbucket\",\n    }\n)\n\ns3_config = config.with_namespace(\"s3\")\n\nsource_bucket = S3Bucket(s3_config.with_namespace(\"source\"))\ndest_bucket = S3Bucket(s3_config.with_namespace(\"dest\"))\n\nprint(repr(source_bucket))\nprint(repr(dest_bucket))\n"
  },
  {
    "path": "examples/components_subclass.py",
    "content": "# components_subclass.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass ComponentA:\n    class Config:\n        foo = Option(default=\"foo_from_a\")\n        bar = Option(default=\"bar_from_a\")\n\n\nclass ComponentB(ComponentA):\n    class Config:\n        foo = Option(default=\"foo_from_b\")\n\n    def __init__(self, config):\n        self.config = config.with_options(self)\n\n\nconfig = ConfigManager.basic_config()\ncompb = ComponentB(config)\n\nprint(compb.config(\"foo\"))\nprint(compb.config(\"bar\"))\n"
  },
  {
    "path": "examples/environments.py",
    "content": "# environments.py\n\nfrom everett import NO_VALUE\nfrom everett.manager import listify\n\n\nclass NoOpEnv(object):\n    def get(self, key, namespace=None):\n        # The namespace is either None, a string or a list of\n        # strings. This converts it into a list.\n        namespace = listify(namespace)\n\n        # FIXME: Your code to extract the key in namespace here.\n\n        # At this point, the key doesn't exist in the namespace\n        # for this environment, so return a ``NO_VALUE``.\n        return NO_VALUE\n"
  },
  {
    "path": "examples/handling_exceptions.py",
    "content": "# handling_exceptions.py\n\nimport logging\n\nfrom everett import InvalidValueError\nfrom everett.manager import ConfigManager\n\nlogging.basicConfig()\n\nconfig = ConfigManager.from_dict({\"debug_mode\": \"monkey\"})\n\ntry:\n    some_val = config(\"debug_mode\", parser=bool, doc=\"set debug mode\")\nexcept InvalidValueError:\n    print(\"I'm sorry dear user, but DEBUG_MODE must be 'true' or 'false'.\")\n"
  },
  {
    "path": "examples/msg_builder.py",
    "content": "# msg_builder.py\n\nfrom everett.manager import ConfigManager, ConfigOSEnv\n\n\ndef build_msg_for_ini(namespace, key, parser, msg=\"\", option_doc=\"\", config_doc=\"\"):\n    namespace = namespace or [\"main\"]\n    namespace = \"_\".join(namespace)\n\n    return f\"Dear user. {key} in section [{namespace}] is not correct. Please fix it.\"\n\n\nconfig = ConfigManager(\n    environments=[ConfigOSEnv()],\n    msg_builder=build_msg_for_ini,\n)\n\nconfig(\"debug\", default=\"false\", parser=bool)\n"
  },
  {
    "path": "examples/myserver.py",
    "content": "# myserver.py\n\n\"\"\"\nMinimal example showing how to use configuration for a web app.\n\"\"\"\n\nfrom everett.manager import ConfigManager\n\nconfig = ConfigManager.basic_config(\n    doc=\"Check https://example.com/configuration for documentation.\"\n)\n\nhost = config(\"host\", default=\"localhost\")\nport = config(\"port\", default=\"8000\", parser=int)\ndebug_mode = config(\n    \"debug\",\n    default=\"False\",\n    parser=bool,\n    doc=\"Set to True for debugmode; False for regular mode\",\n)\n\nprint(f\"host: {host}\")\nprint(f\"port: {port}\")\nprint(f\"debug_mode: {debug_mode}\")\n"
  },
  {
    "path": "examples/myserver_with_environments.py",
    "content": "# myserver_with_environments.py\n\n\"\"\"\nMinimal example showing how to use configuration for a web app that pulls\nconfiguration from specified environments.\n\"\"\"\n\nimport os\nfrom everett.ext.inifile import ConfigIniEnv\nfrom everett.manager import ConfigManager, ConfigOSEnv, ConfigDictEnv\n\nconfig = ConfigManager(\n    [\n        # Pull from the OS environment first\n        ConfigOSEnv(),\n        # Fall back to the file specified by the FOO_INI OS environment\n        # variable if such file exists\n        ConfigIniEnv(os.environ.get(\"FOO_INI\")),\n        # Fall back to this dict of defaults\n        ConfigDictEnv({\"FOO_VAR\": \"bar\"}),\n    ],\n    doc=\"Check https://example.com/configuration for documentation.\",\n)\n\nhost = config(\"host\", default=\"localhost\")\nport = config(\"port\", default=\"8000\", parser=int)\ndebug_mode = config(\n    \"debug\",\n    default=\"False\",\n    parser=bool,\n    doc=\"Set to True for debugmode; False for regular mode\",\n)\n\nprint(f\"host: {host}\")\nprint(f\"port: {port}\")\nprint(f\"debug_mode: {debug_mode}\")\n"
  },
  {
    "path": "examples/namespaces.py",
    "content": "# namespaces.py\n\nfrom everett.manager import ConfigManager\n\n\ndef open_connection(config):\n    username = config(\"username\")\n    password = config(\"password\")\n    port = config(\"port\", default=\"5432\", parser=int)\n\n    print(f\"Opened database with {username}/{password} on port {port}\")\n\n\nconfig = ConfigManager.from_dict(\n    {\n        \"DB_USERNAME\": \"admin\",\n        \"DB_PASSWORD\": \"ou812\",\n    }\n)\n\n# Database configuration keys are all prefixed with \"db\", so we want to\n# retrieve database configuration keys with the \"db\" namespace\ndb_config = config.with_namespace(\"db\")\n\nopen_connection(db_config)\n"
  },
  {
    "path": "examples/namespaces2.py",
    "content": "# namespaces2.py\n\nfrom everett.manager import ConfigManager\n\n\ndef open_connection(config):\n    username = config(\"username\")\n    password = config(\"password\")\n    port = config(\"port\", default=\"5432\", parser=int)\n\n    print(f\"Opened database with {username}/{password} on port {port}\")\n\n\nconfig = ConfigManager.from_dict(\n    {\n        \"SOURCE_DB_USERNAME\": \"admin\",\n        \"SOURCE_DB_PASSWORD\": \"ou812\",\n        \"DEST_DB_USERNAME\": \"admin\",\n        \"DEST_DB_PASSWORD\": \"P9rwvnnj8CidECMb\",\n    }\n)\n\n# Database configuration keys are all prefixed with \"db\", so we want to\n# retrieve database configuration keys with the \"db\" namespace\nsource_db_config = config.with_namespace(\"source_db\")\ndest_db_config = config.with_namespace(\"dest_db\")\n\nsource_conn = open_connection(source_db_config)\ndest_conn = open_connection(dest_db_config)\n"
  },
  {
    "path": "examples/parser_examples.py",
    "content": "# parser_examples.py\n\nfrom everett.manager import ConfigManager, get_parser\n\n\ndef parse_ynm(val):\n    \"\"\"Returns True, False or None (empty string)\"\"\"\n    val = val.strip().lower()\n    if not val:\n        return None\n\n    return val[0] == \"y\"\n\n\nconfig = ConfigManager.from_dict(\n    {\"NO_ANSWER\": \"\", \"YES\": \"yes\", \"ALSO_YES\": \"y\", \"NO\": \"no\"}\n)\n\nassert config(\"no_answer\", parser=parse_ynm) is None\nassert config(\"yes\", parser=parse_ynm) is True\nassert config(\"also_yes\", parser=parse_ynm) is True\nassert config(\"no\", parser=parse_ynm) is False\n\n\nclass Pairs(object):\n    def __init__(self, val_parser):\n        self.val_parser = val_parser\n\n    def __call__(self, val):\n        val_parser = get_parser(self.val_parser)\n        out = []\n        for part in val.split(\",\"):\n            k, v = part.split(\":\")\n            out.append((k, val_parser(v)))\n        return out\n\n\nconfig = ConfigManager.from_dict({\"FOO\": \"a:1,b:2,c:3\"})\n\nassert config(\"FOO\", parser=Pairs(int)) == [(\"a\", 1), (\"b\", 2), (\"c\", 3)]\n"
  },
  {
    "path": "examples/recipes_alternate_keys.py",
    "content": "# recipes_alternate_keys.py\n\nfrom everett.manager import ConfigManager, Option\n\n\nclass DatabaseReader:\n    class Config:\n        username = Option(alternate_keys=[\"root:db_username\"])\n        password = Option(alternate_keys=[\"root:db_password\"])\n\n    def __init__(self, config):\n        self.config = config.with_options(self)\n\n\nclass DatabaseWriter:\n    class Config:\n        username = Option(alternate_keys=[\"root:db_username\"])\n        password = Option(alternate_keys=[\"root:db_password\"])\n\n    def __init__(self, config):\n        self.config = config.with_options(self)\n\n\n# Define a shared configuration\nconfig = ConfigManager.from_dict({\"DB_USERNAME\": \"foo\", \"DB_PASSWORD\": \"bar\"})\n\nreader = DatabaseReader(config.with_namespace(\"reader\"))\nassert reader.config(\"username\") == \"foo\"\nassert reader.config(\"password\") == \"bar\"\n\nwriter = DatabaseWriter(config.with_namespace(\"writer\"))\nassert writer.config(\"username\") == \"foo\"\nassert writer.config(\"password\") == \"bar\"\n\n\n# Or define different credentials\nconfig = ConfigManager.from_dict(\n    {\n        \"READER_USERNAME\": \"joe\",\n        \"READER_PASSWORD\": \"foo\",\n        \"WRITER_USERNAME\": \"pete\",\n        \"WRITER_PASSWORD\": \"bar\",\n    }\n)\n\nreader = DatabaseReader(config.with_namespace(\"reader\"))\nassert reader.config(\"username\") == \"joe\"\nassert reader.config(\"password\") == \"foo\"\n\nwriter = DatabaseWriter(config.with_namespace(\"writer\"))\nassert writer.config(\"username\") == \"pete\"\nassert writer.config(\"password\") == \"bar\"\n"
  },
  {
    "path": "examples/recipes_appconfig.py",
    "content": "# recipes_appconfig.py\n\nimport logging\n\nfrom everett.manager import ConfigManager, Option\n\n\nTEXT_TO_LOGGING_LEVEL = {\n    \"CRITICAL\": 50,\n    \"ERROR\": 40,\n    \"WARNING\": 30,\n    \"INFO\": 20,\n    \"DEBUG\": 10,\n}\n\n\ndef parse_loglevel(value):\n    try:\n        return TEXT_TO_LOGGING_LEVEL[value.upper()]\n    except KeyError as exc:\n        raise ValueError(\n            f'\"{value}\" is not a valid logging level. Try CRITICAL, ERROR, '\n            \"WARNING, INFO, DEBUG\"\n        ) from exc\n\n\nclass AppConfig:\n    class Config:\n        debug = Option(\n            parser=bool,\n            default=\"false\",\n            doc=\"Turns on debug mode for the application\",\n        )\n        loglevel = Option(\n            parser=parse_loglevel,\n            default=\"INFO\",\n            doc=(\n                \"Log level for the application; CRITICAL, ERROR, WARNING, INFO, DEBUG\"\n            ),\n        )\n\n\ndef init_app():\n    manager = ConfigManager.from_dict({})\n    config = manager.with_options(AppConfig())\n\n    logging.basicConfig(level=config(\"loglevel\"))\n\n    if config(\"debug\"):\n        logging.info(\"debug mode!\")\n\n\nif __name__ == \"__main__\":\n    init_app()\n"
  },
  {
    "path": "examples/recipes_djangosettings.py",
    "content": "# recipes_djangosettings.py\n\nfrom everett.manager import ConfigManager\n\n\n_config = ConfigManager.basic_config()\n\n\nDEBUG = _config(\n    \"debug\", parser=bool, default=\"False\", doc=\"Whether or not DEBUG mode is enabled.\"\n)\n\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.cache.backends.memcached.PyMemcacheCache\",\n        \"LOCATION\": _config(\n            \"cache_location\", default=\"127.0.0.1:11211\", doc=\"Memcache cache location.\"\n        ),\n        \"TIMEOUT\": _config(\n            \"cache_timeout\",\n            default=\"500\",\n            parser=int,\n            doc=\"Timeout to use when accessing cache.\",\n        ),\n        \"KEY_PREFIX\": _config(\n            \"cache_key_prefix\",\n            default=\"socorro\",\n            doc=\"Key prefix to use for all cache keys.\",\n        ),\n    }\n}\n"
  },
  {
    "path": "examples/recipes_shared.py",
    "content": "# recipes_shared.py\n\nimport os\n\nfrom everett.manager import ConfigManager, Option, parse_class\n\n\nclass App:\n    class Config:\n        basedir = Option()\n        reader = Option(parser=parse_class)\n        writer = Option(parser=parse_class)\n\n    def __init__(self, config):\n        self.config = config.with_options(self)\n\n        self.basedir = self.config(\"basedir\")\n        self.reader = self.config(\"reader\")(config, self.basedir)\n        self.writer = self.config(\"writer\")(config, self.basedir)\n\n\nclass FilesystemReader:\n    class Config:\n        file_type = Option(default=\"json\")\n\n    def __init__(self, config, basedir):\n        self.config = config.with_options(self)\n        self.read_dir = os.path.join(basedir, \"read\")\n\n\nclass FilesystemWriter:\n    class Config:\n        file_type = Option(default=\"json\")\n\n    def __init__(self, config, basedir):\n        self.config = config.with_options(self)\n        self.write_dir = os.path.join(basedir, \"write\")\n\n\nconfig = ConfigManager.from_dict(\n    {\n        \"BASEDIR\": \"/tmp\",\n        \"READER\": \"__main__.FilesystemReader\",\n        \"WRITER\": \"__main__.FilesystemWriter\",\n        \"READER_FILE_TYPE\": \"json\",\n        \"WRITER_FILE_TYPE\": \"yaml\",\n    }\n)\n\n\napp = App(config)\nassert app.reader.read_dir == \"/tmp/read\"\nassert app.writer.write_dir == \"/tmp/write\"\n"
  },
  {
    "path": "examples/testdebug.py",
    "content": "# testdebug.py\n\n\"\"\"\nMinimal example showing how to override configuration values when testing.\n\"\"\"\n\nimport unittest\n\nfrom everett.manager import ConfigManager, config_override\n\n\nclass App:\n    def __init__(self):\n        config = ConfigManager.basic_config()\n        self.debug = config(\"debug\", default=\"False\", parser=bool)\n\n\nclass TestDebug(unittest.TestCase):\n    def test_debug_on(self):\n        with config_override(DEBUG=\"on\"):\n            app = App()\n            self.assertTrue(app.debug)\n\n    def test_debug_off(self):\n        with config_override(DEBUG=\"off\"):\n            app = App()\n            self.assertFalse(app.debug)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "justfile",
    "content": "sphinxbuild := \"../.venv/bin/sphinx-build\"\n\n@_default:\n    just --list\n\n# Build a development environment\ndevenv:\n    uv sync --extra sphinx --extra ini --extra yaml --extra dev --refresh --upgrade\n\n# Run tests, linting, and static typechecking\ntest: devenv\n    uv run tox\n\n# Format files\nformat: devenv\n    uv run tox exec -e py310-lint -- ruff format\n\n# Lint files\nlint: devenv\n    uv run tox -e py310-lint\n\n# Wipe devenv and build artifacts\nclean:\n    rm -rf .venv uv.lock\n    rm -rf build dist src/everett.egg-info .tox .pytest_cache .mypy_cache\n    rm -rf docs/_build/*\n    find src/ tests/ -name __pycache__ | xargs rm -rf\n    find src/ tests/ -name '*.pyc' | xargs rm -rf\n\n# Runs cog and builds Sphinx docs\ndocs: devenv\n    uv run python -m cogapp -r README.rst\n    uv run python -m cogapp -r docs/components.rst\n    uv run python -m cogapp -r docs/configmanager.rst\n    uv run python -m cogapp -r docs/configuration.rst\n    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ clean\n    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ doctest\n    SPHINXBUILD={{sphinxbuild}} make -e -C docs/ html\n\n# Build files for relase\nbuild: devenv\n    rm -rf build/ dist/\n    uv run python -m build\n    uv run twine check dist/*\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"everett\"\ndescription = \"Configuration library for Python applications\"\nversion = \"3.5.0\"\nreadme = \"README.rst\"\nkeywords = [\"conf\", \"config\", \"configuration\", \"ini\", \"env\", \"yaml\"]\nauthors = [{name = \"Will Kahn-Greene\"}]\nlicense = {text = \"MPLv2\"}\nrequires-python = \">=3.10\"\ndependencies = []\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)\",\n    \"Natural Language :: English\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n    \"Programming Language :: Python :: Implementation :: CPython\",\n    \"Topic :: Software Development :: Libraries :: Python Modules\",\n]\nurls.Homepage = \"https://everett.readthedocs.io/\"\nurls.Source = \"https://github.com/willkg/everett/\"\nurls.Issues = \"https://github.com/willkg/everett/issues\"\n\n[project.optional-dependencies]\nsphinx = [\n    \"sphinx\",\n]\nini = [\n    \"configobj\",\n]\nyaml = [\n    \"PyYAML\",\n]\ndev = [\n    \"build\",\n    \"cogapp\",\n    \"mypy\",\n    \"pytest\",\n    \"ruff\",\n    \"tox\",\n    \"tox-gh-actions\",\n    \"tox-uv\",\n    \"twine\",\n    \"types-PyYAML\",\n    \"Sphinx==7.2.6\",\n    \"sphinx_rtd_theme\",\n]\n\n\n[build-system]\nrequires = [\"setuptools\", \"setuptools-scm\"]\nbuild-backend = \"setuptools.build_meta\"\n\n\n[tool.ruff]\ntarget-version = \"py310\"\nsrc = [\"src\"]\nline-length = 88\n\n[tool.ruff.lint]\n# Enable pycodestyle (E), pyflakes (F), and bugbear (B) rules\nselect = [\"E\", \"F\", \"B\"]\n\n# Ignore line length violations; ruff format does its best and we can rely on\n# that\nignore = [\"E501\"]\n\n[tool.ruff.lint.flake8-quotes]\ndocstring-quotes = \"double\"\n\n\n[tool.mypy]\npython_version = \"3.10\"\ndisallow_untyped_defs = true\n\n[[tool.mypy.overrides]]\nmodule = \"configobj.*\"\nignore_missing_imports = true\n\n[[tool.mypy.overrides]]\nmodule = \"docutils.*\"\nignore_missing_imports = true\n\n\n[tool.pytest.ini_options]\nfilterwarnings = [\n    \"error\",\n    \"ignore:::babel[.*]\",\n    \"ignore:::jinja2[.*]\",\n    \"ignore:::yaml[.*]\",\n    # Sphinx 4.2.0 uses distutils and it's deprecated in 3.10\n    \"ignore::DeprecationWarning:sphinx\",\n]\n\n\n[tool.tox]\nlegacy_tox_ini = \"\"\"\n[tox]\nenvlist =\n    py310\n    py311\n    py312\n    py313\n    py314\n    py310-doctest\n    py310-lint\n    py310-typecheck\nuv_python_preference = only-managed\n\n[gh-actions]\npython =\n    3.10: py310\n    3.11: py311\n    3.12: py312\n    3.13: py313\n    3.14: py314\n\n[testenv]\nextras = dev,ini,sphinx,yaml\ncommands = pytest {posargs} tests/\n\n[testenv:py310-doctest]\nbasepython = python3.10\ncommands = pytest --doctest-modules src/\n\n[testenv:py310-lint]\nallowlist_externals = ruff\nbasepython = python3.10\nchangedir = {toxinidir}\ncommands =\n    ruff format --check tests docs examples\n    ruff check src tests docs examples\n\n[testenv:py310-typecheck]\nbasepython = python3.10\nchangedir = {toxinidir}\ncommands =\n    mypy src/everett/\n\"\"\"\n"
  },
  {
    "path": "src/everett/__init__.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\n\"\"\"Everett is a Python library for configuration.\"\"\"\n\nfrom importlib.metadata import (\n    version as importlib_version,\n    PackageNotFoundError,\n)\nfrom typing import Callable, Union\n\n\ntry:\n    __version__ = importlib_version(\"everett\")\nexcept PackageNotFoundError:\n    __version__ = \"unknown\"\n\n\n__all__ = [\n    \"NO_VALUE\",\n    \"ConfigurationError\",\n    \"DetailedConfigurationError\",\n    \"InvalidKeyError\",\n    \"InvalidValueError\",\n    \"ConfigurationMissingError\",\n]\n\n\n# NoValue instances are always false\nclass NoValue:\n    def __nonzero__(self) -> bool:\n        return False\n\n    def __bool__(self) -> bool:\n        return False\n\n    def __repr__(self) -> str:\n        return \"NO_VALUE\"\n\n\n#: Singleton indicating a non-value.\nNO_VALUE = NoValue()\n\n\nclass ConfigurationError(Exception):\n    \"\"\"Configuration error base class.\"\"\"\n\n    pass\n\n\nclass InvalidKeyError(ConfigurationError):\n    \"\"\"Error that indicates the key is not valid for this component.\"\"\"\n\n    pass\n\n\nclass DetailedConfigurationError(ConfigurationError):\n    \"\"\"Base class for configuration errors that have a msg, namespace, key, and parser.\"\"\"\n\n    def __init__(\n        self, msg: str, namespace: Union[list[str], None], key: str, parser: Callable\n    ):\n        self.msg = msg\n        self.namespace = namespace\n        self.key = key\n        self.parser = parser\n        super().__init__(msg, namespace, key, parser)\n\n    def __str__(self) -> str:\n        return self.msg\n\n\nclass ConfigurationMissingError(DetailedConfigurationError):\n    \"\"\"Error that indicates that required configuration is missing.\"\"\"\n\n    pass\n\n\nclass InvalidValueError(DetailedConfigurationError):\n    \"\"\"Error that indicates that the value is not valid.\n\n    .. Note::\n\n       Parsers should not raise this exception. Parsers should raise ``ValueError``\n       when the value is not a valid value.\n\n    \"\"\"\n\n    pass\n"
  },
  {
    "path": "src/everett/ext/__init__.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Holds env files that have other requirements.\"\"\"\n"
  },
  {
    "path": "src/everett/ext/inifile.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Holds the ConfigIniEnv environment.\n\nTo use this, you must install the optional requirements::\n\n    $ pip install 'everett[ini]'\n\n\"\"\"\n\nimport logging\nimport os\nfrom typing import Optional, Union\n\nfrom configobj import ConfigObj\n\nfrom everett import NO_VALUE, NoValue\nfrom everett.manager import generate_uppercase_key, get_key_from_envs, listify\n\n\nlogger = logging.getLogger(\"everett\")\n\n\nclass ConfigIniEnv:\n    \"\"\"Source for pulling configuration from INI files.\n\n    This requires optional dependencies. You can install them with::\n\n        $ pip install 'everett[ini]'\n\n    Takes a path or list of possible paths to look for a INI file. It uses\n    the first INI file it can find.\n\n    If it finds no INI files in the possible paths, then this configuration\n    source will be a no-op.\n\n    This will expand ``~`` as well as work relative to the current working\n    directory.\n\n    This example looks just for the INI file specified in the environment::\n\n        from everett.manager import ConfigManager\n        from everett.ext.inifile import ConfigIniEnv\n\n        config = ConfigManager([\n            ConfigIniEnv(possible_paths=os.environ.get(\"FOO_INI\"))\n        ])\n\n\n    If there's no ``FOO_INI`` in the environment, then the path will be\n    ignored.\n\n    Here's an example that looks for the INI file specified in the environment\n    variable ``FOO_INI`` and failing that will look for ``.antenna.ini`` in the\n    user's home directory::\n\n        from everett.manager import ConfigManager\n        from everett.ext.inifile import ConfigIniEnv\n\n        config = ConfigManager([\n            ConfigIniEnv(\n                possible_paths=[\n                    os.environ.get(\"FOO_INI\"),\n                    \"~/.antenna.ini\"\n                ]\n            )\n        ])\n\n\n    This example looks for a ``config/local.ini`` file which overrides values\n    in a ``config/base.ini`` file both are relative to the current working\n    directory::\n\n        from everett.manager import ConfigManager\n        from everett.ext.inifile import ConfigIniEnv\n\n        config = ConfigManager([\n            ConfigIniEnv(possible_paths=\"config/local.ini\"),\n            ConfigIniEnv(possible_paths=\"config/base.ini\")\n        ])\n\n\n    Note how you can have multiple ``ConfigIniEnv`` files and this is how you\n    can set Everett up to have values in one INI file override values in\n    another INI file.\n\n    INI files must have a \"main\" section. This is where keys that aren't in a\n    namespace are placed.\n\n    Minimal INI file::\n\n        [main]\n\n\n    In the INI file, namespace is a section. So key \"user\" in namespace \"foo\"\n    is::\n\n        [foo]\n        user=someval\n\n\n    Everett uses configobj, so it supports nested sections like this::\n\n        [main]\n        foo=bar\n\n        [namespace]\n        foo2=bar2\n\n          [[namespace2]]\n          foo3=bar3\n\n\n    Which gives you these:\n\n    * ``FOO``\n    * ``NAMESPACE_FOO2``\n    * ``NAMESPACE_NAMESPACE2_FOO3``\n\n    See more details here:\n    http://configobj.readthedocs.io/en/latest/configobj.html#the-config-file-format\n\n    \"\"\"\n\n    def __init__(self, possible_paths: Union[str, list[str]]) -> None:\n        \"\"\"\n        :param possible_paths: either a single string with a file path (e.g.\n            ``\"/etc/project.ini\"`` or a list of strings with file paths\n\n        \"\"\"\n        self.cfg = {}\n        self.path = None\n        possible_paths = listify(possible_paths)\n\n        for path in possible_paths:\n            if not path:\n                continue\n\n            path = os.path.abspath(os.path.expanduser(path.strip()))\n            if path and os.path.isfile(path):\n                self.path = path\n                self.cfg.update(self.parse_ini_file(path))\n                break\n\n        if not self.path:\n            logger.debug(\"No INI file found: %s\", possible_paths)\n\n    def parse_ini_file(self, path: str) -> dict:\n        \"\"\"Parse ini file at ``path`` and return dict.\"\"\"\n        cfgobj = ConfigObj(path, list_values=False)\n\n        def extract_section(namespace: list[str], d: dict) -> dict:\n            cfg = {}\n            for key, val in d.items():\n                if isinstance(d[key], dict):\n                    cfg.update(extract_section(namespace + [key], d[key]))\n                else:\n                    cfg[\"_\".join(namespace + [key]).upper()] = val\n\n            return cfg\n\n        return extract_section([], cfgobj.dict())\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        if not self.path:\n            return NO_VALUE\n\n        # NOTE(willkg): The \"main\" section is considered the root mainspace.\n        namespace = namespace or [\"main\"]\n        logger.debug(\"Searching %r for key: %s, namespace: %s\", self, key, namespace)\n        full_key = generate_uppercase_key(key, namespace)\n        return get_key_from_envs(self.cfg, full_key)\n\n    def __repr__(self) -> str:\n        return \"<ConfigIniEnv: %s>\" % self.path\n"
  },
  {
    "path": "src/everett/ext/yamlfile.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Holds the ConfigYamlEnv environment.\n\nTo use this, you must install the optional requirements::\n\n    $ pip install 'everett[yaml]'\n\n\"\"\"\n\nimport logging\nimport os\nfrom typing import Optional, Union\n\nimport yaml\n\nfrom everett import ConfigurationError, NO_VALUE, NoValue\nfrom everett.manager import generate_uppercase_key, get_key_from_envs, listify\n\n\nlogger = logging.getLogger(\"everett\")\n\n\nclass ConfigYamlEnv:\n    \"\"\"Source for pulling configuration from YAML files.\n\n    This requires optional dependencies. You can install them with::\n\n        $ pip install 'everett[yaml]'\n\n\n    Takes a path or list of possible paths to look for a YAML file. It uses\n    the first YAML file it can find.\n\n    If it finds no YAML files in the possible paths, then this configuration\n    source will be a no-op.\n\n    This will expand ``~`` as well as work relative to the current working\n    directory.\n\n    This example looks just for the YAML file specified in the environment::\n\n        from everett.manager import ConfigManager\n        from everett.ext.yamlfile import ConfigYamlEnv\n\n        config = ConfigManager([\n            ConfigYamlEnv(os.environ.get('FOO_YAML'))\n        ])\n\n    If there's no ``FOO_YAML`` in the environment, then the path will be\n    ignored.\n\n    Here's an example that looks for the YAML file specified in the environment\n    variable ``FOO_YAML`` and failing that will look for ``.antenna.yaml`` in\n    the user's home directory::\n\n        from everett.manager import ConfigManager\n        from everett.ext.yamlfile import ConfigYamlEnv\n\n        config = ConfigManager([\n            ConfigYamlEnv([\n                os.environ.get('FOO_YAML'),\n                '~/.antenna.yaml'\n            ])\n        ])\n\n    This example looks for a ``config/local.yaml`` file which overrides values\n    in a ``config/base.yaml`` file both are relative to the current working\n    directory::\n\n        from everett.manager import ConfigManager\n        from everett.ext.yamlfile import ConfigYamlEnv\n\n        config = ConfigManager([\n            ConfigYamlEnv('config/local.yaml'),\n            ConfigYamlEnv('config/base.yaml')\n        ])\n\n\n    Note how you can have multiple ``ConfigYamlEnv`` files. This is how you\n    can set Everett up to have values in one YAML file override values in\n    another YAML file.\n\n    Everett looks for keys and values in YAML files. YAML files can be split\n    into multiple documents, but Everett only looks at the first one.\n\n    Keys are case-insensitive. You can do namespaces either in the key itself\n    using ``_`` as a separator or as nested mappings.\n\n    All values should be double-quoted.\n\n    Here's an example::\n\n        foo: \"bar\"\n        FOO2: \"bar\"\n        namespace_foo: \"bar\"\n        namespace:\n            namespace2:\n                foo: \"bar\"\n\n    Giving you these namespaced keys:\n\n    * ``FOO``\n    * ``FOO2``\n    * ``NAMESPACE_FOO``\n    * ``NAMESPACE_NAMEPSACE2_FOO``\n\n    \"\"\"\n\n    def __init__(self, possible_paths: Union[str, list[str]]) -> None:\n        \"\"\"\n        :param possible_paths: either a single string with a file path (e.g.\n            ``\"/etc/project.yaml\"`` or a list of strings with file paths\n\n        \"\"\"\n        self.cfg = {}\n        self.path = None\n        possible_paths = listify(possible_paths)\n\n        for path in possible_paths:\n            if not path:\n                continue\n\n            path = os.path.abspath(os.path.expanduser(path.strip()))\n            if path and os.path.isfile(path):\n                self.path = path\n                self.cfg = self.parse_yaml_file(path)\n                break\n\n        if not self.path:\n            logger.debug(\"No YAML file found: %s\", possible_paths)\n\n    def parse_yaml_file(self, path: str) -> dict:\n        \"\"\"Parse yaml file at ``path`` and return a dict.\"\"\"\n        with open(path) as fp:\n            data = yaml.safe_load(fp)\n\n        if not data:\n            return {}\n\n        def traverse(namespace: list[str], d: dict) -> dict:\n            cfg = {}\n            for key, val in d.items():\n                if isinstance(val, dict):\n                    cfg.update(traverse(namespace + [key], val))\n                elif isinstance(val, str):\n                    cfg[\"_\".join(namespace + [key]).upper()] = val\n                else:\n                    # All values should be double-quoted strings so they\n                    # parse as strings; anything else is a configuration\n                    # error at parse-time\n                    raise ConfigurationError(\n                        \"Invalid value %r in file %s: values must be double-quoted strings\"\n                        % (val, path)\n                    )\n\n            return cfg\n\n        return traverse([], data)\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        if not self.path:\n            return NO_VALUE\n\n        logger.debug(\"Searching %r for key: %s, namepsace: %s\", self, key, namespace)\n        full_key = generate_uppercase_key(key, namespace)\n        return get_key_from_envs(self.cfg, full_key)\n\n    def __repr__(self) -> str:\n        return \"<ConfigYamlEnv: %s>\" % self.path\n"
  },
  {
    "path": "src/everett/manager.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Contains configuration infrastructure.\n\nThis module contains the configuration classes and functions for deriving\nconfiguration values from specified sources in the order specified.\n\n\"\"\"\n\nfrom functools import wraps\nimport importlib\nimport inspect\nimport logging\nimport os\nimport re\nimport sys\nfrom types import TracebackType\nfrom typing import (\n    Any,\n    Callable,\n    Optional,\n    Union,\n)\nfrom collections.abc import Iterable, Mapping\n\nfrom everett import (\n    ConfigurationError,\n    ConfigurationMissingError,\n    InvalidValueError,\n    InvalidKeyError,\n    NO_VALUE,\n    NoValue,\n)\n\n\n__all__ = [\n    \"ChoiceOf\",\n    \"ConfigDictEnv\",\n    \"ConfigEnvFileEnv\",\n    \"ConfigManager\",\n    \"ConfigObjEnv\",\n    \"ConfigOSEnv\",\n    \"config_override\",\n    \"get_config_for_class\",\n    \"get_runtime_config\",\n    \"ListOf\",\n    \"Option\",\n    \"parse_bool\",\n    \"parse_class\",\n    \"parse_data_size\",\n    \"parse_env_file\",\n    \"parse_time_period\",\n]\n\n\n# Regex for valid keys in an env file\nENV_KEY_RE = re.compile(r\"^[a-z_][a-z0-9_]*$\", flags=re.IGNORECASE)\n\nlogger = logging.getLogger(\"everett\")\n\n\ndef qualname(thing: Any) -> str:\n    \"\"\"Return the Python dotted name for a given thing.\n\n    >>> import everett.manager\n    >>> qualname(str)\n    'str'\n    >>> qualname(everett.manager.parse_class)\n    'everett.manager.parse_class'\n    >>> qualname(everett.manager)\n    'everett.manager'\n\n    :param thing: the thing to get the qualname from\n\n    :returns: the Python dotted name\n\n    \"\"\"\n    parts = []\n\n    # Add the module, unless it's a builtin\n    mod = inspect.getmodule(thing)\n    if mod and mod.__name__ not in (\"__main__\", \"__builtin__\", \"builtins\"):\n        parts.append(mod.__name__)\n\n    if hasattr(thing, \"__qualname__\"):\n        parts.append(thing.__qualname__)\n        return \".\".join(parts)\n\n    # If it's a module\n    if inspect.ismodule(thing):\n        return \".\".join(parts)\n\n    # It's an instance, so ... let's call repr on it\n    return repr(thing)\n\n\ndef build_msg(\n    namespace: Optional[list[str]],\n    key: Optional[str],\n    parser: Optional[Callable],\n    msg: str = \"\",\n    option_doc: str = \"\",\n    config_doc: str = \"\",\n) -> str:\n    \"\"\"Builds a message for a configuration error exception\n\n    :param namespace: list of strings that represent the configuration variable namespace or ``None``\n    :param key: the configuration variable key or ``None``\n    :param parser: the parser that will be used to parse the value for this configuration variable or ``None``\n    :param msg: the error message\n    :param option_doc: the configuration option documentation\n    :param config_doc: the ConfigManager documentation\n\n    :returns: the error message string\n\n    \"\"\"\n    text = [msg]\n    if key and parser:\n        full_key = generate_uppercase_key(key, namespace)\n        text.append(f\"{full_key} requires a value parseable by {qualname(parser)}\")\n    else:\n        full_key = None\n    if option_doc and full_key:\n        text.append(f\"{full_key} docs: {option_doc}\")\n    if config_doc:\n        text.append(f\"Project docs: {config_doc}\")\n\n    return \"\\n\".join([line for line in text if line])\n\n\n# FIXME(willkg): we can rewrite this as a dataclass as soon as we can drop\n# Python 3.6 support\nclass Option:\n    \"\"\"Settings for a single configuration option.\n\n    Use this when creating Everett configuration components.\n\n    Example::\n\n        from everett.manager import Option\n\n        class MyService:\n            # Note: The Config class has to be called \"Config\".\n            class Config:\n                host = Option(default=\"localhost\")\n                port = Option(default=\"8000\", parser=int)\n\n    \"\"\"\n\n    def __init__(\n        self,\n        default: Union[str, NoValue] = NO_VALUE,\n        alternate_keys: Optional[list[str]] = None,\n        doc: str = \"\",\n        parser: Callable = str,\n        meta: Any = None,\n    ):\n        \"\"\"\n        :param default: the default value (if any); this must be a string that is\n            parseable by the specified parser; if no default is provided, this\n            will raise an error or return ``everett.NO_VALUE`` depending on\n            the value of ``raise_error``\n\n        :param alternate_keys: the list of alternate keys to look up;\n            supports a ``root:`` key prefix which will cause this to look at\n            the configuration root rather than the current namespace\n\n            .. versionadded:: 0.3\n\n        :param doc: documentation for this config option\n\n            .. versionadded:: 0.6\n\n        :param parser: the parser for converting this value to a Python object\n\n        :param meta: any meta information that's tied to this option; useful\n            for noting which options are related in some way or which are secrets\n            that shouldn't be logged\n\n        \"\"\"\n        self.default = default\n        self.alternate_keys = alternate_keys\n        self.doc = doc\n        self.parser = parser\n        self.meta = meta or {}\n\n    def __eq__(self, obj: Any) -> bool:\n        return (\n            isinstance(obj, Option)\n            and obj.default == self.default\n            and obj.alternate_keys == self.alternate_keys\n            and obj.doc == self.doc\n            and obj.parser == self.parser\n            and obj.meta == self.meta\n        )\n\n\ndef get_config_for_class(cls: type) -> dict[str, tuple[Option, type]]:\n    \"\"\"Roll up configuration options for this class and parent classes.\n\n    This handles subclasses overriding configuration options in parent classes.\n\n    :param cls: the component class to return configuration options for\n\n    :returns: final dict of configuration options for this class in\n        ``key -> (option, cls)`` form\n\n    \"\"\"\n    options = {}\n    for subcls in reversed(cls.__mro__):\n        if not hasattr(subcls, \"Config\"):\n            continue\n\n        subcls_config = subcls.Config\n        for attr in subcls_config.__dict__.keys():\n            if attr.startswith(\"__\"):\n                continue\n\n            val = getattr(subcls_config, attr)\n            if isinstance(val, Option):\n                options[attr] = (val, subcls)\n    return options\n\n\ndef traverse_tree(\n    instance: Any, namespace: Optional[list[str]] = None\n) -> Iterable[tuple[list[str], str, Option, Any]]:\n    \"\"\"Traverses a tree of objects and computes the configuration for it\n\n    Note: This expects the tree not to have any loops or repeated nodes.\n\n    :param instance: the component to traverse\n    :param namespace: the list of strings forming the namespace or None\n\n    :returns: list of ``(namespace, key, value, option, component)``\n\n    \"\"\"\n    namespace = namespace or []\n\n    # Check to see if this class has options; if it does, capture those and\n    # traverse the tree\n    this_options = get_config_for_class(instance.__class__)\n    if not this_options:\n        return []\n\n    options = [\n        (namespace, key, option, instance)\n        for key, (option, cls) in this_options.items()\n    ]\n\n    # Now go through attributes for other options classes\n    for attr in dir(instance):\n        if attr.startswith(\"__\"):\n            continue\n        # NOTE(willkg): we skip slots; maybe they could be component classes,\n        # but that seems bizarre and I'd like to see a reasonable example\n        # before supporting it\n        val = getattr(instance, attr, None)\n        if not val or isinstance(val, Option):\n            continue\n\n        options.extend(traverse_tree(val, namespace + [attr]))\n\n    return options\n\n\ndef parse_env_file(envfile: Iterable[str]) -> dict:\n    \"\"\"Parse the content of an iterable of lines as ``.env``.\n\n    Return a dict of config variables.\n\n    >>> from everett.manager import parse_env_file\n    >>> parse_env_file([\"DUDE=Abides\"])\n    {'DUDE': 'Abides'}\n\n    \"\"\"\n    data = {}\n    for line_no, line in enumerate(envfile):\n        line = line.strip()\n        if not line or line.startswith(\"#\"):\n            continue\n        if \"=\" not in line:\n            raise ConfigurationError(\n                f\"Env file line missing = operator (line {line_no + 1})\"\n            )\n        k, v = line.split(\"=\", 1)\n        k = k.strip()\n        if not ENV_KEY_RE.match(k):\n            raise ConfigurationError(\n                f\"Invalid variable name {k!r} in env file (line {line_no + 1})\"\n            )\n\n        v = v.strip()\n\n        # Need to strip matching ' and \" from beginning and end--but only one\n        # round\n        for quote in \"'\\\"\":\n            if v.startswith(quote) and v.endswith(quote):\n                v = v[1:-1]\n                break\n\n        data[k] = v\n\n    return data\n\n\ndef parse_bool(val: str) -> bool:\n    \"\"\"Parse a bool value.\n\n    Handles a series of values, but you should probably standardize on\n    \"true\" and \"false\".\n\n    >>> from everett.manager import parse_bool\n    >>> parse_bool(\"y\")\n    True\n    >>> parse_bool(\"FALSE\")\n    False\n\n    \"\"\"\n    true_vals = (\"t\", \"true\", \"yes\", \"y\", \"1\", \"on\")\n    false_vals = (\"f\", \"false\", \"no\", \"n\", \"0\", \"off\")\n\n    val = val.lower()\n    if val in true_vals:\n        return True\n    if val in false_vals:\n        return False\n\n    raise ValueError(f\"{val!r} is not a valid bool value\")\n\n\ndef parse_class(val: str) -> Any:\n    \"\"\"Parse a string, imports the module and returns the class.\n\n    >>> from everett.manager import parse_class\n    >>> parse_class(\"everett.manager.Option\")\n    <class 'everett.manager.Option'>\n\n    \"\"\"\n    if \".\" not in val:\n        raise ValueError(f\"{val!r} is not a valid Python dotted-path\")\n\n    module_name, class_name = val.rsplit(\".\", 1)\n    module = importlib.import_module(module_name)\n    try:\n        return getattr(module, class_name)\n    except AttributeError as exc:\n        raise ValueError(\n            f\"{class_name!r} is not a valid member of {qualname(module)}\"\n        ) from exc\n\n\n_DATA_SIZE_METRIC_TO_MULTIPLIER = {\n    \"\": 1,\n    \"b\": 1,\n    \"kb\": 1_000,\n    \"mb\": pow(1_000, 2),\n    \"gb\": pow(1_000, 3),\n    \"tb\": pow(1_000, 4),\n    \"kib\": 1_024,\n    \"mib\": pow(1_024, 2),\n    \"gib\": pow(1_024, 3),\n    \"tib\": pow(1_024, 4),\n}\n_DATA_SIZE_RE = re.compile(\n    r\"^([0-9_]+)(\" + \"|\".join(_DATA_SIZE_METRIC_TO_MULTIPLIER.keys()) + \")?$\"\n)\n\n\ndef parse_data_size(val: str) -> Any:\n    \"\"\"Parse a string denoting a data size into an int of bytes.\n\n    This allows you to parse data sizes with a number and then the metric. Examples:\n\n    * 10b - 10 bytes\n    * 100kb - 100 kilobytes = 100 * 1000\n    * 40gb - 40 gigabytes = 40 * 1000^3\n    * 23gib - 40 gibibytes = 23 * 1024^3\n\n    Supported metrics:\n\n    * b - bytes\n    * decimal:\n\n      * kb - kilobytes\n      * mb - megabytes\n      * gb - gigabytes\n      * tb - terabytes\n      * pb - petabytes\n\n    * binary:\n\n      * kib - kibibytes\n      * mib - mebibytes\n      * gib - gibibytes\n      * tib - tebibytes\n      * pib - pebibytes\n\n    The metrics are not case sensitive--it supports upper, lower, and mixed case.\n\n    >>> from everett.manager import parse_data_size\n    >>> parse_data_size(\"40_000_000\")\n    40000000\n    >>> parse_data_size(\"40gb\")\n    40000000000\n    >>> parse_data_size(\"20KiB\")\n    20480\n\n    \"\"\"\n    fixed_val = val.lower().strip()\n    try:\n        return int(fixed_val)\n    except ValueError:\n        pass\n\n    match = _DATA_SIZE_RE.match(fixed_val)\n    if not match:\n        raise ValueError(f\"{val!r} is not a valid data size\")\n    amount, metric = match.groups()\n    return int(amount) * _DATA_SIZE_METRIC_TO_MULTIPLIER[metric]\n\n\n_TIME_UNIT_TO_MULTIPLIER = {\n    \"w\": 7 * 24 * 60 * 60,\n    \"d\": 24 * 60 * 60,\n    \"h\": 60 * 60,\n    \"m\": 60,\n    \"s\": 1,\n}\n_TIME_RE = re.compile(r\"([0-9_]+)([\" + \"\".join(_TIME_UNIT_TO_MULTIPLIER.keys()) + r\"])\")\n\n\ndef parse_time_period(val: str) -> Any:\n    \"\"\"Parse a string denoting a time period into a number of seconds.\n\n    Units:\n\n    * w - week\n    * d - day\n    * h - hour\n    * m - minute\n    * s - second\n\n    >>> from everett.manager import parse_time_period\n    >>> parse_time_period(\"103\")\n    103\n    >>> parse_time_period(\"1_000m\")\n    60000\n    >>> parse_time_period(\"15m4s\")\n    904\n\n    \"\"\"\n    fixed_val = val.lower().strip()\n    try:\n        return int(fixed_val)\n    except ValueError:\n        pass\n\n    parts = _TIME_RE.findall(fixed_val)\n    if not parts:\n        raise ValueError(f\"{val!r} is not a valid time period\")\n\n    total = 0\n    for part in parts:\n        amount, unit = part\n        total = total + (int(amount) * _TIME_UNIT_TO_MULTIPLIER[unit])\n    return total\n\n\ndef get_parser(parser: Callable) -> Callable:\n    \"\"\"Return a parsing function for a given parser.\"\"\"\n    # Special case bool so that we can explicitly give bool values otherwise\n    # all values would be True since they're non-empty strings.\n    if parser is bool:\n        return parse_bool\n    return parser\n\n\ndef listify(thing: Any) -> list[Any]:\n    \"\"\"Convert thing to a list.\n\n    If thing is a string, then returns a list of thing. Otherwise\n    returns thing.\n\n    :param thing: string or list of things\n\n    :returns: list\n\n    \"\"\"\n    if thing is None:\n        return []\n    if isinstance(thing, str):\n        return [thing]\n    return thing\n\n\ndef generate_uppercase_key(key: str, namespace: Optional[list[str]] = None) -> str:\n    \"\"\"Given a key and a namespace, generates a final uppercase key.\n\n    >>> generate_uppercase_key(\"foo\")\n    'FOO'\n    >>> generate_uppercase_key(\"foo\", [\"namespace\"])\n    'NAMESPACE_FOO'\n    >>> generate_uppercase_key(\"foo\", [\"namespace\", \"subnamespace\"])\n    'NAMESPACE_SUBNAMESPACE_FOO'\n\n    \"\"\"\n    if namespace:\n        namespace = [part for part in listify(namespace) if part]\n        key = \"_\".join(namespace + [key])\n\n    key = key.upper()\n    return key\n\n\ndef get_key_from_envs(envs: Iterable[Any], key: str) -> Union[str, NoValue]:\n    \"\"\"Return the value of a key from the given dict respecting namespaces.\n\n    Data can also be a list of data dicts.\n\n    \"\"\"\n    # if it barks like a dict, make it a list have to use `get` since dicts and\n    # lists both have __getitem__\n    if hasattr(envs, \"get\"):\n        envs = [envs]\n\n    for env in envs:\n        if key in env:\n            return env[key]\n\n    return NO_VALUE\n\n\nclass ListOf:\n    \"\"\"Parse a delimiter-separated list of things.\n\n    After delimiting items, this strips the whitespace at the beginning and end\n    of each string. Then it passes each string into the parser to get the final\n    value.\n\n    >>> from everett.manager import ListOf\n    >>> ListOf(str)('')\n    []\n    >>> ListOf(str)('a,b,c,d')\n    ['a', 'b', 'c', 'd']\n    >>> ListOf(int)('1,2,3,4')\n    [1, 2, 3, 4]\n    >>> ListOf(str)('1, 2  ,3,4')\n    ['1', '2', '3', '4']\n\n    ``ListOf`` defaults to using a comma as a delimiter, but supports other\n    delimiters:\n\n    >>> ListOf(str, delimiter=\":\")(\"/path/a/:/path/b/\")\n    ['/path/a/', '/path/b/']\n\n    ``ListOf`` supports raising a configuration error when one of the values\n    is an empty string:\n\n    >>> ListOf(str, allow_empty=False)(\"a,,b\")\n    Traceback (most recent call last):\n      ...\n    ValueError: 'a,,b' can not have empty values\n\n    The user will get a configuration error like this::\n\n        ValueError: 'a,,b' can not have empty values\n        NAMES requires a value parseable by <ListOf(str, delimiter=',', allow_empty=False)>\n\n    Note: This doesn't handle quotes or backslashes or any complicated string\n    parsing.\n\n    For example:\n\n    >>> ListOf(str)('\"a,b\",c,d')\n    ['\"a', 'b\"', 'c', 'd']\n\n    \"\"\"\n\n    def __init__(\n        self, parser: Callable, delimiter: str = \",\", allow_empty: bool = True\n    ):\n        self.sub_parser = parser\n        self.delimiter = delimiter\n        self.allow_empty = allow_empty\n\n    def __call__(self, value: str) -> list[Any]:\n        parser = get_parser(self.sub_parser)\n        if value:\n            parsed_values = []\n            for token in value.split(self.delimiter):\n                token = token.strip()\n                if not token and not self.allow_empty:\n                    raise ValueError(f\"{value!r} can not have empty values\")\n                parsed_values.append(parser(token))\n            return parsed_values\n        else:\n            return []\n\n    def __repr__(self) -> str:\n        return (\n            f\"<ListOf({qualname(self.sub_parser)}, \"\n            + f\"delimiter={self.delimiter!r}, \"\n            + f\"allow_empty={self.allow_empty!r})>\"\n        )\n\n\nclass ChoiceOf:\n    \"\"\"Parser that enforces values are in a specified value domain.\n\n    Choices can be a list of string values that are parseable by the sub\n    parser. For example, say you only supported two cloud providers and need\n    the configuration value to be one of \"aws\" or \"gcp\":\n\n    >>> from everett.manager import ChoiceOf\n    >>> ChoiceOf(str, choices=[\"aws\", \"gcp\"])(\"aws\")\n    'aws'\n\n    Choices works with the int sub-parser:\n\n    >>> from everett.manager import ChoiceOf\n    >>> ChoiceOf(int, choices=[\"1\", \"2\", \"3\"])(\"1\")\n    1\n\n    Choices works with any sub-parser:\n\n    >>> from everett.manager import ChoiceOf, parse_data_size\n    >>> ChoiceOf(parse_data_size, choices=[\"1kb\", \"1mb\", \"1gb\"])(\"1mb\")\n    1000000\n\n    Note: The choices list is a list of strings--these are values before being\n    parsed. This makes it easier for people who are doing configuration to know\n    what the values they put in their configuration files need to look like.\n\n    \"\"\"\n\n    def __init__(self, parser: Callable, choices: list[str]):\n        self.sub_parser = parser\n        if not choices or not all(isinstance(choice, str) for choice in choices):\n            raise ValueError(f\"choices {choices!r} must be a non-empty list of strings\")\n\n        self.choices = choices\n\n    def __call__(self, value: str) -> Any:\n        parser = get_parser(self.sub_parser)\n        if value and value in self.choices:\n            return parser(value)\n        raise ValueError(f\"{value!r} is not a valid choice\")\n\n    def __repr__(self) -> str:\n        return f\"<ChoiceOf({qualname(self.sub_parser)}, {self.choices})>\"\n\n\nclass ConfigOverrideEnv:\n    \"\"\"Override configuration layer for testing.\"\"\"\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        global _CONFIG_OVERRIDE\n\n        # Short-circuit to reduce overhead.\n        if not _CONFIG_OVERRIDE:\n            return NO_VALUE\n        full_key = generate_uppercase_key(key, namespace)\n        logger.debug(f\"Searching {self!r} for {full_key}\")\n        return get_key_from_envs(reversed(_CONFIG_OVERRIDE), full_key)\n\n    def __repr__(self) -> str:\n        return \"<ConfigOverrideEnv>\"\n\n\nclass ConfigObjEnv:\n    \"\"\"Source for pulling configuration values out of a Python object.\n\n    This is handy for a few weird situations. For example, you can use this to\n    \"bridge\" Everett configuration with command line arguments. The argparse\n    Namespace works fine here.\n\n    Namespace (the Everett one--not the argparse one) is prefixed. So key \"foo\"\n    in namespace \"bar\" is \"foo_bar\".\n\n    For example::\n\n        import argparse\n\n        from everett.manager import ConfigObjEnv, ConfigManager\n\n        parser = argparse.ArgumentParser()\n        parser.add_argument(\n            \"--debug\", help=\"to debug or not to debug\"\n        )\n        parsed_vals = parser.parse_known_args()[0]\n\n        config = ConfigManager([\n            ConfigObjEnv(parsed_vals)\n        ])\n\n        print config(\"debug\", parser=bool)\n\n\n    Keys are not case-sensitive--everything is converted to lowercase before\n    pulling it from the object.\n\n\n    .. Note::\n\n       ConfigObjEnv has nothing to do with the library configobj.\n\n    .. versionadded:: 0.6\n\n    \"\"\"\n\n    def __init__(self, obj: Any, force_lower: bool = True):\n        self.obj = obj\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        full_key = generate_uppercase_key(key, namespace)\n        full_key = full_key.lower()\n\n        logger.debug(f\"Searching {self!r} for {full_key}\")\n\n        # Build a map of lowercase -> actual key\n        obj_keys = {\n            item.lower(): item for item in dir(self.obj) if not item.startswith(\"__\")\n        }\n\n        if full_key in obj_keys:\n            val = getattr(self.obj, obj_keys[full_key])\n\n            # If the value is None, then we're going to treat it as a non-valid\n            # value.\n            if val is not None:\n                # This is goofy, but this allows people to specify arg parser\n                # defaults, but do the right thing in Everett where everything\n                # is a string until it's parsed.\n                return str(val)\n\n        return NO_VALUE\n\n    def __repr__(self) -> str:\n        return \"<ConfigObjEnv>\"\n\n\nclass ConfigDictEnv:\n    \"\"\"Source for pulling configuration out of a dict.\n\n    This is handy for testing. You might also use it if you wanted to move all\n    your defaults values into one centralized place.\n\n    Keys are prefixed by namespaces and the whole thing is uppercased.\n\n    For example, namespace \"bar\" for key \"foo\" becomes ``BAR_FOO`` in the\n    dict.\n\n    For example::\n\n        from everett.manager import ConfigDictEnv, ConfigManager\n\n        config = ConfigManager([\n            ConfigDictEnv({\n                \"FOO_BAR\": \"someval\",\n                \"BAT\": \"1\",\n            })\n        ])\n\n    Keys are not case sensitive. This also works::\n\n        from everett.manager import ConfigDictEnv, ConfigManager\n\n        config = ConfigManager([\n            ConfigDictEnv({\n                \"foo_bar\": \"someval\",\n                \"bat\": \"1\",\n            })\n        ])\n\n        print config(\"foo_bar\")\n        print config(\"FOO_BAR\")\n        print config.with_namespace(\"foo\")(\"bar\")\n\n\n    Also, ``ConfigManager`` has a convenience classmethod for creating a\n    ``ConfigManager`` with just a dict environment::\n\n        from everett.manager import ConfigManager\n\n        config = ConfigManager.from_dict({\n            \"FOO_BAR\": \"bat\"\n        })\n\n\n    .. versionchanged:: 0.3\n       Keys are no longer case-sensitive.\n\n    \"\"\"\n\n    def __init__(self, cfg: dict):\n        self.cfg = {key.upper(): val for key, val in cfg.items()}\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        full_key = generate_uppercase_key(key, namespace)\n        logger.debug(f\"Searching {self!r} for {full_key}\")\n        return get_key_from_envs(self.cfg, full_key)\n\n    def __repr__(self) -> str:\n        return f\"<ConfigDictEnv: {self.cfg!r}>\"\n\n\nclass ConfigEnvFileEnv:\n    \"\"\"Source for pulling configuration out of ``.env`` files.\n\n    This source lets you specify configuration in an .env file. This\n    is useful for local development when in production you use values\n    in environment variables.\n\n    Keys are prefixed by namespaces and the whole thing is uppercased.\n\n    For example, key \"foo\" will be ``FOO`` in the file.\n\n    For example, namespace \"bar\" for key \"foo\" becomes ``BAR_FOO`` in the\n    file.\n\n    Key and namespace can consist of alphanumeric characters and ``_``.\n\n    To use, instantiate and toss in the source list::\n\n        from everett.manager import ConfigEnvFileEnv, ConfigManager\n\n        config = ConfigManager([\n            ConfigEnvFileEnv('.env')\n        ])\n\n\n    For multiple paths::\n\n        from everett.manager import ConfigEnvFileEnv, ConfigManager\n\n        config = ConfigManager([\n            ConfigEnvFileEnv([\n                '.env',\n                'config/prod.env'\n            ])\n        ])\n\n\n    Here's an example .env file::\n\n        DEBUG=true\n\n        # secrets\n        SECRET_KEY=ou812\n\n        # database setup\n        DB_HOST=localhost\n        DB_PORT=5432\n\n        # CSP reporting\n        CSP_SCRIPT_SRC=\"'self' www.googletagmanager.com\"\n\n    \"\"\"\n\n    def __init__(self, possible_paths: Union[str, list[str]]):\n        self.data = {}\n        self.path = None\n\n        possible_paths = listify(possible_paths)\n        for path in possible_paths:\n            if not path:\n                continue\n\n            path = os.path.abspath(os.path.expanduser(path.strip()))\n            if path and os.path.isfile(path):\n                self.path = path\n                with open(path) as envfile:\n                    self.data = parse_env_file(envfile)\n                    break\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        full_key = generate_uppercase_key(key, namespace)\n        logger.debug(f\"Searching {self!r} for {full_key}\")\n        return get_key_from_envs(self.data, full_key)\n\n    def __repr__(self) -> str:\n        return f\"<ConfigEnvFileEnv: {self.path!r}>\"\n\n\nclass ConfigOSEnv:\n    \"\"\"Source for pulling configuration out of the environment.\n\n    This source lets you specify configuration in the environment. This is\n    useful for infrastructure related configuration like usernames and ports\n    and secret configuration like passwords.\n\n    Keys are prefixed by namespaces and the whole thing is uppercased.\n\n    For example, key \"foo\" will be ``FOO`` in the environment.\n\n    For example, namespace \"bar\" for key \"foo\" becomes ``BAR_FOO`` in the\n    environment.\n\n    Key and namespace can consist of alphanumeric characters and ``_``.\n\n    .. Note::\n\n       Unlike other config environments, this one is case sensitive in that\n       keys defined in the environment **must** be all uppercase.\n\n       For example, these are good::\n\n           FOO=bar\n           FOO_BAR=bar\n           FOO_BAR1=bar\n\n\n       This is bad::\n\n           foo=bar\n\n\n    To use, instantiate and toss in the source list::\n\n        from everett.manager import ConfigOSEnv, ConfigManager\n\n        config = ConfigManager([\n            ConfigOSEnv()\n        ])\n\n    \"\"\"\n\n    def get(\n        self, key: str, namespace: Optional[list[str]] = None\n    ) -> Union[str, NoValue]:\n        \"\"\"Retrieve value for key.\"\"\"\n        full_key = generate_uppercase_key(key, namespace)\n        logger.debug(f\"Searching {self!r} for {full_key}\")\n        return get_key_from_envs(os.environ, full_key)\n\n    def __repr__(self) -> str:\n        return \"<ConfigOSEnv>\"\n\n\ndef _get_component_name(component: Any) -> str:\n    if not inspect.isclass(component):\n        cls = component.__class__\n    else:\n        cls = component\n    return cls.__module__ + \".\" + cls.__name__\n\n\ndef get_runtime_config(\n    config: \"ConfigManager\",\n    component: Any,\n    traverse: Callable = traverse_tree,\n) -> list[tuple[list[str], str, Any, Option]]:\n    \"\"\"Returns configuration specification and values for a component tree\n\n    For example, if you had a tree of components instantiated, you could\n    traverse the tree and log the configuration::\n\n        from everett.manager import (\n            ConfigManager,\n            generate_uppercase_key,\n            get_runtime_config,\n            Option,\n            parse_class,\n        )\n\n        class App:\n            class Config:\n                debug = Option(default=\"False\", parser=bool)\n                reader = Option(parser=parse_class)\n                writer = Option(parser=parse_class)\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n                # App has a reader and a writer each of which has configuration\n                # options\n                self.reader = self.config(\"reader\")(config.with_namespace(\"reader\"))\n                self.writer = self.config(\"writer\")(config.with_namespace(\"writer\"))\n\n        class Reader:\n            class Config:\n                input_file = Option()\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n        class Writer:\n            class Config:\n                output_file = Option()\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n        cm = ConfigManager.from_dict(\n            {\n                # This specifies which reader component to use. Because we\n                # specified this one, we need to define a READER_INPUT_FILE\n                # value.\n                \"READER\": \"__main__.Reader\",\n                \"READER_INPUT_FILE\": \"input.txt\",\n\n                # Same thing for the writer component.\n                \"WRITER\": \"__main__.Writer\",\n                \"WRITER_OUTPUT_FILE\": \"output.txt\",\n            }\n        )\n\n        my_app = App(cm)\n\n        # This traverses the component tree starting with my_app and then\n        # traversing .reader and .writer attributes.\n        for namespace, key, value, option in get_runtime_config(cm, my_app):\n            full_key = generate_uppercase_key(key, namespace)\n            print(f\"{full_key.upper()}={value or ''}\")\n\n        # This should print out:\n        # DEBUG=False\n        # READER=__main__.Reader\n        # READER_INPUT_FILE=input.txt\n        # WRITER=__main__.Writer\n        # WRITER_OUTPUT_FILE=output.txt\n\n    :param config: a configuration manager instance\n    :param component: a component or tree of components\n    :param traverse: the function for traversing the component tree; see\n        :py:func:`everett.manager.traverse_tree` for signature\n\n    :returns: a list of (namespace, key, value, option) tuples\n\n    \"\"\"\n    runtime_config = []\n    for namespace, key, option, obj in traverse(component):\n        runtime_config.append(\n            (\n                namespace,\n                key,\n                config.with_namespace(namespace).with_options(obj)(\n                    key, raise_error=False, raw_value=True\n                ),\n                option,\n            )\n        )\n    return runtime_config\n\n\nclass ConfigManager:\n    \"\"\"Manage multiple configuration environment layers.\"\"\"\n\n    def __init__(\n        self,\n        environments: list[Any],\n        doc: str = \"\",\n        msg_builder: Callable = build_msg,\n        with_override: bool = True,\n    ):\n        \"\"\"Instantiate a ConfigManager.\n\n        :param environments: list of configuration sources to look through in\n            the order they should be looked through\n\n        :param doc: help text printed to users when they encounter configuration\n            errors\n\n            .. versionadded:: 0.6\n\n        :param msg_builder: function that takes arguments and builds an exception\n            message intended to be printed or conveyed to the user\n\n            For example::\n\n                def build_msg(namespace, key, parser, msg=\"\", option_doc=\"\", config_doc=\"\"):\n                    full_key = namespace or []\n                    full_key = \"_\".join(full_key + [key]).upper()\n\n                    return (\n                        f\"{full_key} requires a value parseable by {qualname(parser)}\\\\n\"\n                        + option_doc + \"\\\\n\"\n                        + config_doc + \"\\\\n\"\n                    )\n\n        :param with_override: whether or not to insert the special override\n            environment used for testing as the first environment in the list\n            of sources\n\n        \"\"\"\n        self.with_override = with_override\n        if with_override:\n            # Add ConfigOverrideEnv if it's not in the environments list already\n            override = [\n                env for env in environments if isinstance(env, ConfigOverrideEnv)\n            ]\n            if not override:\n                environments.insert(0, ConfigOverrideEnv())\n\n        self.envs = environments\n        self.doc = doc\n        self.msg_builder = msg_builder\n\n        self.namespace: list[str] = []\n\n        self.bound_component: Any = None\n        self.bound_component_prefix: list[str] = []\n        self.bound_component_options: Mapping[str, Any] = {}\n\n        self.original_manager = self\n\n    @classmethod\n    def basic_config(cls, env_file: str = \".env\", doc: str = \"\") -> \"ConfigManager\":\n        \"\"\"Return a basic ConfigManager.\n\n        This sets up a ConfigManager that will look for configuration in\n        this order:\n\n        1. environment\n        2. specified ``env_file`` defaulting to ``.env``\n\n        This is for a fast one-line opinionated setup.\n\n        Example::\n\n            from everett.manager import ConfigManager\n\n            config = ConfigManager.basic_config()\n\n\n        This is shorthand for::\n\n            config = ConfigManager(\n                environments=[\n                    ConfigOSEnv(),\n                    ConfigEnvFileEnv(['.env'])\n                ]\n            )\n\n\n        :param env_file: the name of the env file to use\n        :param doc: help text printed to users when they encounter configuration\n            errors\n\n        :returns: a :py:class:`everett.manager.ConfigManager`\n\n        \"\"\"\n        return cls(environments=[ConfigOSEnv(), ConfigEnvFileEnv([env_file])], doc=doc)\n\n    @classmethod\n    def from_dict(cls, dict_config: dict) -> \"ConfigManager\":\n        \"\"\"Create a ConfigManager with specified configuration as a Python dict.\n\n        This is shorthand for::\n\n            config = ConfigManager([ConfigDictEnv(dict_config)])\n\n\n        This is handy for writing tests for the app you're using Everett in.\n\n        :param dict_config: Python dict holding the configuration for this\n            manager\n\n        :returns: ConfigManager with specified configuration\n\n        .. versionadded:: 0.3\n\n        \"\"\"\n        return cls([ConfigDictEnv(dict_config)])\n\n    def get_bound_component(self) -> Any:\n        \"\"\"Retrieve the bound component for this config object.\n\n        :returns: component or None\n\n        \"\"\"\n        return self.bound_component\n\n    def get_namespace(self) -> list[str]:\n        \"\"\"Retrieve the complete namespace for this config object.\n\n        :returns: namespace as a list of strings\n\n        \"\"\"\n        return self.namespace\n\n    def _get_base_config(self) -> \"ConfigManager\":\n        return self.original_manager\n\n    def clone(self) -> \"ConfigManager\":\n        my_clone = ConfigManager(\n            environments=list(self.envs),\n            doc=self.doc,\n            msg_builder=self.msg_builder,\n            with_override=self.with_override,\n        )\n        my_clone.namespace = list(self.namespace)\n        my_clone.bound_component = self.bound_component\n        my_clone.bound_component_prefix = []\n        my_clone.bound_component_options = self.bound_component_options\n\n        my_clone.original_manager = self.original_manager\n\n        return my_clone\n\n    def with_namespace(self, namespace: Union[list[str], str]) -> \"ConfigManager\":\n        \"\"\"Apply a namespace to this configuration.\n\n        Namespaces accumulate as you add them.\n\n        :param namepace: namespace as a string or list of strings\n\n        :returns: a clone of the ConfigManager instance with the namespace applied\n\n        \"\"\"\n        namespace = listify(namespace)\n        if not namespace:\n            return self\n\n        my_clone = self.clone()\n        if my_clone.bound_component:\n            my_clone.bound_component_prefix.extend(namespace)\n        else:\n            my_clone.namespace.extend(namespace)\n        return my_clone\n\n    def with_options(self, component: Any) -> \"ConfigManager\":\n        \"\"\"Apply options component options to this configuration.\n\n        :param component: the instance or class with a Config to bind this\n            ConfigManager to\n\n        :returns: a clone of the ConfigManager instance bound to specified\n            component\n\n        \"\"\"\n        # If this is an instance, get the class\n        if not inspect.isclass(component):\n            component = component.__class__\n\n        options = get_config_for_class(component)\n        # NOTE(willkg): if the component has no options, then there's nothing\n        # to bind to\n        if not options:\n            return self\n\n        my_clone = self.clone()\n        my_clone.bound_component = component\n        my_clone.bound_component_prefix = []\n        my_clone.bound_component_options = options\n\n        # IF there's a bound component with a prefix, then it means someone is doing\n        # something like:\n        #\n        # config = config.with_options(Comp).with_namespace(\"foo\").with_options(SubComp)\n        #\n        # In that case, we want the namespace \"foo\" to be part of the namespace\n        # and not part of the key prefix for SubComp.\n        if self.bound_component_prefix:\n            my_clone.namespace.extend(self.bound_component_prefix)\n\n        return my_clone\n\n    def __call__(\n        self,\n        key: str,\n        namespace: Union[list[str], str, None] = None,\n        default: Union[str, NoValue] = NO_VALUE,\n        default_if_empty: bool = True,\n        alternate_keys: Optional[list[str]] = None,\n        doc: str = \"\",\n        parser: Callable = str,\n        raise_error: bool = True,\n        raw_value: bool = False,\n    ) -> Any:\n        \"\"\"Return a parsed value from the environment.\n\n        :param key: the key to look up\n\n        :param namespace: the namespace for the key--different environments\n            use this differently\n\n        :param default: the default value (if any); this must be a string that is\n            parseable by the specified parser; if no default is provided, this\n            will raise an error or return ``everett.NO_VALUE`` depending on\n            the value of ``raise_error``\n\n            If this ConfigManager is bound to a component, the default will be\n            the default of the option in the bound component configuration.\n\n        :param default_if_empty: if True, treat empty string values as a\n            non-value and return the specified default\n\n        :param alternate_keys: the list of alternate keys to look up;\n            supports a ``root:`` key prefix which will cause this to look at\n            the configuration root rather than the current namespace\n\n            If this ConfigManager is bound to a component, the alternate_keys\n            will be the alternate_keys of the option in the bound component\n            configuration.\n\n            .. versionadded:: 0.3\n\n        :param doc: documentation for this config option\n\n            If this ConfigManager is bound to a component, the doc will be the\n            doc of the option in the bound component configuration.\n\n            .. versionadded:: 0.6\n\n        :param parser: the parser for converting this value to a Python object\n\n            If this ConfigManager is bound to a component, the parser will be\n            the parser of the option in the bound component configuration.\n\n        :param raise_error: True if you want a lack of value to raise a\n            ``everett.ConfigurationError``\n\n        :param raw_value: True if you want the raw unparsed value, False otherwise\n\n        :raises everett.ConfigurationMissingError: if the required bit of configuration\n            is missing from all the environments\n\n        :raises everett.InvalidKeyError: if the configuration key doesn't exist for\n            that component\n\n        :raises everett.InvalidValueError: if the configuration value is\n            invalid in some way (not an integer, not a bool, etc)\n\n        .. Note::\n\n           The default value should **always** be a string that is parseable by\n           the parser. This simplifies thinking about values since **all**\n           values are strings that are parsed by the parser rather than default\n           values do one thing and non-default values doa nother. Further, it\n           simplifies documentation for the user since the default value is an\n           example value.\n\n           The parser can be any callable that takes a string value and returns\n           a parsed value.\n\n        \"\"\"\n        if not (default is NO_VALUE or isinstance(default, str)):\n            raise ConfigurationError(f\"default value {default!r} is not a string\")\n\n        # If we have a bound component, then the \"namespace\" is a key prefix,\n        # so do that. Otherwise it's a namespace.\n        if self.bound_component:\n            key = \"_\".join(\n                listify(self.bound_component_prefix) + listify(namespace) + [key]\n            )\n            namespace = self.namespace\n\n        else:\n            namespace = self.namespace + listify(namespace)\n\n        # If this is a bound config, then apply everything to that\n        if self.bound_component:\n            try:\n                option, cls = self.bound_component_options[key]\n            except KeyError as exc:\n                if raise_error:\n                    raise InvalidKeyError(\n                        f\"{key!r} is not a valid key for this component\"\n                    ) from exc\n                return None\n\n            default = option.default\n            alternate_keys = option.alternate_keys\n            doc = option.doc\n            parser = option.parser\n\n        if raw_value:\n            # If we're returning raw values, then we can just use str which is\n            # a no-op.\n            parser = str\n        else:\n            parser = get_parser(parser)\n\n        # Go through all possible keys\n        all_keys = [key]\n        if alternate_keys:\n            all_keys = all_keys + alternate_keys\n\n        for possible_key in all_keys:\n            if possible_key.startswith(\"root:\"):\n                # If this is a root-anchored key, we drop the namespace.\n                possible_key = possible_key[5:]\n                use_namespace = None\n            else:\n                use_namespace = namespace\n\n            logger.debug(f\"Looking up key: {possible_key}, namespace: {use_namespace}\")\n\n            # Go through environments in reverse order\n            for env in self.envs:\n                val = env.get(possible_key, use_namespace)\n\n                # If the value is the empty string and default_if_empty is\n                # True, treat it as a non-value\n                if val == \"\" and default_if_empty:\n                    val = NO_VALUE\n\n                if val is not NO_VALUE:\n                    try:\n                        parsed_val = parser(val)\n                        logger.debug(f\"Returning raw: {val!r}, parsed: {parsed_val!r}\")\n                        return parsed_val\n                    except ConfigurationError:\n                        # Re-raise ConfigurationError and friends since that's\n                        # what we want to be raising.\n                        raise\n                    except Exception as exc:\n                        exc_type, exc_value, exc_traceback = sys.exc_info()\n                        exc_type_name = exc_type.__name__ if exc_type else \"None\"\n\n                        msg = self.msg_builder(\n                            namespace=use_namespace,\n                            key=key,\n                            parser=parser,\n                            msg=f\"{exc_type_name}: {exc_value}\",\n                            option_doc=doc,\n                            config_doc=self.doc,\n                        )\n\n                        raise InvalidValueError(msg, namespace, key, parser) from exc\n\n        # Return the default if there is one\n        if default is not NO_VALUE:\n            try:\n                parsed_val = parser(default)\n                logger.debug(\n                    f\"Returning default raw: {default!r}, parsed: {parsed_val!r}\"\n                )\n                return parsed_val\n            except ConfigurationError:\n                # Re-raise ConfigurationError and friends since that's\n                # what we want to be raising.\n                raise\n            except Exception as exc:\n                # FIXME(willkg): This is a programmer error--not a user\n                # configuration error. We might want to denote that better.\n                exc_type, exc_value, exc_traceback = sys.exc_info()\n                exc_type_name = exc_type.__name__ if exc_type else \"None\"\n\n                msg = self.msg_builder(\n                    namespace=use_namespace,\n                    key=key,\n                    parser=parser,\n                    msg=f\"{exc_type_name}: {exc_value} (default value)\",\n                    option_doc=doc,\n                    config_doc=self.doc,\n                )\n\n                raise InvalidValueError(msg, namespace, key, parser) from exc\n\n        # No value specified and no default, so raise an error to the user\n        if raise_error:\n            msg = self.msg_builder(\n                namespace=use_namespace,\n                key=key,\n                parser=parser,\n                option_doc=doc,\n                config_doc=self.doc,\n            )\n\n            raise ConfigurationMissingError(msg, namespace, key, parser)\n\n        logger.debug(\"Found nothing--returning NO_VALUE\")\n        # Otherwise return NO_VALUE\n        return NO_VALUE\n\n    def raise_configuration_error(self, msg: str) -> None:\n        \"\"\"Convenience function for raising configuration errors.\n\n        This is helpful for situations where you need to do additional checking\n        of configuration values and need to raise a configuration error for the\n        user that includes the configuration documentation.\n\n        For example::\n\n            from everett.manager import ConfigManager\n\n            config = ConfigManager.basic_config()\n            host = config(\"host\")\n            port = config(\"port\")\n\n            if host is None or port is None:\n                config.raise_configuration_error(\n                    \"Both HOST and PORT must be specified.\"\n                )\n\n        :param msg: the configuration error message\n\n        \"\"\"\n\n        msg = self.msg_builder(\n            namespace=None,\n            key=None,\n            parser=None,\n            msg=msg,\n            option_doc=None,\n            config_doc=self.doc,\n        )\n        raise ConfigurationError(msg)\n\n    def __repr__(self) -> str:\n        if self.bound_component:\n            name = _get_component_name(self.bound_component)\n            return f\"<ConfigManager({name}): namespace:{self.get_namespace()}>\"\n        else:\n            return f\"<ConfigManager: namespace:{self.get_namespace()}>\"\n\n\n# This is a stack of overrides to be examined in reverse order\n_CONFIG_OVERRIDE = []\n\n\nclass ConfigOverride:\n    \"\"\"Handle contexts and decoration for overriding config in testing.\"\"\"\n\n    def __init__(self, **cfg: str):\n        self._cfg = cfg\n\n    def push_config(self) -> None:\n        \"\"\"Push ``self._cfg`` as a config layer onto the stack.\"\"\"\n        _CONFIG_OVERRIDE.append(self._cfg)\n\n    def pop_config(self) -> None:\n        \"\"\"Pop a config layer off.\n\n        :raises IndexError: If there are no layers to pop off\n\n        \"\"\"\n        _CONFIG_OVERRIDE.pop()\n\n    def __enter__(self) -> None:\n        self.push_config()\n\n    def __exit__(\n        self,\n        exc_type: Optional[type[BaseException]],\n        exc_value: Optional[BaseException],\n        traceback: Optional[TracebackType],\n    ) -> None:\n        self.pop_config()\n\n    def decorate(self, fun: Callable) -> Callable:\n        \"\"\"Decorate a function for overriding configuration.\"\"\"\n\n        @wraps(fun)\n        def _decorated(*args: Any, **kwargs: Any) -> Any:\n            # Push the config, run the function and pop it afterwards.\n            self.push_config()\n            try:\n                return fun(*args, **kwargs)\n            finally:\n                self.pop_config()\n\n        return _decorated\n\n    def __call__(self, class_or_fun: Callable) -> Callable:\n        if inspect.isclass(class_or_fun):\n            # If class_or_fun is a class, decorate all of its methods\n            # that start with 'test'.\n            for attr in class_or_fun.__dict__.keys():\n                prop = getattr(class_or_fun, attr)\n                if attr.startswith(\"test\") and callable(prop):\n                    setattr(class_or_fun, attr, self.decorate(prop))\n            return class_or_fun\n\n        else:\n            return self.decorate(class_or_fun)\n\n\ndef config_override(**cfg: str) -> ConfigOverride:\n    \"\"\"Allow you to override config for writing tests.\n\n    This can be used as a class decorator::\n\n        @config_override(FOO=\"bar\", BAZ=\"bat\")\n        class FooTestClass(object):\n            ...\n\n\n    This can be used as a function decorator::\n\n        @config_override(FOO=\"bar\")\n        def test_foo():\n            ...\n\n\n    This can also be used as a context manager::\n\n        def test_foo():\n            with config_override(FOO=\"bar\"):\n                ...\n\n    \"\"\"\n    return ConfigOverride(**cfg)\n"
  },
  {
    "path": "src/everett/sphinxext.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Sphinx extension for auto-documenting components with configuration.\n\nTo use this, you must install the optional requirements::\n\n    $ pip install 'everett[sphinx]'\n\n\"\"\"\n\nimport ast\nfrom importlib import import_module\nimport re\nimport textwrap\nfrom typing import (\n    TYPE_CHECKING,\n    Any,\n    Optional,\n    Union,\n)\nfrom collections.abc import Generator\n\nfrom docutils import nodes\nfrom docutils.parsers.rst import Directive, directives\nfrom docutils.statemachine import ViewList, StringList\nfrom sphinx import addnodes\nfrom sphinx.addnodes import desc_signature, pending_xref\nfrom sphinx.directives import ObjectDescription\nfrom sphinx.domains import Domain, ObjType\nfrom sphinx.locale import _ as gettext\nfrom sphinx.roles import XRefRole\nfrom sphinx.util import ws_re\nfrom sphinx.util import logging\nfrom sphinx.util.docfields import Field\nfrom sphinx.util.docstrings import prepare_docstring\nfrom sphinx.util.nodes import make_refnode\n\nfrom everett import NO_VALUE, __version__\nfrom everett.manager import qualname, get_config_for_class\n\n\nif TYPE_CHECKING:\n    from sphinx.builders import Builder\n    from sphinx.environment import BuildEnvironment\n\n\nLOGGER = logging.getLogger(__name__)\n\n\ndef split_clspath(clspath: str) -> list[str]:\n    \"\"\"Split clspath into module and class names.\n\n    Note: This is a really simplistic implementation.\n\n    \"\"\"\n    return clspath.rsplit(\".\", 1)\n\n\ndef get_module_and_objpath(path: str) -> Any:\n    \"\"\"Given a path, imports the module part of the path and returns the module\n    and the rest of the path.\n\n    :arg clspath: a \"a.b.c.Class\" style path\n\n    :returns: \"a.b.c\" module and \"Class\"\n\n    \"\"\"\n    module = None\n    parts = path.split(\".\")\n\n    # Figure out the module\n    for i in range(len(parts)):\n        modpath = parts[:i]\n        objpath = parts[i:]\n        if not modpath:\n            continue\n\n        try:\n            module = import_module(\".\".join(modpath))\n        except ImportError:\n            break\n\n    return module, \".\".join(objpath)\n\n\ndef import_class(clspath: str) -> Any:\n    \"\"\"Given a clspath, returns the class.\n\n    Note: This is a really simplistic implementation.\n\n    :arg clspath: a \"a.b.c.Class\" style path\n\n    :returns: the Class\n\n    \"\"\"\n    module, objpath = get_module_and_objpath(clspath)\n    if module is None:\n        raise ValueError(f\"{clspath!r} does not point to a valid thing\")\n\n    obj = module\n    for part in objpath.split(\",\"):\n        obj = getattr(obj, part)\n\n    return obj\n\n\ndef upper_lower_none(arg: Optional[str]) -> Union[str, None]:\n    \"\"\"Validate arg value as \"upper\", \"lower\", or None.\"\"\"\n    if not arg:\n        return arg\n\n    arg = arg.strip().lower()\n    if arg in [\"upper\", \"lower\"]:\n        return arg\n\n    raise ValueError('argument must be \"upper\", \"lower\" or None')\n\n\nclass EverettOption(ObjectDescription):\n    \"\"\"An Everett config option.\"\"\"\n\n    indextemplate = \"everett option; %s\"\n\n    option_spec = {\n        # This is the parser for the option\n        \"parser\": directives.unchanged_required,\n        # The default for this option; no value (not NO_VALUE--that's different) is\n        # treated as an empty string\n        \"default\": directives.unchanged_required,\n        # Whether or not this option is required\n        \"required\": directives.flag,\n    }\n\n    def handle_signature(self, sig: str, signode: desc_signature) -> str:\n        signode.clear()\n        signode += addnodes.desc_name(sig, sig)\n        name = ws_re.sub(\" \", sig)\n        return name\n\n    def add_target_and_index(\n        self, name: str, sig: str, signode: desc_signature\n    ) -> None:\n        ref = self.env.ref_context.get(\"everett:component\")\n        if ref:\n            targetname = f\"{self.objtype}-{ref}.{name}\"\n            # If this is in a component, we change the name to include the\n            # component name\n            name = f\"{ref}.{name}\"\n        else:\n            targetname = f\"{self.objtype}-{name}\"\n\n        if targetname not in self.state.document.ids:\n            signode[\"names\"].append(targetname)\n            signode[\"ids\"].append(targetname)\n            signode[\"first\"] = not self.names\n            self.state.document.note_explicit_target(signode)\n\n            objects = self.env.domaindata[\"everett\"][\"objects\"]\n            key = (self.objtype, name)\n            if key in objects:\n                self.state_machine.reporter.warning(\n                    f\"duplicate description of {self.objtype} {name!r}, \"\n                    + f\"other instance in {self.env.doc2path(objects[key][0])}\",\n                    line=self.lineno,\n                )\n\n            objects[key] = (self.env.docname, targetname)\n\n        indextext = gettext(\"%s (component)\") % name\n        if self.indexnode is not None:\n            self.indexnode[\"entries\"].append(\n                (\"single\", indextext, targetname, \"\", None)\n            )\n\n    def transform_content(self, contentnode: addnodes.desc_content) -> None:\n        # We want to insert some stuff before the content\n\n        lines = StringList()\n\n        sourcename = \"everett option\"\n\n        parser = self.options.get(\"parser\", \"str\")\n        default = self.options.get(\"default\")\n        is_required = (\"required\" in self.options) or (default is None)\n        required = \"Yes\" if is_required else \"No\"\n\n        lines.append(f\":Parser: *{parser}*\", sourcename)\n\n        if default is not None:\n            lines.append(f\":Default: {default}\", sourcename)\n\n        lines.append(f\":Required: {required}\", sourcename)\n        lines.append(\"\", sourcename)\n\n        node = nodes.paragraph()\n        node.document = self.state.document\n        self.state.nested_parse(lines, 0, node)\n\n        # Insert  our new nodes before the rest of the content\n        contentnode.children = node.children + contentnode.children\n\n\nclass EverettComponent(ObjectDescription):\n    \"\"\"Description of an Everett component.\"\"\"\n\n    doc_field_types = [\n        Field(\n            \"options\",\n            names=(\"option\",),\n            label=gettext(\"Options\"),\n            rolename=\"option\",\n        )\n    ]\n\n    allow_nesting = False\n\n    # FIXME(willkg): What's the signode here?\n    def handle_signature(self, sig: str, signode: Any) -> str:\n        \"\"\"Create a signature for this thing.\"\"\"\n        if sig != \"Configuration\":\n            signode.clear()\n\n            # Add \"component\" which is the type of this thing\n            signode += addnodes.desc_annotation(\"component \", \"component \")\n\n            if \".\" in sig:\n                modname, clsname = sig.rsplit(\".\", 1)\n            else:\n                modname, clsname = \"\", sig\n\n            # If there's a module name, then we add the module\n            if modname:\n                signode += addnodes.desc_addname(modname + \".\", modname + \".\")\n\n            # Add the class name\n            signode += addnodes.desc_name(clsname, clsname)\n        else:\n            # Add just \"Configuration\"\n            signode += addnodes.desc_name(sig, sig)\n\n        return sig\n\n    def add_target_and_index(\n        self, name: str, sig: str, signode: desc_signature\n    ) -> None:\n        \"\"\"Add a target and index for this thing.\"\"\"\n        targetname = f\"{self.objtype}-{name}\"\n\n        if targetname not in self.state.document.ids:\n            signode[\"names\"].append(targetname)\n            signode[\"ids\"].append(targetname)\n            signode[\"first\"] = not self.names\n            self.state.document.note_explicit_target(signode)\n\n            objects = self.env.domaindata[\"everett\"][\"objects\"]\n            key = (self.objtype, name)\n            if key in objects:\n                self.state_machine.reporter.warning(\n                    f\"duplicate description of {self.objtype} {name!r}, \"\n                    + f\"other instance in {self.env.doc2path(objects[key][0])}\",\n                    line=self.lineno,\n                )\n            objects[key] = (self.env.docname, targetname)\n\n        indextext = gettext(\"%s (component)\") % name\n        if self.indexnode is not None:\n            self.indexnode[\"entries\"].append(\n                (\"single\", indextext, targetname, \"\", None)\n            )\n\n    def before_content(self) -> None:\n        if self.names:\n            self.env.ref_context[\"everett:component\"] = self.names[-1]\n\n    def after_content(self) -> None:\n        self.env.ref_context[\"everett:component\"] = None\n\n\nclass EverettDomain(Domain):\n    \"\"\"Everett domain for component configuration.\"\"\"\n\n    name = \"everett\"\n    label = \"Everett\"\n\n    object_types = {\n        \"component\": ObjType(gettext(\"component\"), \"component\"),\n        \"option\": ObjType(gettext(\"option\"), \"option\"),\n    }\n    directives = {\n        \"component\": EverettComponent,\n        \"option\": EverettOption,\n    }\n    roles = {\n        \"component\": XRefRole(),\n        \"option\": XRefRole(),\n    }\n    initial_data: dict[str, dict] = {\n        # (typ, clspath) -> sphinx document name\n        \"objects\": {}\n    }\n\n    @property\n    def objects(self) -> dict[tuple[str, str], tuple[str, str]]:\n        return self.data.setdefault(\"objects\", {})\n\n    def clear_doc(self, docname: str) -> None:\n        key: Any = None\n        for key, val in list(self.objects.items()):\n            if val[0] == docname:\n                del self.objects[key]\n\n    # FIXME(willkg): What's the value in otherdata dict?\n    def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:\n        for key, val in otherdata[\"objects\"].items():\n            if val[0] in docnames:\n                self.objects[key] = val\n\n    def resolve_xref(\n        self,\n        env: \"BuildEnvironment\",\n        fromdocname: str,\n        builder: \"Builder\",\n        typ: str,\n        target: str,\n        node: pending_xref,\n        contnode: nodes.Element,\n    ) -> Optional[nodes.Element]:\n        objtypes = self.objtypes_for_role(typ) or []\n        for objtype in objtypes:\n            if (objtype, target) in self.objects:\n                docname, labelid = self.objects[objtype, target]\n                break\n\n        else:\n            docname, labelid = \"\", \"\"\n\n        if docname:\n            return make_refnode(builder, fromdocname, docname, labelid, contnode)\n\n        return None\n\n\nclass ConfigDirective(Directive):\n    \"\"\"Base class for generating configuration\"\"\"\n\n    def add_line(self, line: str, source: str, *lineno: int) -> None:\n        \"\"\"Add a line to the result\"\"\"\n        self.result.append(line, source, *lineno)\n        # NOTE(willkg): This makes figuring out issues easier. Leaving it here\n        # for future me.\n        # if line.strip():\n        #     print(f\">>> {line} [{source} {lineno}]\")\n        # else:\n        #     print(\">>> \")\n\n    def generate_docs(\n        self,\n        component_name: str,\n        component_index: str,\n        docstring: str,\n        sourcename: str,\n        option_data: list[dict],\n        more_content: Any,\n    ) -> None:\n        indent = \"   \"\n\n        # Add the classname or 'Configuration'\n        self.add_line(\".. everett:component:: %s\" % component_name, sourcename)\n        self.add_line(\"\", sourcename)\n\n        # Add the docstring if there is one and if show-docstring\n        if \"show-docstring\" in self.options and docstring:\n            docstringlines = prepare_docstring(docstring)\n            for i, line in enumerate(docstringlines):\n                self.add_line(indent + line, sourcename, i)\n            self.add_line(\"\", \"\")\n\n        # Add content from the directive if there was any\n        if more_content:\n            for line, src in zip(more_content.data, more_content.items, strict=True):\n                self.add_line(indent + line, src[0], src[1])\n            self.add_line(\"\", \"\")\n\n        if \"show-table\" in self.options and option_data:\n            self.add_line(indent + \"Configuration summary:\", sourcename)\n            self.add_line(\"\", sourcename)\n\n            # Build a table of metric items\n            table: list[list[str]] = []\n            table.append([\"Setting\", \"Parser\", \"Required?\"])\n            for option_item in option_data:\n                ref = f\"{component_name}.{option_item['key']}\"\n                table.append(\n                    [\n                        f\":everett:option:`{option_item['key']} <{ref}>`\",\n                        f\"*{option_item['parser']}*\",\n                        \"Yes\" if option_item[\"default\"] is NO_VALUE else \"\",\n                    ]\n                )\n\n            for line in build_table(table):\n                self.add_line(indent + line, sourcename)\n\n            self.add_line(\"\", sourcename)\n\n            self.add_line(indent + \"Configuration options:\", sourcename)\n            self.add_line(\"\", sourcename)\n\n        sourcename = \"class definition\"\n        if option_data:\n            # List the options and details\n            for option_item in option_data:\n                key = option_item[\"key\"]\n                self.add_line(f\"{indent}.. everett:option:: {key}\", sourcename)\n\n                self.add_line(\n                    f\"{indent}   :parser: {option_item['parser']}\", sourcename\n                )\n                if option_item[\"default\"] is not NO_VALUE:\n                    self.add_line(\n                        f'{indent}   :default: \"{option_item[\"default\"]}\"', sourcename\n                    )\n                else:\n                    self.add_line(f\"{indent}   :required:\", sourcename)\n                self.add_line(\"\", sourcename)\n\n                doc = option_item[\"doc\"]\n                for doc_line in doc.splitlines():\n                    self.add_line(f\"{indent}   {doc_line}\", sourcename)\n\n                self.add_line(\"\", sourcename)\n        else:\n            # There are no options\n            self.add_line(f\"{indent}No configuration options.\", sourcename)\n\n        self.add_line(\"\", sourcename)\n\n\nclass AutoComponentConfigDirective(ConfigDirective):\n    \"\"\"Directive for documenting configuration for an Everett component.\"\"\"\n\n    has_content = True\n    required_arguments = 1\n    optional_arguments = 0\n    final_argument_whitespace = False\n\n    option_spec = {\n        # Whether or not to show the class docstring--if None, don't show the\n        # docstring, if empty string use __doc__, otherwise use the value of\n        # the attribute on the class\n        \"show-docstring\": directives.unchanged,\n        # Whether or not to hide the class name\n        \"hide-name\": directives.flag,\n        # Prepend a specified namespace\n        \"namespace\": directives.unchanged,\n        # Render keys in specified case\n        \"case\": upper_lower_none,\n        # Whether or not to show a table\n        \"show-table\": directives.flag,\n    }\n\n    def extract_configuration(\n        self,\n        obj: Any,\n        namespace: Optional[str] = None,\n        case: Optional[str] = None,\n    ) -> list[dict]:\n        \"\"\"Extracts configuration values from list of Everett configuration options\n\n        :param obj: object/class to extract configuration from\n        :param namespace: namespace if any that these options are in\n        :param case: None, \"upper\", or \"lower\" for converting the name\n\n        :returns: list of dicts each representing an option\n\n        \"\"\"\n        config = get_config_for_class(obj)\n        options: list[dict] = []\n\n        # Go through options and figure out relevant information\n        for key, (option, _) in config.items():\n            if namespace:\n                namespaced_key = namespace + \"_\" + key\n            else:\n                namespaced_key = key\n\n            if case == \"upper\":\n                namespaced_key = namespaced_key.upper()\n            elif case == \"lower\":\n                namespaced_key = namespaced_key.lower()\n\n            options.append(\n                {\n                    \"key\": namespaced_key,\n                    \"default\": option.default,\n                    \"parser\": qualname(option.parser),\n                    \"doc\": option.doc,\n                    \"meta\": {},\n                }\n            )\n        return options\n\n    def run(self) -> list[nodes.Node]:\n        self.reporter = self.state.document.reporter\n        self.result = ViewList()\n\n        clspath = self.arguments[0]\n\n        obj = import_class(clspath)\n        sourcename = \"configuration of %s\" % clspath\n\n        option_data = self.extract_configuration(\n            obj=obj,\n            namespace=self.options.get(\"namespace\"),\n            case=self.options.get(\"case\"),\n        )\n\n        if \"hide-name\" not in self.options:\n            modname, clsname = split_clspath(clspath)\n            component_name = clspath\n            component_index = clsname\n        else:\n            component_name = \"Configuration\"\n            component_index = \"Configuration\"\n\n        # Add the docstring if there is one and if show-docstring\n        if \"show-docstring\" in self.options:\n            docstring_attr = self.options[\"show-docstring\"] or \"__doc__\"\n            docstring = getattr(obj, docstring_attr, \"\")\n        else:\n            docstring = \"\"\n\n        self.generate_docs(\n            component_name=component_name,\n            component_index=component_index,\n            docstring=docstring,\n            sourcename=sourcename,\n            option_data=option_data,\n            more_content=self.content,\n        )\n\n        if not self.result:\n            return []\n\n        node = nodes.paragraph()\n        node.document = self.state.document\n        self.state.nested_parse(self.result, 0, node)\n        return node.children\n\n\nSETTING_RE = re.compile(r\"^[A-Z_]+$\")\n\n\ndef build_table(table: list[list[str]]) -> list[str]:\n    \"\"\"Generates reST for a table.\n\n    :param table: a 2d array of rows and columns\n\n    :returns: list of strings\n\n    \"\"\"\n    output: list[str] = []\n\n    col_size = [0] * len(table[0])\n    for row in table:\n        for i, col in enumerate(row):\n            col_size[i] = max(col_size[i], len(col))\n\n    col_size = [width + 2 for width in col_size]\n\n    # Build header\n    output.append(\"  \".join(\"=\" * width for width in col_size))\n    output.append(\n        \"  \".join(\n            header + (\" \" * (width - len(header)))\n            for header, width in zip(table[0], col_size, strict=True)\n        )\n    )\n    output.append(\"  \".join(\"=\" * width for width in col_size))\n\n    # Iterate through rows\n    for row in table[1:]:\n        output.append(\n            \"  \".join(\n                col + (\" \" * (width - len(col)))\n                for col, width in zip(row, col_size, strict=True)\n            )\n        )\n    output.append(\"  \".join(\"=\" * width for width in col_size))\n    return output\n\n\nclass AutoModuleConfigDirective(ConfigDirective):\n    \"\"\"Directive for documenting configuration for a module.\"\"\"\n\n    has_content = True\n    # path/to/module.py variablename\n    required_arguments = 1\n    optional_arguments = 0\n    final_argument_whitespace = False\n\n    option_spec = {\n        # Whether or not to show the class docstring--if None, don't show the\n        # docstring, if empty string use __doc__, otherwise use the value of\n        # the attribute on the class\n        \"show-docstring\": directives.unchanged,\n        # Whether or not to hide the name\n        \"hide-name\": directives.flag,\n        # Prepend a specified namespace\n        \"namespace\": directives.unchanged,\n        # Render keys in specified case\n        \"case\": upper_lower_none,\n        # Whether or not to show a table\n        \"show-table\": directives.flag,\n    }\n\n    def _walk_ast(self, tree: ast.AST) -> Generator[ast.AST, None, None]:\n        \"\"\"Walks an AST returning Assign nodes\n\n        :param tree: the tree to walk\n\n        :returns: generator of Assign nodes\n\n        \"\"\"\n        for node in ast.walk(tree):\n            if isinstance(node, (ast.Assign, ast.Dict)):\n                yield node\n\n    def extract_configuration(\n        self,\n        filepath: str,\n        variable_name: str,\n        namespace: Optional[str] = None,\n        case: Optional[str] = None,\n    ) -> list[dict]:\n        \"\"\"Extracts configuration values from a module at filepath\n\n        :param filepath: the filepath to parse configuration from\n        :param variable_name: the ConfigurationManager variable name\n        :param namespace: namespace if any that these options are in\n        :param case: None, \"upper\", or \"lower\" for converting the name\n\n        :returns: list of dicts each representing an option\n\n        \"\"\"\n        with open(filepath) as fp:\n            source = fp.read()\n\n        tree = ast.parse(source=source, filename=filepath, mode=\"exec\")\n        config_nodes = []\n\n        for node in self._walk_ast(tree):\n            if isinstance(node, ast.Assign):\n                # Covers:\n                #\n                # SOMESETTING = _config(\"option\", default=\"foo\", ...)\n                if (\n                    len(node.targets) == 1\n                    and isinstance(node.targets[0], ast.Name)\n                    and SETTING_RE.match(node.targets[0].id)\n                    and isinstance(node.value, ast.Call)\n                    and isinstance(node.value.func, ast.Name)\n                    and node.value.func.id == variable_name\n                ):\n                    config_nodes.append((node.targets[0].id, node.value))\n\n            elif isinstance(node, ast.Dict):\n                # Covers:\n                #\n                # SOMESETTING = {\n                #     \"NAME\": _config(\"option\", default=\"foo\", ...),\n                #     \"NAME2\": _config(\"option2\", default=\"foo\", ...),\n                # }\n                for key, val in zip(node.keys, node.values, strict=True):\n                    if (\n                        isinstance(key, ast.Constant)\n                        and isinstance(val, ast.Call)\n                        and isinstance(val.func, ast.Name)\n                        and val.func.id == variable_name\n                    ):\n                        config_nodes.append((str(key.value), val))\n\n        CONFIG_ARGS = [\n            \"key\",\n            \"default\",\n            \"parser\",\n            \"doc\",\n            \"meta\",\n        ]\n\n        def extract_value(source: str, val: ast.AST) -> tuple[str, str]:\n            \"\"\"Returns (category, value)\"\"\"\n            if isinstance(val, ast.Constant):\n                return \"constant\", str(val.value)\n            if isinstance(val, ast.Name):\n                return \"name\", val.id\n            if isinstance(val, ast.BinOp) and isinstance(val.op, ast.Add):\n                _, left = extract_value(source, val.left)\n                _, right = extract_value(source, val.right)\n                return \"binop\", left + right\n            return \"unknown\", ast.get_source_segment(source, val) or \"?\"\n\n        # Using a dict here avoids the case where configuration options are\n        # defined multiple times\n        configuration = {}\n\n        for name, node in config_nodes:\n            args: dict[str, Any] = {\n                \"key\": name,\n                \"default\": NO_VALUE,\n                \"parser\": \"str\",\n                \"doc\": \"\",\n                \"meta\": {},\n            }\n            for i, arg in enumerate(node.args):\n                cat, value = extract_value(source, arg)\n\n                # NOTE(willkg): we're dropping the cat here; but we might want\n                # to do something with the category in the future, so I'm\n                # leaving the figuring in for now\n                args[CONFIG_ARGS[i]] = value\n\n            for keyword in node.keywords:\n                # NOTE(willkg): mypy thinks this can be None for some reason,\n                # but I'm not sure why. If it is None, we should skip it.\n                if keyword.arg is None:\n                    continue\n\n                cat, value = extract_value(source, keyword.value)\n                if keyword.arg == \"doc\":\n                    value = textwrap.dedent(value)\n\n                # NOTE(willkg): we're dropping the cat here; but we might want\n                # to do something with the category in the future, so I'm\n                # leaving the figuring in for now\n                args[keyword.arg] = value\n\n            key = args[\"key\"]\n            if namespace:\n                namespaced_key = f\"{namespace}_{key}\"\n            else:\n                namespaced_key = str(key)\n\n            if case == \"upper\":\n                namespaced_key = namespaced_key.upper()\n            elif case == \"lower\":\n                namespaced_key = namespaced_key.lower()\n\n            args[\"key\"] = namespaced_key\n            configuration[name] = args\n\n        return list(configuration.values())\n\n    def run(self) -> list[nodes.Node]:\n        self.reporter = self.state.document.reporter\n        self.result = ViewList()\n\n        clspath = self.arguments[0]\n\n        module, objpath = get_module_and_objpath(clspath)\n        if module is None:\n            raise ValueError(f\"{clspath!r} does not point to a valid thing\")\n\n        filepath = module.__file__\n        variable_name = objpath\n\n        if not variable_name:\n            raise ValueError(\"Variable in module is unknown\")\n\n        sourcename = \"configuration of %s\" % clspath\n\n        option_data = self.extract_configuration(\n            filepath=filepath,\n            variable_name=variable_name,\n            namespace=self.options.get(\"namespace\"),\n            case=self.options.get(\"case\"),\n        )\n\n        if \"hide-name\" not in self.options:\n            modname, clsname = split_clspath(clspath)\n            component_name = clspath\n            component_index = clsname\n        else:\n            component_name = \"Configuration\"\n            component_index = \"Configuration\"\n\n        # Add the docstring if there is one and if show-docstring\n        if \"show-docstring\" in self.options:\n            obj = module\n            docstring_attr = self.options[\"show-docstring\"] or \"__doc__\"\n            docstring = getattr(obj, docstring_attr, \"\")\n        else:\n            docstring = \"\"\n\n        self.generate_docs(\n            component_name=component_name,\n            component_index=component_index,\n            docstring=docstring,\n            sourcename=sourcename,\n            option_data=option_data,\n            more_content=self.content,\n        )\n\n        if not self.result:\n            return []\n\n        node = nodes.paragraph()\n        node.document = self.state.document\n        self.state.nested_parse(self.result, 0, node)\n        return node.children\n\n\n# FIXME(willkg): this takes a Sphinx app\ndef setup(app: Any) -> dict[str, Any]:\n    \"\"\"Register domain and directive in Sphinx.\"\"\"\n    app.add_domain(EverettDomain)\n    app.add_directive(\"autocomponentconfig\", AutoComponentConfigDirective)\n    app.add_directive(\"automoduleconfig\", AutoModuleConfigDirective)\n\n    return {\n        \"version\": __version__,\n        \"parallel_read_safe\": True,\n        \"parallel_write_safe\": True,\n    }\n"
  },
  {
    "path": "tests/basic_component_config.py",
    "content": "\"\"\"Basic component config.\"\"\"\n\nfrom everett.manager import ListOf, Option, parse_class\n\n\nclass ComponentBasic:\n    \"\"\"Basic component.\n\n    Multiple lines.\n\n    \"\"\"\n\n    HELP = \"Help attribute value.\"\n\n    class Config:\n        user = Option()\n\n\nclass ComponentNoOptions:\n    \"\"\"Basic component with no options.\"\"\"\n\n    class Config:\n        pass\n\n\nclass ComponentSubclass(ComponentBasic):\n    \"\"\"A different docstring.\"\"\"\n\n\nclass ComponentOptionDefault:\n    class Config:\n        user = Option(default=\"ou812\")\n\n\nclass ComponentOptionDoc:\n    class Config:\n        user = Option(doc=\"ou812\")\n\n\nclass ComponentOptionDocMultiline:\n    class Config:\n        user = Option(doc=\"ou812\")\n        password = Option(doc=\"First ``paragraph``.\\n\\nSecond paragraph.\")\n\n\nclass ComponentOptionDocDefault:\n    class Config:\n        user = Option(doc=\"This is some docs.\", default=\"ou812\")\n\n\nclass Foo:\n    @classmethod\n    def parse_foo_class(cls, value):\n        pass\n\n    def parse_foo_instance(self, value):\n        pass\n\n\nclass ComponentOptionParser:\n    class Config:\n        user_builtin = Option(parser=int)\n        user_parse_class = Option(parser=parse_class)\n        user_listof = Option(parser=ListOf(str))\n        user_class_method = Option(parser=Foo.parse_foo_class)\n        user_instance_method = Option(parser=Foo().parse_foo_instance)\n\n\nclass ComponentWithDocstring:\n    \"\"\"This component is the best.\n\n    The best!\n\n    \"\"\"\n\n    class Config:\n        user = Option()\n\n\nclass ComponentDocstringOtherAttribute:\n    \"\"\"Programming-focused help\"\"\"\n\n    __everett_help__ = \"\"\"\n        User-focused help\n    \"\"\"\n\n    class Config:\n        user = Option()\n"
  },
  {
    "path": "tests/basic_module_config.py",
    "content": "\"\"\"Basic module config.\"\"\"\n\nfrom everett.manager import ConfigManager\n\n\n_config = ConfigManager.from_dict(\n    {\"debug\": \"False\", \"logging_level\": \"INFO\", \"password\": \"pwd\", \"fun\": \"0.0\"}\n)\n\n\ndef parse_logging_level(s: str) -> str:\n    if s not in (\"CRITICAL\", \"WARNING\", \"INFO\", \"ERROR\"):\n        raise ValueError(\"invalid logging level value\")\n    return s\n\n\nDEBUG = _config(key=\"debug\", parser=bool, default=\"False\", doc=\"Debug mode.\")\n\nLOGGING_LEVEL = _config(key=\"logging_level\", parser=parse_logging_level, doc=\"Level.\")\n\nPASSWORD = _config(key=\"password\", doc=\"Password field.\\n\\nMust be provided.\")\n\nFUN = _config(key=\"fun\", parser=(int if 0 else float), doc=\"Woah.\")\n\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django.core.cache.backends.memcached.PyMemcacheCache\",\n        \"LOCATION\": _config(\n            \"cache_location\", default=\"127.0.0.1:11211\", doc=\"The location\"\n        ),\n    }\n}\n\nLONG_DESC = _config(\n    key=\"long_description\",\n    default=\"\",\n    doc=(\n        \"This configuration item has a really long description that spans \"\n        + \"several lines so we can test runtime string concatenation.\\n\\n\"\n        + \"Multiple lines should work, too.\"\n    ),\n)\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport os\nimport pytest\n\n\n@pytest.fixture\ndef datadir():\n    return os.path.join(os.path.dirname(__file__), \"data\")\n"
  },
  {
    "path": "tests/data/config_test.ini",
    "content": "[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",
    "content": "[main]\nfoo_original = original\n"
  },
  {
    "path": "tests/ext/test_inifile.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport os\n\nfrom everett import NO_VALUE\nfrom everett.ext.inifile import ConfigIniEnv\n\n\nclass TestConfigIniEnv:\n    def test_basic_usage(self, datadir):\n        ini_filename = os.path.join(datadir, \"config_test.ini\")\n        cie = ConfigIniEnv([ini_filename])\n        assert cie.get(\"foo\") == \"bar\"\n        assert cie.get(\"FOO\") == \"bar\"\n        assert cie.get(\"foo\", namespace=\"nsbaz\") == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\"]) == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\", \"nsbaz2\"]) == \"bat2\"\n\n        cie = ConfigIniEnv([\"/a/b/c/bogus/filename\"])\n        assert cie.get(\"foo\") == NO_VALUE\n\n    def test_multiple_files(self, datadir):\n        ini_filename = os.path.join(datadir, \"config_test.ini\")\n        ini_filename_original = os.path.join(datadir, \"config_test_original.ini\")\n        cie = ConfigIniEnv([ini_filename, ini_filename_original])\n        # Only the first found file is loaded, so foo_original does not exist\n        assert cie.get(\"foo_original\") == NO_VALUE\n        cie = ConfigIniEnv([ini_filename_original])\n        # ... but it is there if only the original is loaded (safety check)\n        assert cie.get(\"foo_original\") == \"original\"\n\n    def test_does_not_parse_lists(self, datadir):\n        ini_filename = os.path.join(datadir, \"config_test.ini\")\n        cie = ConfigIniEnv([ini_filename])\n        assert cie.get(\"bar\") == \"test1,test2\"\n"
  },
  {
    "path": "tests/ext/test_yamlfile.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport pytest\n\nfrom everett import NO_VALUE, ConfigurationError\nfrom everett.ext.yamlfile import ConfigYamlEnv\n\n\nYAML_FLAT = \"\"\"\\\nfoo: \"bar\"\nbar: \"test1,test2\"\nnsbaz_foo: \"bat\"\nnsbaz_nsbaz2_foo: \"bat2\"\n\"\"\"\n\nYAML_FLAT_2 = \"\"\"\\\nfoo_original: \"original\"\n\"\"\"\n\nYAML_FLAT_CASE = \"\"\"\\\nFOO: \"bar\"\nNSBAZ_Foo: \"bat\"\n\"\"\"\n\nYAML_NON_STRING_VALUES = \"\"\"\\\nfoo: 5\n\"\"\"\n\nYAML_HIERARCHY = \"\"\"\\\nfoo: \"bar\"\nbar: \"test1,test2\"\nnsbaz:\n    foo: \"bat\"\n    nsbaz2:\n        foo: \"bat2\"\n\"\"\"\n\n\nclass TestConfigYamlEnv:\n    def test_missing_file(self):\n        cie = ConfigYamlEnv([\"/a/b/c/bogus/filename\"])\n        assert cie.get(\"foo\") == NO_VALUE\n\n    def test_flat(self, tmpdir):\n        \"\"\"Test flat specification works\"\"\"\n        yaml_filename = tmpdir / \"config.yaml\"\n        yaml_filename.write(YAML_FLAT)\n\n        cie = ConfigYamlEnv([str(yaml_filename)])\n        assert cie.get(\"foo\") == \"bar\"\n        assert cie.get(\"foo\", namespace=\"nsbaz\") == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\"]) == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\", \"nsbaz2\"]) == \"bat2\"\n\n    def test_flat_caps(self, tmpdir):\n        \"\"\"Test case-insensitive\"\"\"\n        yaml_filename = tmpdir / \"config.yaml\"\n        yaml_filename.write(YAML_FLAT)\n\n        cie = ConfigYamlEnv([str(yaml_filename)])\n        assert cie.get(\"foo\") == \"bar\"\n        assert cie.get(\"FOO\") == \"bar\"\n        assert cie.get(\"Foo\") == \"bar\"\n        assert cie.get(\"foo\", namespace=\"nsbaz\") == \"bat\"\n        assert cie.get(\"foo\", namespace=\"NsBaz\") == \"bat\"\n        assert cie.get(\"FOO\", namespace=\"NSBAZ\") == \"bat\"\n\n    def test_hierarchical(self, tmpdir):\n        \"\"\"Test hierarchical specification works\"\"\"\n        yaml_filename = tmpdir / \"config.yaml\"\n        yaml_filename.write(YAML_HIERARCHY)\n\n        cie = ConfigYamlEnv([str(yaml_filename)])\n        assert cie.get(\"foo\") == \"bar\"\n        assert cie.get(\"foo\", namespace=\"nsbaz\") == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\"]) == \"bat\"\n        assert cie.get(\"foo\", namespace=[\"nsbaz\", \"nsbaz2\"]) == \"bat2\"\n\n    def test_multiple_files(self, tmpdir):\n        \"\"\"Test multiple files--uses first found\"\"\"\n        yaml_filename = tmpdir / \"config.yaml\"\n        yaml_filename.write(YAML_FLAT)\n\n        yaml_filename_2 = tmpdir / \"config2.yaml\"\n        yaml_filename_2.write(YAML_FLAT_2)\n\n        cie = ConfigYamlEnv([str(yaml_filename), str(yaml_filename_2)])\n        # Only the first found file is loaded, so foo_original does not exist\n        assert cie.get(\"foo_original\") == NO_VALUE\n\n        cie = ConfigYamlEnv([str(yaml_filename_2)])\n        # ... but it is there if only the original is loaded (safety check)\n        assert cie.get(\"foo_original\") == \"original\"\n\n    def test_non_string_values(self, tmpdir):\n        yaml_filename = tmpdir / \"config.yaml\"\n        yaml_filename.write(YAML_NON_STRING_VALUES)\n\n        with pytest.raises(ConfigurationError):\n            ConfigYamlEnv([str(yaml_filename)])\n"
  },
  {
    "path": "tests/simple_module_config.py",
    "content": "\"\"\"Simple module config.\"\"\"\n\nfrom everett.manager import ConfigManager\n\n\nHELP = \"Help attribute value.\"\n\n\n_config = ConfigManager.from_dict({\"host\": \"localhost\"})\n\nHOST = _config(key=\"host\", default=\"localhost\", doc=\"The host.\")\n"
  },
  {
    "path": "tests/test_manager.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport argparse\nimport os\n\nimport pytest\n\nfrom everett import (\n    ConfigurationError,\n    ConfigurationMissingError,\n    InvalidValueError,\n    NO_VALUE,\n)\nimport everett.manager\nfrom everett.manager import (\n    ChoiceOf,\n    ConfigDictEnv,\n    ConfigEnvFileEnv,\n    ConfigManager,\n    ConfigObjEnv,\n    ConfigOSEnv,\n    config_override,\n    generate_uppercase_key,\n    get_config_for_class,\n    get_key_from_envs,\n    get_parser,\n    get_runtime_config,\n    listify,\n    ListOf,\n    Option,\n    parse_bool,\n    parse_class,\n    parse_data_size,\n    parse_env_file,\n    parse_time_period,\n    qualname,\n)\n\n\n@pytest.mark.parametrize(\n    \"thing, expected\",\n    [\n        # built-in\n        (str, \"str\"),\n        # function in a module\n        (qualname, \"everett.manager.qualname\"),\n        # module\n        (everett.manager, \"everett.manager\"),\n        # class\n        (ConfigManager, \"everett.manager.ConfigManager\"),\n        # class method\n        (ConfigManager.basic_config, \"everett.manager.ConfigManager.basic_config\"),\n        # instance\n        (ListOf(bool), \"<ListOf(bool, delimiter=',', allow_empty=True)>\"),\n        # instance\n        (ChoiceOf(int, [\"1\", \"10\", \"100\"]), \"<ChoiceOf(int, ['1', '10', '100'])>\"),\n        # instance method\n        (ConfigOSEnv().get, \"everett.manager.ConfigOSEnv.get\"),\n    ],\n)\ndef test_qualname(thing, expected):\n    assert qualname(thing) == expected\n\n\ndef test_get_config_for_class():\n    \"\"\"Verify that get_config_for_class works for a trivial class\"\"\"\n\n    class Component:\n        class Config:\n            user = Option(doc=\"no help\")\n\n    options = get_config_for_class(Component)\n    assert list(options.keys()) == [\"user\"]\n\n\ndef test_get_config_for_class_complex_mro():\n    \"\"\"Verify get_config_for_class with an MRO that has a diamond shape to it.\n\n    The goal here is to make sure the C class has the right options from the\n    right components and in the right order.\n\n    \"\"\"\n\n    class ComponentBase:\n        pass\n\n    class A(ComponentBase):\n        class Config:\n            a = Option(default=\"a\")\n\n    class B(ComponentBase):\n        class Config:\n            b = Option(default=\"b\")\n            bd = Option(default=\"b\")\n\n    class C(B, A):\n        class Config:\n            # Note: This overrides B's b.\n            b = Option(default=\"c\")\n            c = Option(default=\"c\")\n\n    options = get_config_for_class(C)\n    assert list(\n        sorted([(key, opt.default) for key, (opt, cls) in options.items()])\n    ) == [\n        (\"a\", \"a\"),  # from A\n        (\"b\", \"c\"),  # from C (overrides B's)\n        (\"bd\", \"b\"),  # from B\n        (\"c\", \"c\"),  # from C\n    ]\n\n\ndef test_no_value():\n    assert bool(NO_VALUE) is False\n    assert NO_VALUE is not True\n    assert str(NO_VALUE) == \"NO_VALUE\"\n\n\ndef test_parse_bool_error():\n    with pytest.raises(ValueError):\n        parse_bool(\"\")\n\n\n@pytest.mark.parametrize(\n    \"data,expected\",\n    [\n        (None, []),\n        (\"\", [\"\"]),\n        ([], []),\n        (\"foo\", [\"foo\"]),\n        ([\"foo\"], [\"foo\"]),\n        ([\"foo\", \"bar\"], [\"foo\", \"bar\"]),\n    ],\n)\ndef test_listify(data, expected):\n    assert listify(data) == expected\n\n\n@pytest.mark.parametrize(\n    \"data\", [\"t\", \"true\", \"True\", \"TRUE\", \"y\", \"yes\", \"YES\", \"1\", \"on\", \"On\", \"ON\"]\n)\ndef test_parse_bool_true(data):\n    assert parse_bool(data) is True\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    [\"f\", \"false\", \"False\", \"FALSE\", \"n\", \"no\", \"No\", \"NO\", \"0\", \"off\", \"Off\", \"OFF\"],\n)\ndef test_parse_bool_false(data):\n    assert parse_bool(data) is False\n\n\ndef test_parse_bool_with_config():\n    config = ConfigManager.from_dict({\"foo\": \"bar\"})\n\n    # Test key is there, but value is bad\n    with pytest.raises(InvalidValueError) as excinfo:\n        config(\"foo\", parser=bool)\n    assert str(excinfo.value) == (\n        \"ValueError: 'bar' is not a valid bool value\\n\"\n        \"FOO requires a value parseable by everett.manager.parse_bool\"\n    )\n\n    # Test key is not there and default is bad\n    with pytest.raises(InvalidValueError) as excinfo:\n        config(\"phil\", default=\"foo\", parser=bool)\n    assert str(excinfo.value) == (\n        \"ValueError: 'foo' is not a valid bool value (default value)\\n\"\n        \"PHIL requires a value parseable by everett.manager.parse_bool\"\n    )\n\n\ndef test_parse_missing_class():\n    with pytest.raises(ImportError):\n        parse_class(\"doesnotexist.class\")\n\n    with pytest.raises(ValueError):\n        parse_class(\"hashlib.doesnotexist\")\n\n\ndef test_parse_class():\n    from hashlib import md5\n\n    assert parse_class(\"hashlib.md5\") == md5\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"0\", 0),\n        (\"1_000\", 1_000),\n        # decimal\n        (\"10b\", 10),\n        (\"  10b  \", 10),\n        (\"1_000b\", 1_000),\n        (\"1kb\", 1_000),\n        (\"10kb\", 10_000),\n        (\"5mb\", 5_000_000),\n        (\"7gb\", 7_000_000_000),\n        (\"32tb\", 32_000_000_000_000),\n        # binary\n        (\"10kib\", 10_240),\n        (\"2MiB\", 2_097_152),\n        (\"17gib\", 18_253_611_008),\n        (\"4tib\", 4_398_046_511_104),\n    ],\n)\ndef test_parse_data_size(text, expected):\n    assert parse_data_size(text) == expected\n\n\n@pytest.mark.parametrize(\"text\", [\"\", \"gb\", \"15yy\"])\ndef test_parse_data_size_bad_values(text):\n    with pytest.raises(ValueError):\n        parse_data_size(text)\n\n\n@pytest.mark.parametrize(\n    \"text, expected\",\n    [\n        (\"15\", 15),\n        (\"1_000\", 1_000),\n        (\"1s\", 1),\n        (\"10m\", 600),\n        (\"1_000m\", 60_000),\n        (\"10m3s\", 603),\n        (\"1d10m\", 87_000),\n        (\"1w2d\", 777_600),\n        (\"  1w 2d  \", 777_600),\n    ],\n)\ndef test_parse_time_period(text, expected):\n    assert parse_time_period(text) == expected\n\n\n@pytest.mark.parametrize(\"text\", [\"\", \"m\", \"10j\"])\ndef test_parse_time_period_bad_values(text):\n    with pytest.raises(ValueError):\n        parse_time_period(text)\n\n\ndef test_parse_class_config():\n    config = ConfigManager.from_dict(\n        {\"foo_cls\": \"hashlib.doesnotexist\", \"bar_cls\": \"doesnotexist.class\"}\n    )\n\n    with pytest.raises(InvalidValueError) as exc_info:\n        config(\"foo_cls\", parser=parse_class)\n    assert str(exc_info.value) == (\n        \"ValueError: 'doesnotexist' is not a valid member of hashlib\\n\"\n        \"FOO_CLS requires a value parseable by everett.manager.parse_class\"\n    )\n\n    with pytest.raises(InvalidValueError) as exc_info:\n        config(\"bar_cls\", parser=parse_class)\n    assert str(exc_info.value) == (\n        \"ModuleNotFoundError: No module named 'doesnotexist'\\n\"\n        \"BAR_CLS requires a value parseable by everett.manager.parse_class\"\n    )\n\n\ndef test_get_parser():\n    assert get_parser(bool) == parse_bool\n    assert get_parser(str) is str\n\n    def foo():\n        pass\n\n    assert get_parser(foo) == foo\n\n\ndef test_ListOf():\n    assert ListOf(str)(\"\") == []\n    assert ListOf(str)(\"foo\") == [\"foo\"]\n    assert ListOf(bool)(\"t,f\") == [True, False]\n    assert ListOf(int)(\"1,2,3\") == [1, 2, 3]\n    assert ListOf(str)(\"1  , 2, 3\") == [\"1\", \"2\", \"3\"]\n    assert ListOf(int, delimiter=\":\")(\"1:2\") == [1, 2]\n    assert ListOf(str)(\"a,,b\") == [\"a\", \"\", \"b\"]\n\n\ndef test_ListOf_error():\n    config = ConfigManager.from_dict({\"bools\": \"t,f,badbool\"})\n    with pytest.raises(InvalidValueError) as exc_info:\n        config(\"bools\", parser=ListOf(bool))\n\n    assert str(exc_info.value) == (\n        \"ValueError: 'badbool' is not a valid bool value\\n\"\n        \"BOOLS requires a value parseable by \"\n        \"<ListOf(bool, delimiter=',', allow_empty=True)>\"\n    )\n\n\ndef test_ListOf_allow_empty_error():\n    config = ConfigManager.from_dict({\"names\": \"bob,,alice\"})\n    with pytest.raises(InvalidValueError) as exc_info:\n        config(\"names\", parser=ListOf(str, allow_empty=False))\n\n    assert str(exc_info.value) == (\n        \"ValueError: 'bob,,alice' can not have empty values\\n\"\n        \"NAMES requires a value parseable by \"\n        \"<ListOf(str, delimiter=',', allow_empty=False)>\"\n    )\n\n\ndef test_ChoiceOf():\n    # Supports any choice\n    assert ChoiceOf(str, [\"a\", \"b\", \"c\"])(\"a\") == \"a\"\n    assert ChoiceOf(str, [\"a\", \"b\", \"c\"])(\"b\") == \"b\"\n    assert ChoiceOf(str, [\"a\", \"b\", \"c\"])(\"c\") == \"c\"\n\n    # Supports different parsers\n    assert ChoiceOf(int, [\"1\", \"2\", \"3\"])(\"1\") == 1\n\n\ndef test_ChoiceOf_bad_choices():\n    # Must provide choices\n    with pytest.raises(ValueError) as exc_info:\n        ChoiceOf(str, [])\n    assert str(exc_info.value) == \"choices [] must be a non-empty list of strings\"\n\n    # Must be a list of strings\n    with pytest.raises(ValueError) as exc_info:\n        ChoiceOf(str, [1, 2, 3])\n    assert (\n        str(exc_info.value) == \"choices [1, 2, 3] must be a non-empty list of strings\"\n    )\n\n\ndef test_ChoiceOf_error():\n    # Value is the wrong case\n    with pytest.raises(ValueError) as exc_info:\n        ChoiceOf(str, [\"A\", \"B\", \"C\"])(\"c\")\n    assert str(exc_info.value) == \"'c' is not a valid choice\"\n\n    # Value isn't a valid choice\n    config = ConfigManager.from_dict({\"cloud_provider\": \"foo\"})\n    with pytest.raises(InvalidValueError) as exc_info:\n        config(\"cloud_provider\", parser=ChoiceOf(str, [\"aws\", \"gcp\"]))\n    assert str(exc_info.value) == (\n        \"ValueError: 'foo' is not a valid choice\\n\"\n        \"CLOUD_PROVIDER requires a value parseable by <ChoiceOf(str, ['aws', 'gcp'])>\"\n    )\n\n\nclass TestConfigObjEnv:\n    def test_basic(self):\n        class Namespace:\n            pass\n\n        obj = Namespace()\n        setattr(obj, \"foo\", \"bar\")  # noqa\n        setattr(obj, \"foo_baz\", \"bar\")  # noqa\n\n        coe = ConfigObjEnv(obj)\n        assert coe.get(\"foo\") == \"bar\"\n        assert coe.get(\"FOO\") == \"bar\"\n        assert coe.get(\"FOO_BAZ\") == \"bar\"\n\n    def test_with_argparse(self):\n        parser = argparse.ArgumentParser()\n        parser.add_argument(\"--debug\", help=\"to debug or not to debug\")\n        parsed_vals = parser.parse_known_args([])[0]\n\n        config = ConfigManager([ConfigObjEnv(parsed_vals)])\n\n        assert config(\"debug\", parser=bool, raise_error=False) is NO_VALUE\n\n        parsed_vals = parser.parse_known_args([\"--debug=y\"])[0]\n\n        config = ConfigManager([ConfigObjEnv(parsed_vals)])\n\n        assert config(\"debug\", parser=bool) is True\n\n    def test_with_argparse_actions(self):\n        parser = argparse.ArgumentParser()\n        parser.add_argument(\n            \"--debug\", help=\"to debug or not to debug\", action=\"store_true\"\n        )\n        parsed_vals = parser.parse_known_args([])[0]\n\n        config = ConfigManager([ConfigObjEnv(parsed_vals)])\n\n        # What happens is that argparse doesn't see an arg, so saves\n        # debug=False. ConfigObjEnv converts that to \"False\". That gets parsed\n        # as False by the Everett parse_bool function. That's kind of\n        # roundabout but \"works\" for some/most cases.\n        assert config(\"debug\", parser=bool) is False\n\n        parsed_vals = parser.parse_known_args([\"--debug\"])[0]\n\n        config = ConfigManager([ConfigObjEnv(parsed_vals)])\n\n        assert config(\"debug\", parser=bool) is True\n\n\ndef test_ConfigDictEnv():\n    cde = ConfigDictEnv(\n        {\"FOO\": \"bar\", \"A_FOO\": \"a_bar\", \"A_B_FOO\": \"a_b_bar\", \"lower_foo\": \"bar\"}\n    )\n    assert cde.get(\"foo\") == \"bar\"\n    assert cde.get(\"foo\", namespace=[\"a\"]) == \"a_bar\"\n    assert cde.get(\"foo\", namespace=[\"a\", \"b\"]) == \"a_b_bar\"\n    assert cde.get(\"FOO\", namespace=[\"a\"]) == \"a_bar\"\n    assert cde.get(\"foo\", namespace=[\"A\"]) == \"a_bar\"\n    assert cde.get(\"FOO\", namespace=[\"A\"]) == \"a_bar\"\n\n    cde = ConfigDictEnv({\"foo\": \"bar\"})\n    assert cde.get(\"foo\") == \"bar\"\n    assert cde.get(\"FOO\") == \"bar\"\n\n\ndef test_ConfigOSEnv():\n    os.environ[\"EVERETT_TEST_FOO\"] = \"bar\"\n    os.environ[\"EVERETT_TEST_FOO\"] = \"bar\"\n    cose = ConfigOSEnv()\n\n    assert cose.get(\"everett_test_foo\") == \"bar\"\n    assert cose.get(\"EVERETT_test_foo\") == \"bar\"\n    assert cose.get(\"foo\", namespace=[\"everett\", \"test\"]) == \"bar\"\n\n\ndef test_ConfigEnvFileEnv(datadir):\n    env_filename = os.path.join(datadir, \".env\")\n    cefe = ConfigEnvFileEnv([\"/does/not/exist/.env\", env_filename])\n    assert cefe.get(\"not_a\", namespace=\"youre\") == \"golfer\"\n    assert cefe.get(\"loglevel\") == \"walter\"\n    assert cefe.get(\"LOGLEVEL\") == \"walter\"\n    assert cefe.get(\"_typer_standard_traceback\") == \"1\"\n    assert cefe.get(\"missing\") is NO_VALUE\n    assert cefe.data == {\n        \"LOGLEVEL\": \"walter\",\n        \"DEBUG\": \"True\",\n        \"YOURE_NOT_A\": \"golfer\",\n        \"DATABASE_URL\": \"sqlite:///kahlua.db\",\n        \"_TYPER_STANDARD_TRACEBACK\": \"1\",\n    }\n\n    cefe = ConfigEnvFileEnv(env_filename)\n    assert cefe.get(\"not_a\", namespace=\"youre\") == \"golfer\"\n\n    cefe = ConfigEnvFileEnv(\"/does/not/exist/.env\")\n    assert cefe.get(\"loglevel\") is NO_VALUE\n\n\n@pytest.mark.parametrize(\n    \"line, expected\",\n    [\n        (\"PLAN9=outerspace\", {\"PLAN9\": \"outerspace\"}),\n        ('KEY=\"val\"', {\"KEY\": \"val\"}),\n        (\"KEY='val'\", {\"KEY\": \"val\"}),\n        # Only strips outer-most quote\n        (\"KEY=\\\"'val'\\\"\", {\"KEY\": \"'val'\"}),\n        (\"KEY='\\\"val\\\"'\", {\"KEY\": '\"val\"'}),\n        (\n            \"CSP_SCRIPT_SRC='self' www.googletagmanager.com\",\n            {\"CSP_SCRIPT_SRC\": \"'self' www.googletagmanager.com\"},\n        ),\n        (\n            \"CSP_SCRIPT_SRC=\\\"'self' www.googletagmanager.com\\\"\",\n            {\"CSP_SCRIPT_SRC\": \"'self' www.googletagmanager.com\"},\n        ),\n    ],\n)\ndef test_parse_env_file_line(line, expected):\n    assert parse_env_file([line]) == expected\n\n\ndef test_parse_env_file_errors():\n    with pytest.raises(ConfigurationError) as exc_info:\n        parse_env_file([\"3AMIGOS=infamous\"])\n    assert str(exc_info.value) == \"Invalid variable name '3AMIGOS' in env file (line 1)\"\n\n    with pytest.raises(ConfigurationError) as exc_info:\n        parse_env_file([\"INVALID-CHAR=value\"])\n    assert str(exc_info.value) == (\n        \"Invalid variable name 'INVALID-CHAR' in env file (line 1)\"\n    )\n\n    with pytest.raises(ConfigurationError) as exc_info:\n        parse_env_file([\"\", \"MISSING-equals\"])\n    assert str(exc_info.value) == \"Env file line missing = operator (line 2)\"\n\n\n@pytest.mark.parametrize(\n    \"key, ns, expected\",\n    [\n        (\"k\", None, \"K\"),\n        (\"a_b\", None, \"A_B\"),\n        (\"k\", \"ns\", \"NS_K\"),\n        (\"k\", [\"ns1\", \"ns2\"], \"NS1_NS2_K\"),\n        (\"k\", [\"ns1\", \"\", \"ns2\"], \"NS1_NS2_K\"),\n    ],\n)\ndef test_generate_uppercase_key(key, ns, expected):\n    full_key = generate_uppercase_key(key, ns)\n    assert full_key == expected\n\n\ndef test_get_key_from_envs():\n    assert get_key_from_envs({\"K\": \"v\"}, \"K\") == \"v\"\n    assert get_key_from_envs([{\"K\": \"v\"}, {\"L\": \"w\"}], \"L\") == \"w\"\n    assert get_key_from_envs({\"K\": \"v\"}, \"Q\") is NO_VALUE\n    # first match wins\n    envs = [{\"K\": \"v\"}, {\"L\": \"w\"}, {\"K\": \"z\"}]\n    assert get_key_from_envs(envs, \"K\") == \"v\"\n    # works with reversed iterator\n    envs = reversed([{\"L\": \"v\"}, {\"L\": \"w\"}])\n    assert get_key_from_envs(envs, \"L\") == \"w\"\n    # works with os.environ\n    os.environ[\"DUDE_ABIDES\"] = \"yeah, man\"\n    assert get_key_from_envs(os.environ, \"DUDE_ABIDES\") == \"yeah, man\"\n\n\ndef test_config():\n    config = ConfigManager([])\n\n    # Don't raise an error and no default yields NO_VALUE\n    assert config(\"DOESNOTEXISTNOWAY\", raise_error=False) is NO_VALUE\n\n    # Defaults to raising an error\n    with pytest.raises(ConfigurationMissingError) as exc_info:\n        config(\"DOESNOTEXISTNOWAY\")\n    assert str(exc_info.value) == \"DOESNOTEXISTNOWAY requires a value parseable by str\"\n\n    # Raises an error if raise_error is True\n    with pytest.raises(ConfigurationMissingError) as exc_info:\n        config(\"DOESNOTEXISTNOWAY\", raise_error=True)\n    assert str(exc_info.value) == \"DOESNOTEXISTNOWAY requires a value parseable by str\"\n\n    # With a default, returns the default\n    assert config(\"DOESNOTEXISTNOWAY\", default=\"ohreally\") == \"ohreally\"\n\n    # Test doc\n    with pytest.raises(ConfigurationMissingError) as exc_info:\n        config(\"DOESNOTEXISTNOWAY\", doc=\"Nothing to see here.\")\n    assert str(exc_info.value) == (\n        \"DOESNOTEXISTNOWAY requires a value parseable by str\\n\"\n        \"DOESNOTEXISTNOWAY docs: Nothing to see here.\"\n    )\n\n\ndef test_raise_configuration_error():\n    config = ConfigManager.from_dict({\"foo_bar\": \"bat\"})\n    config.doc = \"Check https://example.com/configuration for documentation.\"\n\n    with pytest.raises(ConfigurationError) as excinfo:\n        config.raise_configuration_error(\"This is an error\")\n    assert str(excinfo.value) == \"This is an error\\nProject docs: \" + config.doc\n\n\ndef test_invalidvalueerror():\n    config = ConfigManager.from_dict({\"foo_bar\": \"bat\"})\n    with pytest.raises(InvalidValueError) as excinfo:\n        config(\"bar\", namespace=\"foo\", parser=bool)\n    assert excinfo.value.namespace == [\"foo\"]\n    assert excinfo.value.key == \"bar\"\n    assert excinfo.value.parser == parse_bool\n\n\ndef test_configurationmissingerror():\n    # Verify ConfigurationMissingError has the right values\n    config = ConfigManager([])\n\n    # Defaults to raising an error\n    with pytest.raises(ConfigurationMissingError) as exc_info:\n        config(\"DOESNOTEXISTNOWAY\", namespace=\"foo\")\n\n    assert (\n        exc_info.value.args[0]\n        == \"FOO_DOESNOTEXISTNOWAY requires a value parseable by str\"\n    )\n    assert exc_info.value.namespace == [\"foo\"]\n    assert exc_info.value.key == \"DOESNOTEXISTNOWAY\"\n    assert exc_info.value.parser is str\n\n\ndef test_config_from_dict():\n    config = ConfigManager.from_dict({})\n\n    assert config(\"FOO\", raise_error=False) is NO_VALUE\n\n    config = ConfigManager.from_dict({\"FOO\": \"bar\"})\n\n    assert config(\"FOO\", raise_error=False) == \"bar\"\n\n\ndef test_basic_config(datadir):\n    os.environ[\"EVERETT_BASIC_CONFIG_TEST\"] = \"foo\"\n    env_filename = os.path.join(datadir, \".env\")\n    config = ConfigManager.basic_config(env_filename)\n\n    # This doesn't exist in either the environment or the env file\n    assert config(\"FOO\", raise_error=False) is NO_VALUE\n\n    # This exists in the environment\n    assert config(\"EVERETT_BASIC_CONFIG_TEST\") == \"foo\"\n\n    # This exists in the env file\n    assert config(\"LOGLEVEL\") == \"walter\"\n\n\ndef test_basic_config_with_docs(datadir):\n    config = ConfigManager.basic_config(doc=\"foo\")\n\n    assert config.doc == \"foo\"\n\n\ndef test_config_manager_doc():\n    config = ConfigManager(\n        [ConfigDictEnv({\"foo\": \"bar\"})], doc=\"See http://example.com/configuration\"\n    )\n\n    # Test ConfigManager doc shows up\n    with pytest.raises(ConfigurationError) as exc_info:\n        config(\"foo\", parser=int)\n    assert str(exc_info.value) == (\n        \"ValueError: invalid literal for int() with base 10: 'bar'\\n\"\n        \"FOO requires a value parseable by int\\n\"\n        \"Project docs: See http://example.com/configuration\"\n    )\n\n    # Test config doc and ConfigManager doc show up\n    with pytest.raises(ConfigurationError) as exc_info:\n        config(\"foo\", parser=int, doc=\"Port to listen on.\")\n    assert str(exc_info.value) == (\n        \"ValueError: invalid literal for int() with base 10: 'bar'\\n\"\n        \"FOO requires a value parseable by int\\n\"\n        \"FOO docs: Port to listen on.\\n\"\n        \"Project docs: See http://example.com/configuration\"\n    )\n\n\ndef test_config_override():\n    config = ConfigManager([])\n\n    # Make sure the key doesn't exist\n    assert config(\"DOESNOTEXISTNOWAY\", raise_error=False) is NO_VALUE\n\n    # Try one override\n    with config_override(DOESNOTEXISTNOWAY=\"bar\"):\n        assert config(\"DOESNOTEXISTNOWAY\") == \"bar\"\n\n    # Try nested overrides--innermost one rules supreme!\n    with config_override(DOESNOTEXISTNOWAY=\"bar\"):\n        with config_override(DOESNOTEXISTNOWAY=\"bat\"):\n            assert config(\"DOESNOTEXISTNOWAY\") == \"bat\"\n\n\ndef test_default_must_be_string():\n    config = ConfigManager([])\n\n    with pytest.raises(ConfigurationError):\n        assert config(\"DOESNOTEXIST\", default=True)\n\n\ndef test_default_if_empty():\n    config = ConfigManager.from_dict({\"FOO\": \"\"})\n\n    assert config(\"FOO\", default=\"5\", parser=int) == 5\n    assert config(\"FOO\", default=\"5\", parser=int, default_if_empty=True) == 5\n    with pytest.raises(InvalidValueError):\n        assert config(\"FOO\", default=\"5\", parser=int, default_if_empty=False)\n\n\ndef test_with_namespace():\n    config = ConfigManager(\n        [ConfigDictEnv({\"FOO_BAR\": \"foobaz\", \"BAR\": \"baz\", \"BAT\": \"bat\"})]\n    )\n\n    # Verify the values first\n    assert config(\"bar\", namespace=[\"foo\"]) == \"foobaz\"\n    assert config(\"bar\") == \"baz\"\n    assert config(\"bat\") == \"bat\"\n\n    # Create the namespaced config\n    config_with_namespace = config.with_namespace(\"foo\")\n    assert config_with_namespace(\"bar\") == \"foobaz\"\n\n    # Verify 'bat' is not available because it's not in the namespace\n    with pytest.raises(ConfigurationError):\n        config_with_namespace(\"bat\")\n\n\ndef test_get_namespace():\n    config = ConfigManager.from_dict(\n        {\"FOO\": \"abc\", \"FOO_BAR\": \"abc\", \"FOO_BAR_BAZ\": \"abc\"}\n    )\n    assert config.get_namespace() == []\n\n    ns_foo_config = config.with_namespace(\"foo\")\n    assert ns_foo_config.get_namespace() == [\"foo\"]\n\n    ns_foo_bar_config = ns_foo_config.with_namespace(\"bar\")\n    assert ns_foo_bar_config.get_namespace() == [\"foo\", \"bar\"]\n\n\n@pytest.mark.parametrize(\n    \"key,alternate_keys,expected\",\n    [\n        # key, alternate keys, expected\n        (\"FOO\", [], \"foo_abc\"),\n        (\"FOO\", [\"FOO_BAR\"], \"foo_abc\"),\n        (\"BAD_KEY\", [\"FOO_BAR\"], \"foo_bar_abc\"),\n        (\"BAD_KEY\", [\"BAD_KEY1\", \"BAD_KEY2\", \"FOO_BAR_BAZ\"], \"foo_bar_baz_abc\"),\n    ],\n)\ndef test_alternate_keys(key, alternate_keys, expected):\n    config = ConfigManager.from_dict(\n        {\"FOO\": \"foo_abc\", \"FOO_BAR\": \"foo_bar_abc\", \"FOO_BAR_BAZ\": \"foo_bar_baz_abc\"}\n    )\n\n    assert config(key, alternate_keys=alternate_keys) == expected\n\n\n@pytest.mark.parametrize(\n    \"key,alternate_keys,expected\",\n    [\n        # key, alternate keys, expected\n        (\"BAR\", [], \"foo_bar_abc\"),\n        (\"BAD_KEY\", [\"BAD_KEY1\", \"BAR_BAZ\"], \"foo_bar_baz_abc\"),\n        (\"bad_key\", [\"bad_key1\", \"bar_baz\"], \"foo_bar_baz_abc\"),\n        (\"bad_key\", [\"root:common_foo\"], \"common_foo_abc\"),\n    ],\n)\ndef test_alternate_keys_with_namespace(key, alternate_keys, expected):\n    config = ConfigManager.from_dict(\n        {\n            \"COMMON_FOO\": \"common_foo_abc\",\n            \"FOO\": \"foo_abc\",\n            \"FOO_BAR\": \"foo_bar_abc\",\n            \"FOO_BAR_BAZ\": \"foo_bar_baz_abc\",\n        }\n    )\n\n    config = config.with_namespace(\"FOO\")\n\n    assert config(key, alternate_keys=alternate_keys) == expected\n\n\ndef test_raw_value():\n    config = ConfigManager.from_dict({\"FOO_BAR\": \"1\"})\n    assert config(\"FOO_BAR\", parser=int) == 1\n    assert config(\"FOO_BAR\", parser=int, raw_value=True) == \"1\"\n\n    assert str(config(\"NOEXIST\", parser=int, raise_error=False)) == \"NO_VALUE\"\n\n    config = config.with_namespace(\"FOO\")\n    assert config(\"BAR\", parser=int) == 1\n    assert config(\"BAR\", parser=int, raw_value=True) == \"1\"\n\n\ndef test_with_options():\n    \"\"\"Verify .with_options() restricts configuration\"\"\"\n    config = ConfigManager(\n        [ConfigDictEnv({\"FOO_BAR\": \"a\", \"FOO_BAZ\": \"b\", \"BAR\": \"c\", \"BAZ\": \"d\"})]\n    )\n\n    class SomeComponent:\n        class Config:\n            baz = Option(default=\"\", doc=\"some help here\", parser=str)\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    # Create the component with regular config\n    comp = SomeComponent(config)\n    assert comp.config(\"baz\") == \"d\"\n    with pytest.raises(ConfigurationError):\n        # This is not a valid option for this component\n        comp.config(\"bar\")\n\n    # Create the component with config in the \"foo\" namespace\n    comp2 = SomeComponent(config.with_namespace(\"foo\"))\n    assert comp2.config(\"baz\") == \"b\"\n    with pytest.raises(ConfigurationError):\n        # This is not a valid option for this component\n        comp2.config(\"bar\")\n\n\ndef test_nested_options():\n    \"\"\"Verify nested BoundOptions works.\"\"\"\n\n    class Foo:\n        class Config:\n            option1 = Option(default=\"opt1default\", parser=str)\n\n    class Bar:\n        class Config:\n            option2 = Option(default=\"opt2default\", parser=str)\n\n    config = ConfigManager.basic_config()\n    config = config.with_options(Foo)\n    config = config.with_options(Bar)\n\n    assert config(\"option2\") == \"opt2default\"\n    with pytest.raises(ConfigurationError):\n        config(\"option1\")\n\n\ndef test_namespace_and_options():\n    \"\"\"Verify namespace and then options works\"\"\"\n\n    class Foo:\n        class Config:\n            option = Option(default=\"opt1default\", parser=str)\n\n    config = ConfigManager.from_dict({\"NS_OPTION\": \"val\"})\n    config = config.with_namespace(\"ns\").with_options(Foo)\n\n    assert config(\"option\") == \"val\"\n\n\ndef test_options_and_namespace():\n    \"\"\"Verify options and then namespace works\"\"\"\n\n    # NOTE(willkg): This doesn't make sense to me, but there's no technical\n    # reason it shouldn't work.\n    class App:\n        class Config:\n            http_port = Option(default=\"80\")\n            db_port = Option(default=\"3306\")\n\n    config = ConfigManager.from_dict({})\n    config = config.with_options(App)\n\n    http_config = config.with_namespace(\"http\")\n    db_config = config.with_namespace(\"db\")\n\n    assert http_config(\"port\") == \"80\"\n    assert db_config(\"port\") == \"3306\"\n\n\ndef test_default_comes_from_options():\n    \"\"\"Verify that the default is picked up from options\"\"\"\n    config = ConfigManager([])\n\n    class SomeComponent:\n        class Config:\n            foo = Option(default=\"abc\")\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config)\n    assert comp.config(\"foo\") == \"abc\"\n\n\ndef test_parser_comes_from_options():\n    \"\"\"Verify the parser is picked up from options\"\"\"\n    config = ConfigManager([ConfigDictEnv({\"FOO\": \"1\"})])\n\n    class SomeComponent:\n        class Config:\n            foo = Option(parser=int)\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config)\n    assert comp.config(\"foo\") == 1\n\n\ndef test_component_get_namespace():\n    config = ConfigManager.from_dict(\n        {\"FOO\": \"abc\", \"FOO_BAR\": \"abc\", \"FOO_BAR_BAZ\": \"abc\"}\n    )\n    assert config.get_namespace() == []\n\n    class SomeComponent:\n        class Config:\n            foo = Option(parser=int)\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n        def my_namespace_is(self):\n            return self.config.get_namespace()\n\n    comp = SomeComponent(config)\n    assert comp.my_namespace_is() == []\n\n    comp = SomeComponent(config.with_namespace(\"foo\"))\n    assert comp.my_namespace_is() == [\"foo\"]\n\n\ndef test_component_alternate_keys():\n    config = ConfigManager.from_dict(\n        {\"COMMON\": \"common_abc\", \"FOO\": \"abc\", \"FOO_BAR\": \"abc\", \"FOO_BAR_BAZ\": \"abc\"}\n    )\n\n    class SomeComponent:\n        class Config:\n            bad_key = Option(alternate_keys=[\"root:common\"])\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config)\n\n    # The key is invalid, so it tries the alternate keys\n    assert comp.config(\"bad_key\") == \"common_abc\"\n\n\ndef test_component_doc():\n    config = ConfigManager.from_dict({\"FOO_BAR\": \"bat\"})\n\n    class SomeComponent:\n        class Config:\n            foo_bar = Option(parser=int, doc=\"omg!\")\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config)\n\n    try:\n        # This throws an exception becase \"bat\" is not an int\n        comp.config(\"foo_bar\")\n    except Exception as exc:\n        # We're going to lazily assert that omg! is in exc msg because if it\n        # is, it came from the option and that's what we want to know.\n        assert \"omg!\" in str(exc)\n\n\ndef test_component_raw_value():\n    config = ConfigManager.from_dict({\"FOO_BAR\": \"1\"})\n\n    class SomeComponent:\n        class Config:\n            foo_bar = Option(parser=int)\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config)\n\n    assert comp.config(\"foo_bar\") == 1\n    assert comp.config(\"foo_bar\", raw_value=True) == \"1\"\n\n    class SomeComponent:\n        class Config:\n            bar = Option(parser=int)\n\n        def __init__(self, config):\n            self.config = config.with_options(self)\n\n    comp = SomeComponent(config.with_namespace(\"foo\"))\n\n    assert comp.config(\"bar\") == 1\n    assert comp.config(\"bar\", raw_value=True) == \"1\"\n\n\nclass TestGetRuntimeConfig:\n    def test_bound_config(self):\n        config = ConfigManager.from_dict({\"foo\": 12345})\n\n        class ComponentA:\n            class Config:\n                foo = Option(parser=int)\n                bar = Option(parser=int, default=\"1\")\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n        comp = ComponentA(config)\n        assert list(get_runtime_config(config, comp)) == [\n            ([], \"foo\", \"12345\", Option(parser=int)),\n            ([], \"bar\", \"1\", Option(parser=int, default=\"1\")),\n        ]\n\n    def test_tree_with_specified_namespace(self):\n        config = ConfigManager.from_dict({})\n\n        class ComponentB:\n            class Config:\n                foo = Option(parser=int, default=\"2\")\n                bar = Option(parser=int, default=\"1\")\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n        class ComponentA:\n            class Config:\n                baz = Option(default=\"abc\")\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n                self.biff = ComponentB(config.with_namespace(\"biff\"))\n\n        comp = ComponentA(config)\n\n        assert list(get_runtime_config(config, comp)) == [\n            ([], \"baz\", \"abc\", Option(default=\"abc\")),\n            ([\"biff\"], \"foo\", \"2\", Option(parser=int, default=\"2\")),\n            ([\"biff\"], \"bar\", \"1\", Option(parser=int, default=\"1\")),\n        ]\n\n    def test_tree_inferred_namespace(self):\n        \"\"\"Test get_runtime_config can pull namespace from config.\"\"\"\n\n        class ComponentB:\n            class Config:\n                foo = Option(parser=int, default=\"2\")\n                bar = Option(parser=int, default=\"1\")\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n\n        class ComponentA:\n            class Config:\n                baz = Option(default=\"abc\")\n\n            def __init__(self, config):\n                self.config = config.with_options(self)\n                self.boff = ComponentB(config.with_namespace(\"boff\"))\n\n        config = ConfigManager.from_dict({})\n        comp = ComponentA(config)\n\n        assert list(get_runtime_config(config, comp)) == [\n            ([], \"baz\", \"abc\", Option(default=\"abc\")),\n            ([\"boff\"], \"foo\", \"2\", Option(parser=int, default=\"2\")),\n            ([\"boff\"], \"bar\", \"1\", Option(parser=int, default=\"1\")),\n        ]\n\n    def test_slots(self):\n        \"\"\"Test get_runtime_config works with classes using slots.\"\"\"\n        config = ConfigManager.from_dict({})\n\n        class Base:\n            __slots__ = (\"_slotattr\",)\n\n        class ComponentA(Base):\n            class Config:\n                key = Option(default=\"abc\")\n\n            def __init__(self, config_manager):\n                self.config = config_manager.with_options(self)\n\n        comp = ComponentA(config)\n\n        assert list(get_runtime_config(config, comp)) == [\n            ([], \"key\", \"abc\", Option(default=\"abc\"))\n        ]\n"
  },
  {
    "path": "tests/test_sphinxext.py",
    "content": "# 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 distributed with this\n# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n\n\"\"\"Test sphinxext directives.\"\"\"\n\nfrom textwrap import dedent\n\nimport pytest\nfrom sphinx.cmd.build import main as sphinx_main\n\n\ndef run_sphinx(docsdir, text, builder=\"text\"):\n    # set up conf.py\n    with open(str(docsdir / \"conf.py\"), \"w\") as fp:\n        fp.write('master_doc = \"index\"\\n')\n        fp.write('extensions = [\"everett.sphinxext\"]\\n')\n\n    # set up index.rst file\n    text = \"BEGINBEGIN\\n\\n%s\\n\\nENDEND\" % text\n    with open(str(docsdir / \"index.rst\"), \"w\") as fp:\n        fp.write(text)\n\n    args = [\"-b\", builder, \"-v\", \"-E\", str(docsdir), str(docsdir / \"_build\" / builder)]\n    print(args)\n    if sphinx_main(args):\n        raise RuntimeError(\"Sphinx build failed\")\n\n    extension = \"txt\" if builder == \"text\" else \"html\"\n    with open(\n        str(docsdir / \"_build\" / builder / f\"index.{extension}\"), encoding=\"utf8\"\n    ) as fp:\n        data = fp.read()\n\n    # index.text has a bunch of stuff in it. BEGINBEGIN and ENDEND are markers,\n    # so we just return the bits in between.\n    data = data[data.find(\"BEGINBEGIN\") + 10 : data.find(\"ENDEND\")]\n    # Strip the whitespace, but add a \\n to make tests easier to read.\n    data = data.strip() + \"\\n\"\n    return data\n\n\ndef test_infrastructure(tmpdir):\n    # Verify parsing is working at all. This seems like a no-op, but really\n    # it's going through all the Sphinx stuff to generate the text that it\n    # started off with.\n    assert run_sphinx(tmpdir, \"*foo*\") == dedent(\n        \"\"\"\\\n        *foo*\n        \"\"\"\n    )\n\n\ndef test_everett_component(tmpdir, capsys):\n    # Test .. everett:component:: with an option and verify Sphinx isn't\n    # spitting out warnings\n    rst = dedent(\n        \"\"\"\\\n    .. everett:component:: mymodule.ComponentBasic\n\n       .. everett:option:: opt1\n          :parser: str\n          :default: \"foo\"\n\n          First option.\n\n    \"\"\"\n    )\n\n    assert run_sphinx(tmpdir, rst) == dedent(\n        \"\"\"\\\n        component mymodule.ComponentBasic\n\n           opt1\n\n              Parser:\n                 *str*\n\n              Default:\n                 \"foo\"\n\n              Required:\n                 No\n\n              First option.\n        \"\"\"\n    )\n    captured = capsys.readouterr()\n    assert \"WARNING\" not in captured.out\n    assert \"WARNING\" not in captured.err\n\n\nclass Test_autocomponentconfig:\n    def test_basic(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentBasic\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_no_option_data(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentNoOptions\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentNoOptions\n\n               No configuration options.\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_hide_name(self, tmpdir, capsys):\n        # Test hide-name\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n           :hide-name:\n\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            Configuration\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_namespace(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n           :namespace: foo\n\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentBasic\n\n               foo_user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_case_bad_value(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n           :case: foo\n\n        \"\"\"\n        )\n\n        # Because \"foo\" isn't valid, nothing ends up in the file\n        assert run_sphinx(tmpdir, rst) == \"\\n\"\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert 'argument must be \"upper\", \"lower\" or None.' in captured.err\n\n    def test_case_lower(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n           :case: lower\n\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentBasic\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_case_upper(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n        .. autocomponentconfig:: basic_component_config.ComponentBasic\n           :case: upper\n\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentBasic\n\n               USER\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_show_docstring_class_has_no_docstring(self, tmpdir, capsys):\n        # Test docstring-related things\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentBasic\n               :show-docstring:\n\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentBasic\n\n               Basic component.\n\n               Multiple lines.\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_show_docstring(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentWithDocstring\n               :show-docstring:\n\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentWithDocstring\n\n               This component is the best.\n\n               The best!\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_show_docstring_other_attribute(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentDocstringOtherAttribute\n               :show-docstring: __everett_help__\n\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentDocstringOtherAttribute\n\n               User-focused help\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_show_docstring_subclass(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentSubclass\n               :show-docstring:\n\n        \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentSubclass\n\n               A different docstring.\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_option_default(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentOptionDefault\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentOptionDefault\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"ou812\"\n\n                  Required:\n                     No\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_option_doc(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentOptionDoc\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentOptionDoc\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n\n                  ou812\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_option_doc_multiline(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentOptionDocMultiline\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentOptionDocMultiline\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n\n                  ou812\n\n               password\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n\n                  First \"paragraph\".\n\n                  Second paragraph.\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_option_doc_default(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentOptionDocDefault\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentOptionDocDefault\n\n               user\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"ou812\"\n\n                  Required:\n                     No\n\n                  This is some docs.\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_option_parser(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. autocomponentconfig:: basic_component_config.ComponentOptionParser\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_component_config.ComponentOptionParser\n\n               user_builtin\n\n                  Parser:\n                     *int*\n\n                  Required:\n                     Yes\n\n               user_parse_class\n\n                  Parser:\n                     *everett.manager.parse_class*\n\n                  Required:\n                     Yes\n\n               user_listof\n\n                  Parser:\n                     *<ListOf(str, delimiter=',', allow_empty=True)>*\n\n                  Required:\n                     Yes\n\n               user_class_method\n\n                  Parser:\n                     *basic_component_config.Foo.parse_foo_class*\n\n                  Required:\n                     Yes\n\n               user_instance_method\n\n                  Parser:\n                     *basic_component_config.Foo.parse_foo_instance*\n\n                  Required:\n                     Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n\nclass Test_automoduleconfig:\n    def test_basic(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: basic_module_config._config\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component basic_module_config._config\n\n               debug\n\n                  Parser:\n                     *bool*\n\n                  Default:\n                     \"False\"\n\n                  Required:\n                     No\n\n                  Debug mode.\n\n               logging_level\n\n                  Parser:\n                     *parse_logging_level*\n\n                  Required:\n                     Yes\n\n                  Level.\n\n               password\n\n                  Parser:\n                     *str*\n\n                  Required:\n                     Yes\n\n                  Password field.\n\n                  Must be provided.\n\n               fun\n\n                  Parser:\n                     *int if 0 else float*\n\n                  Required:\n                     Yes\n\n                  Woah.\n\n               long_description\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"\"\n\n                  Required:\n                     No\n\n                  This configuration item has a really long description that spans\n                  several lines so we can test runtime string concatenation.\n\n                  Multiple lines should work, too.\n\n               cache_location\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"127.0.0.1:11211\"\n\n                  Required:\n                     No\n\n                  The location\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        print(captured.out)\n        assert \"WARNING\" not in captured.out\n        print(captured.err)\n        assert \"WARNING\" not in captured.err\n\n    def test_hide_name(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :hide-name:\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            Configuration\n\n               host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_namespace(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :namespace: app\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               app_host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_case_upper(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :case: upper\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               HOST\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_case_lower(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :case: lower\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_show_table(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :show-table:\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               Configuration summary:\n\n               +--------------------------------------------------------------+----------+-------------+\n               | Setting                                                      | Parser   | Required?   |\n               |==============================================================|==========|=============|\n               | \"host\"                                                       | *str*    |             |\n               +--------------------------------------------------------------+----------+-------------+\n\n               Configuration options:\n\n               host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_show_docstring(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :show-docstring:\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               Simple module config.\n\n               host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_show_docstring_by_attribute(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: simple_module_config._config\n               :show-docstring: HELP\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component simple_module_config._config\n\n               Help attribute value.\n\n               host\n\n                  Parser:\n                     *str*\n\n                  Default:\n                     \"localhost\"\n\n                  Required:\n                     No\n\n                  The host.\n            \"\"\"\n        )\n\n    def test_import_error(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. automoduleconfig:: badpath\n            \"\"\"\n        )\n\n        with pytest.raises(RuntimeError):\n            run_sphinx(tmpdir, rst)\n\n            captured = capsys.readouterr()\n            assert (\n                \"ValueError: 'badpath' does not point to a valid thing\" in captured.err\n            )\n\n\nclass Test_everett_option:\n    def test_basic(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. everett:option:: debug\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            debug\n\n               Parser:\n                  *str*\n\n               Required:\n                  Yes\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n\n    def test_thorough(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. everett:option:: debug\n               :parser: bool\n               :default: \"false\"\n\n               Set to \"true\" for debug mode\n            \"\"\"\n        )\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            debug\n\n               Parser:\n                  *bool*\n\n               Default:\n                  \"false\"\n\n               Required:\n                  No\n\n               Set to \"true\" for debug mode\n            \"\"\"\n        )\n\n    def test_no_default_means_required(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. everett:option:: debug\n               :parser: bool\n\n               Set to \"true\" for debug mode\n            \"\"\"\n        )\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            debug\n\n               Parser:\n                  *bool*\n\n               Required:\n                  Yes\n\n               Set to \"true\" for debug mode\n            \"\"\"\n        )\n\n\nclass Test_everett_component:\n    def test_basic(self, tmpdir, capsys):\n        rst = dedent(\n            \"\"\"\\\n            .. everett:component:: MyClass\n\n               Some details about my class.\n\n               .. everett:option:: debug\n                  :parser: bool\n\n                  Set to \"true\" for debug mode\n            \"\"\"\n        )\n\n        assert run_sphinx(tmpdir, rst) == dedent(\n            \"\"\"\\\n            component MyClass\n\n               Some details about my class.\n\n               debug\n\n                  Parser:\n                     *bool*\n\n                  Required:\n                     Yes\n\n                  Set to \"true\" for debug mode\n            \"\"\"\n        )\n        captured = capsys.readouterr()\n        assert \"WARNING\" not in captured.out\n        assert \"WARNING\" not in captured.err\n"
  }
]