plaster-1.0/0000755000076500000240000000000013167325414013557 5ustar michaelstaff00000000000000plaster-1.0/PKG-INFO0000644000076500000240000001360613167325414014662 0ustar michaelstaff00000000000000Metadata-Version: 1.2 Name: plaster Version: 1.0 Summary: A loader interface around multiple config file formats. Home-page: https://docs.pylonsproject.org/projects/plaster/en/latest/ Author: Michael Merickel Author-email: pylons-discuss@googlegroups.com License: UNKNOWN Description: ======= plaster ======= .. image:: https://img.shields.io/pypi/v/plaster.svg :target: https://pypi.python.org/pypi/plaster .. image:: https://img.shields.io/travis/Pylons/plaster/master.svg :target: https://travis-ci.org/Pylons/plaster .. 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. 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+`` instead of simply ```` (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. Keywords: plaster pastedeploy ini config Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* plaster-1.0/rtd.txt0000644000076500000240000000001312727445043015105 0ustar michaelstaff00000000000000-e .[docs] plaster-1.0/CONTRIBUTING.rst0000644000076500000240000000576213117413167016230 0ustar michaelstaff00000000000000.. 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 plaster-1.0/tests/0000755000076500000240000000000013167325414014721 5ustar michaelstaff00000000000000plaster-1.0/tests/conftest.py0000644000076500000240000000105213066032455017114 0ustar michaelstaff00000000000000import os.path import pkg_resources import pytest import sys @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 pkg_resources 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) pkg_resources.working_set.add_entry(info_dir) plaster-1.0/tests/test_exceptions.py0000644000076500000240000001037213067114041020505 0ustar michaelstaff00000000000000class TestInvalidURI(object): 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(object): 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(object): 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(object): def __init__(self, scheme): self.scheme = scheme plaster-1.0/tests/__init__.py0000644000076500000240000000000012723732240017014 0ustar michaelstaff00000000000000plaster-1.0/tests/test_loaders.py0000644000076500000240000001443313114150260017753 0ustar michaelstaff00000000000000import pytest @pytest.mark.usefixtures('fake_packages') class Test_get_loader(object): 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(object): 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 = set([l.scheme for l 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(object): 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) == set(['a', 'b']) def test_it_bad(self): with pytest.raises(Exception): self._callFUT('development.bad') @pytest.mark.usefixtures('fake_packages') class Test_get_settings(object): 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(object): 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') plaster-1.0/tests/fake_packages/0000755000076500000240000000000013167325414017465 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app2/0000755000076500000240000000000013167325414020327 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app2/app2.egg-info/0000755000076500000240000000000013167325414022663 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app2/app2.egg-info/PKG-INFO0000644000076500000240000000026013113726453023755 0ustar michaelstaff00000000000000Metadata-Version: 1.0 Name: app2 Version: 1.0 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN plaster-1.0/tests/fake_packages/app2/app2.egg-info/SOURCES.txt0000644000076500000240000000027013113726453024545 0ustar michaelstaff00000000000000setup.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.txtplaster-1.0/tests/fake_packages/app2/app2.egg-info/entry_points.txt0000644000076500000240000000014313113726453026156 0ustar michaelstaff00000000000000[plaster.loader_factory] dup = app2.loaders:DuplicateLoader yaml+bar = app2.loaders:YAMLBarLoader plaster-1.0/tests/fake_packages/app2/app2.egg-info/top_level.txt0000644000076500000240000000000513113726453025407 0ustar michaelstaff00000000000000app2 plaster-1.0/tests/fake_packages/app2/app2.egg-info/dependency_links.txt0000644000076500000240000000000113113726453026730 0ustar michaelstaff00000000000000 plaster-1.0/tests/fake_packages/app2/app2/0000755000076500000240000000000013167325414021171 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app2/app2/loaders.py0000644000076500000240000000161713114150260023164 0ustar michaelstaff00000000000000import 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' plaster-1.0/tests/fake_packages/app2/app2/__init__.py0000644000076500000240000000000013065103407023261 0ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app2/setup.py0000644000076500000240000000044413066126036022040 0ustar michaelstaff00000000000000from setuptools import setup, find_packages setup( name="app2", version="1.0", packages=find_packages(), entry_points={ 'plaster.loader_factory': [ 'dup=app2.loaders:DuplicateLoader', 'yaml+bar=app2.loaders:YAMLBarLoader', ], }, ) plaster-1.0/tests/fake_packages/app1/0000755000076500000240000000000013167325414020326 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app1/setup.py0000644000076500000240000000216713114150260022031 0ustar michaelstaff00000000000000from setuptools import setup, find_packages 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', ], }, ) plaster-1.0/tests/fake_packages/app1/app1.egg-info/0000755000076500000240000000000013167325414022661 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app1/app1.egg-info/PKG-INFO0000644000076500000240000000026013113726461023752 0ustar michaelstaff00000000000000Metadata-Version: 1.0 Name: app1 Version: 1.0 Summary: UNKNOWN Home-page: UNKNOWN Author: UNKNOWN Author-email: UNKNOWN License: UNKNOWN Description: UNKNOWN Platform: UNKNOWN plaster-1.0/tests/fake_packages/app1/app1.egg-info/SOURCES.txt0000644000076500000240000000027013113726461024542 0ustar michaelstaff00000000000000setup.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.txtplaster-1.0/tests/fake_packages/app1/app1.egg-info/entry_points.txt0000644000076500000240000000125413114150260026144 0ustar michaelstaff00000000000000[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 plaster-1.0/tests/fake_packages/app1/app1.egg-info/top_level.txt0000644000076500000240000000000513113726461025404 0ustar michaelstaff00000000000000app1 plaster-1.0/tests/fake_packages/app1/app1.egg-info/dependency_links.txt0000644000076500000240000000000113113726461026725 0ustar michaelstaff00000000000000 plaster-1.0/tests/fake_packages/app1/app1/0000755000076500000240000000000013167325414021167 5ustar michaelstaff00000000000000plaster-1.0/tests/fake_packages/app1/app1/loaders.py0000644000076500000240000000325113114150260023156 0ustar michaelstaff00000000000000import 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(object): def __init__(self, uri): self.uri = uri class WontBeLoaded(LoaderBase): entry_point_key = 'ini' plaster-1.0/tests/fake_packages/app1/app1/__init__.py0000644000076500000240000000000013065103407023257 0ustar michaelstaff00000000000000plaster-1.0/tests/test_uri.py0000644000076500000240000000575413114150260017127 0ustar michaelstaff00000000000000import os.path import pytest class TestURL(object): 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 == '' plaster-1.0/tests/test_protocols.py0000644000076500000240000000204213066330375020354 0ustar michaelstaff00000000000000def 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() plaster-1.0/tests/example_configs/0000755000076500000240000000000013167325414020064 5ustar michaelstaff00000000000000plaster-1.0/tests/example_configs/test_default.ini0000644000076500000240000000012212726170561023244 0ustar michaelstaff00000000000000[DEFAULT] a = 1 b = 2 [main] foo = bar baz = %(a)s [other] foo = baz bar = %(c)splaster-1.0/MANIFEST.in0000644000076500000240000000042213125107660015306 0ustar michaelstaff00000000000000graft src graft tests graft docs include README.rst include CHANGES.rst include LICENSE.txt include CONTRIBUTING.rst include CONTRIBUTORS.txt include .coveragerc include tox.ini appveyor.yml .travis.yml rtd.txt recursive-exclude * __pycache__ *.py[cod] prune docs/_build plaster-1.0/.coveragerc0000644000076500000240000000030013066377664015705 0ustar michaelstaff00000000000000[run] parallel = true source = plaster tests omit = tests/fake_packages/* [paths] source = src/plaster */site-packages/plaster [report] show_missing = true precision = 2 plaster-1.0/docs/0000755000076500000240000000000013167325414014507 5ustar michaelstaff00000000000000plaster-1.0/docs/index.rst0000644000076500000240000002055413114151640016344 0ustar michaelstaff00000000000000======= plaster ======= ``plaster`` is a loader interface around arbitrary config file formats. It exists to define a common API for applications to use when they wish to load configuration settings. 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. The library helps your application find an appropriate loader based on a :term:`config uri` and a desired set of :term:`loader protocol` identifiers. Some possible ``config_uri`` formats: * ``development.ini`` * ``development.ini#myapp`` * ``development.ini?http_port=8080#main`` * ``ini://development.conf`` * ``pastedeploy+ini:///path/to/development.ini`` * ``pastedeploy+ini://development.ini#foo`` * ``egg:MyApp?debug=false#foo`` An example application that does not care what file format the settings are sourced from, as long as they are in a section named ``my-settings``: .. code-block:: python import plaster import sys if __name__ == '__main__': config_uri = sys.argv[1] settings = plaster.get_settings(config_uri, 'my-settings') This script can support any config format so long as the application (or the user) has installed the loader they expect to use. For example, ``pip install plaster_pastedeploy``. The loader is then found by :func:`plaster.get_settings` based on the specific :term:`config uri` provided. The application does not need to configure the loaders. They are discovered via `pkg_resources entrypoints `__ and registered for specific schemes. Protocols ========= ``plaster`` supports custom loader protocols which loaders may choose to implement to provide extra functionality over the basic :class:`plaster.ILoader` interface. A :term:`loader protocol` is intentionally very loosely defined but it basically boils down to a loader object that supports extra methods with agreed-upon signatures. Right now the only officially-supported protocol is ``wsgi`` which defines a loader that should implement the :class:`plaster.protocols.IWSGIProtocol` interface. Known Loaders ============= * `plaster_pastedeploy `__ **officially supported** File types: * ``file+ini``, ``ini``, ``pastedeploy+ini`` * ``egg``, ``pastedeploy+egg`` Protocols: * ``wsgi`` - :class:`plaster.protocols.IWSGIProtocol` Installation ============ Stable release -------------- To install plaster, run this command in your terminal: .. code-block:: console $ pip install plaster If you don't have `pip`_ installed, this `Python installation guide`_ can guide you through the process. .. _pip: https://pip.pypa.io .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ From sources ------------ The sources for plaster can be downloaded from the `Github repo`_. .. code-block:: console $ git clone https://github.com/Pylons/plaster.git Once you have a copy of the source, you can install it with: .. code-block:: console $ pip install -e . .. _Github repo: https://github.com/Pylons/plaster Usage ===== Loading settings ---------------- A goal of ``plaster`` is to allow a configuration source to be used for multiple purposes. For example, an INI file is split into separate sections which provide settings for separate applications. This works because each application can parse the INI file easily and pull out only the section it cares about. In order to load settings, use the :func:`plaster.get_settings`. The application may accept a path to a config file, allowing the user to specify the name of the section (``myapp``) to be loaded: .. code-block:: python import plaster config_uri = 'development.ini#myapp' settings = plaster.get_settings(config_uri) Alternatively, the application may depend on a specifically named section: .. code-block:: python import plaster config_uri = 'development.ini#myapp' settings = plaster.get_settings(config_uri, section='thisapp') Configuring logging ------------------- ``plaster`` requires a :term:`loader` to provide a way to configure Python's stdlib logging module. In order to utilize this feature, simply call :func:`plaster.setup_logging` from your application. .. code-block:: python import plaster config_uri = 'redis://username@password:hostname/db?opt=val' plaster.setup_logging(config_uri) Finding a loader ---------------- At the heart of ``plaster`` is the ``config_uri`` format. This format is basically ``://`` with a few variations. The ``scheme`` is used to find an :class:`plaster.ILoaderFactory`. .. code-block:: python import plaster config_uri = 'pastedeploy+ini://development.ini#myapp' loader = plaster.get_loader(config_uri, protocols=['wsgi']) settings = loader.get_settings() A ``config_uri`` may be a file path or an :rfc:`3986` URI. In the case of a file path, the file extension is used as the scheme. In either case the scheme and the protocols are the only items that ``plaster`` cares about with respect to finding an :class:`plaster.ILoaderFactory`. It's also possible to lookup the exact loader by prefixing the scheme with the name of the package containing the loader: .. code-block:: python settings = plaster.get_settings('plaster_pastedeploy+ini://') Writing your own loader ----------------------- ``plaster`` finds loaders registered for the ``plaster.loader_factory`` entry point in your ``setup.py``: .. code-block:: python from setuptools import setup setup( name='myapp', # ... entry_points={ 'plaster.loader_factory': [ 'dict = myapp:Loader', ], }, ) In this example the importable ``myapp.Loader`` class will be used as :class:`plaster.ILoaderFactory` for creating :class:`plaster.ILoader` objects. Each loader is passed a :class:`plaster.PlasterURL` instance, the result of parsing the ``config_uri`` to determine the scheme and fragment. If the loader should be found automatically via file extension then it should broadcast support for the special ``file+`` scheme. For example, to support ``development.ini`` instead of ``myscheme://development.ini`` the loader should be registered for the ``file+ini`` scheme. .. code-block:: python import plaster class Loader(plaster.ILoader): def __init__(self, uri): self.uri = uri def get_sections(self): return ['myapp', 'yourapp'] def get_settings(self, section=None, defaults=None): # fallback to the fragment from config_uri if no section is given if not section: section = self.uri.fragment # if section is still none we could fallback to some # loader-specific default result = {} if defaults is not None: result.update(defaults) if section == 'myapp': result.update({'a': 1}) elif section == 'yourapp': result.update({'b': 1}) return result This loader may then be used: .. code-block:: python import plaster settings = plaster.get_settings('dict://', section='myapp') assert settings['a'] == 1 settings2 = plaster.get_settings('myapp+dict://', section='myapp') assert settings == settings2 Supporting a custom protocol ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, loaders are exposed via the ``plaster.loader_factory`` entry point. In order to register a loader that supports a custom protocol it should register itself on a ``plaster._loader_factory`` entry point. A scheme **MUST** point to the same loader factory for every protocol, including the default (empty) protocol. If it does not then no compatible loader will be found if the end-user requests a loader satisfying both protocols. Acknowledgments =============== This API is heavily inspired by conversations, contributions, and design put forth in https://github.com/inklesspen/montague and https://metaclassical.com/announcing-montague-the-new-way-to-configure-python-applications/. More Information ================ .. toctree:: :maxdepth: 1 api glossary contributing changes Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` plaster-1.0/docs/contributing.rst0000644000076500000240000000004112723700605017737 0ustar michaelstaff00000000000000.. include:: ../CONTRIBUTING.rst plaster-1.0/docs/Makefile0000644000076500000240000001515612723700605016153 0ustar michaelstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/plaster.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/plaster.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/plaster" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/plaster" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." plaster-1.0/docs/conf.py0000644000076500000240000002030712727446264016020 0ustar michaelstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # plaster documentation build configuration file, created by # sphinx-quickstart on Tue Jul 9 22:26:36 2013. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os import pkg_resources # If extensions (or modules to document with autodoc) are in another # directory, add these directories to sys.path here. If the directory is # relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # Get the project root dir, which is the parent dir of this cwd = os.getcwd() project_root = os.path.dirname(cwd) # Insert the project root dir as the first element in the PYTHONPATH. # This lets us ensure that the source package is imported, and that its # version is used. sys.path.insert(0, project_root) # ensure the code is importable for use with autodoc import plaster # -- General configuration --------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'plaster' copyright = u'2016, Michael Merickel' # The version info for the project you're documenting, acts as replacement # for |version| and |release|, also used in various other places throughout # the built documents. # # The short X.Y version. version = pkg_resources.get_distribution('plaster').version # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to # some non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built # documents. #keep_warnings = False # -- Options for HTML output ------------------------------------------- # Add and use Pylons theme sys.path.append(os.path.abspath('_themes')) import pylons_sphinx_themes html_theme_path = pylons_sphinx_themes.get_html_themes_path() html_theme = 'pylons' html_theme_options = { 'github_url': 'https://github.com/Pylons/pyramid_jinja2' } # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as # html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the # top of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon # of the docs. This file should be a Windows icon file (.ico) being # 16x16 or 32x32 pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) # here, relative to this directory. They are copied after the builtin # static files, so a file named "default.css" will overwrite the builtin # "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names # to template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. # Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. # Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages # will contain a tag referring to it. The value of this option # must be the base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'plasterdoc' # -- Options for LaTeX output ------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'plaster.tex', u'plaster Documentation', u'Michael Merickel', 'manual'), ] # The name of an image file (relative to this directory) to place at # the top of the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings # are parts, not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output ------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'plaster', u'plaster Documentation', [u'Michael Merickel'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ---------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'plaster', u'plaster Documentation', u'Michael Merickel', 'plaster', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False plaster-1.0/docs/_static/0000755000076500000240000000000013167325414016135 5ustar michaelstaff00000000000000plaster-1.0/docs/_static/.keep0000644000076500000240000000000013062650025017041 0ustar michaelstaff00000000000000plaster-1.0/docs/make.bat0000644000076500000240000001447512723700605016123 0ustar michaelstaff00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\plaster.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\plaster.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end plaster-1.0/docs/changes.rst0000644000076500000240000000006512723735017016653 0ustar michaelstaff00000000000000======= Changes ======= .. include:: ../CHANGES.rst plaster-1.0/docs/api.rst0000644000076500000240000000145113067114041016002 0ustar michaelstaff00000000000000================== :mod:`plaster` API ================== .. automodule:: plaster Application API =============== .. autofunction:: get_settings .. autofunction:: setup_logging .. autofunction:: get_loader .. autofunction:: find_loaders .. autoclass:: ILoaderInfo :members: Loader API ========== .. autoclass:: ILoader :members: .. autoclass:: ILoaderFactory :members: .. automethod:: __call__ .. autofunction:: parse_uri .. autoclass:: PlasterURL :members: .. _protocols: Protocols ========= .. autoclass:: plaster.protocols.IWSGIProtocol :members: Exceptions ========== .. autoexception:: PlasterError :members: .. autoexception:: InvalidURI :members: .. autoexception:: LoaderNotFound :members: .. autoexception:: MultipleLoadersFound :members: plaster-1.0/docs/glossary.rst0000644000076500000240000000157313066532073017111 0ustar michaelstaff00000000000000.. _glossary: Glossary ======== .. glossary:: :sorted: config uri In most cases this is simply an absolute or relative path to a config file on the system. However, it can also be a :rfc:`1738`-style string pointing at a remote service or a specific parser without relying on the file extension. For example, ``my-ini://foo.ini`` may point to a loader named ``my-ini`` that can parse the relative ``foo.ini`` file. loader An object conforming to the :class:`plaster.ILoader` interface. A loader is responsible for locating and parsing the underlying configuration format for the given :term:`config uri`. loader protocol A :term:`loader` may implement zero or more custom named protocols. An example would be the ``wsgi`` protocol which requires that a loader implement certain methods like ``wsgi_app = get_wsgi_app(name=None, defaults=None)``. plaster-1.0/appveyor.yml0000644000076500000240000000101013066342232016132 0ustar michaelstaff00000000000000environment: matrix: - PYTHON: "C:\\Python36" TOXENV: "py36" - PYTHON: "C:\\Python35" TOXENV: "py35" - PYTHON: "C:\\Python27" TOXENV: "py27" - PYTHON: "C:\\Python36-x64" TOXENV: "py36" - PYTHON: "C:\\Python35-x64" TOXENV: "py35" - PYTHON: "C:\\Python27-x64" TOXENV: "py27" cache: - '%LOCALAPPDATA%\pip\Cache' version: '{branch}.{build}' install: - "%PYTHON%\\python.exe -m pip install tox" build: off test_script: - "%PYTHON%\\Scripts\\tox.exe" plaster-1.0/setup.py0000644000076500000240000000323513167325071015273 0ustar michaelstaff00000000000000from setuptools import setup, find_packages def readfile(name): with open(name) as f: return f.read() readme = readfile('README.rst') changes = readfile('CHANGES.rst') requires = [ 'setuptools', # for pkg_resources ] docs_require = [ 'Sphinx', 'pylons-sphinx-themes', ] tests_require = [ 'pytest', 'pytest-cov', ] setup( name='plaster', version='1.0', description='A loader interface around multiple config file formats.', long_description=readme + '\n\n' + changes, author='Michael Merickel', author_email='pylons-discuss@googlegroups.com', url='https://docs.pylonsproject.org/projects/plaster/en/latest/', packages=find_packages('src', exclude=['tests']), package_dir={'': 'src'}, include_package_data=True, python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', install_requires=requires, extras_require={ 'docs': docs_require, 'testing': tests_require, }, test_suite='tests', zip_safe=False, keywords='plaster pastedeploy ini config', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], ) plaster-1.0/.gitignore0000644000076500000240000000150012730151534015536 0ustar michaelstaff00000000000000# 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/ plaster-1.0/tox.ini0000644000076500000240000000171013125110531015053 0ustar michaelstaff00000000000000[tox] envlist = lint, py27,py34,py35,py36,pypy, docs,coverage [testenv] basepython = py27: python2.7 py34: python3.4 py35: python3.5 py36: python3.6 pypy: pypy py2: python2.7 py3: python3.5 pypy3: pypy3 commands = pip install plaster[testing] py.test --cov --cov-report= {posargs:} setenv = COVERAGE_FILE=.coverage.{envname} [testenv:coverage] skip_install = True basepython = python3.5 commands = coverage combine coverage report --fail-under=100 deps = coverage setenv = COVERAGE_FILE=.coverage [testenv:docs] skip_install = True basepython = python3.5 whitelist_externals = make commands = pip install plaster[docs] make -C docs html BUILDDIR={envdir} SPHINXOPTS="-W -E" [testenv:lint] skip_install = True basepython = python3.5 commands = flake8 src/plaster/ python setup.py check -r -s -m check-manifest deps = flake8 readme_renderer check-manifest plaster-1.0/setup.cfg0000644000076500000240000000060513167325414015401 0ustar michaelstaff00000000000000[wheel] universal = 1 [metadata] license_file = LICENSE.txt [flake8] exclude = tests/fake_packages, show-source = True max-line-length = 80 [check-manifest] ignore = .gitignore PKG-INFO *.egg-info *.egg-info/* ignore-default-rules = true ignore-bad-ideas = tests/* [tool:pytest] python_files = test_*.py testpaths = src/plaster tests [egg_info] tag_build = tag_date = 0 plaster-1.0/README.rst0000644000076500000240000000171313167324424015250 0ustar michaelstaff00000000000000======= plaster ======= .. image:: https://img.shields.io/pypi/v/plaster.svg :target: https://pypi.python.org/pypi/plaster .. image:: https://img.shields.io/travis/Pylons/plaster/master.svg :target: https://travis-ci.org/Pylons/plaster .. 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. plaster-1.0/LICENSE.txt0000644000076500000240000000204413066357442015406 0ustar michaelstaff00000000000000Copyright (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. plaster-1.0/CONTRIBUTORS.txt0000644000076500000240000001150513167324424016257 0ustar michaelstaff00000000000000Pylons 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) plaster-1.0/CHANGES.rst0000644000076500000240000000611213167325062015360 0ustar michaelstaff000000000000001.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+`` instead of simply ```` (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. plaster-1.0/.travis.yml0000644000076500000240000000075613125107700015666 0ustar michaelstaff00000000000000sudo: false cache: directories: - $HOME/.cache/pip language: python matrix: include: - python: '3.6' env: TOXENV=py36 - python: '3.5' env: TOXENV=py35 - python: '3.4' env: TOXENV=py34 - python: '2.7' env: TOXENV=py27 - python: 'pypy' env: TOXENV=pypy - python: '3.5' env: TOXENV=py27,py35,coverage - python: '3.5' env: TOXENV=docs - python: '3.5' env: TOXENV=lint install: pip install tox script: tox plaster-1.0/src/0000755000076500000240000000000013167325414014346 5ustar michaelstaff00000000000000plaster-1.0/src/plaster.egg-info/0000755000076500000240000000000013167325414017512 5ustar michaelstaff00000000000000plaster-1.0/src/plaster.egg-info/PKG-INFO0000644000076500000240000001360613167325414020615 0ustar michaelstaff00000000000000Metadata-Version: 1.2 Name: plaster Version: 1.0 Summary: A loader interface around multiple config file formats. Home-page: https://docs.pylonsproject.org/projects/plaster/en/latest/ Author: Michael Merickel Author-email: pylons-discuss@googlegroups.com License: UNKNOWN Description: ======= plaster ======= .. image:: https://img.shields.io/pypi/v/plaster.svg :target: https://pypi.python.org/pypi/plaster .. image:: https://img.shields.io/travis/Pylons/plaster/master.svg :target: https://travis-ci.org/Pylons/plaster .. 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. 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+`` instead of simply ```` (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. Keywords: plaster pastedeploy ini config Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.* plaster-1.0/src/plaster.egg-info/not-zip-safe0000644000076500000240000000000113113721770021734 0ustar michaelstaff00000000000000 plaster-1.0/src/plaster.egg-info/SOURCES.txt0000644000076500000240000000311713167325414021400 0ustar michaelstaff00000000000000.coveragerc .gitignore .travis.yml CHANGES.rst CONTRIBUTING.rst CONTRIBUTORS.txt LICENSE.txt MANIFEST.in README.rst appveyor.yml rtd.txt setup.cfg setup.py tox.ini docs/Makefile docs/api.rst docs/changes.rst docs/conf.py docs/contributing.rst docs/glossary.rst docs/index.rst docs/make.bat docs/_static/.keep src/plaster/__init__.py src/plaster/compat.py src/plaster/exceptions.py src/plaster/interfaces.py src/plaster/loaders.py src/plaster/protocols.py src/plaster/uri.py src/plaster.egg-info/PKG-INFO src/plaster.egg-info/SOURCES.txt src/plaster.egg-info/dependency_links.txt src/plaster.egg-info/not-zip-safe src/plaster.egg-info/requires.txt src/plaster.egg-info/top_level.txt tests/__init__.py tests/conftest.py tests/test_exceptions.py tests/test_loaders.py tests/test_protocols.py tests/test_uri.py tests/example_configs/test_default.ini tests/fake_packages/app1/setup.py tests/fake_packages/app1/app1/__init__.py tests/fake_packages/app1/app1/loaders.py tests/fake_packages/app1/app1.egg-info/PKG-INFO tests/fake_packages/app1/app1.egg-info/SOURCES.txt tests/fake_packages/app1/app1.egg-info/dependency_links.txt tests/fake_packages/app1/app1.egg-info/entry_points.txt tests/fake_packages/app1/app1.egg-info/top_level.txt tests/fake_packages/app2/setup.py tests/fake_packages/app2/app2/__init__.py tests/fake_packages/app2/app2/loaders.py tests/fake_packages/app2/app2.egg-info/PKG-INFO tests/fake_packages/app2/app2.egg-info/SOURCES.txt tests/fake_packages/app2/app2.egg-info/dependency_links.txt tests/fake_packages/app2/app2.egg-info/entry_points.txt tests/fake_packages/app2/app2.egg-info/top_level.txtplaster-1.0/src/plaster.egg-info/requires.txt0000644000076500000240000000011413167325414022106 0ustar michaelstaff00000000000000setuptools [docs] Sphinx pylons-sphinx-themes [testing] pytest pytest-cov plaster-1.0/src/plaster.egg-info/top_level.txt0000644000076500000240000000001013167325414022233 0ustar michaelstaff00000000000000plaster plaster-1.0/src/plaster.egg-info/dependency_links.txt0000644000076500000240000000000113167325414023560 0ustar michaelstaff00000000000000 plaster-1.0/src/plaster/0000755000076500000240000000000013167325414016020 5ustar michaelstaff00000000000000plaster-1.0/src/plaster/interfaces.py0000644000076500000240000000646313067114041020515 0ustar michaelstaff00000000000000import abc from .compat import add_metaclass @add_metaclass(abc.ABCMeta) class ILoader(object): """ 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. """ @add_metaclass(abc.ABCMeta) class ILoaderFactory(object): @abc.abstractmethod def __call__(self, uri): """ A factory which accepts a :class:`plaster.PlasterURL` and returns a :class:`plaster.ILoader` object. """ @add_metaclass(abc.ABCMeta) class ILoaderInfo(object): """ 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`. """ plaster-1.0/src/plaster/compat.py0000644000076500000240000000141213065137660017654 0ustar michaelstaff00000000000000# flake8: noqa import sys PY2 = sys.version_info[0] == 2 def add_metaclass(metaclass): # pragma: no cover """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper if PY2: import urlparse from urllib import urlencode else: from urllib import parse urlparse = parse urlencode = parse.urlencode plaster-1.0/src/plaster/protocols.py0000644000076500000240000000770213067114041020413 0ustar michaelstaff00000000000000import abc from .compat import add_metaclass @add_metaclass(abc.ABCMeta) class IWSGIProtocol(object): @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): """ 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_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. """ plaster-1.0/src/plaster/loaders.py0000644000076500000240000001500713114150260020011 0ustar michaelstaff00000000000000import pkg_resources 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 ``ini+pastedeploy``. :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 += [ 'plaster.{0}_loader_factory'.format(proto) 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: distro = pkg_resources.get_distribution(parts[0]) except pkg_resources.DistributionNotFound: pass else: ep = _find_ep_in_dist(distro, 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(ep, protocols)] # find any distributions supporting the default loader protocol possible_entry_points = [ ep for ep in pkg_resources.iter_entry_points('plaster.loader_factory') if scheme is None or scheme == ep.name.lower() ] distros = {ep.dist for ep in possible_entry_points} matched_entry_points = list(filter(None, [ _find_ep_in_dist(distro, scheme, matching_groups) for distro in distros ])) return [ EntryPointLoaderInfo(ep, protocols=protocols) for ep in matched_entry_points ] def _find_ep_in_dist(distro, scheme, groups): # find the scheme's entry point in each group matched_entry_points = list(filter(None, [ distro.get_entry_info(group, scheme) for group in groups ])) # verify that the entry point from each group points to the same factory if len({str(ep) for ep in matched_entry_points}) == 1: return matched_entry_points[0] class EntryPointLoaderInfo(ILoaderInfo): def __init__(self, ep, protocols=None): self.entry_point = ep self.scheme = '{0}+{1}'.format(ep.dist.project_name, 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) plaster-1.0/src/plaster/__init__.py0000644000076500000240000000057513067114041020127 0ustar michaelstaff00000000000000# public api # flake8: noqa from .exceptions import ( InvalidURI, LoaderNotFound, MultipleLoadersFound, PlasterError, ) from .interfaces import ( ILoader, ILoaderInfo, ILoaderFactory, ) from .loaders import ( find_loaders, get_loader, get_sections, get_settings, setup_logging, ) from .uri import ( PlasterURL, parse_uri, ) plaster-1.0/src/plaster/uri.py0000644000076500000240000000725613167324424017203 0ustar michaelstaff00000000000000from collections import OrderedDict import os.path from .compat import ( urlencode, urlparse, ) from .exceptions import InvalidURI class PlasterURL(object): """ 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 += '?' + urlencode(self.options) if self.fragment: result += '#' + self.fragment return result def __repr__(self): return 'PlasterURL(\'{0}\')'.format(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 = urlparse.urlparse(config_uri) # reconstruct the path without the scheme and fragment path = urlparse.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(urlparse.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 "{0}"'.format(config_uri))) return PlasterURL( scheme=scheme, path=path, options=options, fragment=fragment, ) plaster-1.0/src/plaster/exceptions.py0000644000076500000240000000511213067114041020541 0ustar michaelstaff00000000000000class 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 = 'Unable to parse config_uri "{0}".'.format(uri) super(InvalidURI, self).__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 = 'scheme "{0}"'.format(scheme) if protocols is not None: scheme_msg += ', protocol "{0}"'.format(', '.join(protocols)) message = ( 'Could not find a matching loader for the {0}.' .format(scheme_msg)) super(LoaderNotFound, self).__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 = 'scheme "{0}"'.format(scheme) if protocols is not None: scheme_msg += ', protocol "{0}"'.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 {0}. ' 'Please specify a more specific config_uri. ' 'Matched loaders: {1}' ).format(scheme_msg, loader_list) super(MultipleLoadersFound, self).__init__(message) self.message = message self.scheme = scheme self.protocols = protocols self.loaders = loaders