Skip to content

Commit

Permalink
Merge pull request #9 from osantana/discovery-ng
Browse files Browse the repository at this point in the history
Lazy loading and new root_path default to '/'
  • Loading branch information
osantana committed Aug 12, 2015
2 parents 9d92c51 + 88c611b commit c5651c7
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 63 deletions.
12 changes: 11 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
1.0.0
=====

- First stable release! hooray!
- Make configuration load lazy to make possible change root_path and
starting_path in prettyconf.config
- Default root_path is "/" instead of $HOME (backward incompatible change)
- Add missing requirements in requirements.txt and make tox use it.
- Small PEP-8 and code formatting fixes

0.4.1
=====

- Add MacOSX travis builds.

0.4.0
=====

- Add ``root_path`` to stop looking indefinitely for configuration files until the OS root path
- Add advanced usage docs
- Include a simple (but working) tox configuration for py27 + py34 to the project
Expand Down
81 changes: 62 additions & 19 deletions docs/source/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,31 +83,40 @@ Useful third-parties casts

Advanced usage
~~~~~~~~~~~~~~
You can always create your own ``Configuration`` instance and change it's default behaviour.

Most of the time you can use the ``prettyconf.config`` function to get your
settings and use the ``prettyconf``'s standard behaviour. But some times
you need to change this behaviour.

To make this changes possible you can always create your own
``Configuration()`` instance and change it's default behaviour:

.. code-block:: python
from prettyconf.configuration import Configuration
config = Configuration()
Set the starting path
+++++++++++++++++++++
By default the library will use the directory of the file where ``config()`` was called
as the starting directory to look for configuration files. Consider the following file
structure:

.. code-block:: shell
By default library will use the directory of the file where ``config()`` was
called as the start directory to look for configuration files. Consider the
following file structure:

.. code-block:: text
project/
settings.ini
app/
settings.py
When calling ``config()`` from ``project/app/settings.py`` the library will start looking
for configuration files at ``project/app``
If you call ``config()`` from ``project/app/settings.py`` the library will start looking
for configuration files at ``project/app``.

You can change that behaviour, by setting a different ``starting_path`` when instantiating
your ``Configuration``:
your ``Configuration()``:

.. code-block:: python
Expand All @@ -117,28 +126,42 @@ your ``Configuration``:
from prettyconf.configuration import Configuration
project_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
config = Configuration(project_path)
config = Configuration(starting_path=project_path)
The example above will start looking for files at ``project/`` instead of ``project/app``.

You can also set ``starting_path`` attribute in ``prettyconf.config`` before use it:

.. code-block:: python
# Code example in project/app/settings.py
import os
from prettyconf import config
project_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
config.starting_path = project_path
Set a different root path
+++++++++++++++++++++++++
By default, the library will try to look for configuration files until it finds valid
configuration files **or** it reaches ``Configuration.root_path``.

By default, the library will try to look for configuration files until it finds
valid configuration files **or** it reaches ``root_path``. The default
``root_path`` is set to the root directory "``/``".

Consider the following file structure:

.. code-block:: shell
.. code-block:: text
/projects/
any_settings.ini
project/
app/
settings.py
The default root path is set to the user home directory (e.g. ``$HOME`` or
``/home/yourusername``). You can change this behaviour by setting any parent directory
of the ``starting_path`` as the ``root_path`` when instantiating ``Configuration``:
You can change this behaviour by setting any parent directory of the
``starting_path`` as the ``root_path`` when instantiating ``Configuration``:

.. code-block:: python
Expand All @@ -147,15 +170,35 @@ of the ``starting_path`` as the ``root_path`` when instantiating ``Configuration
from prettyconf.configuration import Configuration
app_path = os.path.dirname(__file__)
project_path = os.path.realpath(os.path.join(app_path), '..'))
config = Configuration(root_path=project_path)
The example above will start looking for files at ``project/app/`` and will stop looking
for configuration files at ``project/``, actually never looking at ``any_settings.ini``
and no configuration being loaded at all.

You can also set ``root_path`` attribute in ``prettyconf.config`` before use it:

.. code-block:: python
# Code example in project/app/settings.py
from prettyconf import config
project_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
config.root_path = project_path
The ``root_path`` must be a parent directory of ``starting_path``:

.. code-block:: python
# Code example in project/app/settings.py
from prettyconf import config
config.starting_path = "/foo/bar"
config.root_path = "/baz" # /baz is not parent of /foo/bar
MY_CONFIG = config("PROJECT_MY_CONFIG") # raises an InvalidPath exception here
.. _dj-database-url: https://github.com/kennethreitz/dj-database-url
.. _django-cache-url: https://github.com/ghickman/django-cache-url

32 changes: 18 additions & 14 deletions prettyconf/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from .casts import Boolean, List, Option


MAGIC_FRAME_DEPTH = 2
MAGIC_FRAME_DEPTH = 3


class ConfigurationDiscovery(object):
default_filetypes = (EnvFileConfigurationLoader, IniFileConfigurationLoader)

def __init__(self, starting_path, filetypes=None, root_path=None):
def __init__(self, starting_path, filetypes=None, root_path="/"):
"""
Setup the configuration file discovery.
Expand All @@ -26,17 +26,17 @@ def __init__(self, starting_path, filetypes=None, root_path=None):
the current user directory
"""
self.starting_path = os.path.realpath(os.path.abspath(starting_path))
self.root_path = os.path.realpath(root_path)

if not self.starting_path.startswith(self.root_path):
raise InvalidPath('Invalid root path given')

if filetypes is None:
filetypes = self.default_filetypes
if root_path is None:
root_path = os.path.expanduser('~')

self.root_path = os.path.realpath(root_path)
self.filetypes = filetypes
self._config_files = None

if self.root_path not in self.starting_path:
raise InvalidPath('Invalid root path given')

def _scan_path(self, path):
config_files = []
Expand Down Expand Up @@ -79,15 +79,13 @@ class Configuration(object):
list = List()
option = Option

def __init__(self, configs=None, starting_path=None, root_path=None):
def __init__(self, configs=None, starting_path=None, root_path="/"):
if configs is None:
configs = [EnvVarConfigurationLoader()]
self.configurations = configs
self.starting_path = starting_path
self.root_path = root_path

if starting_path:
self._init_configs()
self._initialized = False

@staticmethod
def _caller_path():
Expand All @@ -98,16 +96,22 @@ def _caller_path():
return path

def _init_configs(self):
if self._initialized:
return

if self.starting_path is None:
self.starting_path = self._caller_path()

discovery = ConfigurationDiscovery(self.starting_path, root_path=self.root_path)
self.configurations.extend(discovery.config_files)

self._initialized = True

def __call__(self, item, cast=lambda v: v, **kwargs):
if not callable(cast):
raise InvalidConfigurationCast("Cast must be callable")

if self.starting_path is None:
self.starting_path = self._caller_path()
self._init_configs()
self._init_configs()

for configuration in self.configurations:
try:
Expand Down
50 changes: 25 additions & 25 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

from setuptools import setup, Command


here = os.path.abspath(os.path.dirname(__file__))


version = "0.0.0"
with open(os.path.join(here, "CHANGES.txt")) as changes:
for line in changes:
version = line.strip()
Expand All @@ -29,27 +28,28 @@ def run(self):
print(version)


setup(name='prettyconf',
version=version,
description='Separation of settings from code.',
author="Osvaldo Santana Neto", author_email="prettyconf@osantana.me",
license="MIT",
packages=['prettyconf'],
platforms='any',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Framework :: Flask',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries',
],
url='http://github.com/osantana/prettyconf',
download_url='https://github.com/osantana/prettyconf/tarball/{}'.format(version),
cmdclass={'version': VersionCommand},
test_suite="tests",
setup(
name='prettyconf',
version=version,
description='Separation of settings from code.',
author="Osvaldo Santana Neto", author_email="prettyconf@osantana.me",
license="MIT",
packages=['prettyconf'],
platforms='any',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Framework :: Django',
'Framework :: Flask',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Libraries',
],
url='http://github.com/osantana/prettyconf',
download_url='https://github.com/osantana/prettyconf/tarball/{}'.format(version),
cmdclass={'version': VersionCommand},
test_suite="tests",
)
7 changes: 3 additions & 4 deletions tests/test_filediscover.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from prettyconf.exceptions import InvalidPath


# noinspection PyStatementEffect
class ConfigFilesDiscoveryTestCase(BaseTestCase):

def setUp(self):
super(ConfigFilesDiscoveryTestCase, self).setUp()
self.tmpdirs = []
Expand Down Expand Up @@ -62,9 +62,9 @@ def test_should_look_for_parent_directory_when_it_finds_invalid_configurations(s
self.assertIn(os.path.abspath(self.test_files_path + '/../../.env'), filenames)
self.assertIn(os.path.abspath(self.test_files_path + '/../../settings.ini'), filenames)

def test_default_root_path_should_default_to_user_directory(self):
def test_default_root_path_should_default_to_root_directory(self):
discovery = ConfigurationDiscovery(os.path.dirname(self.test_files_path))
assert discovery.root_path == os.path.expanduser('~')
assert discovery.root_path == "/"

def test_root_path_should_be_parent_of_starting_path(self):
with self.assertRaises(InvalidPath):
Expand Down Expand Up @@ -100,4 +100,3 @@ def test_lookup_should_stop_at_root_path(self):

discovery = ConfigurationDiscovery(start_path, root_path=root_dir)
self.assertEqual(discovery.config_files, [])

0 comments on commit c5651c7

Please sign in to comment.