././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6748567 django-environ-0.11.2/0000775000175000017500000000000014474450344013722 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6588566 django-environ-0.11.2/.github/0000775000175000017500000000000014474450344015262 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630353048.0 django-environ-0.11.2/.github/FUNDING.yml0000664000175000017500000000112614113233230017056 0ustar00joke2kjoke2k# These are supported funding model platforms github: joke2k patreon: # Replace with a single Patreon username open_collective: joke2k ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/.github/dependabot.yml0000664000175000017500000000162614400225515020104 0ustar00joke2kjoke2kversion: 2 updates: - package-ecosystem: pip # setup.py stored in repository root. directory: '/' # Raise pull requests for version updates # to pip against the `develop` branch target-branch: develop schedule: # Check for updates managed by pip once a week interval: weekly # Specify labels for npm pull requests labels: - pip - dependencies assignees: - sergeyklay - package-ecosystem: github-actions # Workflow files stored in the # default location of `.github/workflows` directory: '/' # Raise pull requests for version updates # to pip against the `develop` branch target-branch: develop schedule: # Check for updates for GitHub actions once a week interval: weekly # Specify labels for npm pull requests labels: - github_actions - dependencies assignees: - sergeyklay ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6628566 django-environ-0.11.2/.github/workflows/0000775000175000017500000000000014474450344017317 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/.github/workflows/build.yml0000664000175000017500000000404114473635227021144 0ustar00joke2kjoke2kname: Build on: push: branches-ignore: # These should always correspond to pull requests, so ignore them for # the push trigger and let them be triggered by the pull_request # trigger, avoiding running the workflow twice. This is a minor # optimization so there's no need to ensure this is comprehensive. - 'dependabot/**' tags: - 'v[0-9]+.[0-9]+.[0-9]+' # The branches below must be a subset of the branches above pull_request: branches: - develop - main jobs: build: name: Build and test package distribution runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3.6.0 - name: Set up Python 3.10 uses: actions/setup-python@v4.7.0 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Check MANIFEST.in for completeness run: tox -e manifest - name: Build and test package run: tox -e build - name: Archive build artifacts if: success() uses: actions/upload-artifact@v3 with: # To ensure that jobs don't overwrite existing artifacts, # use a different name per job. name: build-${{ matrix.os }} path: .tox/build/tmp/build # Artifacts are retained for 90 days by default. # In fact, we don't need such long period. retention-days: 60 install-dev: name: Verify dev environment runs-on: ${{ matrix.os }} strategy: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] steps: - name: Checkout code uses: actions/checkout@v3.6.0 - name: Set up Python 3.10 uses: actions/setup-python@v4.7.0 with: python-version: '3.10' - name: Install in dev mode run: python -m pip install -e . - name: Import package run: python -c 'import environ; print(environ.__version__)' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/.github/workflows/change-pr-target.yml0000664000175000017500000000120014400225515023150 0ustar00joke2kjoke2kname: Make sure new PRs are sent to develop on: pull_request_target: types: [opened, edited] jobs: check-branch: runs-on: ubuntu-latest steps: - uses: Vankka/pr-target-branch-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: target: main exclude: develop # Don't prevent going from develop -> main change-to: develop comment: | Your PR was set to target `main`, PRs should be target `develop` The base branch of this PR has been automatically changed to `develop`, please check that there are no merge conflicts. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/.github/workflows/ci.yml0000664000175000017500000000556014473635227020447 0ustar00joke2kjoke2kname: CI on: push: branches-ignore: # These should always correspond to pull requests, so ignore them for # the push trigger and let them be triggered by the pull_request # trigger, avoiding running the workflow twice. This is a minor # optimization so there's no need to ensure this is comprehensive. - 'dependabot/**' # The branches below must be a subset of the branches above pull_request: branches: - develop - main schedule: - cron: '0 11 * * *' # | | | | | # | | | | |____ day of the week (0 - 6 or SUN-SAT) # | | | |____ month (1 - 12 or JAN-DEC) # | | |____ day of the month (1 - 31) # | |____ hour (0 - 23) # |____ minute (0 - 59) env: PYTHONUNBUFFERED: '1' defaults: run: shell: bash jobs: test: name: Python ${{ matrix.python }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} # The maximum number of minutes to let a workflow run # before GitHub automatically cancels it. Default: 360 timeout-minutes: 30 strategy: # When set to true, GitHub cancels # all in-progress jobs if any matrix job fails. fail-fast: false matrix: python: - '3.8' - '3.9' - '3.10' - '3.11' - 'pypy-3.7' os: [ ubuntu-latest, macos-latest, windows-latest ] # These versions are no longer supported by Python team, and may # eventually be dropped from GitHub Actions. The support of these # versions by django-environ will continue for as long as possible, # and may be discontinued at any time. include: - python: '3.6' os: ubuntu-20.04 - python: '3.7' os: ubuntu-20.04 steps: - name: Checkout code uses: actions/checkout@v3.6.0 with: fetch-depth: 5 - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v4.7.0 with: python-version: ${{ matrix.python }} - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install tox tox-gh-actions - name: Setuptools self-test run: | python setup.py --fullname python setup.py --long-description python setup.py --classifiers - name: Run unit tests with coverage run: tox - name: Combine coverage reports run: tox -e coverage-report - name: Upload coverage report if: success() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_SERVICE_NAME: github run: | python -m pip install coveralls # Do not fail job if coveralls.io is down coveralls || true - name: Success Reporting if: success() run: git log --format=fuller -5 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/.github/workflows/codeql.yml0000664000175000017500000000255214473635227021321 0ustar00joke2kjoke2kname: CodeQL on: push: branches: - develop - main # The branches below must be a subset of the branches above pull_request: branches: - develop - main schedule: - cron: '40 22 * * 5' # | | | | | # | | | | |____ day of the week (0 - 6 or SUN-SAT) # | | | |____ month (1 - 12 or JAN-DEC) # | | |____ day of the month (1 - 31) # | |____ hour (0 - 23) # |____ minute (0 - 59) jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write # The maximum number of minutes to let a workflow run # before GitHub automatically cancels it. Default: 360 timeout-minutes: 30 strategy: # When set to true, GitHub cancels # all in-progress jobs if any matrix job fails. fail-fast: false matrix: language: - python steps: - name: Checkout repository uses: actions/checkout@v3.6.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} - name: Autobuild uses: github/codeql-action/autobuild@v2 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/.github/workflows/cs.yml0000664000175000017500000000177214473635227020462 0ustar00joke2kjoke2kname: CS on: push: branches-ignore: # These should always correspond to pull requests, so ignore them for # the push trigger and let them be triggered by the pull_request # trigger, avoiding running the workflow twice. This is a minor # optimization so there's no need to ensure this is comprehensive. - 'dependabot/**' pull_request: branches: - develop - main jobs: lint: runs-on: ubuntu-latest name: Code linting # The maximum number of minutes to let a workflow run # before GitHub automatically cancels it. Default: 360 timeout-minutes: 30 steps: - name: Checkout code uses: actions/checkout@v3.6.0 - name: Set up Python 3.10 uses: actions/setup-python@v4.7.0 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Lint with tox run: tox -e lint ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/.github/workflows/docs.yml0000664000175000017500000000276114473635227021004 0ustar00joke2kjoke2kname: Docs on: push: branches-ignore: # These should always correspond to pull requests, so ignore them for # the push trigger and let them be triggered by the pull_request # trigger, avoiding running the workflow twice. This is a minor # optimization so there's no need to ensure this is comprehensive. - 'dependabot/**' # The branches below must be a subset of the branches above pull_request: branches: - develop - main jobs: docs: runs-on: ubuntu-latest name: Build and test package documentation # The maximum number of minutes to let a workflow run # before GitHub automatically cancels it. Default: 360 timeout-minutes: 30 steps: - name: Checkout code uses: actions/checkout@v3.6.0 - name: Set up Python 3.10 uses: actions/setup-python@v4.7.0 with: python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip pip install tox tox-gh-actions - name: Check external links in the package documentation run: tox -e linkcheck - name: Build and test package documentation run: tox -e docs - name: Archive docs artifacts if: always() uses: actions/upload-artifact@v3 with: name: docs path: docs # Artifacts are retained for 90 days by default. # In fact, we don't need such long period. retention-days: 60 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/.readthedocs.yml0000664000175000017500000000125614252326653017013 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details --- version: 2 build: os: ubuntu-20.04 tools: # Keep version in sync with tox.ini (testenv:docs) and # docs.yml (GitHub Action Workflow). python: '3.10' python: install: - method: pip path: . extra_requirements: - docs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/AUTHORS.rst0000664000175000017500000000200314252326653015573 0ustar00joke2kjoke2kCredits ======= ``django-environ`` was initially created by `Daniele Faraglia `_ and currently maintained by `Serghei Iakovlev `_. A full list of contributors can be found in `GitHub `__. Acknowledgments =============== The existence of ``django-environ`` would have been impossible without these projects: - `rconradharris/envparse `_ - `jazzband/dj-database-url `_ - `migonzalvar/dj-email-url `_ - `ghickman/django-cache-url `_ - `dstufft/dj-search-url `_ - `julianwachholz/dj-config-url `_ - `nickstenning/honcho `_ - `rconradharris/envparse `_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/BACKERS.rst0000664000175000017500000000207614473635227015537 0ustar00joke2kjoke2kBackers and supporters ====================== You can join them in supporting django-environ development by visiting our page on `Open Collective `_ and becoming a sponsor or a backer! Sponsors -------- Support this project by becoming a sponsor. Your logo will show up here with a link to your website. `Became sponsor `_. |ocsponsor0| |ocsponsor1| Backers ------- Thank you to all our backers! |ocbackerimage| .. |ocsponsor0| image:: https://opencollective.com/django-environ/sponsor/0/avatar.svg :target: https://opencollective.com/triplebyte :alt: Sponsor .. |ocsponsor1| image:: https://images.opencollective.com/static/images/become_sponsor.svg :target: https://opencollective.com/django-environ/contribute/sponsors-3474/checkout :alt: Become a Sponsor .. |ocbackerimage| image:: https://opencollective.com/django-environ/backers.svg?width=890 :target: https://opencollective.com/django-environ :alt: Backers on Open Collective ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602000.0 django-environ-0.11.2/CHANGELOG.rst0000664000175000017500000003153214474450320015741 0ustar00joke2kjoke2kChangelog ========= All notable changes to this project will be documented in this file. The format is inspired by `Keep a Changelog `_ and this project adheres to `Semantic Versioning `_. `v0.11.2`_ - 1-September-2023 ------------------------------- Fixed +++++ - Revert "Add variable expansion." feature due to `#490 `_. `v0.11.1`_ - 30-August-2023 --------------------------- Fixed +++++ - Revert "Add interpolate argument to avoid resolving proxied values." feature due to `#485 `_. `v0.11.0`_ - 30-August-2023 --------------------------- Added +++++ - Added support for Django 4.2 `#456 `_. - Added support for secure Elasticsearch connections `#463 `_. - Added variable expansion `#468 `_. - Added capability to handle comments after #, after quoted values, like ``KEY= 'part1 # part2' # comment`` `#475 `_. - Added support for ``interpolate`` parameter `#415 `_. Changed +++++++ - Used ``mssql-django`` as engine for SQL Server `#446 `_. - Changed handling bool values, stripping whitespace around value `#475 `_. - Use ``importlib.util.find_spec`` to ``replace pkgutil.find_loader`` `#482 `_. Removed +++++++ - Removed support of Python 3.5. `v0.10.0`_ - 2-March-2023 ------------------------- Added +++++ - Use the core redis library by default if running Django >= 4.0 `#356 `_. - Value of dict can now contain an equal sign `#241 `_. - Added support for Python 3.11. - Added ``CONN_HEALTH_CHECKS`` to database base options `#413 `_. - Added ``encoding`` parameter to ``read_env`` with default value 'utf8' `#442 `_. - Added support for Django 4.1 `#416 `_. Deprecated ++++++++++ - Support of Python < 3.6 is deprecated and will be removed in next major version. Changed +++++++ - Used UTF-8 as a encoding when open ``.env`` file. - Provided access to ``DB_SCHEMES`` through ``cls`` rather than ``Env`` in ``db_url_config`` `#414 `_. - Correct CI workflow to use supported Python versions/OS matrix `#441 `_. - Reworked trigger CI workflows strategy `#440 `_. Fixed +++++ - Fixed logic of ``Env.get_value()`` to skip parsing only when ``default=None``, not for all default values that coerce to ``False`` `#404 `_. - Deleted duplicated include in docs/quickstart.rst `#439 `_. Removed +++++++ - Removed deprecated ``Env.unicode()``. - Removed ``environ.register_schemes`` calls and do not modify global ``urllib.parse.urlparse``'s ``uses_*`` variables as this no longer needed `#246 `_. `v0.9.0`_ - 15-June-2022 ------------------------ Added +++++ - Added support for Postgresql cluster URI `#355 `_. - Added support for Django 4.0 `#371 `_. - Added support for prefixed variables `#362 `_. - Amended documentation. Deprecated ++++++++++ - ``Env.unicode()`` is deprecated and will be removed in the next major release. Use ``Env.str()`` instead. Changed +++++++ - Attach cause to ``ImproperlyConfigured`` exception `#360 `_. Fixed +++++ - Fixed ``_cast_urlstr`` unquoting `#357 `_. - Fixed documentation regarding unsafe characters in URLs `#220 `_. - Fixed ``environ.Path.__eq__()`` to compare paths correctly `#86 `_, `#197 `_. `v0.8.1`_ - 20-October-2021 --------------------------- Fixed +++++ - Fixed "Invalid line" spam logs on blank lines in env file `#340 `_. - Fixed ``memcache``/``pymemcache`` URL parsing for correct identification of connection type `#337 `_. `v0.8.0`_ - 17-October-2021 --------------------------- Added +++++ - Log invalid lines when parse ``.env`` file `#283 `_. - Added docker-style file variable support `#189 `_. - Added option to override existing variables with ``read_env`` `#103 `_, `#249 `_. - Added support for empty var with None default value `#209 `_. - Added ``pymemcache`` cache backend for Django 3.2+ `#335 `_. Fixed +++++ - Keep newline/tab escapes in quoted strings `#296 `_. - Handle escaped dollar sign in values `#271 `_. - Fixed incorrect parsing of ``DATABASES_URL`` for Google Cloud MySQL `#294 `_. `v0.7.0`_ - 11-September-2021 ------------------------------ Added +++++ - Added support for negative float strings `#160 `_. - Added Elasticsearch5 to search scheme `#297 `_. - Added Elasticsearch7 to search scheme `#314 `_. - Added the ability to use ``bytes`` or ``str`` as a default value for ``Env.bytes()``. Fixed +++++ - Fixed links in the documentation. - Use default option in ``Env.bytes()`` `#206 `_. - Safely evaluate a string containing an invalid Python literal `#200 `_. Changed +++++++ - Added 'Funding' and 'Say Thanks!' project urls on pypi. - Stop raising ``UserWarning`` if ``.env`` file isn't found. Log a message with ``INFO`` log level instead `#243 `_. `v0.6.0`_ - 4-September-2021 ---------------------------- Added +++++ - Python 3.9, 3.10 and pypy 3.7 are now supported. - Django 3.1 and 3.2 are now supported. - Added missed classifiers to ``setup.py``. - Accept Python 3.6 path-like objects for ``read_env`` `#106 `_, `#286 `_. Fixed +++++ - Fixed various code linting errors. - Fixed typos in the documentation. - Added missed files to the package contents. - Fixed ``db_url_config`` to work the same for all postgres-like schemes `#264 `_, `#268 `_. Changed +++++++ - Refactor tests to use pytest and follow DRY. - Moved CI to GitHub Actions. - Restructuring of project documentation. - Build and test package documentation as a part of CI pipeline. - Build and test package distribution as a part of CI pipeline. - Check ``MANIFEST.in`` in a source package for completeness as a part of CI pipeline. - Added ``pytest`` and ``coverage[toml]`` to setuptools' ``extras_require``. `v0.5.0`_ - 30-August-2021 -------------------------- Added +++++ - Support for Django 2.1 & 2.2. - Added tox.ini targets. - Added secure redis backend URLs via ``rediss://``. - Added ``cast=str`` to ``str()`` method. Fixed +++++ - Fixed misspelling in the documentation. Changed +++++++ - Validate empty cache url and invalid cache schema. - Set ``long_description_content_type`` in setup. - Improved Django 1.11 database configuration support. `v0.4.5`_ - 25-June-2018 ------------------------ Added +++++ - Support for Django 2.0. - Support for smart casting. - Support PostgreSQL unix domain socket paths. - Tip: Multiple env files. Changed +++++++ - Fix parsing option values ``None``, ``True`` and ``False``. - Order of importance of engine configuration in ``db_url_config``. Removed +++++++ - Remove ``django`` and ``six`` dependencies. `v0.4.4`_ - 21-August-2017 -------------------------- Added +++++ - Support for ``django-redis`` multiple locations (master/slave, shards). - Support for Elasticsearch2. - Support for Mysql-connector. - Support for ``pyodbc``. - Added ``__contains__`` feature to Environ class. Fixed +++++ - Fix Path subtracting. `v0.4.3`_ - 21-August-2017 -------------------------- Changed +++++++ - Rollback the default Environ to ``os.environ``. `v0.4.2`_ - 13-April-2017 ------------------------- Added +++++ - Confirm support for Django 1.11. - Support for Redshift database URL. Changed +++++++ - Fixed uwsgi settings reload problem `#55 `_. - Update support for ``django-redis`` urls `#109 `_. `v0.4.1`_ - 13-November-2016 ---------------------------- Added +++++ - Add support for Django 1.10. Changed +++++++ - Fixed for unsafe characters into URLs. - Clarifying warning on missing or unreadable file. Thanks to `@nickcatal `_. - Fixed support for Oracle urls. - Fixed support for ``django-redis``. `v0.4`_ - 23-September-2015 --------------------------- Added +++++ - New email schemes - ``smtp+ssl`` and ``smtp+tls`` (``smtps`` would be deprecated). - Added tuple support. Thanks to `@anonymouzz `_. - Added LDAP url support for database. Thanks to `django-ldapdb/django-ldapdb `_. Changed +++++++ - Fixed non-ascii values (broken in Python 2.x). - ``redis_cache`` replaced by ``django_redis``. - Fixed psql/pgsql url. `v0.3.1`_ - 19 Sep 2015 ----------------------- Added +++++ - Added ``email`` as alias for ``email_url``. - Django 1.7 is now supported. - Added LDAP scheme support for ``db_url_config``. Fixed +++++ - Fixed typos in the documentation. - Fixed ``environ.Path.__add__`` to correctly handle plus operator. - Fixed ``environ.Path.__contains__`` to correctly work on Windows. `v0.3`_ - 03-June-2014 ---------------------- Added +++++ - Added cache url support. - Added email url support. - Added search url support. Changed +++++++ - Rewriting README.rst. v0.2.1 - 19-April-2013 ---------------------- Changed +++++++ - ``Env.__call__`` now uses ``Env.get_value`` instance method. v0.2 - 16-April-2013 -------------------- Added +++++ - Added advanced float parsing (comma and dot symbols to separate thousands and decimals). Fixed +++++ - Fixed typos in the documentation. v0.1 - 2-April-2013 ------------------- Added +++++ - Initial release. .. _v0.11.2: https://github.com/joke2k/django-environ/compare/v0.11.1...v0.11.2 .. _v0.11.1: https://github.com/joke2k/django-environ/compare/v0.11.0...v0.11.1 .. _v0.11.0: https://github.com/joke2k/django-environ/compare/v0.10.0...v0.11.0 .. _v0.10.0: https://github.com/joke2k/django-environ/compare/v0.9.0...v0.10.0 .. _v0.9.0: https://github.com/joke2k/django-environ/compare/v0.8.1...v0.9.0 .. _v0.8.1: https://github.com/joke2k/django-environ/compare/v0.8.0...v0.8.1 .. _v0.8.0: https://github.com/joke2k/django-environ/compare/v0.7.0...v0.8.0 .. _v0.7.0: https://github.com/joke2k/django-environ/compare/v0.6.0...v0.7.0 .. _v0.6.0: https://github.com/joke2k/django-environ/compare/v0.5.0...v0.6.0 .. _v0.5.0: https://github.com/joke2k/django-environ/compare/v0.4.5...v0.5.0 .. _v0.4.5: https://github.com/joke2k/django-environ/compare/v0.4.4...v0.4.5 .. _v0.4.4: https://github.com/joke2k/django-environ/compare/v0.4.3...v0.4.4 .. _v0.4.3: https://github.com/joke2k/django-environ/compare/v0.4.2...v0.4.3 .. _v0.4.2: https://github.com/joke2k/django-environ/compare/v0.4.1...v0.4.2 .. _v0.4.1: https://github.com/joke2k/django-environ/compare/v0.4...v0.4.1 .. _v0.4: https://github.com/joke2k/django-environ/compare/v0.3.1...v0.4 .. _v0.3.1: https://github.com/joke2k/django-environ/compare/v0.3...v0.3.1 .. _v0.3: https://github.com/joke2k/django-environ/compare/v0.2.1...v0.3././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/CONTRIBUTING.rst0000664000175000017500000000622514473635227016374 0ustar00joke2kjoke2kContributing ============ If you would like to contribute to ``django-environ``, please take a look at the `current issues `_. If there is a bug or feature that you want but it isn't listed, make an issue and work on it. Bug reports ----------- *Before raising an issue, please ensure that you are using the latest version of django-environ.* Please provide the following information with your issue to enable us to respond as quickly as possible. * The relevant versions of the packages you are using. * The steps to recreate your issue. * The full stacktrace if there is an exception. * An executable code example where possible Guidelines for bug reports: * **Use the GitHub issue search** — check if the issue has already been reported. * **Check if the issue has been fixed** — try to reproduce it using the latest ``main`` or ``develop`` branch in the repository. * Isolate the problem — create a reduced test case and a live example. A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What OS experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs. Feature requests ---------------- Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. Pull requests ------------- Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. Follow this process if you'd like your work considered for inclusion in the project: 1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. 2. Fork `the repository `_ on GitHub to start making your changes to the ``develop`` branch (or branch off of it). 3. Write a test which shows that the bug was fixed or that the feature works as expected. 4. Send a pull request and bug the maintainer until it gets merged and published. If you are intending to implement a fairly large feature we'd appreciate if you open an issue with GitHub detailing your use case and intended solution to discuss how it might impact other work that is in flight. We also appreciate it if you take the time to update and write tests for any changes you submit. **By submitting a patch, you agree to allow the project owner to license your work under the same license as that used by the project.** Resources --------- * `How to Contribute to Open Source `_ * `Using Pull Requests `_ * `Writing good commit messages `_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/LICENSE.txt0000664000175000017500000000220214114672453015537 0ustar00joke2kjoke2kCopyright (c) 2021, Serghei Iakovlev Copyright (c) 2013-2021, Daniele Faraglia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/MANIFEST.in0000664000175000017500000000216214114672453015457 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. # This file consists of commands, one per line, instructing setuptools to add # or remove some set of files from the sdist. # Include all files matching any of the listed patterns. include *.rst LICENSE.txt *.yml graft .github # The contents of the directory tree tests will first be added to the sdist. # Many OS distributions prefers provide an ability run the tests # during the package installation. recursive-include tests *.py recursive-include tests *.txt include tox.ini # All files in the sdist with a .pyc, .pyo, or .pyd extension will be removed # from the sdist. global-exclude *.py[cod] # Documentation include docs/docutils.conf docs/Makefile recursive-include docs *.png recursive-include docs *.svg recursive-include docs *.py recursive-include docs *.rst recursive-include docs *.gitkeep prune docs/_build ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6748567 django-environ-0.11.2/PKG-INFO0000664000175000017500000002553514474450344015031 0ustar00joke2kjoke2kMetadata-Version: 2.1 Name: django-environ Version: 0.11.2 Summary: A package that allows you to utilize 12factor inspired environment variables to configure your Django application. Home-page: https://django-environ.readthedocs.org Author: Daniele Faraglia Author-email: daniele.faraglia@gmail.com Maintainer: Serghei Iakovlev Maintainer-email: egrep@protonmail.ch License: MIT Project-URL: Documentation, https://django-environ.readthedocs.org Project-URL: Funding, https://opencollective.com/django-environ Project-URL: Say Thanks!, https://saythanks.io/to/joke2k Project-URL: Changelog, https://django-environ.readthedocs.org/en/latest/changelog.html Project-URL: Bug Tracker, https://github.com/joke2k/django-environ/issues Project-URL: Source Code, https://github.com/joke2k/django-environ Keywords: environment,django,variables,12factor Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Django :: 1.11 Classifier: Framework :: Django :: 2.0 Classifier: Framework :: Django :: 2.1 Classifier: Framework :: Django :: 2.2 Classifier: Framework :: Django :: 3.0 Classifier: Framework :: Django :: 3.1 Classifier: Framework :: Django :: 3.2 Classifier: Framework :: Django :: 4.0 Classifier: Framework :: Django :: 4.1 Classifier: Framework :: Django :: 4.2 Classifier: Operating System :: OS Independent Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.6,<4 Description-Content-Type: text/x-rst Provides-Extra: testing Provides-Extra: docs Provides-Extra: develop License-File: LICENSE.txt License-File: AUTHORS.rst ============== django-environ ============== ``django-environ`` is the Python package that allows you to use `Twelve-factor methodology `_ to configure your Django application with environment variables. .. -teaser-end- For that, it gives you an easy way to configure Django application using environment variables obtained from an environment file and provided by the OS: .. -code-begin- .. code-block:: python import environ import os env = environ.Env( # set casting, default value DEBUG=(bool, False) ) # Set the project base directory BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Take environment variables from .env file environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # False if not in os.environ because of casting above DEBUG = env('DEBUG') # Raises Django's ImproperlyConfigured # exception if SECRET_KEY not in os.environ SECRET_KEY = env('SECRET_KEY') # Parse database connection url strings # like psql://user:pass@127.0.0.1:8458/db DATABASES = { # read os.environ['DATABASE_URL'] and raises # ImproperlyConfigured exception if not found # # The db() method is an alias for db_url(). 'default': env.db(), # read os.environ['SQLITE_URL'] 'extra': env.db_url( 'SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db' ) } CACHES = { # Read os.environ['CACHE_URL'] and raises # ImproperlyConfigured exception if not found. # # The cache() method is an alias for cache_url(). 'default': env.cache(), # read os.environ['REDIS_URL'] 'redis': env.cache_url('REDIS_URL') } .. -overview- The idea of this package is to unify a lot of packages that make the same stuff: Take a string from ``os.environ``, parse and cast it to some of useful python typed variables. To do that and to use the `12factor `_ approach, some connection strings are expressed as url, so this package can parse it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ`` are loaded from a ``.env`` file and filled in ``os.environ`` with ``setdefault`` method, to avoid to overwrite the real environ. A similar approach is used in `Two Scoops of Django `_ book and explained in `12factor-django `_ article. Using ``django-environ`` you can stop to make a lot of unversioned ``settings_*.py`` to configure your app. See `cookiecutter-django `_ for a concrete example on using with a django project. **Feature Support** - Fast and easy multi environment for deploy - Fill ``os.environ`` with .env file variables - Variables casting - Url variables exploded to django specific package settings - Optional support for Docker-style file based config variables (use ``environ.FileAwareEnv`` instead of ``environ.Env``) .. -project-information- Project Information =================== ``django-environ`` is released under the `MIT / X11 License `__, its documentation lives at `Read the Docs `_, the code on `GitHub `_, and the latest release on `PyPI `_. It’s rigorously tested on Python 3.6+, and officially supports Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0, 4.1 and 4.2. If you'd like to contribute to ``django-environ`` you're most welcome! .. -support- Support ======= Should you have any question, any remark, or if you find a bug, or if there is something you can't do with the ``django-environ``, please `open an issue `_. Contributing ============ If you would like to contribute to ``django-environ``, please take a look at the `current issues `_. If there is a bug or feature that you want but it isn't listed, make an issue and work on it. Bug reports ----------- *Before raising an issue, please ensure that you are using the latest version of django-environ.* Please provide the following information with your issue to enable us to respond as quickly as possible. * The relevant versions of the packages you are using. * The steps to recreate your issue. * The full stacktrace if there is an exception. * An executable code example where possible Guidelines for bug reports: * **Use the GitHub issue search** — check if the issue has already been reported. * **Check if the issue has been fixed** — try to reproduce it using the latest ``main`` or ``develop`` branch in the repository. * Isolate the problem — create a reduced test case and a live example. A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What OS experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs. Feature requests ---------------- Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. Pull requests ------------- Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. Follow this process if you'd like your work considered for inclusion in the project: 1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. 2. Fork `the repository `_ on GitHub to start making your changes to the ``develop`` branch (or branch off of it). 3. Write a test which shows that the bug was fixed or that the feature works as expected. 4. Send a pull request and bug the maintainer until it gets merged and published. If you are intending to implement a fairly large feature we'd appreciate if you open an issue with GitHub detailing your use case and intended solution to discuss how it might impact other work that is in flight. We also appreciate it if you take the time to update and write tests for any changes you submit. **By submitting a patch, you agree to allow the project owner to license your work under the same license as that used by the project.** Resources --------- * `How to Contribute to Open Source `_ * `Using Pull Requests `_ * `Writing good commit messages `_ Release Information =================== v0.11.2 - 1-September-2023 ------------------------------- Fixed +++++ - Revert "Add variable expansion." feature due to `#490 `_. `Full changelog `_. Security Policy =============== Reporting a Vulnerability ------------------------- If you discover a security vulnerability within ``django-environ``, please send an e-mail to Serghei Iakovlev via egrep@protonmail.ch. All security vulnerabilities will be promptly addressed. Credits ======= ``django-environ`` was initially created by `Daniele Faraglia `_ and currently maintained by `Serghei Iakovlev `_. A full list of contributors can be found in `GitHub `__. Acknowledgments =============== The existence of ``django-environ`` would have been impossible without these projects: - `rconradharris/envparse `_ - `jazzband/dj-database-url `_ - `migonzalvar/dj-email-url `_ - `ghickman/django-cache-url `_ - `dstufft/dj-search-url `_ - `julianwachholz/dj-config-url `_ - `nickstenning/honcho `_ - `rconradharris/envparse `_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/README.rst0000664000175000017500000001231714473635227015421 0ustar00joke2kjoke2k.. raw:: html

django-environ

Latest version released on PyPi Coverage Status CI Status Sponsors on Open Collective Backers on Open Collective Say Thanks! Package license

.. -teaser-begin- ``django-environ`` is the Python package that allows you to use `Twelve-factor methodology `_ to configure your Django application with environment variables. .. -teaser-end- For that, it gives you an easy way to configure Django application using environment variables obtained from an environment file and provided by the OS: .. -code-begin- .. code-block:: python import environ import os env = environ.Env( # set casting, default value DEBUG=(bool, False) ) # Set the project base directory BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Take environment variables from .env file environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # False if not in os.environ because of casting above DEBUG = env('DEBUG') # Raises Django's ImproperlyConfigured # exception if SECRET_KEY not in os.environ SECRET_KEY = env('SECRET_KEY') # Parse database connection url strings # like psql://user:pass@127.0.0.1:8458/db DATABASES = { # read os.environ['DATABASE_URL'] and raises # ImproperlyConfigured exception if not found # # The db() method is an alias for db_url(). 'default': env.db(), # read os.environ['SQLITE_URL'] 'extra': env.db_url( 'SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db' ) } CACHES = { # Read os.environ['CACHE_URL'] and raises # ImproperlyConfigured exception if not found. # # The cache() method is an alias for cache_url(). 'default': env.cache(), # read os.environ['REDIS_URL'] 'redis': env.cache_url('REDIS_URL') } .. -overview- The idea of this package is to unify a lot of packages that make the same stuff: Take a string from ``os.environ``, parse and cast it to some of useful python typed variables. To do that and to use the `12factor `_ approach, some connection strings are expressed as url, so this package can parse it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ`` are loaded from a ``.env`` file and filled in ``os.environ`` with ``setdefault`` method, to avoid to overwrite the real environ. A similar approach is used in `Two Scoops of Django `_ book and explained in `12factor-django `_ article. Using ``django-environ`` you can stop to make a lot of unversioned ``settings_*.py`` to configure your app. See `cookiecutter-django `_ for a concrete example on using with a django project. **Feature Support** - Fast and easy multi environment for deploy - Fill ``os.environ`` with .env file variables - Variables casting - Url variables exploded to django specific package settings - Optional support for Docker-style file based config variables (use ``environ.FileAwareEnv`` instead of ``environ.Env``) .. -project-information- Project Information =================== ``django-environ`` is released under the `MIT / X11 License `__, its documentation lives at `Read the Docs `_, the code on `GitHub `_, and the latest release on `PyPI `_. It’s rigorously tested on Python 3.6+, and officially supports Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0, 4.1 and 4.2. If you'd like to contribute to ``django-environ`` you're most welcome! .. -support- Support ======= Should you have any question, any remark, or if you find a bug, or if there is something you can't do with the ``django-environ``, please `open an issue `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/SECURITY.rst0000664000175000017500000000042714114672453015724 0ustar00joke2kjoke2kSecurity Policy =============== Reporting a Vulnerability ------------------------- If you discover a security vulnerability within ``django-environ``, please send an e-mail to Serghei Iakovlev via egrep@protonmail.ch. All security vulnerabilities will be promptly addressed. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6628566 django-environ-0.11.2/django_environ.egg-info/0000775000175000017500000000000014474450344020416 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602019.0 django-environ-0.11.2/django_environ.egg-info/PKG-INFO0000664000175000017500000002553514474450343021524 0ustar00joke2kjoke2kMetadata-Version: 2.1 Name: django-environ Version: 0.11.2 Summary: A package that allows you to utilize 12factor inspired environment variables to configure your Django application. Home-page: https://django-environ.readthedocs.org Author: Daniele Faraglia Author-email: daniele.faraglia@gmail.com Maintainer: Serghei Iakovlev Maintainer-email: egrep@protonmail.ch License: MIT Project-URL: Documentation, https://django-environ.readthedocs.org Project-URL: Funding, https://opencollective.com/django-environ Project-URL: Say Thanks!, https://saythanks.io/to/joke2k Project-URL: Changelog, https://django-environ.readthedocs.org/en/latest/changelog.html Project-URL: Bug Tracker, https://github.com/joke2k/django-environ/issues Project-URL: Source Code, https://github.com/joke2k/django-environ Keywords: environment,django,variables,12factor Platform: any Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: Django Classifier: Framework :: Django :: 1.11 Classifier: Framework :: Django :: 2.0 Classifier: Framework :: Django :: 2.1 Classifier: Framework :: Django :: 2.2 Classifier: Framework :: Django :: 3.0 Classifier: Framework :: Django :: 3.1 Classifier: Framework :: Django :: 3.2 Classifier: Framework :: Django :: 4.0 Classifier: Framework :: Django :: 4.1 Classifier: Framework :: Django :: 4.2 Classifier: Operating System :: OS Independent Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Utilities Classifier: License :: OSI Approved :: MIT License Requires-Python: >=3.6,<4 Description-Content-Type: text/x-rst Provides-Extra: testing Provides-Extra: docs Provides-Extra: develop License-File: LICENSE.txt License-File: AUTHORS.rst ============== django-environ ============== ``django-environ`` is the Python package that allows you to use `Twelve-factor methodology `_ to configure your Django application with environment variables. .. -teaser-end- For that, it gives you an easy way to configure Django application using environment variables obtained from an environment file and provided by the OS: .. -code-begin- .. code-block:: python import environ import os env = environ.Env( # set casting, default value DEBUG=(bool, False) ) # Set the project base directory BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Take environment variables from .env file environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # False if not in os.environ because of casting above DEBUG = env('DEBUG') # Raises Django's ImproperlyConfigured # exception if SECRET_KEY not in os.environ SECRET_KEY = env('SECRET_KEY') # Parse database connection url strings # like psql://user:pass@127.0.0.1:8458/db DATABASES = { # read os.environ['DATABASE_URL'] and raises # ImproperlyConfigured exception if not found # # The db() method is an alias for db_url(). 'default': env.db(), # read os.environ['SQLITE_URL'] 'extra': env.db_url( 'SQLITE_URL', default='sqlite:////tmp/my-tmp-sqlite.db' ) } CACHES = { # Read os.environ['CACHE_URL'] and raises # ImproperlyConfigured exception if not found. # # The cache() method is an alias for cache_url(). 'default': env.cache(), # read os.environ['REDIS_URL'] 'redis': env.cache_url('REDIS_URL') } .. -overview- The idea of this package is to unify a lot of packages that make the same stuff: Take a string from ``os.environ``, parse and cast it to some of useful python typed variables. To do that and to use the `12factor `_ approach, some connection strings are expressed as url, so this package can parse it and return a ``urllib.parse.ParseResult``. These strings from ``os.environ`` are loaded from a ``.env`` file and filled in ``os.environ`` with ``setdefault`` method, to avoid to overwrite the real environ. A similar approach is used in `Two Scoops of Django `_ book and explained in `12factor-django `_ article. Using ``django-environ`` you can stop to make a lot of unversioned ``settings_*.py`` to configure your app. See `cookiecutter-django `_ for a concrete example on using with a django project. **Feature Support** - Fast and easy multi environment for deploy - Fill ``os.environ`` with .env file variables - Variables casting - Url variables exploded to django specific package settings - Optional support for Docker-style file based config variables (use ``environ.FileAwareEnv`` instead of ``environ.Env``) .. -project-information- Project Information =================== ``django-environ`` is released under the `MIT / X11 License `__, its documentation lives at `Read the Docs `_, the code on `GitHub `_, and the latest release on `PyPI `_. It’s rigorously tested on Python 3.6+, and officially supports Django 1.11, 2.2, 3.0, 3.1, 3.2, 4.0, 4.1 and 4.2. If you'd like to contribute to ``django-environ`` you're most welcome! .. -support- Support ======= Should you have any question, any remark, or if you find a bug, or if there is something you can't do with the ``django-environ``, please `open an issue `_. Contributing ============ If you would like to contribute to ``django-environ``, please take a look at the `current issues `_. If there is a bug or feature that you want but it isn't listed, make an issue and work on it. Bug reports ----------- *Before raising an issue, please ensure that you are using the latest version of django-environ.* Please provide the following information with your issue to enable us to respond as quickly as possible. * The relevant versions of the packages you are using. * The steps to recreate your issue. * The full stacktrace if there is an exception. * An executable code example where possible Guidelines for bug reports: * **Use the GitHub issue search** — check if the issue has already been reported. * **Check if the issue has been fixed** — try to reproduce it using the latest ``main`` or ``develop`` branch in the repository. * Isolate the problem — create a reduced test case and a live example. A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What OS experience the problem? What would you expect to be the outcome? All these details will help people to fix any potential bugs. Feature requests ---------------- Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible. Pull requests ------------- Good pull requests - patches, improvements, new features - are a fantastic help. They should remain focused in scope and avoid containing unrelated commits. Follow this process if you'd like your work considered for inclusion in the project: 1. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. 2. Fork `the repository `_ on GitHub to start making your changes to the ``develop`` branch (or branch off of it). 3. Write a test which shows that the bug was fixed or that the feature works as expected. 4. Send a pull request and bug the maintainer until it gets merged and published. If you are intending to implement a fairly large feature we'd appreciate if you open an issue with GitHub detailing your use case and intended solution to discuss how it might impact other work that is in flight. We also appreciate it if you take the time to update and write tests for any changes you submit. **By submitting a patch, you agree to allow the project owner to license your work under the same license as that used by the project.** Resources --------- * `How to Contribute to Open Source `_ * `Using Pull Requests `_ * `Writing good commit messages `_ Release Information =================== v0.11.2 - 1-September-2023 ------------------------------- Fixed +++++ - Revert "Add variable expansion." feature due to `#490 `_. `Full changelog `_. Security Policy =============== Reporting a Vulnerability ------------------------- If you discover a security vulnerability within ``django-environ``, please send an e-mail to Serghei Iakovlev via egrep@protonmail.ch. All security vulnerabilities will be promptly addressed. Credits ======= ``django-environ`` was initially created by `Daniele Faraglia `_ and currently maintained by `Serghei Iakovlev `_. A full list of contributors can be found in `GitHub `__. Acknowledgments =============== The existence of ``django-environ`` would have been impossible without these projects: - `rconradharris/envparse `_ - `jazzband/dj-database-url `_ - `migonzalvar/dj-email-url `_ - `ghickman/django-cache-url `_ - `dstufft/dj-search-url `_ - `julianwachholz/dj-config-url `_ - `nickstenning/honcho `_ - `rconradharris/envparse `_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602019.0 django-environ-0.11.2/django_environ.egg-info/SOURCES.txt0000664000175000017500000000230014474450343022274 0ustar00joke2kjoke2k.readthedocs.yml AUTHORS.rst BACKERS.rst CHANGELOG.rst CONTRIBUTING.rst LICENSE.txt MANIFEST.in README.rst SECURITY.rst setup.cfg setup.py tox.ini .github/FUNDING.yml .github/dependabot.yml .github/workflows/build.yml .github/workflows/change-pr-target.yml .github/workflows/ci.yml .github/workflows/codeql.yml .github/workflows/cs.yml .github/workflows/docs.yml django_environ.egg-info/PKG-INFO django_environ.egg-info/SOURCES.txt django_environ.egg-info/dependency_links.txt django_environ.egg-info/not-zip-safe django_environ.egg-info/requires.txt django_environ.egg-info/top_level.txt docs/Makefile docs/api.rst docs/backers.rst docs/changelog.rst docs/conf.py docs/contributing.rst docs/deprecations.rst docs/docutils.conf docs/faq.rst docs/index.rst docs/install.rst docs/license.rst docs/quickstart.rst docs/tips.rst docs/types.rst docs/_static/.gitkeep environ/__init__.py environ/compat.py environ/environ.py environ/fileaware_mapping.py tests/__init__.py tests/asserts.py tests/conftest.py tests/fixtures.py tests/test_cache.py tests/test_db.py tests/test_email.py tests/test_env.py tests/test_env.txt tests/test_fileaware.py tests/test_path.py tests/test_schema.py tests/test_search.py tests/test_utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602019.0 django-environ-0.11.2/django_environ.egg-info/dependency_links.txt0000664000175000017500000000000114474450343024463 0ustar00joke2kjoke2k ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630353761.0 django-environ-0.11.2/django_environ.egg-info/not-zip-safe0000664000175000017500000000000114113234541022631 0ustar00joke2kjoke2k ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602019.0 django-environ-0.11.2/django_environ.egg-info/requires.txt0000664000175000017500000000035214474450343023015 0ustar00joke2kjoke2k [develop] coverage[toml]>=5.0a4 pytest>=4.6.11 furo==2021.8.*,>=2021.8.17b43 sphinx>=3.5.0 sphinx-notfound-page [docs] furo==2021.8.*,>=2021.8.17b43 sphinx>=3.5.0 sphinx-notfound-page [testing] coverage[toml]>=5.0a4 pytest>=4.6.11 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602019.0 django-environ-0.11.2/django_environ.egg-info/top_level.txt0000664000175000017500000000001014474450343023136 0ustar00joke2kjoke2kenviron ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6668565 django-environ-0.11.2/docs/0000775000175000017500000000000014474450344014652 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/docs/Makefile0000664000175000017500000001622614252326653016320 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. # Makefile for Sphinx documentation # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-environ.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-environ.qhc" .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-environ" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-environ" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6668565 django-environ-0.11.2/docs/_static/0000775000175000017500000000000014474450344016300 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/docs/_static/.gitkeep0000664000175000017500000000000014114672453017715 0ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/docs/api.rst0000664000175000017500000000134114252326653016153 0ustar00joke2kjoke2k============= API Reference ============= .. currentmodule:: environ The ``__init__`` module ======================= .. automodule:: environ :members: :special-members: :no-undoc-members: The ``compat`` module ====================== .. automodule:: environ.compat :members: :no-undoc-members: The ``environ`` module ====================== .. autoclass:: environ.Env :members: :no-undoc-members: .. autoclass:: environ.FileAwareEnv :members: :no-undoc-members: .. autoclass:: environ.Path :members: :no-undoc-members: The ``fileaware_mapping`` module ================================ .. autoclass:: environ.fileaware_mapping.FileAwareMapping :members: :no-undoc-members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/docs/backers.rst0000664000175000017500000000003414114672453017011 0ustar00joke2kjoke2k.. include:: ../BACKERS.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/docs/changelog.rst0000664000175000017500000000003614114672453017330 0ustar00joke2kjoke2k.. include:: ../CHANGELOG.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/docs/conf.py0000664000175000017500000001361714473635227016165 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2023, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. # # -- Utils --------------------------------------------------------- # import codecs import os import re import sys from datetime import date PROJECT_DIR = os.path.abspath('..') sys.path.insert(0, PROJECT_DIR) def read_file(filepath): """Read content from a UTF-8 encoded text file.""" with codecs.open(filepath, 'rb', 'utf-8') as file_handle: return file_handle.read() def find_version(meta_file): """Extract ``__version__`` from meta_file.""" contents = read_file(os.path.join(PROJECT_DIR, meta_file)) meta_match = re.search( r"^__version__\s+=\s+['\"]([^'\"]*)['\"]", contents, re.M ) if meta_match: return meta_match.group(1) raise RuntimeError( "Unable to find __version__ string in package meta file") # # -- Project information ----------------------------------------------------- # # General information about the project. project = "django-environ" copyright = f'2013-{date.today().year}, Daniele Faraglia and other contributors' author = u"Daniele Faraglia \\and Serghei Iakovlev" # # -- General configuration --------------------------------------------------- # extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.viewcode", "notfound.extension", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # Allow non-local URIs, so we can have images in CHANGELOG etc. suppress_warnings = [ "image.nonlocal_uri", ] # The master toctree document. master_doc = "index" # The version info # The short X.Y version. release = find_version(os.path.join("environ", "__init__.py")) version = release.rsplit(u".", 1)[0] # The full version, including alpha/beta/rc tags. # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = "any" # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # # -- Options for autodoc --------------------------------------------------- # # This value selects if automatically documented members are sorted alphabetical # (value 'alphabetical'), by member type (value 'groupwise') or by source order # (value 'bysource'). The default is alphabetical. # # Note that for source order, the module must be a Python module with the # source code available. autodoc_member_order = 'bysource' # # -- Options for linkcheck --------------------------------------------------- # linkcheck_ignore = [ # We run into GitHub's rate limits. r"https://github.com/.*/(issues|pull)/\d+", # Do not check links to compare tags. r"https://github.com/joke2k/django-environ/compare/.*", ] # # -- Options for nitpick ----------------------------------------------------- # # In nitpick mode (-n), still ignore any of the following "broken" references # to non-types. nitpick_ignore = [ ('py:func', 'str.rfind'), ('py:func', 'str.find'), ] # # -- Options for extlinks ---------------------------------------------------- # extlinks = { "pypi": ("https://pypi.org/project/%s/", ""), } # # -- Options for intersphinx ------------------------------------------------- # intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "sphinx": ("https://www.sphinx-doc.org/en/master", None), } # # -- Options for TODOs ------------------------------------------------------- # todo_include_todos = True # -- Options for HTML output ------------------------------------------------- # html_favicon = None html_theme = "furo" html_title = project html_theme_options = {} # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If false, no module index is generated. html_domain_indices = True # If false, no index is generated. html_use_index = True # If true, the index is split into individual pages for each letter. html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_openserver = '' # Output file base name for HTML help builder. htmlhelp_basename = "django-environ-doc" # # -- Options for manual page output ------------------------------------------ # # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ("index", project, "django-environ Documentation", [author], 1) ] # # -- Options for Texinfo output ---------------------------------------------- # # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", project, "django-environ Documentation", author, project, "Configure Django made easy.", "Miscellaneous", ) ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/docs/contributing.rst0000664000175000017500000000004114114672453020104 0ustar00joke2kjoke2k.. include:: ../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/docs/deprecations.rst0000664000175000017500000000062614400225515020055 0ustar00joke2kjoke2k============ Deprecations ============ Features deprecated in 0.10.0 ============================= Python ------ * Support of Python < 3.6 is deprecated and will be removed in next major version. Features deprecated in 0.9.0 ============================ Methods ------- * The ``environ.Env.unicode`` method is deprecated as it was used for Python 2.x only. Use :meth:`.environ.Env.str` instead. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/docs/docutils.conf0000664000175000017500000000056214252326653017351 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. [parsers] [restructuredtext parser] smart_quotes=yes ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/docs/faq.rst0000664000175000017500000000336414400225515016146 0ustar00joke2kjoke2k=== FAQ === #. **Can django-environ determine the location of .env file automatically?** django-environ will try to get and read ``.env`` file from the project root if you haven't specified the path for it when call :meth:`.environ.Env.read_env`. However, this is not the recommended way. When it is possible always specify the path tho ``.env`` file. Alternatively, you can use a trick with a environment variable pointing to the actual location of ``.env`` file. For details see ":ref:`multiple-env-files-label`". #. **What (where) is the root part of the project, is it part of the project where are settings?** Where your ``manage.py`` file is (that is your project root directory). #. **What kind of file should .env be?** ``.env`` is a plain text file. #. **Should name of the file be simply .env (or something.env)?** Just ``.env``. However, this is not a strict rule, but just a common practice. Formally, you can use any filename. #. **Is .env file going to be imported in settings file?** No need to import, django-environ automatically picks variables from there. #. **Should I commit my .env file?** Credentials should only be accessible on the machines that need access to them. Never commit sensitive information to a repository that is not needed by every development machine and server. #. **Why is it not overriding existing environment variables?** By default, django-environ won't overwrite existing environment variables as it assumes the deployment environment has more knowledge about configuration than the application does. To overwrite existing environment variables you can pass ``overwrite=True`` to :meth:`.environ.Env.read_env`. For more see ":ref:`overwriting-existing-env`" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1655287211.0 django-environ-0.11.2/docs/index.rst0000664000175000017500000000302014252326653016505 0ustar00joke2kjoke2k======================================= Welcome to django-environ documentation ======================================= Release v\ |release| (`What's new? `). .. include:: ../README.rst :start-after: -teaser-begin- :end-before: -teaser-end- Overview ======== .. include:: ../README.rst :start-after: -overview- :end-before: -project-information- ---- Full Table of Contents ====================== The User Guide -------------- This part of the documentation, which is mostly prose, begins with some background information about django-environ, then focuses on step-by-step instructions for getting the most out of django-environ. .. toctree:: :maxdepth: 2 install quickstart The Community Guide ------------------- This part of the documentation, which is mostly prose, details the django-environ ecosystem and community. .. toctree:: :maxdepth: 2 faq types tips .. toctree:: :maxdepth: 1 deprecations changelog The API Documentation / Guide ----------------------------- If you are looking for information on a specific function, class, or method, this part of the documentation is for you. .. toctree:: :maxdepth: 2 api The Contributor Guide --------------------- If you want to contribute to the project, this part of the documentation is for you. .. toctree:: :maxdepth: 3 contributing backers license .. include:: ../README.rst :start-after: -support- .. include:: ../README.rst :start-after: -project-information- :end-before: -support- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/docs/install.rst0000664000175000017500000000335014400225515017040 0ustar00joke2kjoke2k============ Installation ============ Requirements ============ * `Django `_ >= 1.11 * `Python `_ >= 3.5 Installing django-environ ========================= django-environ is a Python-only package `hosted_on_pypi`_. The recommended installation method is `pip`_-installing into a :mod:`virtualenv `: .. code-block:: console $ python -m pip install django-environ .. note:: After installing django-environ, no need to add it to ``INSTALLED_APPS``. .. _hosted_on_pypi: https://pypi.org/project/django-environ/ .. _pip: https://pip.pypa.io/en/stable/ Unstable version ================ The master of all the material is the Git repository at https://github.com/joke2k/django-environ. So, you can also install the latest unreleased development version directly from the ``develop`` branch on GitHub. It is a work-in-progress of a future stable release so the experience might be not as smooth: .. code-block:: console $ pip install -e git://github.com/joke2k/django-environ.git#egg=django-environ # OR $ pip install --upgrade https://github.com/joke2k/django-environ.git/archive/develop.tar.gz This command will download the latest version of django-environ and install it to your system. .. note:: The ``develop`` branch will always contain the latest unstable version, so the experience might be not as smooth. If you wish to check older versions or formal, tagged release, please switch to the relevant `tag `_. More information about ``pip`` and PyPI can be found here: * `Install pip `_ * `Python Packaging User Guide `_ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/docs/license.rst0000664000175000017500000000054014400225515017012 0ustar00joke2kjoke2k=================== License and Credits =================== django-environ is open source software licensed under the `MIT / X11 License `_. The full license text can be also found in the `source code repository `_. .. include:: ../AUTHORS.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602000.0 django-environ-0.11.2/docs/quickstart.rst0000664000175000017500000000371414474450320017575 0ustar00joke2kjoke2k=========== Quick Start =========== Usage ===== Create a ``.env`` file in project root directory. The file format can be understood from the example below: .. code-block:: shell DEBUG=on SECRET_KEY=your-secret-key DATABASE_URL=psql://user:un-githubbedpassword@127.0.0.1:8458/database SQLITE_URL=sqlite:///my-local-sqlite.db CACHE_URL=memcache://127.0.0.1:11211,127.0.0.1:11212,127.0.0.1:11213 REDIS_URL=rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient&password=ungithubbed-secret And use it with ``settings.py`` as follows: .. include:: ../README.rst :start-after: -code-begin- :end-before: -overview- The ``.env`` file should be specific to the environment and not checked into version control, it is best practice documenting the ``.env`` file with an example. For example, you can also add ``.env.dist`` with a template of your variables to the project repo. This file should describe the mandatory variables for the Django application, and it can be committed to version control. This provides a useful reference and speeds up the on-boarding process for new team members, since the time to dig through the codebase to find out what has to be set up is reduced. A good ``.env.dist`` could look like this: .. code-block:: shell # SECURITY WARNING: don't run with the debug turned on in production! DEBUG=True # Should robots.txt allow everything to be crawled? ALLOW_ROBOTS=False # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY=secret # A list of all the people who get code error notifications. ADMINS="John Doe , Mary " # A list of all the people who should get broken link notifications. MANAGERS="Blake , Alice Judge " # By default, Django will send system email from root@localhost. # However, some mail providers reject all email from this address. SERVER_EMAIL=webmaster@example.com ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693428945.0 django-environ-0.11.2/docs/tips.rst0000664000175000017500000002022614473726321016365 0ustar00joke2kjoke2k==== Tips ==== Docker-style file based variables ================================= Docker (swarm) and Kubernetes are two widely used platforms that store their secrets in tmpfs inside containers as individual files, providing a secure way to be able to share configuration data between containers. Use :class:`.environ.FileAwareEnv` rather than :class:`.environ.Env` to first look for environment variables with ``_FILE`` appended. If found, their contents will be read from the file system and used instead. For example, given an app with the following in its settings module: .. code-block:: python import environ env = environ.FileAwareEnv() SECRET_KEY = env("SECRET_KEY") the example ``docker-compose.yml`` for would contain: .. code-block:: yaml secrets: secret_key: external: true services: app: secrets: - secret_key environment: - SECRET_KEY_FILE=/run/secrets/secret_key Using unsafe characters in URLs =============================== In order to use unsafe characters you have to encode with :py:func:`urllib.parse.quote` before you set into ``.env`` file. Encode only the value (i.e. the password) not the whole url. .. code-block:: shell DATABASE_URL=mysql://user:%23password@127.0.0.1:3306/dbname See https://perishablepress.com/stop-using-unsafe-characters-in-urls/ for reference. Smart Casting ============= django-environ has a "Smart-casting" enabled by default, if you don't provide a ``cast`` type, it will be detected from ``default`` type. This could raise side effects (see `#192 `_). To disable it use ``env.smart_cast = False``. .. note:: The next major release will disable it by default. Multiple redis cache locations ============================== For redis cache, multiple master/slave or shard locations can be configured as follows: .. code-block:: shell CACHE_URL='rediscache://master:6379,slave1:6379,slave2:6379/1' Email settings ============== In order to set email configuration for Django you can use this code: .. code-block:: python # The email() method is an alias for email_url(). EMAIL_CONFIG = env.email( 'EMAIL_URL', default='smtp://user:password@localhost:25' ) vars().update(EMAIL_CONFIG) SQLite urls =========== SQLite connects to file based databases. The same URL format is used, omitting the hostname, and using the "file" portion as the filename of the database. This has the effect of four slashes being present for an absolute file path: ``sqlite:////full/path/to/your/database/file.sqlite``. Nested lists ============ Some settings such as Django's ``ADMINS`` make use of nested lists. You can use something like this to handle similar cases. .. code-block:: python # DJANGO_ADMINS=Blake:blake@cyb.org,Alice:alice@cyb.org ADMINS = [x.split(':') for x in env.list('DJANGO_ADMINS')] # or use more specific function from email.utils import getaddresses # DJANGO_ADMINS=Alice Judge ,blake@cyb.org ADMINS = getaddresses([env('DJANGO_ADMINS')]) # another option is to use parseaddr from email.utils # DJANGO_ADMINS="Blake , Alice Judge " from email.utils import parseaddr ADMINS = tuple(parseaddr(email) for email in env.list('DJANGO_ADMINS')) .. _complex_dict_format: Complex dict format =================== Sometimes we need to get a bit more complex dict type than usual. For example, consider Djangosaml2's ``SAML_ATTRIBUTE_MAPPING``: .. code-block:: python SAML_ATTRIBUTE_MAPPING = { 'uid': ('username', ), 'mail': ('email', ), 'cn': ('first_name', ), 'sn': ('last_name', ), } A dict of this format can be obtained as shown below: **.env file**: .. code-block:: shell # .env file contents SAML_ATTRIBUTE_MAPPING="uid=username;mail=email;cn=first_name;sn=last_name;" **settings.py file**: .. code-block:: python # settings.py file contents import environ env = environ.Env() # {'uid': ('username',), 'mail': ('email',), 'cn': ('first_name',), 'sn': ('last_name',)} SAML_ATTRIBUTE_MAPPING = env.dict( 'SAML_ATTRIBUTE_MAPPING', cast={'value': tuple}, default={} ) Multiline value =============== To get multiline value pass ``multiline=True`` to ```str()```. .. note:: You shouldn't escape newline/tab characters yourself if you want to preserve the formatting. The following example demonstrates the above: **.env file**: .. code-block:: shell # .env file contents UNQUOTED_CERT=---BEGIN---\r\n---END--- QUOTED_CERT="---BEGIN---\r\n---END---" ESCAPED_CERT=---BEGIN---\\n---END--- **settings.py file**: .. code-block:: python # settings.py file contents import environ env = environ.Env() print(env.str('UNQUOTED_CERT', multiline=True)) # ---BEGIN--- # ---END--- print(env.str('UNQUOTED_CERT', multiline=False)) # ---BEGIN---\r\n---END--- print(env.str('QUOTED_CERT', multiline=True)) # ---BEGIN--- # ---END--- print(env.str('QUOTED_CERT', multiline=False)) # ---BEGIN---\r\n---END--- print(env.str('ESCAPED_CERT', multiline=True)) # ---BEGIN---\ # ---END--- print(env.str('ESCAPED_CERT', multiline=False)) # ---BEGIN---\\n---END--- Proxy value =========== Values that being with a ``$`` may be interpolated. Pass ``interpolate=True`` to ``environ.Env()`` to enable this feature: .. code-block:: python import environ env = environ.Env(interpolate=True) # BAR=FOO # PROXY=$BAR >>> print(env.str('PROXY')) FOO Escape Proxy ============ If you're having trouble with values starting with dollar sign ($) without the intention of proxying the value to another, You should enable the ``escape_proxy`` and prepend a backslash to it. .. code-block:: python import environ env = environ.Env() env.escape_proxy = True # ESCAPED_VAR=\$baz env.str('ESCAPED_VAR') # $baz Reading env files ================= .. _multiple-env-files-label: Multiple env files ------------------ There is an ability point to the .env file location using an environment variable. This feature may be convenient in a production systems with a different .env file location. The following example demonstrates the above: .. code-block:: shell # /etc/environment file contents DEBUG=False .. code-block:: shell # .env file contents DEBUG=True .. code-block:: python env = environ.Env() env.read_env(env.str('ENV_PATH', '.env')) Now ``ENV_PATH=/etc/environment ./manage.py runserver`` uses ``/etc/environment`` while ``./manage.py runserver`` uses ``.env``. Using Path objects when reading env ----------------------------------- It is possible to use of :py:class:`pathlib.Path` objects when reading environment file from the filesystem: .. code-block:: python import os import pathlib import environ # Build paths inside the project like this: BASE_DIR('subdir'). BASE_DIR = environ.Path(__file__) - 3 env = environ.Env() # The four lines below do the same: env.read_env(BASE_DIR('.env')) env.read_env(os.path.join(BASE_DIR, '.env')) env.read_env(pathlib.Path(str(BASE_DIR)).joinpath('.env')) env.read_env(pathlib.Path(str(BASE_DIR)) / '.env') .. _overwriting-existing-env: Overwriting existing environment values from env files ------------------------------------------------------ If you want variables set within your env files to take higher precedence than an existing set environment variable, use the ``overwrite=True`` argument of :meth:`.environ.Env.read_env`. For example: .. code-block:: python env = environ.Env() env.read_env(BASE_DIR('.env'), overwrite=True) Handling prefixes ================= Sometimes it is desirable to be able to prefix all environment variables. For example, if you are using Django, you may want to prefix all environment variables with ``DJANGO_``. This can be done by setting the ``prefix`` to desired prefix. For example: **.env file**: .. code-block:: shell # .env file contents DJANGO_TEST="foo" **settings.py file**: .. code-block:: python # settings.py file contents import environ env = environ.Env() env.prefix = 'DJANGO_' env.str('TEST') # foo ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/docs/types.rst0000664000175000017500000001267714473635227016571 0ustar00joke2kjoke2k=============== Supported types =============== The following are all type-casting methods of :py:class:`.environ.Env`. * :py:meth:`~.environ.Env.str` * :py:meth:`~.environ.Env.bool` * :py:meth:`~.environ.Env.int` * :py:meth:`~.environ.Env.float` * :py:meth:`~.environ.Env.json` * :py:meth:`~.environ.Env.url` * :py:meth:`~.environ.Env.list`: (accepts values like ``(FOO=a,b,c)``) * :py:meth:`~.environ.Env.tuple`: (accepts values like ``(FOO=(a,b,c))``) * :py:meth:`~.environ.Env.path`: (accepts values like ``(environ.Path)``) * :py:meth:`~.environ.Env.dict`: (see below, ":ref:`environ-env-dict`" section) * :py:meth:`~.environ.Env.db_url` (see below, ":ref:`environ-env-db-url`" section) * :py:meth:`~.environ.Env.cache_url` (see below, ":ref:`environ-env-cache-url`" section) * :py:meth:`~.environ.Env.search_url` (see below, ":ref:`environ-env-search-url`" section) * :py:meth:`~.environ.Env.email_url` (see below, ":ref:`environ-env-email-url`" section) .. _environ-env-dict: ``environ.Env.dict`` ====================== :py:class:`.environ.Env` may parse complex variables like with the complex type-casting. For example: .. code-block:: python import environ env = environ.Env() # {'key': 'val', 'foo': 'bar'} env.parse_value('key=val,foo=bar', dict) # {'key': 'val', 'foo': 1.1, 'baz': True} env.parse_value( 'key=val;foo=1.1;baz=True', dict(value=str, cast=dict(foo=float,baz=bool)) ) For more detailed example see ":ref:`complex_dict_format`". .. _environ-env-db-url: ``environ.Env.db_url`` ====================== :py:meth:`~.environ.Env.db_url` supports the following URL schemas: .. glossary:: Amazon Redshift **Database Backend:** ``django_redshift_backend`` **URL schema:** ``redshift://`` LDAP **Database Backend:** ``ldapdb.backends.ldap`` **URL schema:** ``ldap://host:port/dn?attrs?scope?filter?exts`` MSSQL **Database Backend:** ``sql_server.pyodbc`` **URL schema:** ``mssql://user:password@host:port/dbname`` With MySQL you can use the following schemas: ``mysql``, ``mysql2``. MySQL (GIS) **Database Backend:** ``django.contrib.gis.db.backends.mysql`` **URL schema:** ``mysqlgis://user:password@host:port/dbname`` MySQL **Database Backend:** ``django.db.backends.mysql`` **URL schema:** ``mysql://user:password@host:port/dbname`` MySQL Connector Python from Oracle **Database Backend:** ``mysql.connector.django`` **URL schema:** ``mysql-connector://`` Oracle **Database Backend:** ``django.db.backends.oracle`` **URL schema:** ``oracle://user:password@host:port/dbname`` PostgreSQL **Database Backend:** ``django.db.backends.postgresql`` **URL schema:** ``postgres://user:password@host:port/dbname`` With PostgreSQL you can use the following schemas: ``postgres``, ``postgresql``, ``psql``, ``pgsql``, ``postgis``. You can also use UNIX domain sockets path instead of hostname. For example: ``postgres://path/dbname``. The ``django.db.backends.postgresql_psycopg2`` will be used if the Django version is less than ``2.0``. PostGIS **Database Backend:** ``django.contrib.gis.db.backends.postgis`` **URL schema:** ``postgis://user:password@host:port/dbname`` PyODBC **Database Backend:** ``sql_server.pyodbc`` **URL schema:** ``pyodbc://`` SQLite **Database Backend:** ``django.db.backends.sqlite3`` **URL schema:** ``sqlite:////absolute/path/to/db/file`` SQLite connects to file based databases. URL schemas ``sqlite://`` or ``sqlite://:memory:`` means the database is in the memory (not a file on disk). SpatiaLite **Database Backend:** ``django.contrib.gis.db.backends.spatialite`` **URL schema:** ``spatialite:///PATH`` SQLite connects to file based databases. URL schemas ``sqlite://`` or ``sqlite://:memory:`` means the database is in the memory (not a file on disk). .. _environ-env-cache-url: ``environ.Env.cache_url`` ========================= :py:meth:`~.environ.Env.cache_url` supports the following URL schemas: * Database: ``dbcache://`` * Dummy: ``dummycache://`` * File: ``filecache://`` * Memory: ``locmemcache://`` * Memcached: * ``memcache://`` (uses ``python-memcached`` backend, deprecated in Django 3.2) * ``pymemcache://`` (uses ``pymemcache`` backend if Django >=3.2 and package is installed, otherwise will use ``pylibmc`` backend to keep config backwards compatibility) * ``pylibmc://`` * Redis: ``rediscache://``, ``redis://``, or ``rediss://`` .. _environ-env-search-url: ``environ.Env.search_url`` ========================== :py:meth:`~.environ.Env.search_url` supports the following URL schemas: * Elasticsearch: ``elasticsearch://`` (http) or ``elasticsearchs://`` (https) * Elasticsearch2: ``elasticsearch2://`` (http) or ``elasticsearch2s://`` (https) * Elasticsearch5: ``elasticsearch5://`` (http) or ``elasticsearch5s://`` (https) * Elasticsearch7: ``elasticsearch7://`` (http) or ``elasticsearch7s://`` (https) * Solr: ``solr://`` * Whoosh: ``whoosh://`` * Xapian: ``xapian://`` * Simple cache: ``simple://`` .. _environ-env-email-url: ``environ.Env.email_url`` ========================== :py:meth:`~.environ.Env.email_url` supports the following URL schemas: * SMTP: ``smtp://`` * SMTP+SSL: ``smtp+ssl://`` * SMTP+TLS: ``smtp+tls://`` * Console mail: ``consolemail://`` * File mail: ``filemail://`` * LocMem mail: ``memorymail://`` * Dummy mail: ``dummymail://`` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6708565 django-environ-0.11.2/environ/0000775000175000017500000000000014474450344015402 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602000.0 django-environ-0.11.2/environ/__init__.py0000664000175000017500000000274714474450320017517 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2023, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. """The top-level module for django-environ package. This module tracks the version of the package as well as the base package info used by various functions within django-environ. Refer to the `documentation `_ for details on the use of this package. """ # noqa: E501 from .environ import * __copyright__ = 'Copyright (C) 2013-2023 Daniele Faraglia' """The copyright notice of the package.""" __version__ = '0.11.2' """The version of the package.""" __license__ = 'MIT' """The license of the package.""" __author__ = 'Daniele Faraglia' """The author of the package.""" __author_email__ = 'daniele.faraglia@gmail.com' """The email of the author of the package.""" __maintainer__ = 'Serghei Iakovlev' """The maintainer of the package.""" __maintainer_email__ = 'egrep@protonmail.ch' """The email of the maintainer of the package.""" __url__ = 'https://django-environ.readthedocs.org' """The URL of the package.""" # pylint: disable=line-too-long __description__ = 'A package that allows you to utilize 12factor inspired environment variables to configure your Django application.' # noqa: E501 """The description of the package.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/environ/compat.py0000664000175000017500000000423214473635227017244 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. """This module handles import compatibility issues.""" from importlib.util import find_spec if find_spec('simplejson'): import simplejson as json else: import json if find_spec('django'): from django import VERSION as DJANGO_VERSION from django.core.exceptions import ImproperlyConfigured else: DJANGO_VERSION = None class ImproperlyConfigured(Exception): """Django is somehow improperly configured""" def choose_rediscache_driver(): """Backward compatibility for RedisCache driver.""" # django-redis library takes precedence if find_spec('django_redis'): return 'django_redis.cache.RedisCache' # use built-in support if Django 4+ if DJANGO_VERSION is not None and DJANGO_VERSION >= (4, 0): return 'django.core.cache.backends.redis.RedisCache' # back compatibility with redis_cache package return 'redis_cache.RedisCache' def choose_postgres_driver(): """Backward compatibility for postgresql driver.""" old_django = DJANGO_VERSION is not None and DJANGO_VERSION < (2, 0) if old_django: return 'django.db.backends.postgresql_psycopg2' return 'django.db.backends.postgresql' def choose_pymemcache_driver(): """Backward compatibility for pymemcache.""" old_django = DJANGO_VERSION is not None and DJANGO_VERSION < (3, 2) if old_django or not find_spec('pymemcache'): # The original backend choice for the 'pymemcache' scheme is # unfortunately 'pylibmc'. return 'django.core.cache.backends.memcached.PyLibMCCache' return 'django.core.cache.backends.memcached.PyMemcacheCache' REDIS_DRIVER = choose_rediscache_driver() """The name of the RedisCache driver.""" DJANGO_POSTGRES = choose_postgres_driver() """The name of the PostgreSQL driver.""" PYMEMCACHE_DRIVER = choose_pymemcache_driver() """The name of the Pymemcache driver.""" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693602000.0 django-environ-0.11.2/environ/environ.py0000664000175000017500000010651614474450320017437 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. """ Django-environ allows you to utilize 12factor inspired environment variables to configure your Django application. """ import ast import itertools import logging import os import re import sys import warnings from urllib.parse import ( parse_qs, ParseResult, quote, unquote, unquote_plus, urlparse, urlunparse, ) from .compat import ( DJANGO_POSTGRES, ImproperlyConfigured, json, PYMEMCACHE_DRIVER, REDIS_DRIVER, ) from .fileaware_mapping import FileAwareMapping Openable = (str, os.PathLike) logger = logging.getLogger(__name__) def _cast(value): # Safely evaluate an expression node or a string containing a Python # literal or container display. # https://docs.python.org/3/library/ast.html#ast.literal_eval try: return ast.literal_eval(value) except (ValueError, SyntaxError): return value def _cast_int(v): """Return int if possible.""" return int(v) if hasattr(v, 'isdigit') and v.isdigit() else v def _cast_urlstr(v): return unquote(v) if isinstance(v, str) else v def _urlparse_quote(url): return urlparse(quote(url, safe=':/?&=@')) class NoValue: """Represent of no value object.""" def __repr__(self): return f'<{self.__class__.__name__}>' class Env: """Provide scheme-based lookups of environment variables so that each caller doesn't have to pass in ``cast`` and ``default`` parameters. Usage::: import environ import os env = environ.Env( # set casting, default value MAIL_ENABLED=(bool, False), SMTP_LOGIN=(str, 'DEFAULT') ) # Set the project base directory BASE_DIR = os.path.dirname( os.path.dirname(os.path.abspath(__file__)) ) # Take environment variables from .env file environ.Env.read_env(os.path.join(BASE_DIR, '.env')) # False if not in os.environ due to casting above MAIL_ENABLED = env('MAIL_ENABLED') # 'DEFAULT' if not in os.environ due to casting above SMTP_LOGIN = env('SMTP_LOGIN') """ ENVIRON = os.environ NOTSET = NoValue() BOOLEAN_TRUE_STRINGS = ('true', 'on', 'ok', 'y', 'yes', '1') URL_CLASS = ParseResult POSTGRES_FAMILY = ['postgres', 'postgresql', 'psql', 'pgsql', 'postgis'] DEFAULT_DATABASE_ENV = 'DATABASE_URL' DB_SCHEMES = { 'postgres': DJANGO_POSTGRES, 'postgresql': DJANGO_POSTGRES, 'psql': DJANGO_POSTGRES, 'pgsql': DJANGO_POSTGRES, 'postgis': 'django.contrib.gis.db.backends.postgis', 'mysql': 'django.db.backends.mysql', 'mysql2': 'django.db.backends.mysql', 'mysql-connector': 'mysql.connector.django', 'mysqlgis': 'django.contrib.gis.db.backends.mysql', 'mssql': 'mssql', 'oracle': 'django.db.backends.oracle', 'pyodbc': 'sql_server.pyodbc', 'redshift': 'django_redshift_backend', 'spatialite': 'django.contrib.gis.db.backends.spatialite', 'sqlite': 'django.db.backends.sqlite3', 'ldap': 'ldapdb.backends.ldap', } _DB_BASE_OPTIONS = [ 'CONN_MAX_AGE', 'ATOMIC_REQUESTS', 'AUTOCOMMIT', 'DISABLE_SERVER_SIDE_CURSORS', 'CONN_HEALTH_CHECKS', ] DEFAULT_CACHE_ENV = 'CACHE_URL' CACHE_SCHEMES = { 'dbcache': 'django.core.cache.backends.db.DatabaseCache', 'dummycache': 'django.core.cache.backends.dummy.DummyCache', 'filecache': 'django.core.cache.backends.filebased.FileBasedCache', 'locmemcache': 'django.core.cache.backends.locmem.LocMemCache', 'memcache': 'django.core.cache.backends.memcached.MemcachedCache', 'pymemcache': PYMEMCACHE_DRIVER, 'pylibmc': 'django.core.cache.backends.memcached.PyLibMCCache', 'rediscache': REDIS_DRIVER, 'redis': REDIS_DRIVER, 'rediss': REDIS_DRIVER, } _CACHE_BASE_OPTIONS = [ 'TIMEOUT', 'KEY_PREFIX', 'VERSION', 'KEY_FUNCTION', 'BINARY', ] DEFAULT_EMAIL_ENV = 'EMAIL_URL' EMAIL_SCHEMES = { 'smtp': 'django.core.mail.backends.smtp.EmailBackend', 'smtps': 'django.core.mail.backends.smtp.EmailBackend', 'smtp+tls': 'django.core.mail.backends.smtp.EmailBackend', 'smtp+ssl': 'django.core.mail.backends.smtp.EmailBackend', 'consolemail': 'django.core.mail.backends.console.EmailBackend', 'filemail': 'django.core.mail.backends.filebased.EmailBackend', 'memorymail': 'django.core.mail.backends.locmem.EmailBackend', 'dummymail': 'django.core.mail.backends.dummy.EmailBackend' } _EMAIL_BASE_OPTIONS = ['EMAIL_USE_TLS', 'EMAIL_USE_SSL'] DEFAULT_SEARCH_ENV = 'SEARCH_URL' SEARCH_SCHEMES = { "elasticsearch": "haystack.backends.elasticsearch_backend." "ElasticsearchSearchEngine", "elasticsearch2": "haystack.backends.elasticsearch2_backend." "Elasticsearch2SearchEngine", "elasticsearch5": "haystack.backends.elasticsearch5_backend." "Elasticsearch5SearchEngine", "elasticsearch7": "haystack.backends.elasticsearch7_backend." "Elasticsearch7SearchEngine", "solr": "haystack.backends.solr_backend.SolrEngine", "whoosh": "haystack.backends.whoosh_backend.WhooshEngine", "xapian": "haystack.backends.xapian_backend.XapianEngine", "simple": "haystack.backends.simple_backend.SimpleEngine", } ELASTICSEARCH_FAMILY = [scheme + s for scheme in SEARCH_SCHEMES if scheme.startswith("elasticsearch") for s in ('', 's')] CLOUDSQL = 'cloudsql' def __init__(self, **scheme): self.smart_cast = True self.escape_proxy = False self.prefix = "" self.scheme = scheme def __call__(self, var, cast=None, default=NOTSET, parse_default=False): return self.get_value( var, cast=cast, default=default, parse_default=parse_default ) def __contains__(self, var): return var in self.ENVIRON def str(self, var, default=NOTSET, multiline=False): """ :rtype: str """ value = self.get_value(var, cast=str, default=default) if multiline: return re.sub(r'(\\r)?\\n', r'\n', value) return value def bytes(self, var, default=NOTSET, encoding='utf8'): """ :rtype: bytes """ value = self.get_value(var, cast=str, default=default) if hasattr(value, 'encode'): return value.encode(encoding) return value def bool(self, var, default=NOTSET): """ :rtype: bool """ return self.get_value(var, cast=bool, default=default) def int(self, var, default=NOTSET): """ :rtype: int """ return self.get_value(var, cast=int, default=default) def float(self, var, default=NOTSET): """ :rtype: float """ return self.get_value(var, cast=float, default=default) def json(self, var, default=NOTSET): """ :returns: Json parsed """ return self.get_value(var, cast=json.loads, default=default) def list(self, var, cast=None, default=NOTSET): """ :rtype: list """ return self.get_value( var, cast=list if not cast else [cast], default=default ) def tuple(self, var, cast=None, default=NOTSET): """ :rtype: tuple """ return self.get_value( var, cast=tuple if not cast else (cast,), default=default ) def dict(self, var, cast=dict, default=NOTSET): """ :rtype: dict """ return self.get_value(var, cast=cast, default=default) def url(self, var, default=NOTSET): """ :rtype: urllib.parse.ParseResult """ return self.get_value( var, cast=urlparse, default=default, parse_default=True ) def db_url(self, var=DEFAULT_DATABASE_ENV, default=NOTSET, engine=None): """Returns a config dictionary, defaulting to DATABASE_URL. The db method is an alias for db_url. :rtype: dict """ return self.db_url_config( self.get_value(var, default=default), engine=engine ) db = db_url def cache_url(self, var=DEFAULT_CACHE_ENV, default=NOTSET, backend=None): """Returns a config dictionary, defaulting to CACHE_URL. The cache method is an alias for cache_url. :rtype: dict """ return self.cache_url_config( self.url(var, default=default), backend=backend ) cache = cache_url def email_url(self, var=DEFAULT_EMAIL_ENV, default=NOTSET, backend=None): """Returns a config dictionary, defaulting to EMAIL_URL. The email method is an alias for email_url. :rtype: dict """ return self.email_url_config( self.url(var, default=default), backend=backend ) email = email_url def search_url(self, var=DEFAULT_SEARCH_ENV, default=NOTSET, engine=None): """Returns a config dictionary, defaulting to SEARCH_URL. :rtype: dict """ return self.search_url_config( self.url(var, default=default), engine=engine ) def path(self, var, default=NOTSET, **kwargs): """ :rtype: Path """ return Path(self.get_value(var, default=default), **kwargs) def get_value(self, var, cast=None, default=NOTSET, parse_default=False): """Return value for given environment variable. :param str var: Name of variable. :param collections.abc.Callable or None cast: Type to cast return value as. :param default: If var not present in environ, return this instead. :param bool parse_default: Force to parse default. :returns: Value from environment or default (if set). :rtype: typing.IO[typing.Any] """ logger.debug( "get '%s' casted as '%s' with default '%s'", var, cast, default) var_name = f'{self.prefix}{var}' if var_name in self.scheme: var_info = self.scheme[var_name] try: has_default = len(var_info) == 2 except TypeError: has_default = False if has_default: if not cast: cast = var_info[0] if default is self.NOTSET: try: default = var_info[1] except IndexError: pass else: if not cast: cast = var_info try: value = self.ENVIRON[var_name] except KeyError as exc: if default is self.NOTSET: error_msg = f'Set the {var} environment variable' raise ImproperlyConfigured(error_msg) from exc value = default # Resolve any proxied values prefix = b'$' if isinstance(value, bytes) else '$' escape = rb'\$' if isinstance(value, bytes) else r'\$' if hasattr(value, 'startswith') and value.startswith(prefix): value = value.lstrip(prefix) value = self.get_value(value, cast=cast, default=default) if self.escape_proxy and hasattr(value, 'replace'): value = value.replace(escape, prefix) # Smart casting if self.smart_cast: if cast is None and default is not None and \ not isinstance(default, NoValue): cast = type(default) value = None if default is None and value == '' else value if value != default or (parse_default and value is not None): value = self.parse_value(value, cast) return value @classmethod def parse_value(cls, value, cast): """Parse and cast provided value :param value: Stringed value. :param cast: Type to cast return value as. :returns: Casted value """ if cast is None: return value if cast is bool: try: value = int(value) != 0 except ValueError: value = value.lower().strip() in cls.BOOLEAN_TRUE_STRINGS elif isinstance(cast, list): value = list(map(cast[0], [x for x in value.split(',') if x])) elif isinstance(cast, tuple): val = value.strip('(').strip(')').split(',') value = tuple(map(cast[0], [x for x in val if x])) elif isinstance(cast, dict): key_cast = cast.get('key', str) value_cast = cast.get('value', str) value_cast_by_key = cast.get('cast', {}) value = dict(map( lambda kv: ( key_cast(kv[0]), cls.parse_value( kv[1], value_cast_by_key.get(kv[0], value_cast) ) ), [val.split('=') for val in value.split(';') if val] )) elif cast is dict: value = dict([v.split('=', 1) for v in value.split(',') if v]) elif cast is list: value = [x for x in value.split(',') if x] elif cast is tuple: val = value.strip('(').strip(')').split(',') # pylint: disable=consider-using-generator value = tuple([x for x in val if x]) elif cast is float: # clean string float_str = re.sub(r'[^\d,.-]', '', value) # split for avoid thousand separator and different # locale comma/dot symbol parts = re.split(r'[,.]', float_str) if len(parts) == 1: float_str = parts[0] else: float_str = f"{''.join(parts[0:-1])}.{parts[-1]}" value = float(float_str) else: value = cast(value) return value @classmethod # pylint: disable=too-many-statements def db_url_config(cls, url, engine=None): # pylint: enable-msg=too-many-statements """Parse an arbitrary database URL. Supports the following URL schemas: * PostgreSQL: ``postgres[ql]?://`` or ``p[g]?sql://`` * PostGIS: ``postgis://`` * MySQL: ``mysql://`` or ``mysql2://`` * MySQL (GIS): ``mysqlgis://`` * MySQL Connector Python from Oracle: ``mysql-connector://`` * SQLite: ``sqlite://`` * SQLite with SpatiaLite for GeoDjango: ``spatialite://`` * Oracle: ``oracle://`` * Microsoft SQL Server: ``mssql://`` * PyODBC: ``pyodbc://`` * Amazon Redshift: ``redshift://`` * LDAP: ``ldap://`` :param urllib.parse.ParseResult or str url: Database URL to parse. :param str or None engine: If None, the database engine is evaluates from the ``url``. :return: Parsed database URL. :rtype: dict """ if not isinstance(url, cls.URL_CLASS): if url == 'sqlite://:memory:': # this is a special case, because if we pass this URL into # urlparse, urlparse will choke trying to interpret "memory" # as a port number return { 'ENGINE': cls.DB_SCHEMES['sqlite'], 'NAME': ':memory:' } # note: no other settings are required for sqlite try: url = urlparse(url) # handle Invalid IPv6 URL except ValueError: url = _urlparse_quote(url) config = {} # handle unexpected URL schemes with special characters if not url.path: url = _urlparse_quote(urlunparse(url)) # Remove query strings. path = url.path[1:] path = unquote_plus(path.split('?', 2)[0]) if url.scheme == 'sqlite': if path == '': # if we are using sqlite and we have no path, then assume we # want an in-memory database (this is the behaviour of # sqlalchemy) path = ':memory:' if url.netloc: warnings.warn( f'SQLite URL contains host component {url.netloc!r}, ' 'it will be ignored', stacklevel=3 ) if url.scheme == 'ldap': path = f'{url.scheme}://{url.hostname}' if url.port: path += f':{url.port}' user_host = url.netloc.rsplit('@', 1) if url.scheme in cls.POSTGRES_FAMILY and ',' in user_host[-1]: # Parsing postgres cluster dsn hinfo = list( itertools.zip_longest( *( host.rsplit(':', 1) for host in user_host[-1].split(',') ) ) ) hostname = ','.join(hinfo[0]) port = ','.join(filter(None, hinfo[1])) if len(hinfo) == 2 else '' else: hostname = url.hostname port = url.port # Update with environment configuration. config.update({ 'NAME': path or '', 'USER': _cast_urlstr(url.username) or '', 'PASSWORD': _cast_urlstr(url.password) or '', 'HOST': hostname or '', 'PORT': _cast_int(port) or '', }) if ( url.scheme in cls.POSTGRES_FAMILY and path.startswith('/') or cls.CLOUDSQL in path and path.startswith('/') ): config['HOST'], config['NAME'] = path.rsplit('/', 1) if url.scheme == 'oracle' and path == '': config['NAME'] = config['HOST'] config['HOST'] = '' if url.scheme == 'oracle': # Django oracle/base.py strips port and fails on non-string value if not config['PORT']: del config['PORT'] else: config['PORT'] = str(config['PORT']) if url.query: config_options = {} for k, v in parse_qs(url.query).items(): if k.upper() in cls._DB_BASE_OPTIONS: config.update({k.upper(): _cast(v[0])}) else: config_options.update({k: _cast_int(v[0])}) config['OPTIONS'] = config_options if engine: config['ENGINE'] = engine else: config['ENGINE'] = url.scheme if config['ENGINE'] in cls.DB_SCHEMES: config['ENGINE'] = cls.DB_SCHEMES[config['ENGINE']] if not config.get('ENGINE', False): warnings.warn(f'Engine not recognized from url: {config}') return {} return config @classmethod def cache_url_config(cls, url, backend=None): """Parse an arbitrary cache URL. :param urllib.parse.ParseResult or str url: Cache URL to parse. :param str or None backend: If None, the backend is evaluates from the ``url``. :return: Parsed cache URL. :rtype: dict """ if not isinstance(url, cls.URL_CLASS): if not url: return {} url = urlparse(url) if url.scheme not in cls.CACHE_SCHEMES: raise ImproperlyConfigured(f'Invalid cache schema {url.scheme}') location = url.netloc.split(',') if len(location) == 1: location = location[0] config = { 'BACKEND': cls.CACHE_SCHEMES[url.scheme], 'LOCATION': location, } # Add the drive to LOCATION if url.scheme == 'filecache': config.update({ 'LOCATION': url.netloc + url.path, }) # urlparse('pymemcache://127.0.0.1:11211') # => netloc='127.0.0.1:11211', path='' # # urlparse('pymemcache://memcached:11211/?key_prefix=ci') # => netloc='memcached:11211', path='/' # # urlparse('memcache:///tmp/memcached.sock') # => netloc='', path='/tmp/memcached.sock' if not url.netloc and url.scheme in ['memcache', 'pymemcache']: config.update({ 'LOCATION': 'unix:' + url.path, }) elif url.scheme.startswith('redis'): if url.hostname: scheme = url.scheme.replace('cache', '') else: scheme = 'unix' locations = [scheme + '://' + loc + url.path for loc in url.netloc.split(',')] if len(locations) == 1: config['LOCATION'] = locations[0] else: config['LOCATION'] = locations if url.query: config_options = {} for k, v in parse_qs(url.query).items(): opt = {k.upper(): _cast(v[0])} if k.upper() in cls._CACHE_BASE_OPTIONS: config.update(opt) else: config_options.update(opt) config['OPTIONS'] = config_options if backend: config['BACKEND'] = backend return config @classmethod def email_url_config(cls, url, backend=None): """Parse an arbitrary email URL. :param urllib.parse.ParseResult or str url: Email URL to parse. :param str or None backend: If None, the backend is evaluates from the ``url``. :return: Parsed email URL. :rtype: dict """ config = {} url = urlparse(url) if not isinstance(url, cls.URL_CLASS) else url # Remove query strings path = url.path[1:] path = unquote_plus(path.split('?', 2)[0]) # Update with environment configuration config.update({ 'EMAIL_FILE_PATH': path, 'EMAIL_HOST_USER': _cast_urlstr(url.username), 'EMAIL_HOST_PASSWORD': _cast_urlstr(url.password), 'EMAIL_HOST': url.hostname, 'EMAIL_PORT': _cast_int(url.port), }) if backend: config['EMAIL_BACKEND'] = backend elif url.scheme not in cls.EMAIL_SCHEMES: raise ImproperlyConfigured(f'Invalid email schema {url.scheme}') elif url.scheme in cls.EMAIL_SCHEMES: config['EMAIL_BACKEND'] = cls.EMAIL_SCHEMES[url.scheme] if url.scheme in ('smtps', 'smtp+tls'): config['EMAIL_USE_TLS'] = True elif url.scheme == 'smtp+ssl': config['EMAIL_USE_SSL'] = True if url.query: config_options = {} for k, v in parse_qs(url.query).items(): opt = {k.upper(): _cast_int(v[0])} if k.upper() in cls._EMAIL_BASE_OPTIONS: config.update(opt) else: config_options.update(opt) config['OPTIONS'] = config_options return config @classmethod def _parse_common_search_params(cls, url): cfg = {} prs = {} if not url.query or str(url.query) == '': return cfg, prs prs = parse_qs(url.query) if 'EXCLUDED_INDEXES' in prs: cfg['EXCLUDED_INDEXES'] = prs['EXCLUDED_INDEXES'][0].split(',') if 'INCLUDE_SPELLING' in prs: val = prs['INCLUDE_SPELLING'][0] cfg['INCLUDE_SPELLING'] = cls.parse_value(val, bool) if 'BATCH_SIZE' in prs: cfg['BATCH_SIZE'] = cls.parse_value(prs['BATCH_SIZE'][0], int) return cfg, prs @classmethod def _parse_elasticsearch_search_params(cls, url, path, secure, params): cfg = {} split = path.rsplit('/', 1) if len(split) > 1: path = '/'.join(split[:-1]) index = split[-1] else: path = "" index = split[0] cfg['URL'] = urlunparse( ('https' if secure else 'http', url[1], path, '', '', '') ) if 'TIMEOUT' in params: cfg['TIMEOUT'] = cls.parse_value(params['TIMEOUT'][0], int) if 'KWARGS' in params: cfg['KWARGS'] = params['KWARGS'][0] cfg['INDEX_NAME'] = index return cfg @classmethod def _parse_solr_search_params(cls, url, path, params): cfg = {} cfg['URL'] = urlunparse(('http',) + url[1:2] + (path,) + ('', '', '')) if 'TIMEOUT' in params: cfg['TIMEOUT'] = cls.parse_value(params['TIMEOUT'][0], int) if 'KWARGS' in params: cfg['KWARGS'] = params['KWARGS'][0] return cfg @classmethod def _parse_whoosh_search_params(cls, params): cfg = {} if 'STORAGE' in params: cfg['STORAGE'] = params['STORAGE'][0] if 'POST_LIMIT' in params: cfg['POST_LIMIT'] = cls.parse_value(params['POST_LIMIT'][0], int) return cfg @classmethod def _parse_xapian_search_params(cls, params): cfg = {} if 'FLAGS' in params: cfg['FLAGS'] = params['FLAGS'][0] return cfg @classmethod def search_url_config(cls, url, engine=None): """Parse an arbitrary search URL. :param urllib.parse.ParseResult or str url: Search URL to parse. :param str or None engine: If None, the engine is evaluates from the ``url``. :return: Parsed search URL. :rtype: dict """ config = {} url = urlparse(url) if not isinstance(url, cls.URL_CLASS) else url # Remove query strings. path = unquote_plus(url.path[1:].split('?', 2)[0]) scheme = url.scheme secure = False # elasticsearch supports secure schemes, similar to http -> https if scheme in cls.ELASTICSEARCH_FAMILY and scheme.endswith('s'): scheme = scheme[:-1] secure = True if scheme not in cls.SEARCH_SCHEMES: raise ImproperlyConfigured(f'Invalid search schema {url.scheme}') config['ENGINE'] = cls.SEARCH_SCHEMES[scheme] # check commons params cfg, params = cls._parse_common_search_params(url) config.update(cfg) if url.scheme == 'simple': return config # remove trailing slash if path.endswith('/'): path = path[:-1] if url.scheme == 'solr': config.update(cls._parse_solr_search_params(url, path, params)) return config if url.scheme in cls.ELASTICSEARCH_FAMILY: config.update(cls._parse_elasticsearch_search_params( url, path, secure, params)) return config config['PATH'] = '/' + path if url.scheme == 'whoosh': config.update(cls._parse_whoosh_search_params(params)) elif url.scheme == 'xapian': config.update(cls._parse_xapian_search_params(params)) if engine: config['ENGINE'] = engine return config @classmethod def read_env(cls, env_file=None, overwrite=False, encoding='utf8', **overrides): r"""Read a .env file into os.environ. If not given a path to a dotenv path, does filthy magic stack backtracking to find the dotenv in the same directory as the file that called ``read_env``. Existing environment variables take precedent and are NOT overwritten by the file content. ``overwrite=True`` will force an overwrite of existing environment variables. Refs: * https://wellfire.co/learn/easier-12-factor-django :param env_file: The path to the ``.env`` file your application should use. If a path is not provided, `read_env` will attempt to import the Django settings module from the Django project root. :param overwrite: ``overwrite=True`` will force an overwrite of existing environment variables. :param encoding: The encoding to use when reading the environment file. :param \**overrides: Any additional keyword arguments provided directly to read_env will be added to the environment. If the key matches an existing environment variable, the value will be overridden. """ if env_file is None: # pylint: disable=protected-access frame = sys._getframe() env_file = os.path.join( os.path.dirname(frame.f_back.f_code.co_filename), '.env' ) if not os.path.exists(env_file): logger.info( "%s doesn't exist - if you're not configuring your " "environment separately, create one.", env_file) return try: if isinstance(env_file, Openable): # Python 3.5 support (wrap path with str). with open(str(env_file), encoding=encoding) as f: content = f.read() else: with env_file as f: content = f.read() except OSError: logger.info( "%s not found - if you're not configuring your " "environment separately, check this.", env_file) return logger.debug('Read environment variables from: %s', env_file) def _keep_escaped_format_characters(match): """Keep escaped newline/tabs in quoted strings""" escaped_char = match.group(1) if escaped_char in 'rnt': return '\\' + escaped_char return escaped_char for line in content.splitlines(): m1 = re.match(r'\A(?:export )?([A-Za-z_0-9]+)=(.*)\Z', line) if m1: key, val = m1.group(1), m1.group(2) # Look for value in quotes, ignore post-# comments # (outside quotes) m2 = re.match(r"\A\s*'(? 1: base_path = os.path.join(base_path, '') return item.__root__.startswith(base_path) def __repr__(self): return f'' def __str__(self): return self.__root__ def __unicode__(self): return self.__str__() def __getitem__(self, *args, **kwargs): return self.__str__().__getitem__(*args, **kwargs) def __fspath__(self): return self.__str__() def rfind(self, *args, **kwargs): """Proxy method to :py:func:`str.rfind`""" return str(self).rfind(*args, **kwargs) def find(self, *args, **kwargs): """Proxy method to :py:func:`str.find`""" return str(self).find(*args, **kwargs) @staticmethod def _absolute_join(base, *paths, **kwargs): absolute_path = os.path.abspath(os.path.join(base, *paths)) if kwargs.get('required', False) and not os.path.exists(absolute_path): raise ImproperlyConfigured( f'Create required path: {absolute_path}' ) return absolute_path ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/environ/fileaware_mapping.py0000664000175000017500000000607314400225515021421 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. """Docker-style file variable support module.""" import os from collections.abc import MutableMapping class FileAwareMapping(MutableMapping): """ A mapping that wraps os.environ, first checking for the existence of a key appended with ``_FILE`` whenever reading a value. If a matching file key is found then the value is instead read from the file system at this location. By default, values read from the file system are cached so future lookups do not hit the disk again. A ``_FILE`` key has higher precedence than a value is set directly in the environment, and an exception is raised if the file can not be found. """ def __init__(self, env=None, cache=True): """ Initialize the mapping. :param env: where to read environment variables from (defaults to ``os.environ``) :param cache: cache environment variables read from the file system (defaults to ``True``) """ self.env = env if env is not None else os.environ self.cache = cache self.files_cache = {} def __getitem__(self, key): if self.cache and key in self.files_cache: return self.files_cache[key] key_file = self.env.get(key + "_FILE") if key_file: with open(key_file, encoding='utf-8') as f: value = f.read() if self.cache: self.files_cache[key] = value return value return self.env[key] def __iter__(self): """ Iterate all keys, also always including the shortened key if ``_FILE`` keys are found. """ for key in self.env: yield key if key.endswith("_FILE"): no_file_key = key[:-5] if no_file_key and no_file_key not in self.env: yield no_file_key def __len__(self): """ Return the length of the file, also always counting shortened keys for any ``_FILE`` key found. """ return len(tuple(iter(self))) def __setitem__(self, key, value): self.env[key] = value if self.cache and key.endswith("_FILE"): no_file_key = key[:-5] if no_file_key and no_file_key in self.files_cache: del self.files_cache[no_file_key] def __delitem__(self, key): file_key = key + "_FILE" if file_key in self.env: del self[file_key] if key in self.env: del self.env[key] return if self.cache and key.endswith("_FILE"): no_file_key = key[:-5] if no_file_key and no_file_key in self.files_cache: del self.files_cache[no_file_key] del self.env[key] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6748567 django-environ-0.11.2/setup.cfg0000664000175000017500000000010314474450344015535 0ustar00joke2kjoke2k[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/setup.py0000664000175000017500000001631714473635227015450 0ustar00joke2kjoke2k#!/usr/bin/env python # # This file is part of the django-environ. # # Copyright (c) 2021-2023, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import codecs import re from os import path from setuptools import find_packages, setup # Use this code block for future deprecations of Python version: # # import warnings # import sys # # if sys.version_info < (3, 6): # warnings.warn( # "Support of Python < 3.6 is deprecated" # "and will be removed in a future release.", # DeprecationWarning # ) def read_file(filepath): """Read content from a UTF-8 encoded text file.""" with codecs.open(filepath, 'rb', 'utf-8') as file_handle: return file_handle.read() PKG_NAME = 'django-environ' PKG_DIR = path.abspath(path.dirname(__file__)) META_PATH = path.join(PKG_DIR, 'environ', '__init__.py') META_CONTENTS = read_file(META_PATH) def load_long_description(): """Load long description from file README.rst.""" def changes(): changelog = path.join(PKG_DIR, 'CHANGELOG.rst') pattern = ( r'(`(v\d+.\d+.\d+)`_( - \d{1,2}-\w+-\d{4}\r?\n-+\r?\n.*?))' r'\r?\n\r?\n\r?\n`v\d+.\d+.\d+`_' ) result = re.search(pattern, read_file(changelog), re.S) return result.group(2) + result.group(3) if result else '' try: title = PKG_NAME head = '=' * (len(title)) contents = ( head, format(title.strip(' .')), head, read_file(path.join(PKG_DIR, 'README.rst')).split( '.. -teaser-begin-' )[1], '', read_file(path.join(PKG_DIR, 'CONTRIBUTING.rst')), '', 'Release Information', '===================\n', changes(), '', '`Full changelog <{}/en/latest/changelog.html>`_.'.format( find_meta('url') ), '', read_file(path.join(PKG_DIR, 'SECURITY.rst')), '', read_file(path.join(PKG_DIR, 'AUTHORS.rst')), ) return '\n'.join(contents) except (RuntimeError, FileNotFoundError) as read_error: message = 'Long description could not be read from README.rst' raise RuntimeError('%s: %s' % (message, read_error)) from read_error def is_canonical_version(version): """Check if a version string is in the canonical format of PEP 440.""" pattern = ( r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))' r'*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))' r'?(\.dev(0|[1-9][0-9]*))?$') return re.match(pattern, version) is not None def find_meta(meta): """Extract __*meta*__ from META_CONTENTS.""" meta_match = re.search( r"^__{meta}__\s+=\s+['\"]([^'\"]*)['\"]".format(meta=meta), META_CONTENTS, re.M ) if meta_match: return meta_match.group(1) raise RuntimeError( 'Unable to find __%s__ string in package meta file' % meta) def get_version_string(): """Return package version as listed in `__version__` in meta file.""" # Parse version string version_string = find_meta('version') # Check validity if not is_canonical_version(version_string): message = ( 'The detected version string "{}" is not in canonical ' 'format as defined in PEP 440.'.format(version_string)) raise ValueError(message) return version_string # What does this project relate to? KEYWORDS = [ 'environment', 'django', 'variables', '12factor', ] # Classifiers: available ones listed at https://pypi.org/classifiers CLASSIFIERS = [ 'Development Status :: 5 - Production/Stable', 'Framework :: Django', 'Framework :: Django :: 1.11', 'Framework :: Django :: 2.0', 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', 'Framework :: Django :: 3.2', 'Framework :: Django :: 4.0', 'Framework :: Django :: 4.1', 'Framework :: Django :: 4.2', 'Operating System :: OS Independent', 'Intended Audience :: Developers', 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Utilities', 'License :: OSI Approved :: MIT License', ] # Dependencies that are downloaded by pip on installation and why. INSTALL_REQUIRES = [] DEPENDENCY_LINKS = [] # List additional groups of dependencies here (e.g. testing dependencies). # You can install these using the following syntax, for example: # # $ pip install -e .[testing,docs,develop] # EXTRAS_REQUIRE = { # Dependencies that are required to run tests 'testing': [ 'coverage[toml]>=5.0a4', # Code coverage measurement for Python 'pytest>=4.6.11', # Our tests framework ], # Dependencies that are required to build documentation 'docs': [ 'furo>=2021.8.17b43,==2021.8.*', # Sphinx documentation theme 'sphinx>=3.5.0', # Python documentation generator 'sphinx-notfound-page', # Create a custom 404 page ], } # Dependencies that are required to develop package DEVELOP_REQUIRE = [] # Dependencies that are required to develop package EXTRAS_REQUIRE['develop'] = \ DEVELOP_REQUIRE + EXTRAS_REQUIRE['testing'] + EXTRAS_REQUIRE['docs'] # Project's URLs PROJECT_URLS = { 'Documentation': find_meta('url'), 'Funding': 'https://opencollective.com/django-environ', 'Say Thanks!': 'https://saythanks.io/to/joke2k', 'Changelog': '{}/en/latest/changelog.html'.format(find_meta('url')), 'Bug Tracker': 'https://github.com/joke2k/django-environ/issues', 'Source Code': 'https://github.com/joke2k/django-environ', } if __name__ == '__main__': setup( name=PKG_NAME, version=get_version_string(), author=find_meta('author'), author_email=find_meta('author_email'), maintainer=find_meta('maintainer'), maintainer_email=find_meta('maintainer_email'), license=find_meta('license'), description=find_meta('description'), long_description=load_long_description(), long_description_content_type='text/x-rst', keywords=KEYWORDS, url=find_meta('url'), project_urls=PROJECT_URLS, classifiers=CLASSIFIERS, packages=find_packages(exclude=['tests.*', 'tests']), platforms=['any'], include_package_data=True, zip_safe=False, python_requires='>=3.6,<4', install_requires=INSTALL_REQUIRES, dependency_links=DEPENDENCY_LINKS, extras_require=EXTRAS_REQUIRE, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1693602019.6748567 django-environ-0.11.2/tests/0000775000175000017500000000000014474450344015064 5ustar00joke2kjoke2k././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/tests/__init__.py0000664000175000017500000000046614114672453017201 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/tests/asserts.py0000664000175000017500000000065614114672453017127 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. def assert_type_and_value(type_, expected, actual): assert isinstance(actual, type_) assert actual == expected ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1631372631.0 django-environ-0.11.2/tests/conftest.py0000664000175000017500000000236214117142527017261 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import os import pathlib import sys import pytest @pytest.fixture def solr_url(): """Return Solr URL.""" return 'solr://127.0.0.1:8983/solr' @pytest.fixture def whoosh_url(): """Return Whoosh URL.""" return 'whoosh:///home/search/whoosh_index' @pytest.fixture def xapian_url(): """Return Xapian URL.""" return 'xapian:///home/search/xapian_index' @pytest.fixture def simple_url(): """Return simple URL.""" return 'simple:///' @pytest.fixture def volume(): """Return volume name is OS is Windows, otherwise None.""" if sys.platform == 'win32': return pathlib.Path(os.getcwd()).parts[0] return None @pytest.fixture(params=[ 'solr://127.0.0.1:8983/solr', 'elasticsearch://127.0.0.1:9200/index', 'whoosh:///home/search/whoosh_index', 'xapian:///home/search/xapian_index', 'simple:///' ]) def search_url(request): """Return Search Engine URL.""" return request.param ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tests/fixtures.py0000664000175000017500000001073614473635227017322 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. from environ.compat import json class FakeEnv: URL = 'http://www.google.com/' POSTGRES = 'postgres://uf07k1:wegauwhg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722' MYSQL = 'mysql://bea6eb0:69772142@us-cdbr-east.cleardb.com/heroku_97681?reconnect=true' MYSQL_CLOUDSQL_URL = 'mysql://djuser:hidden-password@//cloudsql/arvore-codelab:us-central1:mysqlinstance/mydatabase' MYSQLGIS = 'mysqlgis://user:password@127.0.0.1/some_database' SQLITE = 'sqlite:////full/path/to/your/database/file.sqlite' ORACLE_TNS = 'oracle://user:password@sid/' ORACLE = 'oracle://user:password@host:1521/sid' CUSTOM_BACKEND = 'custom.backend://user:password@example.com:5430/database' REDSHIFT = 'redshift://user:password@examplecluster.abc123xyz789.us-west-2.redshift.amazonaws.com:5439/dev' MEMCACHE = 'memcache://127.0.0.1:11211' REDIS = 'rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient&password=secret' EMAIL = 'smtps://user@domain.com:password@smtp.example.com:587' JSON = dict(one='bar', two=2, three=33.44) DICT = dict(foo='bar', test='on') DICT_WITH_EQ = dict(key1='sub_key1=sub_value1', key2='value2') PATH = '/home/dev' EXPORTED = 'exported var' SAML_ATTRIBUTE_MAPPING = dict( uid=('username',), mail=('email',), cn=('first_name',), sn=('last_name',) ) @classmethod def generate_data(cls): return dict(STR_VAR='bar', STR_QUOTED_IGNORE_COMMENT='foo', STR_QUOTED_INCLUDE_HASH='foo # with hash', MULTILINE_STR_VAR='foo\\nbar', MULTILINE_QUOTED_STR_VAR='---BEGIN---\\r\\n---END---', MULTILINE_ESCAPED_STR_VAR='---BEGIN---\\\\n---END---', INT_VAR='42', FLOAT_VAR='33.3', FLOAT_COMMA_VAR='33,3', FLOAT_STRANGE_VAR1='123,420,333.3', FLOAT_STRANGE_VAR2='123.420.333,3', FLOAT_NEGATIVE_VAR='-1.0', BOOL_TRUE_STRING_LIKE_INT='1', BOOL_TRUE_INT=1, BOOL_TRUE_STRING_LIKE_BOOL='True', BOOL_TRUE_STRING_LIKE_BOOL_WITH_COMMENT='True', BOOL_TRUE_STRING_1='on', BOOL_TRUE_STRING_2='ok', BOOL_TRUE_STRING_3='yes', BOOL_TRUE_STRING_4='y', BOOL_TRUE_STRING_5='true', BOOL_TRUE_BOOL=True, BOOL_TRUE_BOOL_WITH_COMMENT=True, BOOL_FALSE_STRING_LIKE_INT='0', BOOL_FALSE_INT=0, BOOL_FALSE_STRING_LIKE_BOOL='False', BOOL_FALSE_BOOL=False, PROXIED_VAR='$STR_VAR', ESCAPED_VAR=r'\$baz', INT_LIST='42,33', INT_TUPLE='(42,33)', MIX_TUPLE='(42,Test)', STR_LIST_WITH_SPACES=' foo, spaces', STR_LIST_WITH_SPACES_QUOTED="' foo', ' quoted'", EMPTY_LIST='', DICT_VAR='foo=bar,test=on', DICT_WITH_EQ_VAR='key1=sub_key1=sub_value1,key2=value2', DATABASE_URL=cls.POSTGRES, DATABASE_MYSQL_URL=cls.MYSQL, DATABASE_MYSQL_GIS_URL=cls.MYSQLGIS, DATABASE_SQLITE_URL=cls.SQLITE, DATABASE_ORACLE_URL=cls.ORACLE, DATABASE_ORACLE_TNS_URL=cls.ORACLE_TNS, DATABASE_REDSHIFT_URL=cls.REDSHIFT, DATABASE_CUSTOM_BACKEND_URL=cls.CUSTOM_BACKEND, DATABASE_MYSQL_CLOUDSQL_URL=cls.MYSQL_CLOUDSQL_URL, CACHE_URL=cls.MEMCACHE, CACHE_REDIS=cls.REDIS, EMAIL_URL=cls.EMAIL, URL_VAR=cls.URL, JSON_VAR=json.dumps(cls.JSON), PATH_VAR=cls.PATH, EXPORTED_VAR=cls.EXPORTED, SAML_ATTRIBUTE_MAPPING='uid=username;mail=email;cn=first_name;sn=last_name;', PREFIX_TEST='foo', ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tests/test_cache.py0000664000175000017500000002264214473635227017552 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. from unittest import mock import pytest import environ.compat from environ import Env from environ.compat import ( ImproperlyConfigured, PYMEMCACHE_DRIVER, REDIS_DRIVER, ) def test_base_options_parsing(): url = ('memcache://127.0.0.1:11211/?timeout=0&' 'key_prefix=cache_&key_function=foo.get_key&version=1') url = Env.cache_url_config(url) assert url['KEY_PREFIX'] == 'cache_' assert url['KEY_FUNCTION'] == 'foo.get_key' assert url['TIMEOUT'] == 0 assert url['VERSION'] == 1 url = 'redis://127.0.0.1:6379/?timeout=None' url = Env.cache_url_config(url) assert url['TIMEOUT'] is None @pytest.mark.parametrize( 'url,backend,location', [ ('dbcache://my_cache_table', 'django.core.cache.backends.db.DatabaseCache', 'my_cache_table'), ('filecache:///var/tmp/django_cache', 'django.core.cache.backends.filebased.FileBasedCache', '/var/tmp/django_cache'), ('filecache://C:/foo/bar', 'django.core.cache.backends.filebased.FileBasedCache', 'C:/foo/bar'), ('locmemcache://', 'django.core.cache.backends.locmem.LocMemCache', ''), ('locmemcache://unique-snowflake', 'django.core.cache.backends.locmem.LocMemCache', 'unique-snowflake'), ('dummycache://', 'django.core.cache.backends.dummy.DummyCache', ''), ('rediss://127.0.0.1:6379/1', REDIS_DRIVER, 'rediss://127.0.0.1:6379/1'), ('rediscache://:redispass@127.0.0.1:6379/0', REDIS_DRIVER, 'redis://:redispass@127.0.0.1:6379/0'), ('rediscache://host1:6379,host2:6379,host3:9999/1', REDIS_DRIVER, ['redis://host1:6379/1', 'redis://host2:6379/1', 'redis://host3:9999/1']), ('rediscache:///path/to/socket:1', REDIS_DRIVER, 'unix:///path/to/socket:1'), ('memcache:///tmp/memcached.sock', 'django.core.cache.backends.memcached.MemcachedCache', 'unix:/tmp/memcached.sock'), ('memcache://172.19.26.240:11211,172.19.26.242:11212', 'django.core.cache.backends.memcached.MemcachedCache', ['172.19.26.240:11211', '172.19.26.242:11212']), ('memcache://127.0.0.1:11211', 'django.core.cache.backends.memcached.MemcachedCache', '127.0.0.1:11211'), ('pymemcache://127.0.0.1:11211', PYMEMCACHE_DRIVER, '127.0.0.1:11211'), ('pymemcache://memcached:11211/?key_prefix=ci', PYMEMCACHE_DRIVER, 'memcached:11211'), ], ids=[ 'dbcache', 'filecache', 'filecache_win', 'locmemcache_empty', 'locmemcache', 'dummycache', 'rediss', 'redis_with_password', 'redis_multiple', 'redis_socket', 'memcached_socket', 'memcached_multiple', 'memcached', 'pylibmccache', 'pylibmccache_trailing_slash', ], ) def test_cache_parsing(url, backend, location): url = Env.cache_url_config(url) assert url['BACKEND'] == backend assert url['LOCATION'] == location @pytest.mark.parametrize('django_version', ((3, 2), (3, 1), None)) @pytest.mark.parametrize('pymemcache_installed', (True, False)) def test_pymemcache_compat(django_version, pymemcache_installed): old = 'django.core.cache.backends.memcached.PyLibMCCache' new = 'django.core.cache.backends.memcached.PyMemcacheCache' with mock.patch.object(environ.compat, 'DJANGO_VERSION', django_version): with mock.patch('environ.compat.find_spec') as mock_find_spec: mock_find_spec.return_value = pymemcache_installed driver = environ.compat.choose_pymemcache_driver() if django_version and django_version < (3, 2): assert driver == old else: assert driver == new if pymemcache_installed else old @pytest.mark.parametrize('django_version', ((4, 0), (3, 2), None)) @pytest.mark.parametrize('django_redis_installed', (True, False)) def test_rediscache_compat(django_version, django_redis_installed): django_new = 'django.core.cache.backends.redis.RedisCache' redis_cache = 'redis_cache.RedisCache' django_redis = 'django_redis.cache.RedisCache' with mock.patch.object(environ.compat, 'DJANGO_VERSION', django_version): with mock.patch('environ.compat.find_spec') as mock_find_spec: mock_find_spec.return_value = django_redis_installed driver = environ.compat.choose_rediscache_driver() if django_redis_installed: assert driver == django_redis elif django_version and django_version >= (4, 0): assert driver == django_new else: assert driver == redis_cache def test_redis_parsing(): url = ('rediscache://127.0.0.1:6379/1?client_class=' 'django_redis.client.DefaultClient&password=secret') url = Env.cache_url_config(url) assert url['BACKEND'] == REDIS_DRIVER assert url['LOCATION'] == 'redis://127.0.0.1:6379/1' assert url['OPTIONS'] == { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': 'secret', } def test_redis_socket_url(): url = 'redis://:redispass@/path/to/socket.sock?db=0' url = Env.cache_url_config(url) assert REDIS_DRIVER == url['BACKEND'] assert url['LOCATION'] == 'unix://:redispass@/path/to/socket.sock' assert url['OPTIONS'] == { 'DB': 0 } def test_options_parsing(): url = 'filecache:///var/tmp/django_cache?timeout=60&max_entries=1000&cull_frequency=0' url = Env.cache_url_config(url) assert url['BACKEND'] == 'django.core.cache.backends.filebased.FileBasedCache' assert url['LOCATION'] == '/var/tmp/django_cache' assert url['TIMEOUT'] == 60 assert url['OPTIONS'] == { 'MAX_ENTRIES': 1000, 'CULL_FREQUENCY': 0, } def test_custom_backend(): url = 'memcache://127.0.0.1:5400?foo=option&bars=9001' backend = 'django_redis.cache.RedisCache' url = Env.cache_url_config(url, backend) assert url['BACKEND'] == backend assert url['LOCATION'] == '127.0.0.1:5400' assert url['OPTIONS'] == { 'FOO': 'option', 'BARS': 9001, } def test_unknown_backend(): url = 'unknown-scheme://127.0.0.1:1000' with pytest.raises(ImproperlyConfigured) as excinfo: Env.cache_url_config(url) assert str(excinfo.value) == 'Invalid cache schema unknown-scheme' def test_empty_url_is_mapped_to_empty_config(): assert Env.cache_url_config('') == {} assert Env.cache_url_config(None) == {} @pytest.mark.parametrize( 'chars', ['!', '$', '&', "'", '(', ')', '*', '+', ';', '=', '-', '.', '-v1.2'] ) def test_cache_url_password_using_sub_delims(monkeypatch, chars): """Ensure CACHE_URL passwords may contains some unsafe characters. See: https://github.com/joke2k/django-environ/issues/200 for details.""" url = 'rediss://enigma:secret{}@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url result = env.cache_url_config(url) assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url url = 'rediss://enigma:sec{}ret@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url result = env.cache_url_config(url) assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url url = 'rediss://enigma:{}secret@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url result = env.cache_url_config(url) assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url @pytest.mark.parametrize( 'chars', ['%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%2C'] ) def test_cache_url_password_using_gen_delims(monkeypatch, chars): """Ensure CACHE_URL passwords may contains %-encoded characters. See: https://github.com/joke2k/django-environ/issues/200 for details.""" url = 'rediss://enigma:secret{}@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url url = 'rediss://enigma:sec{}ret@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url url = 'rediss://enigma:{}secret@ondigitalocean.com:25061/2'.format(chars) monkeypatch.setenv('CACHE_URL', url) env = Env() result = env.cache() assert result['BACKEND'] == REDIS_DRIVER assert result['LOCATION'] == url def test_cache_url_env_using_default(): env = Env(CACHE_URL=(str, "locmemcache://")) result = env.cache() assert result["BACKEND"] == "django.core.cache.backends.locmem.LocMemCache" assert result["LOCATION"] == "" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tests/test_db.py0000664000175000017500000001715514473635227017077 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import warnings import pytest from environ import Env from environ.compat import DJANGO_POSTGRES @pytest.mark.parametrize( 'url,engine,name,host,user,passwd,port', [ # postgres://user:password@host:port/dbname ('postgres://enigma:secret@example.com:5431/dbname', DJANGO_POSTGRES, 'dbname', 'example.com', 'enigma', 'secret', 5431), # postgres://path/dbname ('postgres:////var/run/postgresql/dbname', DJANGO_POSTGRES, 'dbname', '/var/run/postgresql', '', '', ''), # postgis://user:password@host:port/dbname ('postgis://enigma:secret@example.com:5431/dbname', 'django.contrib.gis.db.backends.postgis', 'dbname', 'example.com', 'enigma', 'secret', 5431), # postgres://user:password@host:port,host:port,host:port/dbname ('postgres://username:p@ss:12,wor:34d@host1:111,22.55.44.88:222,[2001:db8::1234]:333/db', DJANGO_POSTGRES, 'db', 'host1,22.55.44.88,[2001:db8::1234]', 'username', 'p@ss:12,wor:34d', '111,222,333' ), # postgres://host,host,host/dbname ('postgres://node1,node2,node3/db', DJANGO_POSTGRES, 'db', 'node1,node2,node3', '', '', '' ), # mysqlgis://user:password@host:port/dbname ('mysqlgis://enigma:secret@example.com:5431/dbname', 'django.contrib.gis.db.backends.mysql', 'dbname', 'example.com', 'enigma', 'secret', 5431), # mysql://user:password@host/dbname?options ('mysql://enigma:secret@reconnect.com/dbname?reconnect=true', 'django.db.backends.mysql', 'dbname', 'reconnect.com', 'enigma', 'secret', ''), # mysql://user@host/dbname ('mysql://enigma@localhost/dbname', 'django.db.backends.mysql', 'dbname', 'localhost', 'enigma', '', ''), # sqlite:// ('sqlite://', 'django.db.backends.sqlite3', ':memory:', '', '', '', ''), # sqlite:////absolute/path/to/db/file ('sqlite:////full/path/to/your/file.sqlite', 'django.db.backends.sqlite3', '/full/path/to/your/file.sqlite', '', '', '', ''), # sqlite://:memory: ('sqlite://:memory:', 'django.db.backends.sqlite3', ':memory:', '', '', '', ''), # ldap://user:password@host ('ldap://cn=admin,dc=nodomain,dc=org:secret@example.com', 'ldapdb.backends.ldap', 'ldap://example.com', 'example.com', 'cn=admin,dc=nodomain,dc=org', 'secret', ''), # mysql://user:password@host/dbname ('mssql://enigma:secret@example.com/dbname' '?driver=ODBC Driver 13 for SQL Server', 'mssql', 'dbname', 'example.com', 'enigma', 'secret', ''), # mysql://user:password@host:port/dbname ('mssql://enigma:secret@amazonaws.com\\insnsnss:12345/dbname' '?driver=ODBC Driver 13 for SQL Server', 'mssql', 'dbname', 'amazonaws.com\\insnsnss', 'enigma', 'secret', 12345), # mysql://user:password@host:port/dbname ('mysql://enigma:><{~!@#$%^&*}[]@example.com:1234/dbname', 'django.db.backends.mysql', 'dbname', 'example.com', 'enigma', '><{~!@#$%^&*}[]', 1234), # mysql://user:password@host/dbname ('mysql://enigma:]password]@example.com/dbname', 'django.db.backends.mysql', 'dbname', 'example.com', 'enigma', ']password]', ''), ], ids=[ 'postgres', 'postgres_unix_domain', 'postgis', 'postgres_cluster', 'postgres_no_ports', 'mysqlgis', 'cleardb', 'mysql_no_password', 'sqlite_empty', 'sqlite_file', 'sqlite_memory', 'ldap', 'mssql', 'mssql_port', 'mysql_password_special_chars', 'mysql_invalid_ipv6_password', ], ) def test_db_parsing(url, engine, name, host, user, passwd, port): config = Env.db_url_config(url) assert config['ENGINE'] == engine assert config['NAME'] == name if url != 'sqlite://:memory:': assert config['PORT'] == port assert config['PASSWORD'] == passwd assert config['USER'] == user assert config['HOST'] == host if engine == 'sql_server.pyodbc': assert config['OPTIONS'] == {'driver': 'ODBC Driver 13 for SQL Server'} if host == 'reconnect.com': assert config['OPTIONS'] == {'reconnect': 'true'} def test_postgres_complex_db_name_parsing(): """Make sure we can use complex postgres host.""" env_url = ( 'postgres://user:password@//cloudsql/' 'project-1234:us-central1:instance/dbname' ) url = Env.db_url_config(env_url) assert url['ENGINE'] == DJANGO_POSTGRES assert url['HOST'] == '/cloudsql/project-1234:us-central1:instance' assert url['NAME'] == 'dbname' assert url['USER'] == 'user' assert url['PASSWORD'] == 'password' assert url['PORT'] == '' @pytest.mark.parametrize( 'scheme', ['postgres', 'postgresql', 'psql', 'pgsql', 'postgis'], ) def test_postgres_like_scheme_parsing(scheme): """Verify all the postgres-like schemes parsed the same as postgres.""" env_url1 = ( 'postgres://user:password@//cloudsql/' 'project-1234:us-central1:instance/dbname' ) env_url2 = ( '{}://user:password@//cloudsql/' 'project-1234:us-central1:instance/dbname' ).format(scheme) url1 = Env.db_url_config(env_url1) url2 = Env.db_url_config(env_url2) assert url2['NAME'] == url1['NAME'] assert url2['PORT'] == url1['PORT'] assert url2['PASSWORD'] == url1['PASSWORD'] assert url2['USER'] == url1['USER'] assert url2['HOST'] == url1['HOST'] if scheme == 'postgis': assert url2['ENGINE'] == 'django.contrib.gis.db.backends.postgis' else: assert url2['ENGINE'] == url1['ENGINE'] def test_memory_sqlite_url_warns_about_netloc(recwarn): warnings.simplefilter("always") url = 'sqlite://missing-slash-path' url = Env.db_url_config(url) assert len(recwarn) == 1 assert recwarn.pop(UserWarning) assert url['ENGINE'] == 'django.db.backends.sqlite3' assert url['NAME'] == ':memory:' def test_database_options_parsing(): url = 'postgres://user:pass@host:1234/dbname?conn_max_age=600' url = Env.db_url_config(url) assert url['CONN_MAX_AGE'] == 600 url = ('postgres://user:pass@host:1234/dbname?' 'conn_max_age=None&autocommit=True&atomic_requests=False') url = Env.db_url_config(url) assert url['CONN_MAX_AGE'] is None assert url['AUTOCOMMIT'] is True assert url['ATOMIC_REQUESTS'] is False url = ('mysql://user:pass@host:1234/dbname?init_command=SET ' 'storage_engine=INNODB') url = Env.db_url_config(url) assert url['OPTIONS'] == { 'init_command': 'SET storage_engine=INNODB', } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1630762283.0 django-environ-0.11.2/tests/test_email.py0000664000175000017500000000151614114672453017565 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. from environ import Env def test_smtp_parsing(): url = 'smtps://user@domain.com:password@smtp.example.com:587' url = Env.email_url_config(url) assert len(url) == 7 assert url['EMAIL_BACKEND'] == 'django.core.mail.backends.smtp.EmailBackend' assert url['EMAIL_HOST'] == 'smtp.example.com' assert url['EMAIL_HOST_PASSWORD'] == 'password' assert url['EMAIL_HOST_USER'] == 'user@domain.com' assert url['EMAIL_PORT'] == 587 assert url['EMAIL_USE_TLS'] is True assert url['EMAIL_FILE_PATH'] == '' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693428945.0 django-environ-0.11.2/tests/test_env.py0000664000175000017500000003704314473726321017274 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import os from urllib.parse import quote import pytest from environ import Env, Path from environ.compat import ( DJANGO_POSTGRES, ImproperlyConfigured, REDIS_DRIVER, ) from .asserts import assert_type_and_value from .fixtures import FakeEnv class TestEnv: def setup_method(self, method): """ Setup environment variables. Setup any state tied to the execution of the given method in a class. setup_method is invoked for every test method of a class. """ self.old_environ = os.environ os.environ = Env.ENVIRON = FakeEnv.generate_data() self.env = Env() def teardown_method(self, method): """ Rollback environment variables. Teardown any state that was previously setup with a setup_method call. """ assert self.old_environ is not None os.environ = self.old_environ def test_not_present_with_default(self): assert self.env('not_present', default=3) == 3 def test_not_present_without_default(self): with pytest.raises(ImproperlyConfigured) as excinfo: self.env('not_present') assert str(excinfo.value) == 'Set the not_present environment variable' assert excinfo.value.__cause__ is not None def test_contains(self): assert 'STR_VAR' in self.env assert 'EMPTY_LIST' in self.env assert 'I_AM_NOT_A_VAR' not in self.env @pytest.mark.parametrize( 'var,val,multiline', [ ('STR_VAR', 'bar', False), ('MULTILINE_STR_VAR', 'foo\\nbar', False), ('MULTILINE_STR_VAR', 'foo\nbar', True), ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\\r\\n---END---', False), ('MULTILINE_QUOTED_STR_VAR', '---BEGIN---\n---END---', True), ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\\n---END---', False), ('MULTILINE_ESCAPED_STR_VAR', '---BEGIN---\\\n---END---', True), ], ) def test_str(self, var, val, multiline): assert isinstance(self.env(var), str) if not multiline: assert self.env(var) == val assert self.env.str(var, multiline=multiline) == val @pytest.mark.parametrize( 'var,val,default', [ ('STR_VAR', b'bar', Env.NOTSET), ('NON_EXISTENT_BYTES_VAR', b'some-default', b'some-default'), ('NON_EXISTENT_STR_VAR', b'some-default', 'some-default'), ] ) def test_bytes(self, var, val, default): assert_type_and_value(bytes, val, self.env.bytes(var, default=default)) def test_int(self): assert_type_and_value(int, 42, self.env('INT_VAR', cast=int)) assert_type_and_value(int, 42, self.env.int('INT_VAR')) def test_int_with_none_default(self): assert self.env('NOT_PRESENT_VAR', cast=int, default=None) is None assert self.env('EMPTY_INT_VAR', cast=int, default=None) is None @pytest.mark.parametrize( 'value,variable', [ (33.3, 'FLOAT_VAR'), (33.3, 'FLOAT_COMMA_VAR'), (123420333.3, 'FLOAT_STRANGE_VAR1'), (123420333.3, 'FLOAT_STRANGE_VAR2'), (-1.0, 'FLOAT_NEGATIVE_VAR'), ] ) def test_float(self, value, variable): assert_type_and_value(float, value, self.env.float(variable)) assert_type_and_value(float, value, self.env(variable, cast=float)) @pytest.mark.parametrize( 'value,variable', [ (True, 'BOOL_TRUE_STRING_LIKE_INT'), (True, 'BOOL_TRUE_STRING_LIKE_BOOL'), (True, 'BOOL_TRUE_STRING_LIKE_BOOL_WITH_COMMENT'), (True, 'BOOL_TRUE_INT'), (True, 'BOOL_TRUE_BOOL'), (True, 'BOOL_TRUE_BOOL_WITH_COMMENT'), (True, 'BOOL_TRUE_STRING_1'), (True, 'BOOL_TRUE_STRING_2'), (True, 'BOOL_TRUE_STRING_3'), (True, 'BOOL_TRUE_STRING_4'), (True, 'BOOL_TRUE_STRING_5'), (False, 'BOOL_FALSE_STRING_LIKE_INT'), (False, 'BOOL_FALSE_INT'), (False, 'BOOL_FALSE_STRING_LIKE_BOOL'), (False, 'BOOL_FALSE_BOOL'), ] ) def test_bool_true(self, value, variable): assert_type_and_value(bool, value, self.env.bool(variable)) assert_type_and_value(bool, value, self.env(variable, cast=bool)) def test_proxied_value(self): assert self.env('PROXIED_VAR') == 'bar' def test_escaped_dollar_sign(self): self.env.escape_proxy = True assert self.env('ESCAPED_VAR') == '$baz' def test_escaped_dollar_sign_disabled(self): self.env.escape_proxy = False assert self.env('ESCAPED_VAR') == r'\$baz' def test_int_list(self): assert_type_and_value(list, [42, 33], self.env('INT_LIST', cast=[int])) assert_type_and_value(list, [42, 33], self.env.list('INT_LIST', int)) def test_int_list_cast_tuple(self): assert_type_and_value(tuple, (42, 33), self.env('INT_LIST', cast=(int,))) assert_type_and_value(tuple, (42, 33), self.env.tuple('INT_LIST', int)) assert_type_and_value(tuple, ('42', '33'), self.env.tuple('INT_LIST')) def test_int_tuple(self): assert_type_and_value(tuple, (42, 33), self.env('INT_TUPLE', cast=(int,))) assert_type_and_value(tuple, (42, 33), self.env.tuple('INT_TUPLE', int)) assert_type_and_value(tuple, ('42', '33'), self.env.tuple('INT_TUPLE')) def test_mix_tuple_issue_387(self): """Cast a tuple of mixed types. Casts a string like "(42,Test)" to a tuple like (42, 'Test'). See: https://github.com/joke2k/django-environ/issues/387 for details.""" assert_type_and_value( tuple, (42, 'Test'), self.env( 'MIX_TUPLE', default=(0, ''), cast=lambda t: tuple( map( lambda v: int(v) if v.isdigit() else v.strip(), [c for c in t.strip('()').split(',')] ) ), ) ) def test_str_list_with_spaces(self): assert_type_and_value(list, [' foo', ' spaces'], self.env('STR_LIST_WITH_SPACES', cast=[str])) assert_type_and_value(list, [' foo', ' spaces'], self.env.list('STR_LIST_WITH_SPACES')) def test_empty_list(self): assert_type_and_value(list, [], self.env('EMPTY_LIST', cast=[int])) def test_dict_value(self): assert_type_and_value(dict, FakeEnv.DICT, self.env.dict('DICT_VAR')) assert_type_and_value(dict, FakeEnv.DICT_WITH_EQ, self.env.dict('DICT_WITH_EQ_VAR')) def test_complex_dict_value(self): assert_type_and_value( dict, FakeEnv.SAML_ATTRIBUTE_MAPPING, self.env.dict('SAML_ATTRIBUTE_MAPPING', cast={'value': tuple}) ) @pytest.mark.parametrize( 'value,cast,expected', [ ('a=1', dict, {'a': '1'}), ('a=1', dict(value=int), {'a': 1}), ('a=1', dict(value=float), {'a': 1.0}), ('a=1,2,3', dict(value=[str]), {'a': ['1', '2', '3']}), ('a=1,2,3', dict(value=[int]), {'a': [1, 2, 3]}), ('a=1;b=1.1,2.2;c=3', dict(value=int, cast=dict(b=[float])), {'a': 1, 'b': [1.1, 2.2], 'c': 3}), ('a=uname;c=http://www.google.com;b=True', dict(value=str, cast=dict(b=bool)), {'a': "uname", 'c': "http://www.google.com", 'b': True}), ], ids=[ 'dict', 'dict_int', 'dict_float', 'dict_str_list', 'dict_int_list', 'dict_int_cast', 'dict_str_cast', ], ) def test_dict_parsing(self, value, cast, expected): assert self.env.parse_value(value, cast) == expected def test_url_value(self): url = self.env.url('URL_VAR') assert url.__class__ == self.env.URL_CLASS assert url.geturl() == FakeEnv.URL assert self.env.url('OTHER_URL', default=None) is None def test_url_empty_string_default_value(self): unset_var_name = 'VARIABLE_NOT_SET_IN_ENVIRONMENT' assert unset_var_name not in os.environ url = self.env.url(unset_var_name, '') assert url.__class__ == self.env.URL_CLASS assert url.geturl() == '' def test_url_encoded_parts(self): password_with_unquoted_characters = "#password" encoded_url = "mysql://user:%s@127.0.0.1:3306/dbname" % quote( password_with_unquoted_characters ) parsed_url = self.env.db_url_config(encoded_url) assert parsed_url['PASSWORD'] == password_with_unquoted_characters @pytest.mark.parametrize( 'var,engine,name,host,user,passwd,port', [ (Env.DEFAULT_DATABASE_ENV, DJANGO_POSTGRES, 'd8r82722', 'ec2-107-21-253-135.compute-1.amazonaws.com', 'uf07k1', 'wegauwhg', 5431), ('DATABASE_MYSQL_URL', 'django.db.backends.mysql', 'heroku_97681', 'us-cdbr-east.cleardb.com', 'bea6eb0', '69772142', ''), ('DATABASE_MYSQL_GIS_URL', 'django.contrib.gis.db.backends.mysql', 'some_database', '127.0.0.1', 'user', 'password', ''), ('DATABASE_ORACLE_TNS_URL', 'django.db.backends.oracle', 'sid', '', 'user', 'password', None), ('DATABASE_ORACLE_URL', 'django.db.backends.oracle', 'sid', 'host', 'user', 'password', '1521'), ('DATABASE_REDSHIFT_URL', 'django_redshift_backend', 'dev', 'examplecluster.abc123xyz789.us-west-2.redshift.amazonaws.com', 'user', 'password', 5439), ('DATABASE_SQLITE_URL', 'django.db.backends.sqlite3', '/full/path/to/your/database/file.sqlite', '', '', '', ''), ('DATABASE_CUSTOM_BACKEND_URL', 'custom.backend', 'database', 'example.com', 'user', 'password', 5430), ('DATABASE_MYSQL_CLOUDSQL_URL', 'django.db.backends.mysql', 'mydatabase', '/cloudsql/arvore-codelab:us-central1:mysqlinstance', 'djuser', 'hidden-password', ''), ], ids=[ 'postgres', 'mysql', 'mysql_gis', 'oracle_tns', 'oracle', 'redshift', 'sqlite', 'custom', 'cloudsql', ], ) def test_db_url_value(self, var, engine, name, host, user, passwd, port): config = self.env.db(var) assert config['ENGINE'] == engine assert config['NAME'] == name assert config['HOST'] == host assert config['USER'] == user assert config['PASSWORD'] == passwd if port is None: assert 'PORT' not in config else: assert config['PORT'] == port @pytest.mark.parametrize( 'var,backend,location,options', [ (Env.DEFAULT_CACHE_ENV, 'django.core.cache.backends.memcached.MemcachedCache', '127.0.0.1:11211', None), ('CACHE_REDIS', REDIS_DRIVER, 'redis://127.0.0.1:6379/1', {'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'PASSWORD': 'secret'}), ], ids=[ 'memcached', 'redis', ], ) def test_cache_url_value(self, var, backend, location, options): config = self.env.cache_url(var) assert config['BACKEND'] == backend assert config['LOCATION'] == location if options is None: assert 'OPTIONS' not in config else: assert config['OPTIONS'] == options def test_email_url_value(self): email_config = self.env.email_url() assert email_config['EMAIL_BACKEND'] == ( 'django.core.mail.backends.smtp.EmailBackend' ) assert email_config['EMAIL_HOST'] == 'smtp.example.com' assert email_config['EMAIL_HOST_PASSWORD'] == 'password' assert email_config['EMAIL_HOST_USER'] == 'user@domain.com' assert email_config['EMAIL_PORT'] == 587 assert email_config['EMAIL_USE_TLS'] def test_json_value(self): assert self.env.json('JSON_VAR') == FakeEnv.JSON def test_path(self): root = self.env.path('PATH_VAR') assert_type_and_value(Path, Path(FakeEnv.PATH), root) def test_smart_cast(self): assert self.env.get_value('STR_VAR', default='string') == 'bar' assert self.env.get_value('STR_QUOTED_IGNORE_COMMENT', default='string') == 'foo' assert self.env.get_value('STR_QUOTED_INCLUDE_HASH', default='string') == 'foo # with hash' assert self.env.get_value('BOOL_TRUE_STRING_LIKE_INT', default=True) assert not self.env.get_value( 'BOOL_FALSE_STRING_LIKE_INT', default=True) assert self.env.get_value('INT_VAR', default=1) == 42 assert self.env.get_value('FLOAT_VAR', default=1.2) == 33.3 def test_exported(self): assert self.env('EXPORTED_VAR') == FakeEnv.EXPORTED def test_prefix(self): self.env.prefix = 'PREFIX_' assert self.env('TEST') == 'foo' class TestFileEnv(TestEnv): def setup_method(self, method): """ Setup environment variables. Setup any state tied to the execution of the given method in a class. setup_method is invoked for every test method of a class. """ super().setup_method(method) Env.ENVIRON = {} self.env.read_env( Path(__file__, is_file=True)('test_env.txt'), PATH_VAR=Path(__file__, is_file=True).__root__ ) def create_temp_env_file(self, name): import pathlib import tempfile env_file_path = (pathlib.Path(tempfile.gettempdir()) / name) try: env_file_path.unlink() except FileNotFoundError: pass assert not env_file_path.exists() return env_file_path def test_read_env_path_like(self): env_file_path = self.create_temp_env_file('test_pathlib.env') env_key = 'SECRET' env_val = 'enigma' env_str = env_key + '=' + env_val # open() doesn't take path-like on Python < 3.6 with open(str(env_file_path), 'w', encoding='utf-8') as f: f.write(env_str + '\n') self.env.read_env(env_file_path) assert env_key in self.env.ENVIRON assert self.env.ENVIRON[env_key] == env_val @pytest.mark.parametrize("overwrite", [True, False]) def test_existing_overwrite(self, overwrite): env_file_path = self.create_temp_env_file('test_existing.env') with open(str(env_file_path), 'w') as f: f.write("EXISTING=b") self.env.ENVIRON['EXISTING'] = "a" self.env.read_env(env_file_path, overwrite=overwrite) assert self.env.ENVIRON["EXISTING"] == ("b" if overwrite else "a") class TestSubClass(TestEnv): def setup_method(self, method): """ Setup environment variables. Setup any state tied to the execution of the given method in a class. setup_method is invoked for every test method of a class. """ super().setup_method(method) self.CONFIG = FakeEnv.generate_data() class MyEnv(Env): ENVIRON = self.CONFIG self.env = MyEnv() def test_singleton_environ(self): assert self.CONFIG is self.env.ENVIRON ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tests/test_env.txt0000664000175000017500000000461714473635227017470 0ustar00joke2kjoke2kDICT_VAR=foo=bar,test=on DICT_WITH_EQ_VAR=key1=sub_key1=sub_value1,key2=value2 # Database variables DATABASE_MYSQL_URL=mysql://bea6eb0:69772142@us-cdbr-east.cleardb.com/heroku_97681?reconnect=true DATABASE_MYSQL_CLOUDSQL_URL=mysql://djuser:hidden-password@//cloudsql/arvore-codelab:us-central1:mysqlinstance/mydatabase DATABASE_MYSQL_GIS_URL=mysqlgis://user:password@127.0.0.1/some_database # Cache variables CACHE_URL=memcache://127.0.0.1:11211 CACHE_REDIS=rediscache://127.0.0.1:6379/1?client_class=django_redis.client.DefaultClient&password=secret # Email variables EMAIL_URL=smtps://user@domain.com:password@smtp.example.com:587 # Others URL_VAR=http://www.google.com/ PATH_VAR=/home/dev BOOL_TRUE_STRING_LIKE_INT='1' BOOL_TRUE_INT=1 BOOL_TRUE_STRING_LIKE_BOOL='True' BOOL_TRUE_STRING_1='on' BOOL_TRUE_STRING_2='ok' BOOL_TRUE_STRING_3='yes' BOOL_TRUE_STRING_4='y' BOOL_TRUE_STRING_5='true' BOOL_TRUE_BOOL=True BOOL_TRUE_STRING_LIKE_BOOL_WITH_COMMENT='True' # comment BOOL_TRUE_BOOL_WITH_COMMENT=True # comment BOOL_FALSE_STRING_LIKE_INT='0' BOOL_FALSE_INT=0 BOOL_FALSE_STRING_LIKE_BOOL='False' BOOL_FALSE_BOOL=False DATABASE_SQLITE_URL=sqlite:////full/path/to/your/database/file.sqlite JSON_VAR={"three": 33.44, "two": 2, "one": "bar"} DATABASE_URL=postgres://uf07k1:wegauwhg@ec2-107-21-253-135.compute-1.amazonaws.com:5431/d8r82722 FLOAT_VAR=33.3 FLOAT_COMMA_VAR=33,3 FLOAT_STRANGE_VAR1=123,420,333.3 FLOAT_STRANGE_VAR2=123.420.333,3 FLOAT_NEGATIVE_VAR=-1.0 PROXIED_VAR=$STR_VAR ESCAPED_VAR=\$baz EMPTY_LIST= EMPTY_INT_VAR= INT_VAR=42 STR_LIST_WITH_SPACES= foo, spaces STR_LIST_WITH_SPACES_QUOTED=' foo',' quoted' STR_VAR=bar STR_QUOTED_IGNORE_COMMENT= 'foo' # comment STR_QUOTED_INCLUDE_HASH='foo # with hash' # not comment MULTILINE_STR_VAR=foo\nbar MULTILINE_QUOTED_STR_VAR="---BEGIN---\r\n---END---" MULTILINE_ESCAPED_STR_VAR=---BEGIN---\\n---END--- INT_LIST=42,33 CYRILLIC_VAR=фуубар INT_TUPLE=(42,33) MIX_TUPLE=(42,Test) DATABASE_ORACLE_TNS_URL=oracle://user:password@sid DATABASE_ORACLE_URL=oracle://user:password@host:1521/sid DATABASE_REDSHIFT_URL=redshift://user:password@examplecluster.abc123xyz789.us-west-2.redshift.amazonaws.com:5439/dev DATABASE_CUSTOM_BACKEND_URL=custom.backend://user:password@example.com:5430/database # Djangosaml2's SAML_ATTRIBUTE_MAPPING SAML_ATTRIBUTE_MAPPING="uid=username;mail=email;cn=first_name;sn=last_name;" # Exports export EXPORTED_VAR="exported var" # Prefixed PREFIX_TEST='foo' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/tests/test_fileaware.py0000664000175000017500000001172414400225515020426 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import os import tempfile from contextlib import contextmanager import pytest import environ @contextmanager def make_temp_file(text): with tempfile.NamedTemporaryFile("w", delete=False) as f: f.write(text) f.close() try: yield f.name finally: if os.path.exists(f.name): os.unlink(f.name) @pytest.fixture def tmp_f(): with make_temp_file(text="fish") as f_name: yield f_name def test_mapping(tmp_f): env = environ.FileAwareMapping(env={"ANIMAL_FILE": tmp_f}) assert env["ANIMAL"] == "fish" def test_precidence(tmp_f): env = environ.FileAwareMapping( env={ "ANIMAL_FILE": tmp_f, "ANIMAL": "cat", } ) assert env["ANIMAL"] == "fish" def test_missing_file_raises_exception(): env = environ.FileAwareMapping(env={"ANIMAL_FILE": "non-existant-file"}) with pytest.raises(FileNotFoundError): env["ANIMAL"] def test_iter(): env = environ.FileAwareMapping( env={ "ANIMAL_FILE": "some-file", "VEGETABLE": "leek", "VEGETABLE_FILE": "some-vegetable-file", } ) keys = set(env) assert keys == {"ANIMAL_FILE", "ANIMAL", "VEGETABLE", "VEGETABLE_FILE"} assert "ANIMAL" in keys def test_len(): env = environ.FileAwareMapping( env={ "ANIMAL_FILE": "some-file", "VEGETABLE": "leek", "VEGETABLE_FILE": "some-vegetable-file", } ) assert len(env) == 4 def test_cache(tmp_f): env = environ.FileAwareMapping(env={"ANIMAL_FILE": tmp_f}) assert env["ANIMAL"] == "fish" with open(tmp_f, "w") as f: f.write("cat") assert env["ANIMAL"] == "fish" os.unlink(tmp_f) assert not os.path.exists(env["ANIMAL_FILE"]) assert env["ANIMAL"] == "fish" def test_no_cache(tmp_f): env = environ.FileAwareMapping( cache=False, env={"ANIMAL_FILE": tmp_f}, ) assert env["ANIMAL"] == "fish" with open(tmp_f, "w") as f: f.write("cat") assert env["ANIMAL"] == "cat" os.unlink(tmp_f) assert not os.path.exists(env["ANIMAL_FILE"]) with pytest.raises(FileNotFoundError): assert env["ANIMAL"] def test_setdefault(tmp_f): env = environ.FileAwareMapping(env={"ANIMAL_FILE": tmp_f}) assert env.setdefault("FRUIT", "apple") == "apple" assert env.setdefault("ANIMAL", "cat") == "fish" assert env.env == {"ANIMAL_FILE": tmp_f, "FRUIT": "apple"} class TestDelItem: def test_del_key(self): env = environ.FileAwareMapping(env={"FRUIT": "apple"}) del env["FRUIT"] with pytest.raises(KeyError): env["FRUIT"] def test_del_key_with_file_key(self): env = environ.FileAwareMapping(env={"ANIMAL_FILE": "some-file"}) del env["ANIMAL"] with pytest.raises(KeyError): env["ANIMAL"] def test_del_shadowed_key_with_file_key(self): env = environ.FileAwareMapping( env={"ANIMAL_FILE": "some-file", "ANIMAL": "cat"} ) del env["ANIMAL"] with pytest.raises(KeyError): env["ANIMAL"] def test_del_file_key(self): env = environ.FileAwareMapping( env={ "ANIMAL_FILE": "some-file", "ANIMAL": "fish", } ) del env["ANIMAL_FILE"] assert env["ANIMAL"] == "fish" def test_del_file_key_clears_cache(self, tmp_f): env = environ.FileAwareMapping( env={ "ANIMAL_FILE": tmp_f, "ANIMAL": "cat", } ) assert env["ANIMAL"] == "fish" del env["ANIMAL_FILE"] assert env["ANIMAL"] == "cat" class TestSetItem: def test_set_key(self): env = environ.FileAwareMapping(env={"FRUIT": "apple"}) env["FRUIT"] = "banana" assert env["FRUIT"] == "banana" def test_cant_override_key_with_file_key(self, tmp_f): env = environ.FileAwareMapping( env={ "FRUIT": "apple", "FRUIT_FILE": tmp_f, } ) with open(tmp_f, "w") as f: f.write("banana") env["FRUIT"] = "cucumber" assert env["FRUIT"] == "banana" def test_set_file_key(self, tmp_f): env = environ.FileAwareMapping(env={"ANIMAL": "cat"}) env["ANIMAL_FILE"] = tmp_f assert env["ANIMAL"] == "fish" def test_change_file_key_clears_cache(self, tmp_f): env = environ.FileAwareMapping(env={"ANIMAL_FILE": tmp_f}) assert env["ANIMAL"] == "fish" with make_temp_file(text="cat") as new_tmp_f: env["ANIMAL_FILE"] = new_tmp_f assert env["ANIMAL"] == "cat" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/tests/test_path.py0000664000175000017500000001151514400225515017421 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import os import sys import pytest from environ import Path from environ.compat import ImproperlyConfigured def test_str(volume): root = Path('/home') if sys.platform == 'win32': assert str(root) == '{}home'.format(volume) assert str(root()) == '{}home'.format(volume) assert str(root('dev')) == '{}home\\dev'.format(volume) else: assert str(root) == '/home' assert str(root()) == '/home' assert str(root('dev')) == '/home/dev' def test_path_class(): root = Path(__file__, '..', is_file=True) root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../')) assert root() == root_path assert root.__root__ == root_path web = root.path('public') assert web() == os.path.join(root_path, 'public') assert web('css') == os.path.join(root_path, 'public', 'css') def test_repr(volume): root = Path('/home') if sys.platform == 'win32': assert root.__repr__() == ''.format(volume) else: assert root.__repr__() == '' def test_comparison(volume): root = Path('/home') assert root.__eq__(Path('/home')) assert root in Path('/') assert root not in Path('/other/path') assert Path('/home') == Path('/home') assert Path('/home') != Path('/home/dev') assert Path('/home/foo/').rfind('/') == str(Path('/home/foo')).rfind('/') assert Path('/home/foo/').find('/home') == str(Path('/home/foo/')).find('/home') assert Path('/home/foo/')[1] == str(Path('/home/foo/'))[1] assert Path('/home/foo/').__fspath__() == str(Path('/home/foo/')) assert ~Path('/home') == Path('/') if sys.platform == 'win32': assert Path('/home') == '{}home'.format(volume) assert '{}home'.format(volume) == Path('/home') else: assert Path('/home') == '/home' assert '/home' == Path('/home') assert Path('/home') != '/usr' def test_sum(): """Make sure Path correct handle __add__.""" assert Path('/') + 'home' == Path('/home') assert Path('/') + '/home/public' == Path('/home/public') def test_subtraction(): """Make sure Path correct handle __sub__.""" assert Path('/home/dev/public') - 2 == Path('/home') assert Path('/home/dev/public') - 'public' == Path('/home/dev') def test_subtraction_not_int(): """Subtraction with an invalid type should raise TypeError.""" with pytest.raises(TypeError) as excinfo: Path('/home/dev/') - 'not int' assert str(excinfo.value) == ( "unsupported operand type(s) for -: '' " "and '' unless value of " "ends with value of " ) def test_required_path(): root = Path('/home') with pytest.raises(ImproperlyConfigured) as excinfo: root('dev', 'not_existing_dir', required=True) assert "Create required path:" in str(excinfo.value) with pytest.raises(ImproperlyConfigured) as excinfo: Path('/not/existing/path/', required=True) assert "Create required path:" in str(excinfo.value) def test_complex_manipulation(volume): root = Path('/home') public = root.path('public') assets, scripts = public.path('assets'), public.path('assets', 'scripts') if sys.platform == 'win32': assert public.__repr__() == ''.format(volume) assert str(public.root) == '{}home\\public'.format(volume) assert str(public('styles')) == '{}home\\public\\styles'.format(volume) assert str(assets.root) == '{}home\\public\\assets'.format(volume) assert str(scripts.root) == '{}home\\public\\assets\\scripts'.format( volume ) assert (~assets).__repr__() == ''.format( volume ) assert str(assets + 'styles') == ( '{}home\\public\\assets\\styles'.format(volume) ) assert (assets + 'styles').__repr__() == ( ''.format(volume) ) else: assert public.__repr__() == '' assert str(public.root) == '/home/public' assert str(public('styles')) == '/home/public/styles' assert str(assets.root) == '/home/public/assets' assert str(scripts.root) == '/home/public/assets/scripts' assert str(assets + 'styles') == '/home/public/assets/styles' assert (~assets).__repr__() == '' assert (assets + 'styles').__repr__() == ( '' ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1631372631.0 django-environ-0.11.2/tests/test_schema.py0000664000175000017500000000276414117142527017741 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import os from environ import Env from .fixtures import FakeEnv _old_environ = None def setup_module(): """Setup environment variables to the execution for the current module.""" global _old_environ _old_environ = os.environ os.environ = Env.ENVIRON = FakeEnv.generate_data() def teardown_module(): """Restore environment variables was previously setup in setup_module.""" global _old_environ assert _old_environ is not None os.environ = _old_environ def test_schema(): env = Env(INT_VAR=int, NOT_PRESENT_VAR=(float, 33.3), STR_VAR=str, INT_LIST=[int], DEFAULT_LIST=([int], [2])) assert isinstance(env('INT_VAR'), int) assert env('INT_VAR') == 42 assert isinstance(env('NOT_PRESENT_VAR'), float) assert env('NOT_PRESENT_VAR') == 33.3 assert 'bar' == env('STR_VAR') assert 'foo' == env('NOT_PRESENT2', default='foo') assert isinstance(env('INT_LIST'), list) assert env('INT_LIST') == [42, 33] assert isinstance(env('DEFAULT_LIST'), list) assert env('DEFAULT_LIST') == [2] # Override schema in this one case assert isinstance(env('INT_VAR', cast=str), str) assert env('INT_VAR', cast=str) == '42' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tests/test_search.py0000664000175000017500000001133014473635227017744 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import pytest from environ import Env def test_solr_parsing(solr_url): url = Env.search_url_config(solr_url) assert len(url) == 2 assert url['ENGINE'] == 'haystack.backends.solr_backend.SolrEngine' assert url['URL'] == 'http://127.0.0.1:8983/solr' def test_solr_multicore_parsing(solr_url): timeout = 360 index = 'solr_index' url = '{}/{}?TIMEOUT={}'.format(solr_url, index, timeout) url = Env.search_url_config(url) assert url['ENGINE'] == 'haystack.backends.solr_backend.SolrEngine' assert url['URL'] == 'http://127.0.0.1:8983/solr/solr_index' assert url['TIMEOUT'] == timeout assert 'INDEX_NAME' not in url assert 'PATH' not in url @pytest.mark.parametrize( 'url,engine,scheme', [ ('elasticsearch://127.0.0.1:9200/index', 'elasticsearch_backend.ElasticsearchSearchEngine', 'http',), ('elasticsearchs://127.0.0.1:9200/index', 'elasticsearch_backend.ElasticsearchSearchEngine', 'https',), ('elasticsearch2://127.0.0.1:9200/index', 'elasticsearch2_backend.Elasticsearch2SearchEngine', 'http',), ('elasticsearch2s://127.0.0.1:9200/index', 'elasticsearch2_backend.Elasticsearch2SearchEngine', 'https',), ('elasticsearch5://127.0.0.1:9200/index', 'elasticsearch5_backend.Elasticsearch5SearchEngine', 'http'), ('elasticsearch5s://127.0.0.1:9200/index', 'elasticsearch5_backend.Elasticsearch5SearchEngine', 'https'), ('elasticsearch7://127.0.0.1:9200/index', 'elasticsearch7_backend.Elasticsearch7SearchEngine', 'http'), ('elasticsearch7s://127.0.0.1:9200/index', 'elasticsearch7_backend.Elasticsearch7SearchEngine', 'https'), ], ids=[ 'elasticsearch', 'elasticsearchs', 'elasticsearch2', 'elasticsearch2s', 'elasticsearch5', 'elasticsearch5s', 'elasticsearch7', 'elasticsearch7s', ] ) def test_elasticsearch_parsing(url, engine, scheme): """Ensure all supported Elasticsearch engines are recognized.""" timeout = 360 url = '{}?TIMEOUT={}'.format(url, timeout) url = Env.search_url_config(url) assert url['ENGINE'] == 'haystack.backends.{}'.format(engine) assert 'INDEX_NAME' in url.keys() assert url['INDEX_NAME'] == 'index' assert 'TIMEOUT' in url.keys() assert url['TIMEOUT'] == timeout assert 'PATH' not in url assert url["URL"].startswith(scheme + ":") @pytest.mark.parametrize('storage', ['file', 'ram']) def test_whoosh_parsing(whoosh_url, storage): post_limit = 128 * 1024 * 1024 url = '{}?STORAGE={}&POST_LIMIT={}'.format(whoosh_url, storage, post_limit) url = Env.search_url_config(url) assert url['ENGINE'] == 'haystack.backends.whoosh_backend.WhooshEngine' assert 'PATH' in url.keys() assert url['PATH'] == '/home/search/whoosh_index' assert 'STORAGE' in url.keys() assert url['STORAGE'] == storage assert 'POST_LIMIT' in url.keys() assert url['POST_LIMIT'] == post_limit assert 'INDEX_NAME' not in url @pytest.mark.parametrize('flags', ['myflags']) def test_xapian_parsing(xapian_url, flags): url = '{}?FLAGS={}'.format(xapian_url, flags) url = Env.search_url_config(url) assert url['ENGINE'] == 'haystack.backends.xapian_backend.XapianEngine' assert 'PATH' in url.keys() assert url['PATH'] == '/home/search/xapian_index' assert 'FLAGS' in url.keys() assert url['FLAGS'] == flags assert 'INDEX_NAME' not in url def test_simple_parsing(simple_url): url = Env.search_url_config(simple_url) assert url['ENGINE'] == 'haystack.backends.simple_backend.SimpleEngine' assert 'INDEX_NAME' not in url assert 'PATH' not in url def test_common_args_parsing(search_url): excluded_indexes = 'myapp.indexes.A,myapp.indexes.B' include_spelling = 1 batch_size = 100 params = 'EXCLUDED_INDEXES={}&INCLUDE_SPELLING={}&BATCH_SIZE={}'.format( excluded_indexes, include_spelling, batch_size ) url = '?'.join([search_url, params]) url = Env.search_url_config(url) assert 'EXCLUDED_INDEXES' in url.keys() assert 'myapp.indexes.A' in url['EXCLUDED_INDEXES'] assert 'myapp.indexes.B' in url['EXCLUDED_INDEXES'] assert 'INCLUDE_SPELLING' in url.keys() assert url['INCLUDE_SPELLING'] assert 'BATCH_SIZE' in url.keys() assert url['BATCH_SIZE'] == 100 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1677798221.0 django-environ-0.11.2/tests/test_utils.py0000664000175000017500000000271514400225515017627 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2022, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. import pytest from environ.environ import _cast, _cast_urlstr @pytest.mark.parametrize( 'literal', ['anything-', 'anything*', '*anything', 'anything.', 'anything.1', '(anything', 'anything-v1.2', 'anything-1.2', 'anything='] ) def test_cast(literal): """Safely evaluate a string containing an invalid Python literal. See https://github.com/joke2k/django-environ/issues/200 for details.""" assert _cast(literal) == literal @pytest.mark.parametrize( "quoted_url_str,expected_unquoted_str", [ ("Le-%7BFsIaYnaQw%7Da2B%2F%5BV8bS+", "Le-{FsIaYnaQw}a2B/[V8bS+"), ("my_test-string+", "my_test-string+"), ("my%20test%20string+", "my test string+") ] ) def test_cast_urlstr(quoted_url_str, expected_unquoted_str): """Make sure that a url str that contains plus sign literals does not get unquoted incorrectly Plus signs should not be converted to spaces, since spaces are encoded with %20 in URIs see https://github.com/joke2k/django-environ/issues/357 for details. related to https://github.com/joke2k/django-environ/pull/69""" assert _cast_urlstr(quoted_url_str) == expected_unquoted_str ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1693399703.0 django-environ-0.11.2/tox.ini0000664000175000017500000001071614473635227015246 0ustar00joke2kjoke2k# This file is part of the django-environ. # # Copyright (c) 2021-2023, Serghei Iakovlev # Copyright (c) 2013-2021, Daniele Faraglia # # For the full copyright and license information, please view # the LICENSE.txt file that was distributed with this source code. # Tox (https://tox.readthedocs.io) - run tests in multiple virtualenvs. # Also contains configuration settings for all tools executed by Tox. [tox] minversion = 3.22 envlist = build coverage-report linkcheck docs lint manifest py{36,37,38,39,310,311}-django{111,22} py{36,37,38,39,310,311}-django{30,31,32} py{38,39,310,311}-django{40,41,42} pypy-django{111,22,30,31,32} [gh-actions] python = 3.6: py36 3.7: py37 3.8: py38 3.9: py39 3.10: py310 3.11: py311 pypy-3.7: pypy [testenv] description = Unit tests extras = testing deps = django111: Django>=1.11,<2 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 django32: Django>=3.2,<3.3 django40: Django>=4.0,<4.1 django41: Django>=4.1,<4.2 django42: Django>=4.2,<5.0 commands_pre = python -m pip install --upgrade pip python -m pip install . commands = coverage erase coverage run -m pytest {posargs} [testenv:coverage-report] description = Combine coverage reports skip_install = true deps = coverage[toml]>=5.4 commands = coverage combine coverage report coverage html coverage xml [testenv:lint] description = Static code analysis and code style check skip_install = true deps = flake8 flake8-blind-except flake8-import-order pylint commands_pre = python -m pip install --upgrade pip python -m pip install . commands = flake8 environ setup.py pylint \ --logging-format-style=old \ --good-names-rgxs=m[0-9],f,v \ --disable=too-few-public-methods \ --disable=import-error \ --disable=unused-import \ --disable=too-many-locals \ --disable=too-many-branches \ --disable=too-many-public-methods \ --disable=too-many-lines \ environ [testenv:linkcheck] description = Check external links in the package documentation # Keep basepython in sync with .readthedocs.yml and docs.yml # (GitHub Action Workflow). basepython = python3.10 extras = docs commands = {envpython} -m sphinx \ -j auto \ -b linkcheck \ {tty:--color} \ -n -W -a \ --keep-going \ -d {envtmpdir}/doctrees \ docs \ docs/_build/linkcheck isolated_build = true [testenv:docs] description = Build package documentation (HTML) # Keep basepython in sync with .readthedocs.yml and docs.yml # (GitHub Action Workflow). basepython = python3.10 extras = docs commands = {envpython} -m sphinx \ -j auto \ -b html \ {tty:--color} \ -n -T -W \ -d {envtmpdir}/doctrees \ docs \ docs/_build/html {envpython} -m sphinx \ -j auto \ -b doctest \ {tty:--color} \ -n -T -W \ -d {envtmpdir}/doctrees \ docs \ docs/_build/doctest {envpython} -m doctest \ AUTHORS.rst \ CHANGELOG.rst \ CONTRIBUTING.rst \ README.rst \ SECURITY.rst [testenv:manifest] description = Check MANIFEST.in in a source package for completeness deps = check-manifest skip_install = true commands = check-manifest -v [testenv:build] description = Build and test package distribution skip_install = true deps = twine check-wheel-contents commands_pre = python -m pip install -U pip setuptools wheel python setup.py bdist_wheel -d {envtmpdir}/build python setup.py sdist -d {envtmpdir}/build commands = twine check {envtmpdir}/build/* check-wheel-contents {envtmpdir}/build [pytest] testpaths = tests addopts = --verbose --ignore=.tox [coverage:run] branch = True parallel = True # A list of file name patterns, the files to leave # out of measurement or reporting. omit = .tox/* tests/* */__pycache__/* [coverage:report] precision = 2 show_missing = True [flake8] # Base flake8 configuration: statistics = True show-source = True # TODO: max-complexity = 10 # Plugins: import-order-style = smarkets # A list of mappings of files and the codes that should be ignored for # the entirety of the file: per-file-ignores = environ/__init__.py:F401,F403 environ/compat.py:F401 # Excluding some directories: extend-exclude = .tox build* dist htmlcov