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