File plaster-1.1.2.obscpio of Package python-plaster

07070100000000000081A4000000000000000000000001637AD733000000C0000000000000000000000000000000000000001A00000000plaster-1.1.2/.coveragerc[run]
parallel = true
source =
    plaster
    tests
omit =
    tests/fake_packages/*

[paths]
source =
    src/plaster
    */site-packages/plaster

[report]
show_missing = true
precision = 2
07070100000001000081A4000000000000000000000001637AD73300000198000000000000000000000000000000000000001600000000plaster-1.1.2/.flake8[flake8]
max-line-length = 89
ignore =
    # E203: whitespace before ':' (black fails to be PEP8 compliant)
    E203
    # E731: do not assign a lambda expression, use a def
    E731
    # W503: line break before binary operator (flake8 is not PEP8 compliant)
    W503
    # W504: line break after binary operator (flake8 is not PEP8 compliant)
    W504
show-source = True
exclude =
    tests/fake_packages,
07070100000002000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000001600000000plaster-1.1.2/.github07070100000003000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002500000000plaster-1.1.2/.github/ISSUE_TEMPLATE07070100000004000081A4000000000000000000000001637AD733000002EC000000000000000000000000000000000000003300000000plaster-1.1.2/.github/ISSUE_TEMPLATE/bug_report.md---
name: Bug Report
about: Create a report to help us improve

---

## Get Support
To get help or technical support, see [Get Support](https://pylonsproject.org/community-support.html).

## Bug Report

Please [search the issue tracker](https://github.com/Pylons/plaster/issues) for similar issues before submitting a new issue.

**Describe the bug**
A clear and concise description of the bug.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain the issue.

**Additional context**
Add any other context about the issue here.
07070100000005000081A4000000000000000000000001637AD733000002F1000000000000000000000000000000000000003600000000plaster-1.1.2/.github/ISSUE_TEMPLATE/documentation.md---
name: Documentation Suggestion
about: Create an issue to improve our documentation

---

## Get Support
To get help or technical support, see [Get Support](https://pylonsproject.org/community-support.html).

## Documentation Suggestion

Please [search the issue tracker](https://github.com/Pylons/plaster/issues) for similar issues before submitting a new issue.

**Describe the issue**
A clear and concise description of the issue.

**Include references**
1. Go to the URL '...'
2. Click on '....'
3. Scroll down to '....'

**Describe the improvement**
A clear and concise description of your suggestion.

**Screenshots**
If applicable, add screenshots to help explain the issue.

**Additional context**
Add any other context about the issue here.
07070100000006000081A4000000000000000000000001637AD73300000337000000000000000000000000000000000000003800000000plaster-1.1.2/.github/ISSUE_TEMPLATE/feature_request.md---
name: Feature Request
about: Suggest an idea for this project

---

## Get Support
To get help or technical support, see [Get Support](https://pylonsproject.org/community-support.html).

## Feature Request

Please [search the issue tracker](https://github.com/Pylons/plaster/issues) for similar issues before submitting a new issue.

**Is your feature request related to an issue? Please describe.**
A clear and concise description of the issue. Example: "I'm always frustrated when [...]".

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.
07070100000007000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002000000000plaster-1.1.2/.github/workflows07070100000008000081A4000000000000000000000001637AD73300000A0D000000000000000000000000000000000000002D00000000plaster-1.1.2/.github/workflows/ci-tests.ymlname: Build and test

on:
    # Only on pushes to master or one of the release branches we build on push
    push:
        branches:
            - master
            - "[0-9].[0-9]+-branch"
        tags:
    # Build pull requests
    pull_request:

jobs:
    test:
        strategy:
            matrix:
                py:
                    - "3.7"
                    - "3.8"
                    - "3.9"
                    - "3.10"
                    - "3.11"
                    - "pypy-3.7"
                os:
                    - "ubuntu-latest"
                    # - "windows-latest"
                    - "macos-latest"
                architecture:
                    - x64
                    - x86
                exclude:
                    # Linux and macOS don't have x86 python
                    - os: "ubuntu-latest"
                      architecture: x86
                    - os: "macos-latest"
                      architecture: x86
        name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}"
        runs-on: ${{ matrix.os }}
        steps:
            - uses: actions/checkout@v2
            - name: Setup python
              uses: actions/setup-python@v2
              with:
                  python-version: ${{ matrix.py }}
                  architecture: ${{ matrix.architecture }}
            - run: pip install tox
            - name: Running tox
              run: tox -e py
    coverage:
        runs-on: ubuntu-latest
        name: Validate coverage
        steps:
            - uses: actions/checkout@v2
            - name: Setup python 3.10
              uses: actions/setup-python@v2
              with:
                  python-version: "3.10"
                  architecture: x64

            - run: pip install tox
            - run: tox -e py310,coverage
    docs:
        runs-on: ubuntu-latest
        name: Build the documentation
        steps:
            - uses: actions/checkout@v2
            - name: Setup python
              uses: actions/setup-python@v2
              with:
                  python-version: "3.10"
                  architecture: x64
            - run: pip install tox
            - run: tox -e docs
    lint:
        runs-on: ubuntu-latest
        name: Lint the package
        steps:
            - uses: actions/checkout@v2
            - name: Setup python
              uses: actions/setup-python@v2
              with:
                  python-version: "3.10"
                  architecture: x64
            - run: pip install tox
            - run: tox -e lint
07070100000009000081A4000000000000000000000001637AD73300000340000000000000000000000000000000000000001900000000plaster-1.1.2/.gitignore# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

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

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

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

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

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

env27
.idea
cover

!tests/fake_packages/defaultapp/
!tests/fake_packages/defaultapp/DefaultApp.egg-info/
0707010000000A000081A4000000000000000000000001637AD73300000F8E000000000000000000000000000000000000001A00000000plaster-1.1.2/CHANGES.rst1.1.2 (2022-11-20)
==================

- Fix a bug in which plaster would crash harder than expected if a URI
  is specified to a distribution that does not have the specified entry points.
  Now a LoaderNotFound exception will be raised instead of a bad unpacking
  of tuples.

1.1.1 (2022-11-20)
==================

- Add support for Python 3.11.

- Fix an bug introduced in 1.1 on some systems where
  ``plaster.exceptions.MultipleLoadersFound`` would be raised due to
  ``lib`` and ``lib64`` being symlinked to each other and both added to the
  ``sys.path``.
  See https://github.com/Pylons/plaster/pull/27

1.1 (2022-10-06)
================

- Drop support for Python 2.7, 3.4, 3.5, 3.6.

- Add support for Python 3.8, 3.9, 3.10.

- Drop runtime dependency on setuptools / pkg_resources by switching to
  ``importlib.metadata``.

1.0 (2017-10-11)
================

- Improve the exception message for ``InvalidURI`` to show the ``config_uri``.
  See https://github.com/Pylons/plaster/pull/17

0.5 (2017-06-02)
================

- When a scheme is not supplied, ``plaster.parse_uri`` will now autogenerate
  a scheme from the file extension with the format ``file+<ext>`` instead of
  simply ``<ext>`` (for example, ``file+ini`` instead of ``ini``).
  See https://github.com/Pylons/plaster/pull/16

- Absolute lookups are now pulled from the start of the scheme instead of
  the end. This means that if you want to explicitly define the package that
  the loader is pulled from, use ``package+scheme`` instead of
  ``scheme+package``.
  See https://github.com/Pylons/plaster/pull/16

0.4 (2017-03-30)
================

- Removed the ``plaster.NoSectionError`` exception. It's expected that
  individual loaders should return an empty dictionary of settings in the
  case that a section cannot be found.
  See https://github.com/Pylons/plaster/pull/12

- Expect the ``wsgi`` protocol to raise ``LookupError`` exceptions when
  a named wsgi component cannot be found.
  See https://github.com/Pylons/plaster/pull/12

0.3 (2017-03-27)
================

- Lookup now works differently. First "foo+bar" looks for an installed project
  distribution named "bar" with a loader named "foo". If this fails then it
  looks for any loader named "foo+bar".

- Rename the loader entry point to ``plaster.loader_factory``.

- Add the concept of protocols to ``plaster.get_loader`` and
  ``plaster.find_loaders``.

- ``plaster.find_loaders`` now works on just schemes and protocols
  instead of full ``PlasterURL`` objects and implements the lookup
  algorithm for finding loader factories.

- Change the ``ILoaderInfo`` interface to avoid being coupled to a
  particular uri. ``ILoaderInfo.load`` now takes a ``config_uri``
  parameter.

- Add a ``options`` dictionary to ``PlasterURL`` containing any arguments
  decoded from the query string. Loaders may use these for whatever they wish
  but one good option is default values in a config file.

- Define the ``IWSGIProtocol`` interface which addons can use to implement
  a loader that can return full wsgi apps, servers and filters.

- The scheme is now case-insensitive.

0.2 (2016-06-15)
================

- Allow ``config_uri`` syntax ``scheme:path`` alongside ``scheme://path``.
  See https://github.com/Pylons/plaster/issues/3

- Improve errors to show the user-supplied values in the error message.
  See https://github.com/Pylons/plaster/pull/4

- Add ``plaster.find_loaders`` which can be used by people who need a way
  to recover when ambiguous loaders are discovered via ``plaster.get_loader``.
  See https://github.com/Pylons/plaster/pull/5

- Rename ``plaster.Loader`` to ``plaster.ILoader`` to signify its purpose
  as an interface with no actual implementation.
  See https://github.com/Pylons/plaster/pull/5

- Introduce ``plaster.ILoaderFactory`` to document what the entry point targets
  are expected to implement.
  See https://github.com/Pylons/plaster/pull/5

0.1 (2016-06-12)
================

- Initial release.
0707010000000B000081A4000000000000000000000001637AD73300000BF2000000000000000000000000000000000000001F00000000plaster-1.1.2/CONTRIBUTING.rst.. highlight:: shell

============
Contributing
============

Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions
----------------------

Report Bugs
~~~~~~~~~~~

Report bugs at https://github.com/Pylons/plaster/issues.

If you are reporting a bug, please include:

* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting.
* Detailed steps to reproduce the bug.

Fix Bugs
~~~~~~~~

Look through the GitHub issues for bugs. Anything tagged with "bug"
is open to whoever wants to implement it.

Implement Features
~~~~~~~~~~~~~~~~~~

Look through the GitHub issues for features. Anything tagged with "feature"
is open to whoever wants to implement it.

Write Documentation
~~~~~~~~~~~~~~~~~~~

plaster could always use more documentation, whether as part of the
official plaster docs, in docstrings, or even on the web in blog posts,
articles, and such.

Submit Feedback
~~~~~~~~~~~~~~~

The best way to send feedback is to file an issue at
https://github.com/Pylons/plaster/issues.

If you are proposing a feature:

* Explain in detail how it would work.
* Keep the scope as narrow as possible, to make it easier to implement.
* Remember that this is a volunteer-driven project, and that contributions
  are welcome :)

Get Started!
------------

Ready to contribute? Here's how to set up `plaster` for local development.

1. Fork the `plaster` repo on GitHub.
2. Clone your fork locally::

    $ git clone git@github.com:your_name_here/plaster.git

3. Install your local copy into a virtualenv::

    $ python3 -m venv env
    $ env/bin/pip install -e .[docs,testing]
    $ env/bin/pip install tox

4. Create a branch for local development::

    $ git checkout -b name-of-your-bugfix-or-feature

   Now you can make your changes locally.

5. When you're done making changes, check that your changes pass flake8 and
   the tests, including testing other Python versions with tox::

    $ env/bin/tox

6. Add your name to the ``CONTRIBUTORS.txt`` file in the root of the
   repository.

7. Commit your changes and push your branch to GitHub::

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature

8. Submit a pull request through the GitHub website.

Pull Request Guidelines
-----------------------

Before you submit a pull request, check that it meets these guidelines:

1. The pull request should include tests.
2. If the pull request adds functionality, the docs should be updated. Put
   your new functionality into a function with a docstring, and add the
   feature to the list in README.rst.
3. The pull request should work for Python 2.7, 3.4, 3.5, 3.6, and for PyPy.
   Check
   https://travis-ci.org/Pylons/plaster/pull_requests
   and make sure that the tests pass for all supported Python versions.

Tips
----

To run a subset of tests::

$ env/bin/py.test tests.test_plaster
0707010000000C000081A4000000000000000000000001637AD73300001363000000000000000000000000000000000000001F00000000plaster-1.1.2/CONTRIBUTORS.txtPylons Project Contributor Agreement
====================================

The submitter agrees by adding his or her name within the section below named
"Contributors" and submitting the resulting modified document to the
canonical shared repository location for this software project (whether
directly, as a user with "direct commit access", or via a "pull request"), he
or she is signing a contract electronically.  The submitter becomes a
Contributor after a) he or she signs this document by adding their name
beneath the "Contributors" section below, and b) the resulting document is
accepted into the canonical version control repository.

Treatment of Account
--------------------

Contributor will not allow anyone other than the Contributor to use his or
her username or source repository login to submit code to a Pylons Project
source repository. Should Contributor become aware of any such use,
Contributor will immediately notify Agendaless Consulting.
Notification must be performed by sending an email to
webmaster@agendaless.com.  Until such notice is received, Contributor will be
presumed to have taken all actions made through Contributor's account. If the
Contributor has direct commit access, Agendaless Consulting will have
complete control and discretion over capabilities assigned to Contributor's
account, and may disable Contributor's account for any reason at any time.

Legal Effect of Contribution
----------------------------

Upon submitting a change or new work to a Pylons Project source Repository (a
"Contribution"), you agree to assign, and hereby do assign, a one-half
interest of all right, title and interest in and to copyright and other
intellectual property rights with respect to your new and original portions
of the Contribution to Agendaless Consulting. You and Agendaless Consulting
each agree that the other shall be free to exercise any and all exclusive
rights in and to the Contribution, without accounting to one another,
including without limitation, the right to license the Contribution to others
under the MIT License. This agreement shall run with title to the
Contribution. Agendaless Consulting does not convey to you any right, title
or interest in or to the Program or such portions of the Contribution that
were taken from the Program. Your transmission of a submission to the Pylons
Project source Repository and marks of identification concerning the
Contribution itself constitute your intent to contribute and your assignment
of the work in accordance with the provisions of this Agreement.

License Terms
-------------

Code committed to the Pylons Project source repository (Committed Code) must
be governed by the MIT License or another license acceptable to
Agendaless Consulting.  Until Agendaless Consulting declares in writing an
acceptable license other than the MIT License, only the MIT License shall be
used.  A list of exceptions is detailed within
the "Licensing Exceptions" section of this document, if one exists.

Representations, Warranty, and Indemnification
----------------------------------------------

Contributor represents and warrants that the Committed Code does not violate
the rights of any person or entity, and that the Contributor has legal
authority to enter into this Agreement and legal authority over Contributed
Code. Further, Contributor indemnifies Agendaless Consulting against
violations.

Cryptography
------------

Contributor understands that cryptographic code may be subject to government
regulations with which Agendaless Consulting and/or entities using Committed
Code must comply. Any code which contains any of the items listed below must
not be checked-in until Agendaless Consulting staff has been notified and has
approved such contribution in writing.

- Cryptographic capabilities or features

- Calls to cryptographic features

- User interface elements which provide context relating to cryptography

- Code which may, under casual inspection, appear to be cryptographic.

Notices
-------

Contributor confirms that any notices required will be included in any
Committed Code.

Licensing Exceptions
====================

Code committed within the ``docs/`` subdirectory of the plaster source
control repository and "docstrings" which appear in the documentation
generated by running "make" within this directory are licensed under the
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States
License (https://creativecommons.org/licenses/by-nc-sa/3.0/us/).

List of Contributors
====================

The below-signed are contributors to a code repository that is part of the
project named "plaster".  Each below-signed contributor has read, understand
and agrees to the terms above in the section within this document entitled
"Pylons Project Contributor Agreement" as of the date beside his or her name.

Contributors
------------

- Michael Merickel (2016-06-12)
- Steve Piercy (2017-08-31)
- Bert JW Regeer (2019-05-31)
0707010000000D000081A4000000000000000000000001637AD73300000424000000000000000000000000000000000000001A00000000plaster-1.1.2/LICENSE.txtCopyright (c) 2017 Michael Merickel

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.
0707010000000E000081A4000000000000000000000001637AD73300000129000000000000000000000000000000000000001A00000000plaster-1.1.2/MANIFEST.ingraft src
graft tests
graft docs
graft .github

include README.rst
include CHANGES.rst
include LICENSE.txt
include CONTRIBUTING.rst
include CONTRIBUTORS.txt

include .coveragerc .flake8 pyproject.toml pytest.ini
include tox.ini rtd.txt

recursive-exclude * __pycache__ *.py[cod]
prune docs/_build
0707010000000F000081A4000000000000000000000001637AD73300000438000000000000000000000000000000000000001900000000plaster-1.1.2/README.rst=======
plaster
=======

.. image:: https://img.shields.io/pypi/v/plaster.svg
    :target: https://pypi.python.org/pypi/plaster

.. image:: https://github.com/Pylons/plaster/workflows/Build%20and%20test/badge.svg?branch=master
        :target: https://github.com/Pylons/plaster/actions?query=workflow%3A%22Build+and+test%22
        :alt: master CI Status

.. image:: https://readthedocs.org/projects/plaster/badge/?version=latest
    :target: https://readthedocs.org/projects/plaster/?badge=latest
    :alt: Documentation Status

``plaster`` is a loader interface around multiple config file formats. It
exists to define a common API for applications to use when they wish to load
configuration. The library itself does not aim to handle anything except
a basic API that applications may use to find and load configuration settings.
Any specific constraints should be implemented in a pluggable loader which can
be registered via an entrypoint.

See https://docs.pylonsproject.org/projects/plaster/en/latest/ or
``docs/index.rst`` in this distribution for detailed documentation.
07070100000010000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000001300000000plaster-1.1.2/docs07070100000011000081A4000000000000000000000001637AD733000001F4000000000000000000000000000000000000001D00000000plaster-1.1.2/pyproject.toml[build-system]
requires = ["setuptools >= 41"]
build-backend = "setuptools.build_meta"

[tool.black]
target-version = ['py37', 'py38', 'py39', 'py310']
exclude = '''
/(
  \.git
  | .tox
  | docs
)/
'''

[tool.isort]
profile = "black"
multi_line_output = 3
src_paths = ["src", "tests"]
skip_glob = ["docs/*"]
include_trailing_comma = true
force_grid_wrap = false
combine_as_imports = true
line_length = 88
force_sort_within_sections = true
default_section = "THIRDPARTY"
known_first_party = "plaster"
07070100000012000081A4000000000000000000000001637AD73300000048000000000000000000000000000000000000001900000000plaster-1.1.2/pytest.ini[pytest]
python_files = test_*.py
testpaths =
    src/plaster
    tests
07070100000013000081A4000000000000000000000001637AD7330000000B000000000000000000000000000000000000001600000000plaster-1.1.2/rtd.txt-e .[docs]
07070100000014000081A4000000000000000000000001637AD733000005B7000000000000000000000000000000000000001800000000plaster-1.1.2/setup.cfg[wheel]
universal = 1

[metadata]
name = plaster
version = 1.1.2
author = Michael Merickel
author_email = pylons-discuss@googlegroups.com
description = A loader interface around multiple config file formats.
keywords = plaster, pastedeploy, ini, config
url = https://docs.pylonsproject.org/projects/plaster/en/latest/
long_description = file: README.rst, CHANGES.rst
long_description_content_type = text/x-rst
classifiers =
    Development Status :: 5 - Production/Stable
    Intended Audience :: Developers
    License :: OSI Approved :: MIT License
    Natural Language :: English
    Programming Language :: Python :: 3
    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
license_files = LICENSE.txt

[options]
package_dir =
     = src
packages = find:
zip_safe = False
install_requires =
    importlib-metadata; python_version<"3.8"
include_package_data = True
python_requires = >=3.7

[options.packages.find]
where = src

[options.extras_require]
docs =
    Sphinx
    pylons-sphinx-themes
testing =
    pytest
    pytest-cov

[check-manifest]
ignore =
    .gitignore
    PKG-INFO
    *.egg-info
    *.egg-info/*
ignore-default-rules = true
ignore-bad-ideas =
    tests/**
07070100000015000081A4000000000000000000000001637AD73300000026000000000000000000000000000000000000001700000000plaster-1.1.2/setup.pyfrom setuptools import setup

setup()
07070100000016000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000001200000000plaster-1.1.2/src07070100000017000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000001A00000000plaster-1.1.2/src/plaster07070100000018000081A4000000000000000000000001637AD73300000131000000000000000000000000000000000000002600000000plaster-1.1.2/src/plaster/__init__.py# public api
# flake8: noqa

from .exceptions import InvalidURI, LoaderNotFound, MultipleLoadersFound, PlasterError
from .interfaces import ILoader, ILoaderFactory, ILoaderInfo
from .loaders import find_loaders, get_loader, get_sections, get_settings, setup_logging
from .uri import PlasterURL, parse_uri
07070100000019000081A4000000000000000000000001637AD733000009D0000000000000000000000000000000000000002800000000plaster-1.1.2/src/plaster/exceptions.pyclass PlasterError(Exception):
    """
    A base exception for any error generated by plaster.
    """


class InvalidURI(PlasterError, ValueError):
    """
    Raised by :func:`plaster.parse_uri` when failing to parse a ``config_uri``.

    :ivar uri: The user-supplied ``config_uri`` string.

    """

    def __init__(self, uri, message=None):
        if message is None:
            message = f'Unable to parse config_uri "{uri}".'
        super().__init__(message)
        self.message = message
        self.uri = uri


class LoaderNotFound(PlasterError, ValueError):
    """
    Raised by :func:`plaster.get_loader` when no loaders match the requested
    ``scheme``.

    :ivar scheme: The scheme being matched.
    :ivar protocols: Zero or more :term:`loader protocol` identifiers that
        were requested when finding a loader.

    """

    def __init__(self, scheme, protocols=None, message=None):
        if message is None:
            scheme_msg = f'scheme "{scheme}"'
            if protocols is not None:
                scheme_msg += ', protocol "{}"'.format(", ".join(protocols))
            message = f"Could not find a matching loader for the {scheme_msg}."
        super().__init__(message)
        self.message = message
        self.scheme = scheme
        self.protocols = protocols


class MultipleLoadersFound(PlasterError, ValueError):
    """
    Raised by :func:`plaster.get_loader` when more than one loader matches the
    requested ``scheme``.

    :ivar scheme: The scheme being matched.
    :ivar protocols: Zero or more :term:`loader protocol` identifiers that
        were requested when finding a loader.
    :ivar loaders: A list of :class:`plaster.ILoaderInfo` objects.

    """

    def __init__(self, scheme, loaders, protocols=None, message=None):
        if message is None:
            scheme_msg = f'scheme "{scheme}"'
            if protocols is not None:
                scheme_msg += ', protocol "{}"'.format(", ".join(protocols))
            loader_list = ", ".join(
                loader.scheme for loader in sorted(loaders, key=lambda v: v.scheme)
            )
            message = (
                "Multiple plaster loaders were found for {}. "
                "Please specify a more specific config_uri. "
                "Matched loaders: {}"
            ).format(scheme_msg, loader_list)
        super().__init__(message)
        self.message = message
        self.scheme = scheme
        self.protocols = protocols
        self.loaders = loaders
0707010000001A000081A4000000000000000000000001637AD73300000CE9000000000000000000000000000000000000002800000000plaster-1.1.2/src/plaster/interfaces.pyimport abc


class ILoader(metaclass=abc.ABCMeta):
    """
    An abstraction over an source of configuration settings.

    It is required to implement ``get_sections``, ``get_settings`` and
    ``setup_logging``.

    Optionally, it may also implement other :term:`loader protocol` interfaces
    to provide extra functionality. For example,
    :class:`plaster.protocols.IWSGIProtocol` which requires ``get_wsgi_app``,
    and ``get_wsgi_server`` for loading WSGI configurations. Services that
    depend on such functionality should document the required functionality
    behind a particular :term:`loader protocol` which custom loaders can
    implement.

    :ivar uri: The :class:`plaster.PlasterURL` object used to find the
        :class:`plaster.ILoaderFactory`.

    """

    @abc.abstractmethod
    def get_sections(self):
        """
        Load the list of section names available.

        """

    @abc.abstractmethod
    def get_settings(self, section=None, defaults=None):
        """
        Load the settings for the named ``section``.

        :param section: The name of the section in the config file. If this is
            ``None`` then it is up to the loader to determine a sensible
            default usually derived from the fragment in the ``path#name``
            syntax of the ``config_uri``.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning
            the final configuration dictionary.

        :returns: A ``dict`` of settings. This should return a dictionary
            object even if the section is missing.
        :raises ValueError: If a section name is missing and cannot be
            determined from the ``config_uri``.

        """

    @abc.abstractmethod
    def setup_logging(self, defaults=None):
        """
        Execute the logging configuration defined in the config file.

        This function should, at least, configure the Python standard logging
        module. However, it may also be used to configure any other logging
        subsystems that serve a similar purpose.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning
            the final configuration dictionary.

        """


class ILoaderFactory(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __call__(self, uri):
        """
        A factory which accepts a :class:`plaster.PlasterURL` and returns a
        :class:`plaster.ILoader` object.

        """


class ILoaderInfo(metaclass=abc.ABCMeta):
    """
    An info object describing a specific :class:`plaster.ILoader`.

    :ivar scheme: The full scheme of the loader.
    :ivar protocols: Zero or more supported :term:`loader protocol`
        identifiers.
    :ivar factory: The :class:`plaster.ILoaderFactory`.

    """

    @abc.abstractmethod
    def load(self, config_uri):
        """
        Create and return an :class:`plaster.ILoader` instance.

        :param config_uri: Anything that can be parsed by
            :func:`plaster.parse_uri`.

        """
0707010000001B000081A4000000000000000000000001637AD73300001BB1000000000000000000000000000000000000002500000000plaster-1.1.2/src/plaster/loaders.pytry:
    from importlib import metadata
except ImportError:  # pragma: no cover < py38
    import importlib_metadata as metadata

from .exceptions import LoaderNotFound, MultipleLoadersFound
from .interfaces import ILoaderInfo
from .uri import parse_uri


def get_sections(config_uri):
    """
    Load the list of named sections.

    .. code-block:: python

        sections = plaster.get_sections('development.ini')
        full_config = {
            section: plaster.get_settings('development.ini', section)
            for section in sections
        }

    :param config_uri: Anything that can be parsed by
        :func:`plaster.parse_uri`.

    :returns: A list of section names in the config file.

    """
    loader = get_loader(config_uri)

    return loader.get_sections()


def get_settings(config_uri, section=None, defaults=None):
    """
    Load the settings from a named section.

    .. code-block:: python

        settings = plaster.get_settings(...)
        print(settings['foo'])

    :param config_uri: Anything that can be parsed by
        :func:`plaster.parse_uri`.

    :param section: The name of the section in the config file. If this is
        ``None`` then it is up to the loader to determine a sensible default
        usually derived from the fragment in the ``path#name`` syntax of the
        ``config_uri``.

    :param defaults: A ``dict`` of default values used to populate the
        settings and support variable interpolation. Any values in ``defaults``
        may be overridden by the loader prior to returning the final
        configuration dictionary.

    :returns: A ``dict`` of settings. This should return a dictionary object
        even if no data is available.

    """
    loader = get_loader(config_uri)

    return loader.get_settings(section, defaults)


def setup_logging(config_uri, defaults=None):
    """
    Execute the logging configuration defined in the config file.

    This function should, at least, configure the Python standard logging
    module. However, it may also be used to configure any other logging
    subsystems that serve a similar purpose.

    :param config_uri: Anything that can be parsed by
        :func:`plaster.parse_uri`.

    :param defaults: A ``dict`` of default values used to populate the
        settings and support variable interpolation. Any values in ``defaults``
        may be overridden by the loader prior to returning the final
        configuration dictionary.

    """
    loader = get_loader(config_uri)

    return loader.setup_logging(defaults)


def get_loader(config_uri, protocols=None):
    """
    Find a :class:`plaster.ILoader` object capable of handling ``config_uri``.

    :param config_uri: Anything that can be parsed by
        :func:`plaster.parse_uri`.

    :param protocols: Zero or more :term:`loader protocol` identifiers that
        the loader must implement to match the desired ``config_uri``.

    :returns: A :class:`plaster.ILoader` object.
    :raises plaster.LoaderNotFound: If no loader could be found.
    :raises plaster.MultipleLoadersFound: If multiple loaders match the
        requested criteria. If this happens, you can disambiguate the lookup
        by appending the package name to the scheme for the loader you wish
        to use. For example if ``ini`` is ambiguous then specify
        ``ini+myapp`` to use the ini loader from the ``myapp`` package.

    """
    config_uri = parse_uri(config_uri)
    requested_scheme = config_uri.scheme

    matched_loaders = find_loaders(requested_scheme, protocols=protocols)

    if len(matched_loaders) < 1:
        raise LoaderNotFound(requested_scheme, protocols=protocols)

    if len(matched_loaders) > 1:
        raise MultipleLoadersFound(
            requested_scheme, matched_loaders, protocols=protocols
        )

    loader_info = matched_loaders[0]
    loader = loader_info.load(config_uri)

    return loader


def find_loaders(scheme, protocols=None):
    """
    Find all loaders that match the requested scheme and protocols.

    :param scheme: Any valid scheme. Examples would be something like ``ini``
        or ``pastedeploy+ini``.

    :param protocols: Zero or more :term:`loader protocol` identifiers that
        the loader must implement. If ``None`` then only generic loaders will
        be returned.

    :returns: A list containing zero or more :class:`plaster.ILoaderInfo`
        objects.

    """
    # build a list of all required entry points
    matching_groups = ["plaster.loader_factory"]

    if protocols:
        matching_groups += [f"plaster.{proto}_loader_factory" for proto in protocols]
    scheme = scheme.lower()

    # if a distribution is specified then it overrides the default search
    parts = scheme.split("+", 1)

    if len(parts) == 2:
        try:
            dist = metadata.distribution(parts[0])
        except metadata.PackageNotFoundError:
            pass
        else:
            ep = _find_ep_in_dist(dist, parts[1], matching_groups)

            # if we got one or more loaders from a specific distribution
            # then they override everything else so we'll just return them
            if ep:
                return [EntryPointLoaderInfo(dist, ep, protocols)]

    return [
        EntryPointLoaderInfo(dist, ep, protocols=protocols)
        for (dist, ep) in _iter_ep_in_dists(scheme, matching_groups)
    ]


def _iter_ep_in_dists(scheme, groups):
    # XXX this is a hack to deduplicate distributions because importlib.metadata
    # does not do this for us by default, at least up to Python 3.11.
    # Specifically, if ``lib`` is symlinked to ``lib64`` then a Distribution
    # object will be returned for each path, causing duplicate entry points
    # to be found.
    #
    # See https://github.com/Pylons/plaster/issues/25
    dups = set()
    for dist in metadata.distributions():
        name = dist.metadata["Name"]
        if name in dups:  # pragma: no cover
            continue
        dups.add(name)

        ep = _find_ep_in_dist(dist, scheme, groups)
        if ep:
            yield (dist, ep)


def _find_ep_in_dist(dist, scheme, groups):
    entry_points = [
        entry_point
        for entry_point in dist.entry_points
        if entry_point.group in groups
        and (scheme is None or scheme == entry_point.name.lower())
    ]

    # verify that the entry point from each group points to the same factory
    if len({ep.value for ep in entry_points}) == 1:
        return entry_points[0]


class EntryPointLoaderInfo(ILoaderInfo):
    def __init__(self, dist, ep, protocols=None):
        self.entry_point = ep
        self.scheme = "{}+{}".format(
            dist.metadata["name"] if "name" in dist.metadata else "Unknown", ep.name
        )
        self.protocols = protocols

        self._factory = None

    @property
    def factory(self):
        if self._factory is None:
            self._factory = self.entry_point.load()

        return self._factory

    def load(self, config_uri):
        config_uri = parse_uri(config_uri)

        return self.factory(config_uri)
0707010000001C000081A4000000000000000000000001637AD73300000F07000000000000000000000000000000000000002700000000plaster-1.1.2/src/plaster/protocols.pyimport abc


class IWSGIProtocol(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def get_wsgi_app(self, name=None, defaults=None):
        """
        Create a WSGI application object.

        An example application object may be:

        .. code-block:: python

            def app(environ, start_response):
                start_response(b'200 OK', [(b'Content-Type', b'text/plain')])
                yield [b'hello world\\n']

        :param name: The name of the application referenced in the config.
            If ``None`` then it should default to the
            :attr:`plaster.PlasterURL.fragment`, if available.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning the
            final configuration dictionary.

        :raises LookupError: If a WSGI application cannot be found by the
            specified name.

        """

    @abc.abstractmethod
    def get_wsgi_app_settings(self, name=None, defaults=None):
        """
        Return the settings for a WSGI application.

        This is similar to :meth:`plaster.ILoader.get_settings` for a
        WSGI application.

        :param name: The name of the application referenced in the config.
            If ``None`` then it should default to the
            :attr:`plaster.PlasterURL.fragment`, if available.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning the
            final configuration dictionary.

        :raises LookupError: If a WSGI application cannot be found by the
            specified name.

        """

    @abc.abstractmethod
    def get_wsgi_filter(self, name=None, defaults=None):
        """
        Create a composable WSGI middleware object.

        An example middleware filter may be:

        .. code-block:: python

            class Filter(object):
                def __init__(self, app):
                    self.app = app

                def __call__(self, environ, start_response):
                    return self.app(environ, start_response)

        :param name: The name of the application referenced in the config.
            If ``None`` then it should default to the
            :attr:`plaster.PlasterURL.fragment`, if available.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning the
            final configuration dictionary.

        :raises LookupError: If a WSGI filter cannot be found by the
            specified name.

        """

    @abc.abstractmethod
    def get_wsgi_server(self, name=None, defaults=None):
        """
        Create a WSGI server runner.

        An example server runner may be:

        .. code-block:: python

            def runner(app):
                from wsgiref.simple_server import make_server
                server = make_server('0.0.0.0', 8080, app)
                server.serve_forever()

        :param name: The name of the application referenced in the config.
            If ``None`` then it should default to the
            :attr:`plaster.PlasterURL.fragment`, if available.

        :param defaults: A ``dict`` of default values used to populate the
            settings and support variable interpolation. Any values in
            ``defaults`` may be overridden by the loader prior to returning the
            final configuration dictionary.

        :raises LookupError: If a WSGI server cannot be found by the
            specified name.

        """
0707010000001D000081A4000000000000000000000001637AD73300000E98000000000000000000000000000000000000002100000000plaster-1.1.2/src/plaster/uri.pyfrom collections import OrderedDict
import os.path
from urllib import parse

from .exceptions import InvalidURI


class PlasterURL:
    """
    Represents the components of a URL used to locate a
    :class:`plaster.ILoader`.

    :ivar scheme: The name of the loader backend.

    :ivar path: The loader-specific path string.
        This is the entirety of the ``config_uri`` passed to
        :func:`plaster.parse_uri` without the scheme, fragment and options.
        If this value is falsey it is replaced with an empty string.

    :ivar options: A dictionary of options parsed from the query string as
        url-encoded key=value pairs.

    :ivar fragment: A loader-specific default section name.
        This parameter may be used by loaders in scenarios where they provide
        APIs that support a default name. For example, a loader that provides
        ``get_wsgi_app`` may use the fragment to determine the name of the
        section containing the WSGI app if none was explicitly defined.
        If this value is falsey it is replaced with an empty string.

    """

    def __init__(self, scheme, path="", options=None, fragment=""):
        self.scheme = scheme

        if not path:
            path = ""
        self.path = path

        if options is None:
            options = {}
        self.options = options

        if not fragment:
            fragment = ""
        self.fragment = fragment

    def __str__(self):
        result = "{0.scheme}://{0.path}".format(self)

        if self.options:
            result += "?" + parse.urlencode(self.options)

        if self.fragment:
            result += "#" + self.fragment

        return result

    def __repr__(self):
        return f"PlasterURL('{self}')"


def parse_uri(config_uri):
    """
    Parse the ``config_uri`` into a :class:`plaster.PlasterURL` object.

    ``config_uri`` can be a relative or absolute file path such as
    ``development.ini`` or ``/path/to/development.ini``. The file must have
    an extension that can be handled by a :class:`plaster.ILoader`
    registered with the system.

    Alternatively, ``config_uri`` may be a :rfc:`1738`-style string.

    """

    if isinstance(config_uri, PlasterURL):
        return config_uri

    # force absolute paths to look like a uri for more accurate parsing
    # we throw away the dummy scheme later and parse it from the resolved
    # path extension
    isabs = os.path.isabs(config_uri)

    if isabs:
        config_uri = "dummy://" + config_uri

    # check if the uri is actually a url
    parts = parse.urlparse(config_uri)

    # reconstruct the path without the scheme and fragment
    path = parse.ParseResult(
        scheme="",
        netloc=parts.netloc,
        path=parts.path,
        params="",
        query="",
        fragment="",
    ).geturl()
    # strip off leading //

    if path.startswith("//"):
        path = path[2:]

    if parts.scheme and not isabs:
        scheme = parts.scheme

    else:
        scheme = os.path.splitext(path)[1]

        if scheme.startswith("."):
            scheme = scheme[1:]

        # tag uris coming from file extension as file+scheme

        if scheme:
            scheme = "file+" + scheme

    query = parts.query if parts.query else None
    options = OrderedDict()

    if query:
        options.update(parse.parse_qsl(query))
    fragment = parts.fragment if parts.fragment else None

    if not scheme:
        raise InvalidURI(
            config_uri,
            (
                "Could not determine the loader scheme for the supplied "
                'config_uri "{}"'.format(config_uri)
            ),
        )

    return PlasterURL(scheme=scheme, path=path, options=options, fragment=fragment)
0707010000001E000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000001400000000plaster-1.1.2/tests0707010000001F000081A4000000000000000000000001637AD73300000000000000000000000000000000000000000000002000000000plaster-1.1.2/tests/__init__.py07070100000020000081A4000000000000000000000001637AD733000001E7000000000000000000000000000000000000002000000000plaster-1.1.2/tests/conftest.pyimport os.path
import sys

import pytest


@pytest.fixture(scope="session")
def fake_packages():
    # we'd like to keep this scope more focused but it's proven really
    # difficult to fully monkeypatch importlib.metadata and so for now we just
    # install the packages for the duration of the test suite
    test_dir = os.path.dirname(__file__)

    for name in ("app1", "app2"):
        info_dir = os.path.join(test_dir, "fake_packages", name)
        sys.path.insert(0, info_dir)
07070100000021000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002400000000plaster-1.1.2/tests/example_configs07070100000022000081A4000000000000000000000001637AD73300000052000000000000000000000000000000000000003500000000plaster-1.1.2/tests/example_configs/test_default.ini[DEFAULT]
a = 1
b = 2

[main]
foo = bar
baz = %(a)s

[other]
foo = baz
bar = %(c)s07070100000023000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002200000000plaster-1.1.2/tests/fake_packages07070100000024000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002700000000plaster-1.1.2/tests/fake_packages/app107070100000025000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002C00000000plaster-1.1.2/tests/fake_packages/app1/app107070100000026000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000003500000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info07070100000027000081A4000000000000000000000001637AD733000000B0000000000000000000000000000000000000003E00000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info/PKG-INFOMetadata-Version: 1.0
Name: app1
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
07070100000028000081A4000000000000000000000001637AD733000000B8000000000000000000000000000000000000004100000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info/SOURCES.txtsetup.py
app1/__init__.py
app1/loaders.py
app1.egg-info/PKG-INFO
app1.egg-info/SOURCES.txt
app1.egg-info/dependency_links.txt
app1.egg-info/entry_points.txt
app1.egg-info/top_level.txt07070100000029000081A4000000000000000000000001637AD73300000001000000000000000000000000000000000000004A00000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info/dependency_links.txt
0707010000002A000081A4000000000000000000000001637AD733000002AC000000000000000000000000000000000000004600000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info/entry_points.txt[other.loader]
ini = app1.loaders:WontBeLoaded

[plaster.dummy1_loader_factory]
file+ini = app1.loaders:INIWSGILoader
ini = app1.loaders:INIWSGILoader

[plaster.dummy2_loader_factory]
file+ini = app1.loaders:INILoader
ini = app1.loaders:INILoader

[plaster.loader_factory]
bad = app1.loaders:BadLoader
broken = app1.loaders.BadLoader
conf = app1.loaders:ConfLoader
dup = app1.loaders:DuplicateLoader
file+conf = app1.loaders:ConfLoader
file+ini = app1.loaders:INIWSGILoader
file+yaml = app1.loaders:YAMLLoader
ini = app1.loaders:INIWSGILoader
yaml+foo = app1.loaders:YAMLFooLoader

[plaster.wsgi_loader_factory]
file+ini = app1.loaders:INIWSGILoader
ini = app1.loaders:INIWSGILoader

0707010000002B000081A4000000000000000000000001637AD73300000005000000000000000000000000000000000000004300000000plaster-1.1.2/tests/fake_packages/app1/app1.egg-info/top_level.txtapp1
0707010000002C000081A4000000000000000000000001637AD73300000000000000000000000000000000000000000000003800000000plaster-1.1.2/tests/fake_packages/app1/app1/__init__.py0707010000002D000081A4000000000000000000000001637AD73300000678000000000000000000000000000000000000003700000000plaster-1.1.2/tests/fake_packages/app1/app1/loaders.pyimport plaster
from plaster.protocols import IWSGIProtocol

_SECTIONS = {"a": {"foo": "bar"}, "b": {"baz": "xyz"}}


class LoaderBase(plaster.ILoader):
    entry_point_key = None

    def __init__(self, uri):
        self.uri = uri

    def get_sections(self):
        return list(_SECTIONS.keys())

    def get_settings(self, section=None, defaults=None):
        if section is None:
            section = self.uri.fragment
        if defaults is not None:
            result = defaults.copy()
        else:
            result = {}
        try:
            result.update(_SECTIONS[section])
        except KeyError:
            pass
        return result

    def setup_logging(self, defaults=None):
        self.logging_setup = True
        self.logging_defaults = defaults


class ConfLoader(LoaderBase):
    entry_point_key = "conf"


class INILoader(LoaderBase):
    entry_point_key = "ini"


class INIWSGILoader(IWSGIProtocol, LoaderBase):
    entry_point_key = "ini+wsgi"

    def get_wsgi_app(self, name=None, defaults=None):
        return "wsgi app"

    def get_wsgi_app_settings(self, name=None, defaults=None):
        return {"a": "b"}

    def get_wsgi_filter(self, name=None, defaults=None):
        return "wsgi filter"

    def get_wsgi_server(self, name=None, defaults=None):
        return "wsgi server"


class YAMLLoader(LoaderBase):
    entry_point_key = "yaml"


class YAMLFooLoader(LoaderBase):
    entry_point_key = "yaml+foo"


class DuplicateLoader(LoaderBase):
    entry_point_key = "app1+dup"


class BadLoader:
    def __init__(self, uri):
        self.uri = uri


class WontBeLoaded(LoaderBase):
    entry_point_key = "ini"
0707010000002E000081A4000000000000000000000001637AD73300000460000000000000000000000000000000000000003000000000plaster-1.1.2/tests/fake_packages/app1/setup.pyfrom setuptools import find_packages, setup

setup(
    name="app1",
    version="1.0",
    packages=find_packages(),
    entry_points={
        "plaster.loader_factory": [
            "conf=app1.loaders:ConfLoader",
            "file+conf=app1.loaders:ConfLoader",
            "ini=app1.loaders:INIWSGILoader",
            "file+ini=app1.loaders:INIWSGILoader",
            "file+yaml=app1.loaders:YAMLLoader",
            "yaml+foo=app1.loaders:YAMLFooLoader",
            "dup=app1.loaders:DuplicateLoader",
            "bad=app1.loaders:BadLoader",
            "broken=app1.loaders.BadLoader",
        ],
        "plaster.wsgi_loader_factory": [
            "ini=app1.loaders:INIWSGILoader",
            "file+ini=app1.loaders:INIWSGILoader",
        ],
        "plaster.dummy1_loader_factory": [
            "ini=app1.loaders:INIWSGILoader",
            "file+ini=app1.loaders:INIWSGILoader",
        ],
        "plaster.dummy2_loader_factory": [
            "ini=app1.loaders:INILoader",
            "file+ini=app1.loaders:INILoader",
        ],
        "other.loader": ["ini=app1.loaders:WontBeLoaded"],
    },
)
0707010000002F000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002700000000plaster-1.1.2/tests/fake_packages/app207070100000030000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000002C00000000plaster-1.1.2/tests/fake_packages/app2/app207070100000031000041ED000000000000000000000002637AD73300000000000000000000000000000000000000000000003500000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info07070100000032000081A4000000000000000000000001637AD733000000B0000000000000000000000000000000000000003E00000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info/PKG-INFOMetadata-Version: 1.0
Name: app2
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
07070100000033000081A4000000000000000000000001637AD733000000B8000000000000000000000000000000000000004100000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info/SOURCES.txtsetup.py
app2/__init__.py
app2/loaders.py
app2.egg-info/PKG-INFO
app2.egg-info/SOURCES.txt
app2.egg-info/dependency_links.txt
app2.egg-info/entry_points.txt
app2.egg-info/top_level.txt07070100000034000081A4000000000000000000000001637AD73300000001000000000000000000000000000000000000004A00000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info/dependency_links.txt
07070100000035000081A4000000000000000000000001637AD73300000063000000000000000000000000000000000000004600000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info/entry_points.txt[plaster.loader_factory]
dup = app2.loaders:DuplicateLoader
yaml+bar = app2.loaders:YAMLBarLoader

07070100000036000081A4000000000000000000000001637AD73300000005000000000000000000000000000000000000004300000000plaster-1.1.2/tests/fake_packages/app2/app2.egg-info/top_level.txtapp2
07070100000037000081A4000000000000000000000001637AD73300000000000000000000000000000000000000000000003800000000plaster-1.1.2/tests/fake_packages/app2/app2/__init__.py07070100000038000081A4000000000000000000000001637AD73300000366000000000000000000000000000000000000003700000000plaster-1.1.2/tests/fake_packages/app2/app2/loaders.pyimport plaster

_SECTIONS = {"a": {"foo": "bar"}, "b": {"baz": "xyz"}}


class LoaderBase(plaster.ILoader):
    entry_point_key = None

    def __init__(self, uri):
        self.uri = uri

    def get_sections(self):
        return list(_SECTIONS.keys())

    def get_settings(self, section=None, defaults=None):
        if section is None:
            section = self.uri.fragment
        if defaults is not None:
            result = defaults.copy()
        else:
            result = {}
        try:
            result.update(_SECTIONS[section])
        except KeyError:
            pass
        return result

    def setup_logging(self, defaults=None):
        self.logging_setup = True
        self.logging_defaults = defaults


class YAMLBarLoader(LoaderBase):
    entry_point_key = "yaml+bar"


class DuplicateLoader(LoaderBase):
    entry_point_key = "app2+dup"
07070100000039000081A4000000000000000000000001637AD73300000123000000000000000000000000000000000000003000000000plaster-1.1.2/tests/fake_packages/app2/setup.pyfrom setuptools import find_packages, setup

setup(
    name="app2",
    version="1.0",
    packages=find_packages(),
    entry_points={
        "plaster.loader_factory": [
            "dup=app2.loaders:DuplicateLoader",
            "yaml+bar=app2.loaders:YAMLBarLoader",
        ]
    },
)
0707010000003A000081A4000000000000000000000001637AD733000010D5000000000000000000000000000000000000002700000000plaster-1.1.2/tests/test_exceptions.pyclass TestInvalidURI:
    def _makeOne(self, *args, **kwargs):
        from plaster.exceptions import InvalidURI

        return InvalidURI(*args, **kwargs)

    def test_it(self):
        exc = self._makeOne("foo")
        assert isinstance(exc, ValueError)
        assert exc.message == 'Unable to parse config_uri "foo".'
        assert exc.uri == "foo"

    def test_it_overrides_message(self):
        exc = self._makeOne("foo", message="bar")
        assert isinstance(exc, ValueError)
        assert exc.message == "bar"
        assert exc.uri == "foo"


class TestLoaderNotFound:
    def _makeOne(self, *args, **kwargs):
        from plaster.exceptions import LoaderNotFound

        return LoaderNotFound(*args, **kwargs)

    def test_it(self):
        exc = self._makeOne("foo")
        assert isinstance(exc, ValueError)
        assert exc.scheme == "foo"
        assert exc.protocols is None
        assert exc.message == ('Could not find a matching loader for the scheme "foo".')

    def test_it_with_protocol(self):
        exc = self._makeOne("foo", ["wsgi"])
        assert isinstance(exc, ValueError)
        assert exc.scheme == "foo"
        assert exc.protocols == ["wsgi"]
        assert exc.message == (
            'Could not find a matching loader for the scheme "foo", ' 'protocol "wsgi".'
        )

    def test_it_with_multiple_protocols(self):
        exc = self._makeOne("foo", ["wsgi", "qt"])
        assert isinstance(exc, ValueError)
        assert exc.scheme == "foo"
        assert exc.protocols == ["wsgi", "qt"]
        assert exc.message == (
            'Could not find a matching loader for the scheme "foo", '
            'protocol "wsgi, qt".'
        )

    def test_it_overrides_message(self):
        exc = self._makeOne("foo", message="bar")
        assert isinstance(exc, ValueError)
        assert exc.scheme == "foo"
        assert exc.protocols is None
        assert exc.message == "bar"


class TestMultipleLoadersFound:
    def _makeOne(self, *args, **kwargs):
        from plaster.exceptions import MultipleLoadersFound

        return MultipleLoadersFound(*args, **kwargs)

    def test_it(self):
        dummy1 = DummyLoaderInfo("dummy1")
        dummy2 = DummyLoaderInfo("dummy2")
        exc = self._makeOne("https", [dummy1, dummy2])
        assert isinstance(exc, ValueError)
        assert exc.message == (
            'Multiple plaster loaders were found for scheme "https". '
            "Please specify a more specific config_uri. Matched loaders: "
            "dummy1, dummy2"
        )
        assert exc.scheme == "https"
        assert exc.protocols is None
        assert exc.loaders == [dummy1, dummy2]

    def test_it_with_protocol(self):
        dummy1 = DummyLoaderInfo("dummy1")
        dummy2 = DummyLoaderInfo("dummy2")
        exc = self._makeOne("https", [dummy1, dummy2], protocols=["wsgi"])
        assert isinstance(exc, ValueError)
        assert exc.message == (
            'Multiple plaster loaders were found for scheme "https", '
            'protocol "wsgi". Please specify a more specific config_uri. '
            "Matched loaders: dummy1, dummy2"
        )
        assert exc.scheme == "https"
        assert exc.protocols == ["wsgi"]
        assert exc.loaders == [dummy1, dummy2]

    def test_it_with_multiple_protocols(self):
        dummy1 = DummyLoaderInfo("dummy1")
        dummy2 = DummyLoaderInfo("dummy2")
        exc = self._makeOne("https", [dummy1, dummy2], protocols=["wsgi", "qt"])
        assert isinstance(exc, ValueError)
        assert exc.message == (
            'Multiple plaster loaders were found for scheme "https", '
            'protocol "wsgi, qt". Please specify a more specific '
            "config_uri. Matched loaders: dummy1, dummy2"
        )
        assert exc.scheme == "https"
        assert exc.protocols == ["wsgi", "qt"]
        assert exc.loaders == [dummy1, dummy2]

    def test_it_overrides_message(self):
        dummy = object()
        exc = self._makeOne("https", [dummy], message="foo")
        assert isinstance(exc, ValueError)
        assert exc.message == "foo"
        assert exc.scheme == "https"
        assert exc.protocols is None
        assert exc.loaders == [dummy]


class DummyLoaderInfo:
    def __init__(self, scheme):
        self.scheme = scheme
0707010000003B000081A4000000000000000000000001637AD733000018FE000000000000000000000000000000000000002400000000plaster-1.1.2/tests/test_loaders.pyimport pytest


@pytest.mark.usefixtures("fake_packages")
class Test_get_loader:
    def _callFUT(self, *args, **kwargs):
        from plaster.loaders import get_loader

        return get_loader(*args, **kwargs)

    def test_simple_uri(self):
        loader = self._callFUT("development.conf")
        assert loader.entry_point_key == "conf"

    def test_scheme_uri(self):
        loader = self._callFUT("conf://development.conf")
        assert loader.entry_point_key == "conf"

    def test_scheme_uri_for_pkg(self):
        loader = self._callFUT("app1+conf://")
        assert loader.entry_point_key == "conf"

    def test_path_with_extension(self):
        loader = self._callFUT("development.ini")
        assert loader.entry_point_key == "ini+wsgi"

    def test_path_with_extension_and_protocol(self):
        loader = self._callFUT("development.ini", protocols=["wsgi"])
        assert loader.entry_point_key == "ini+wsgi"

    def test_dup(self):
        from plaster.exceptions import MultipleLoadersFound

        with pytest.raises(MultipleLoadersFound):
            self._callFUT("dup://development.ini")

    def test_dedup_app1(self):
        loader = self._callFUT("app1+dup://development.ini")
        assert loader.entry_point_key == "app1+dup"

    def test_dedup_app2(self):
        loader = self._callFUT("app2+dup://development.ini")
        assert loader.entry_point_key == "app2+dup"

    def test_other_groups(self):
        from plaster.exceptions import LoaderNotFound

        with pytest.raises(LoaderNotFound):
            self._callFUT("other-scheme://development.ini")

    def test_bad(self):
        from app1.loaders import BadLoader

        loader = self._callFUT("bad:development")
        assert isinstance(loader, BadLoader)

    def test_it_broken(self):
        with pytest.raises(Exception):
            self._callFUT("development.broken")

    def test_it_notfound(self):
        from plaster.exceptions import LoaderNotFound

        with pytest.raises(LoaderNotFound):
            self._callFUT("development.notfound")

    def test_fallback_non_pkg_scheme(self):
        loader = self._callFUT("yaml+bar://development.yml")
        assert loader.entry_point_key == "yaml+bar"


@pytest.mark.usefixtures("fake_packages")
class Test_find_loaders:
    def _callFUT(self, *args, **kwargs):
        from plaster.loaders import find_loaders

        return find_loaders(*args, **kwargs)

    def test_simple_uri(self):
        loaders = self._callFUT("conf")
        assert len(loaders) == 1
        assert loaders[0].scheme == "app1+conf"
        loader = loaders[0].load("development.conf")
        assert loader.entry_point_key == "conf"

    def test_case_insensitive_scheme(self):
        loaders = self._callFUT("CONF")
        assert len(loaders) == 1
        assert loaders[0].scheme == "app1+conf"
        loader = loaders[0].load("development.conf")
        assert loader.entry_point_key == "conf"

    def test_scheme_specific_uri(self):
        loaders = self._callFUT("ini")
        assert len(loaders) == 1
        assert loaders[0].scheme == "app1+ini"
        loader = loaders[0].load("development.ini")
        assert loader.entry_point_key == "ini+wsgi"

    def test_multiple_yaml_loaders(self):
        loaders = self._callFUT("dup")
        assert len(loaders) == 2
        schemes = {loader.scheme for loader in loaders}
        assert "app1+dup" in schemes
        assert "app2+dup" in schemes

    def test_one_protocol(self):
        loaders = self._callFUT("ini", protocols=["wsgi"])
        assert len(loaders) == 1
        loader = loaders[0].load("development.ini")
        assert loader.entry_point_key == "ini+wsgi"

    def test_multiple_protocols(self):
        loaders = self._callFUT("ini", protocols=["wsgi", "dummy1"])
        assert len(loaders) == 1
        loader = loaders[0].load("development.ini")
        assert loader.entry_point_key == "ini+wsgi"

    def test_multiple_incompatible_protocols(self):
        loaders = self._callFUT("ini", protocols=["wsgi", "dummy2"])
        assert len(loaders) == 0

    def test_it_notfound(self):
        loaders = self._callFUT("notfound")
        assert len(loaders) == 0


@pytest.mark.usefixtures("fake_packages")
class Test_get_sections:
    def _callFUT(self, config_uri):
        from plaster.loaders import get_sections

        return get_sections(config_uri)

    def test_it(self):
        result = self._callFUT("development.ini")
        assert set(result) == {"a", "b"}

    def test_it_bad(self):
        with pytest.raises(Exception):
            self._callFUT("development.bad")


@pytest.mark.usefixtures("fake_packages")
class Test_get_settings:
    def _callFUT(self, config_uri, section=None, defaults=None):
        from plaster.loaders import get_settings

        return get_settings(config_uri, section=section, defaults=defaults)

    def test_it_explicit_a(self):
        result = self._callFUT("development.ini", "a")
        assert result == {"foo": "bar"}

    def test_it_explicit_b(self):
        result = self._callFUT("development.ini", "b")
        assert result == {"baz": "xyz"}

    def test_it_fragment(self):
        result = self._callFUT("development.ini#a")
        assert result == {"foo": "bar"}

    def test_defaults(self):
        result = self._callFUT("development.ini", "a", {"baz": "foo"})
        assert result == {"foo": "bar", "baz": "foo"}

    def test_invalid_section(self):
        result = self._callFUT("development.ini", "c")
        assert result == {}

    def test_it_bad(self):
        with pytest.raises(Exception):
            self._callFUT("development.bad")


@pytest.mark.usefixtures("fake_packages")
class Test_setup_logging:
    def _makeOne(self, config_uri):
        from plaster.loaders import get_loader

        return get_loader(config_uri)

    def _callFUT(self, config_uri, defaults=None):
        from plaster.loaders import setup_logging

        return setup_logging(config_uri, defaults=defaults)

    def test_it(self):
        loader = self._makeOne("development.ini#a")
        loader.setup_logging()
        assert loader.logging_setup
        assert loader.logging_defaults is None

    def test_it_top_level(self):
        self._callFUT("development.ini#a")

    def test_it_bad(self):
        with pytest.raises(Exception):
            self._callFUT("bad://development.ini")
0707010000003C000081A4000000000000000000000001637AD73300000427000000000000000000000000000000000000002600000000plaster-1.1.2/tests/test_protocols.pydef test_wsgi_protocol():
    from plaster.protocols import IWSGIProtocol

    class WSGILoader(IWSGIProtocol):  # pragma: no cover
        def get_wsgi_app(self, name=None, defaults=None):
            def app(environ, start_response):
                start_response(b"200 OK", [(b"Content-Type", b"text/plain")])
                return [b"hello world"]

            return app

        def get_wsgi_app_settings(self, name=None, defaults=None):
            settings = defaults.copy() if defaults else {}
            return settings

        def get_wsgi_filter(self, name=None, defaults=None):
            def filter(app):
                def wrapper(environ, start_response):
                    return app(environ, start_response)

                return wrapper

            return filter

        def get_wsgi_server(self, name=None, defaults=None):
            def server(app):
                from wsgiref.simple_server import make_server

                server = make_server("0.0.0.0", 8080, app)
                server.serve_forever()

    WSGILoader()
0707010000003D000081A4000000000000000000000001637AD73300000BE6000000000000000000000000000000000000002000000000plaster-1.1.2/tests/test_uri.pyimport os.path

import pytest


class TestURL:
    def _callFUT(self, uri):
        from plaster.uri import parse_uri

        return parse_uri(uri)

    def test_relative_path(self):
        uri = self._callFUT("development.ini")
        assert uri.scheme == "file+ini"
        assert uri.path == "development.ini"
        assert uri.options == {}
        assert uri.fragment == ""

    def test_absolute_path(self):
        path = os.path.abspath("/path/to/development.ini")
        uri = self._callFUT(path)
        assert uri.scheme == "file+ini"
        assert uri.path == path
        assert uri.options == {}
        assert uri.fragment == ""

    def test_absolute_path_with_fragment(self):
        path = os.path.abspath("/path/to/development.ini")
        uri = self._callFUT(path + "?a=b&c=d#main")
        assert uri.scheme == "file+ini"
        assert uri.path == path
        assert uri.options == {"a": "b", "c": "d"}
        assert uri.fragment == "main"

    def test_url(self):
        uri = self._callFUT("redis://username@password:localhost/foo?a=b#main")
        assert uri.scheme == "redis"
        assert uri.path == "username@password:localhost/foo"
        assert uri.options == {"a": "b"}
        assert uri.fragment == "main"

    def test_url_for_file(self):
        uri = self._callFUT("pastedeploy+ini://development.ini")
        assert uri.scheme == "pastedeploy+ini"
        assert uri.path == "development.ini"
        assert uri.options == {}
        assert uri.fragment == ""

    def test_missing_scheme(self):
        from plaster.exceptions import InvalidURI

        with pytest.raises(InvalidURI):
            self._callFUT("foo")

    def test___str__(self):
        uri = self._callFUT("development.ini")
        assert str(uri) == "file+ini://development.ini"

    def test___str___with_options(self):
        uri = self._callFUT("development.ini?a=b&c=d")
        assert str(uri) == "file+ini://development.ini?a=b&c=d"

    def test___str___with_fragment(self):
        uri = self._callFUT("development.ini#main")
        assert str(uri) == "file+ini://development.ini#main"

    def test___repr___(self):
        uri = self._callFUT("development.ini#main")
        assert repr(uri) == "PlasterURL('file+ini://development.ini#main')"

    def test_returns_same_instance(self):
        uri1 = self._callFUT("development.ini")
        uri2 = self._callFUT(uri1)
        assert uri1 is uri2

    def test_colon_prefix_scheme(self):
        uri = self._callFUT("egg:myapp#main")
        assert uri.scheme == "egg"
        assert uri.path == "myapp"
        assert uri.fragment == "main"

    def test_only_scheme(self):
        uri = self._callFUT("egg:")
        assert uri.scheme == "egg"
        assert uri.path == ""
        assert uri.options == {}
        assert uri.fragment == ""


def test_default_url_values():
    from plaster.uri import PlasterURL

    url = PlasterURL("foo")
    assert url.scheme == "foo"
    assert url.path == ""
    assert url.options == {}
    assert url.fragment == ""
0707010000003E000081A4000000000000000000000001637AD7330000056F000000000000000000000000000000000000001600000000plaster-1.1.2/tox.ini[tox]
envlist =
    lint,
    py37,py38,py39,py310,py311,pypy3,
    docs,coverage

[testenv]
commands =
    py.test --cov --cov-report= {posargs:}
extras =
    testing
setenv =
    COVERAGE_FILE=.coverage.{envname}

[testenv:coverage]
skip_install = true
commands =
    coverage combine
    coverage xml
    coverage report --fail-under=100
deps =
    coverage
setenv =
    COVERAGE_FILE=.coverage

[testenv:docs]
whitelist_externals = make
commands =
    make -C docs html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E"
extras =
    docs

[testenv:lint]
skip_install = True
commands =
    isort --check-only --df src/plaster tests
    black --check --diff .
    flake8 src/plaster tests
    check-manifest
    # build sdist/wheel
    python -m build .
    twine check dist/*
deps =
    black
    build
    check-manifest
    flake8
    flake8-bugbear
    isort
    readme_renderer
    twine

[testenv:format]
skip_install = true
commands =
    isort src/plaster tests
    black .
deps =
    black
    isort

[testenv:build]
skip_install = true
commands =
    # clean up build/ and dist/ folders
    python -c 'import shutil; shutil.rmtree("build", ignore_errors=True)'
    # Make sure we aren't forgetting anything
    check-manifest
    # build sdist/wheel
    python -m build .
    # Verify all is well
    twine check dist/*

deps =
    build
    check-manifest
    readme_renderer
    twine
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!148 blocks
openSUSE Build Service is sponsored by