diff --git a/PKG-INFO b/PKG-INFO index b62b00d577fef913e5de77ce8c93c55132d67f4d..b22a155ae9c02ddadeddfd8382a81bd029c2ef42 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,11 +1,200 @@ -Metadata-Version: 1.0 +Metadata-Version: 2.1 Name: scruffington -Version: 0.3.3 +Version: 0.3.8.2 Summary: The janitor Home-page: https://github.com/snare/scruffy Author: snare Author-email: snare@ho.ax License: MIT -Description: UNKNOWN +Description: Scruffy + ======= + + .. image:: https://img.shields.io/travis/snare/scruffy.svg + :target: https://travis-ci.org/snare/scruffy + + .. image:: https://img.shields.io/pypi/format/scruffington.svg + :target: https://pypi.python.org/pypi/scruffington + + .. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest + :target: http://scruffy.readthedocs.org/en/latest/ + + + *Scruffy. The Janitor.* + + Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc. + + A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements: + + * Read a set of configuration defaults + * Read a local configuration file and apply it on top of the defaults + * Allow overriding some configuration options with command line flags or at runtime + * Load a core set of Python-based plugins + * Load a set of user-defined Python-based plugins + * Generate log files whose name, location and other logging settings are based on configuration + * Store application state between runs in a file or database + + Scruffy is used by Voltron_ and Calculon_ + + .. _Voltron: https://github.com/snare/voltron + .. _Calculon: https://github.com/snare/calculon + + Installation + ------------ + + A standard python setup script is included. + + $ python setup.py install + + This will install the Scruffy package wherever that happens on your system. + + Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it). + + $ pip install scruffington + + Documentation + ------------- + + Full documentation is hosted at readthedocs_ + + .. _readthedocs: http://scruffy.readthedocs.io/ + + Quick start + ----------- + + Config + ~~~~~~ + + Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from. + + *thingy.yaml*: + + .. code:: yaml + + some_property: 1 + other_property: a thing + + *thingy.py*: + + .. code:: python + + from scruffy import ConfigFile + + c = ConfigFile('thingy.yaml', load=True, + defaults=File('defaults.yaml', parent=PackageDirectory()) + ) + + print("c.some_property == {c.some_property}".format(c=c)) + print("c.other_property == {c.other_property}".format(c=c)) + + Run it: + + :: + + $ python thingy.py + c.some_property == 1 + c.other_property == a thing + + Plugins + ~~~~~~~ + + Load some plugins. + + *~/.thingy/plugins/example.py*: + + .. code:: python + + from scruffy import Plugin + + class ExamplePlugin(Plugin): + def do_a_thing(self): + print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__)) + + *thingy.py*: + + .. code:: python + + from scruffy import PluginDirectory, PluginRegistry + + pd = PluginDirectory('~/.thingy/plugins') + pd.load() + + for p in PluginRegistry.plugins: + print("Initialising plugin {}".format(p)) + p().do_a_thing() + + Run it: + + :: + + $ python thingy.py + Initialising plugin <class 'example.ExamplePlugin'> + example.ExamplePlugin is doing a thing + + Logging + ~~~~~~~ + + Scruffy's `LogFile` class will do some configuration of Python's `logging` module. + + *log.py*: + + .. code:: python + + import logging + from scruffy import LogFile + + log = logging.getLogger('main') + log.setLevel(logging.INFO) + LogFile('/tmp/thingy.log', logger='main').configure() + + log.info('Hello from log.py') + + */tmp/thingy.log*: + + :: + + Hello from log.py + + Environment + ~~~~~~~~~~~ + + Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects. + + *~/.thingy/config*: + + .. code:: yaml + + log_dir: /tmp/logs + log_file: thingy.log + + *env.py*: + + .. code:: python + + from scruffy import * + + e = Environment( + main_dir=Directory('~/.thingy', create=True, + config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())), + lock=LockFile('lock') + user_plugins=PluginDirectory('plugins') + ), + log_dir=Directory('{config:log_dir}', create=True + LogFile('{config:log_file}', logger='main') + ), + pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory()) + ) + + License + ------- + + See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time. + + Credits + ------- + + Props to richo_. Flat duck pride. + + .. _richo: http://github.com/richo Keywords: scruffy Platform: UNKNOWN +Description-Content-Type: text/x-rst diff --git a/README.rst b/README.rst new file mode 100644 index 0000000000000000000000000000000000000000..1a866667c8897c73fce9c2c32bb5e351f53434c1 --- /dev/null +++ b/README.rst @@ -0,0 +1,189 @@ +Scruffy +======= + +.. image:: https://img.shields.io/travis/snare/scruffy.svg + :target: https://travis-ci.org/snare/scruffy + +.. image:: https://img.shields.io/pypi/format/scruffington.svg + :target: https://pypi.python.org/pypi/scruffington + +.. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest + :target: http://scruffy.readthedocs.org/en/latest/ + + +*Scruffy. The Janitor.* + +Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc. + +A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements: + +* Read a set of configuration defaults +* Read a local configuration file and apply it on top of the defaults +* Allow overriding some configuration options with command line flags or at runtime +* Load a core set of Python-based plugins +* Load a set of user-defined Python-based plugins +* Generate log files whose name, location and other logging settings are based on configuration +* Store application state between runs in a file or database + +Scruffy is used by Voltron_ and Calculon_ + +.. _Voltron: https://github.com/snare/voltron +.. _Calculon: https://github.com/snare/calculon + +Installation +------------ + +A standard python setup script is included. + + $ python setup.py install + +This will install the Scruffy package wherever that happens on your system. + +Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it). + + $ pip install scruffington + +Documentation +------------- + +Full documentation is hosted at readthedocs_ + +.. _readthedocs: http://scruffy.readthedocs.io/ + +Quick start +----------- + +Config +~~~~~~ + +Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from. + +*thingy.yaml*: + +.. code:: yaml + + some_property: 1 + other_property: a thing + +*thingy.py*: + +.. code:: python + + from scruffy import ConfigFile + + c = ConfigFile('thingy.yaml', load=True, + defaults=File('defaults.yaml', parent=PackageDirectory()) + ) + + print("c.some_property == {c.some_property}".format(c=c)) + print("c.other_property == {c.other_property}".format(c=c)) + +Run it: + +:: + + $ python thingy.py + c.some_property == 1 + c.other_property == a thing + +Plugins +~~~~~~~ + +Load some plugins. + +*~/.thingy/plugins/example.py*: + +.. code:: python + + from scruffy import Plugin + + class ExamplePlugin(Plugin): + def do_a_thing(self): + print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__)) + +*thingy.py*: + +.. code:: python + + from scruffy import PluginDirectory, PluginRegistry + + pd = PluginDirectory('~/.thingy/plugins') + pd.load() + + for p in PluginRegistry.plugins: + print("Initialising plugin {}".format(p)) + p().do_a_thing() + +Run it: + +:: + + $ python thingy.py + Initialising plugin <class 'example.ExamplePlugin'> + example.ExamplePlugin is doing a thing + +Logging +~~~~~~~ + +Scruffy's `LogFile` class will do some configuration of Python's `logging` module. + +*log.py*: + +.. code:: python + + import logging + from scruffy import LogFile + + log = logging.getLogger('main') + log.setLevel(logging.INFO) + LogFile('/tmp/thingy.log', logger='main').configure() + + log.info('Hello from log.py') + +*/tmp/thingy.log*: + +:: + + Hello from log.py + +Environment +~~~~~~~~~~~ + +Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects. + +*~/.thingy/config*: + +.. code:: yaml + + log_dir: /tmp/logs + log_file: thingy.log + +*env.py*: + +.. code:: python + + from scruffy import * + + e = Environment( + main_dir=Directory('~/.thingy', create=True, + config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())), + lock=LockFile('lock') + user_plugins=PluginDirectory('plugins') + ), + log_dir=Directory('{config:log_dir}', create=True + LogFile('{config:log_file}', logger='main') + ), + pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory()) + ) + +License +------- + +See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time. + +Credits +------- + +Props to richo_. Flat duck pride. + +.. _richo: http://github.com/richo \ No newline at end of file diff --git a/debian/changelog b/debian/changelog index d9b757c06e7970206bb39559ac861b0eed288a49..08ca1ed387f1d2fa73ce8d540b9ec992af73b76f 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +python-scruffy (0.3.8.2-1) UNRELEASED; urgency=medium + + * New upstream version 0.3.8.2. + * Patch imp to importlib for Python 3.12 compatibility. (Closes: #1058129) + * d/watch: Bump to version 4. + * Bump standards version to 4.6.2 + - Update d/copyright to include upstream copyright notice. + - d/control: Add Rules-Requires-Root field. + + -- Dale Richards <dale@dalerichards.net> Mon, 25 Dec 2023 22:16:27 +0000 + python-scruffy (0.3.3-3) unstable; urgency=medium [ Debian Janitor ] diff --git a/debian/control b/debian/control index 8f9e980d6f8ce56fef9f4592efd931535c149444..5500c41f601ac8e5d6b3da5ef60f6dafee8ec301 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,8 @@ Build-Depends: debhelper-compat (= 13), python3-setuptools, python3-six, python3-yaml, -Standards-Version: 3.9.8 +Standards-Version: 4.6.2 +Rules-Requires-Root: no Homepage: https://github.com/snare/scruffy Vcs-Git: https://salsa.debian.org/python-team/packages/python-scruffy.git Vcs-Browser: https://salsa.debian.org/python-team/packages/python-scruffy diff --git a/debian/copyright b/debian/copyright index 9db7b09736eb5a0c483ccd520bdb5f4fbd0f1f88..3047bc72223393fbbe8f1aea7aaedea45f52af67 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,11 +3,12 @@ Upstream-Name: scruffy Source: <https://github.com/snare/scruffy> Files: * -Copyright: snare <snare@ho.ax> +Copyright: 2016 Loukas K <snare@ho.ax> License: Expat Files: debian/* Copyright: 2016 ChangZhuo Chen (陳昌倬) <czchen@debian.org> + 2023 Dale Richards <dale@dalerichards.net> License: Expat License: Expat diff --git a/debian/patches/0001-Migrate-from-imp-to-importlib.patch b/debian/patches/0001-Migrate-from-imp-to-importlib.patch new file mode 100644 index 0000000000000000000000000000000000000000..9e9188f99e372e3a6bb8e71a88ac355a4b1fdfaa --- /dev/null +++ b/debian/patches/0001-Migrate-from-imp-to-importlib.patch @@ -0,0 +1,32 @@ +From: Dale Richards <dale@dalerichards.net> +Date: Mon, 25 Dec 2023 21:41:37 +0000 +Subject: Migrate from imp to importlib +Forwarded: https://github.com/snare/scruffy/pull/31 + +--- + scruffy/plugin.py | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/scruffy/plugin.py ++++ b/scruffy/plugin.py +@@ -5,7 +5,7 @@ + Classes for representing and loading plugins. + """ + import os +-import imp ++import importlib + import six + + +@@ -56,9 +56,9 @@ + # if it's a file, load it + modname, ext = os.path.splitext(filename) + if os.path.isfile(filepath) and ext == '.py': +- file, path, descr = imp.find_module(modname, [directory]) ++ file, path, descr = importlib.find_spec(modname, [directory]) + if file: +- mod = imp.load_module(modname, file, path, descr) ++ mod = importlib.load_module(modname, file, path, descr) + + # if it's a directory, recurse into it + if os.path.isdir(filepath): diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000000000000000000000000000000000000..953f293cdaf2660aab901bd46644e8eb24714e67 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-Migrate-from-imp-to-importlib.patch diff --git a/debian/watch b/debian/watch index a5e59937cefedbf0d8db4fd385cf2be62f086bf0..2a6a1e040b91e68609521d4bc27c437a28d0c593 100644 --- a/debian/watch +++ b/debian/watch @@ -1,3 +1,3 @@ -version=3 +version=4 opts=uversionmangle=s/(rc|a|b|c)/~$1/ \ https://pypi.debian.net/scruffington/scruffington-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff --git a/scruffington.egg-info/PKG-INFO b/scruffington.egg-info/PKG-INFO index b62b00d577fef913e5de77ce8c93c55132d67f4d..b22a155ae9c02ddadeddfd8382a81bd029c2ef42 100644 --- a/scruffington.egg-info/PKG-INFO +++ b/scruffington.egg-info/PKG-INFO @@ -1,11 +1,200 @@ -Metadata-Version: 1.0 +Metadata-Version: 2.1 Name: scruffington -Version: 0.3.3 +Version: 0.3.8.2 Summary: The janitor Home-page: https://github.com/snare/scruffy Author: snare Author-email: snare@ho.ax License: MIT -Description: UNKNOWN +Description: Scruffy + ======= + + .. image:: https://img.shields.io/travis/snare/scruffy.svg + :target: https://travis-ci.org/snare/scruffy + + .. image:: https://img.shields.io/pypi/format/scruffington.svg + :target: https://pypi.python.org/pypi/scruffington + + .. image:: https://readthedocs.org/projects/scruffy/badge/?version=latest + :target: http://scruffy.readthedocs.org/en/latest/ + + + *Scruffy. The Janitor.* + + Scruffy is a framework for taking care of a bunch of boilerplate in Python apps. It handles the loading of configuration files, the loading and management of plugins, and the management of other filesystem resources such as temporary files and directories, log files, etc. + + A typical use case for Scruffy is a command-line Python tool with some or all of the following requirements: + + * Read a set of configuration defaults + * Read a local configuration file and apply it on top of the defaults + * Allow overriding some configuration options with command line flags or at runtime + * Load a core set of Python-based plugins + * Load a set of user-defined Python-based plugins + * Generate log files whose name, location and other logging settings are based on configuration + * Store application state between runs in a file or database + + Scruffy is used by Voltron_ and Calculon_ + + .. _Voltron: https://github.com/snare/voltron + .. _Calculon: https://github.com/snare/calculon + + Installation + ------------ + + A standard python setup script is included. + + $ python setup.py install + + This will install the Scruffy package wherever that happens on your system. + + Alternately, Scruffy can be installed with `pip` from PyPi (where it's called `scruffington`, because I didn't check for a conflict before I named it). + + $ pip install scruffington + + Documentation + ------------- + + Full documentation is hosted at readthedocs_ + + .. _readthedocs: http://scruffy.readthedocs.io/ + + Quick start + ----------- + + Config + ~~~~~~ + + Load a user config file, and apply it on top of a set of defaults loaded from inside the Python package we're currently running from. + + *thingy.yaml*: + + .. code:: yaml + + some_property: 1 + other_property: a thing + + *thingy.py*: + + .. code:: python + + from scruffy import ConfigFile + + c = ConfigFile('thingy.yaml', load=True, + defaults=File('defaults.yaml', parent=PackageDirectory()) + ) + + print("c.some_property == {c.some_property}".format(c=c)) + print("c.other_property == {c.other_property}".format(c=c)) + + Run it: + + :: + + $ python thingy.py + c.some_property == 1 + c.other_property == a thing + + Plugins + ~~~~~~~ + + Load some plugins. + + *~/.thingy/plugins/example.py*: + + .. code:: python + + from scruffy import Plugin + + class ExamplePlugin(Plugin): + def do_a_thing(self): + print('{}.{} is doing a thing'.format(__name__, self.__class__.__name__)) + + *thingy.py*: + + .. code:: python + + from scruffy import PluginDirectory, PluginRegistry + + pd = PluginDirectory('~/.thingy/plugins') + pd.load() + + for p in PluginRegistry.plugins: + print("Initialising plugin {}".format(p)) + p().do_a_thing() + + Run it: + + :: + + $ python thingy.py + Initialising plugin <class 'example.ExamplePlugin'> + example.ExamplePlugin is doing a thing + + Logging + ~~~~~~~ + + Scruffy's `LogFile` class will do some configuration of Python's `logging` module. + + *log.py*: + + .. code:: python + + import logging + from scruffy import LogFile + + log = logging.getLogger('main') + log.setLevel(logging.INFO) + LogFile('/tmp/thingy.log', logger='main').configure() + + log.info('Hello from log.py') + + */tmp/thingy.log*: + + :: + + Hello from log.py + + Environment + ~~~~~~~~~~~ + + Scruffy's `Environment` class ties all the other stuff together. The other classes can be instantiated as named children of an `Environment`, which will load any `Config` objects, apply the configs to the other objects, and then prepare the other objects. + + *~/.thingy/config*: + + .. code:: yaml + + log_dir: /tmp/logs + log_file: thingy.log + + *env.py*: + + .. code:: python + + from scruffy import * + + e = Environment( + main_dir=Directory('~/.thingy', create=True, + config=ConfigFile('config', defaults=File('defaults.yaml', parent=PackageDirectory())), + lock=LockFile('lock') + user_plugins=PluginDirectory('plugins') + ), + log_dir=Directory('{config:log_dir}', create=True + LogFile('{config:log_file}', logger='main') + ), + pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory()) + ) + + License + ------- + + See LICENSE file. If you use this and don't hate it, buy me a beer at a conference some time. + + Credits + ------- + + Props to richo_. Flat duck pride. + + .. _richo: http://github.com/richo Keywords: scruffy Platform: UNKNOWN +Description-Content-Type: text/x-rst diff --git a/scruffington.egg-info/SOURCES.txt b/scruffington.egg-info/SOURCES.txt index b147ef573d17ea056417a9ecd07a426483049729..fb4f8be83949d6f7dad56a8754569015417ad5eb 100644 --- a/scruffington.egg-info/SOURCES.txt +++ b/scruffington.egg-info/SOURCES.txt @@ -1,3 +1,4 @@ +README.rst setup.cfg setup.py scruffington.egg-info/PKG-INFO diff --git a/scruffington.egg-info/requires.txt b/scruffington.egg-info/requires.txt index 4f1e759df5c275c61ba0b359fab468e2c0f02e48..2cd03d50af1ed3966bd7c277f82c09b0696be8e0 100644 --- a/scruffington.egg-info/requires.txt +++ b/scruffington.egg-info/requires.txt @@ -1,2 +1,2 @@ pyyaml -six \ No newline at end of file +six diff --git a/scruffy/config.py b/scruffy/config.py index 1177430e0864f7ab50d8f77862c5448fe27eaa1c..9ba455269384165dacb7751eb83ba925df34d13c 100644 --- a/scruffy/config.py +++ b/scruffy/config.py @@ -1,9 +1,18 @@ +""" +Config +------ + +Classes for loading and accessing configuration data. +""" +from six import string_types + import copy import os import ast import yaml import re +from six import string_types from .file import File @@ -13,15 +22,15 @@ class ConfigNode(object): Can be accessed as a dictionary, like this: - config['top-level-section']['second-level-property'] + >>> config['top-level-section']['second-level-property'] Or as a dictionary with a key path, like this: - config['top_level_section.second_level_property'] + >>> config['top_level_section.second_level_property'] Or as an object, like this: - config.top_level_section.second_level_property + >>> config.top_level_section.second_level_property """ def __init__(self, data={}, defaults={}, root=None, path=None): super(ConfigNode, self).__init__() @@ -72,9 +81,6 @@ class ConfigNode(object): def __le__(self, other): return self._get_value() <= other - def __le__(self, other): - return self._get_value() <= other - def __eq__(self, other): return self._get_value() == other @@ -90,6 +96,12 @@ class ConfigNode(object): def __contains__(self, key): return key in self._get_value() + def __nonzero__(self): + return self._get_value() != None + + def __bool__(self): + return self._get_value() != None + def items(self): return self._get_value().items() @@ -133,7 +145,7 @@ class ConfigNode(object): the item referred to by the key path. """ # Split up the key path - if type(self._path) == str: + if isinstance(self._path, string_types): key_path = self._path.split('.') else: key_path = [self._path] @@ -195,22 +207,24 @@ class ConfigNode(object): `options` is a dict of keypath/value pairs like this (similar to CherryPy's config mechanism: - { - 'server.port': 8080, - 'server.host': 'localhost', - 'admin.email': 'admin@lol' - } + + >>> c.update(options={ + ... 'server.port': 8080, + ... 'server.host': 'localhost', + ... 'admin.email': 'admin@lol' + ... }) `data` is a dict of actual config data, like this: - { - 'server': { - 'port': 8080, - 'host': 'localhost' - }, - 'admin': { - 'email': 'admin@lol' - } - } + + >>> c.update(data={ + ... 'server': { + ... 'port': 8080, + ... 'host': 'localhost' + ... }, + ... 'admin': { + ... 'email': 'admin@lol' + ... } + ... }) """ # Handle an update with a set of options like CherryPy does for key in options: @@ -244,17 +258,17 @@ class ConfigEnv(ConfigNode): """ Config based on based on environment variables. """ - def __init__(self, *args, **kwargs): + def __init__(self, prefix='SCRUFFY', *args, **kwargs): super(ConfigEnv, self).__init__(*args, **kwargs) - # build options dictionary from environment variables starting with __SC_ + # build options dictionary from environment variables starting with the prefix options = {} - for key in filter(lambda x: x.startswith('__SC_'), os.environ): + for key in [v for v in os.environ if v.startswith('__SC_') or v.startswith(prefix + '_')]: try: val = ast.literal_eval(os.environ[key]) except: val = os.environ[key] - options[key.replace('__SC_', '').lower()] = val + options[key.replace('__SC_', '').replace(prefix + '_', '').lower()] = val # update config with the values we've found self.update(options=options) @@ -264,10 +278,11 @@ class ConfigFile(Config, File): """ Config based on a loaded YAML or JSON file. """ - def __init__(self, path=None, defaults=None, load=False, apply_env=False, *args, **kwargs): + def __init__(self, path=None, defaults=None, load=False, apply_env=False, env_prefix='SCRUFFY', *args, **kwargs): self._loaded = False self._defaults_file = defaults self._apply_env = apply_env + self._env_prefix = env_prefix Config.__init__(self) File.__init__(self, path=path, *args, **kwargs) @@ -280,7 +295,7 @@ class ConfigFile(Config, File): """ if reload or not self._loaded: # load defaults - if self._defaults_file and type(self._defaults_file) == str: + if self._defaults_file and isinstance(self._defaults_file, string_types): self._defaults_file = File(self._defaults_file, parent=self._parent) defaults = {} if self._defaults_file: @@ -298,7 +313,7 @@ class ConfigFile(Config, File): # if specified, apply environment variables if self._apply_env: - self.update(ConfigEnv()) + self.update(ConfigEnv(self._env_prefix)) self._loaded = True @@ -308,7 +323,7 @@ class ConfigFile(Config, File): """ Save the config back to the config file. """ - self.write(yaml.safe_dump(self._data)) + self.write(yaml.safe_dump(self._data, default_flow_style=False)) def prepare(self): """ @@ -328,7 +343,7 @@ class ConfigApplicator(object): """ Apply the config to an object. """ - if type(obj) == str: + if isinstance(obj, string_types): return self.apply_to_str(obj) def apply_to_str(self, obj): @@ -370,20 +385,23 @@ def update_dict(target, source): dictionary. For example: - target before = { - 'thing': 123, - 'thang': { - 'a': 1, - 'b': 2 - } - } - source = { - 'thang': { - 'a': 666, - 'c': 777 - } - } - target after = { + + >>> target = { + ... 'thing': 123, + ... 'thang': { + ... 'a': 1, + ... 'b': 2 + ... } + ... } + >>> source = { + ... 'thang': { + ... 'a': 666, + ... 'c': 777 + ... } + ... } + >>> update_dict(target, source) + >>> target + { 'thing': 123, 'thang': { 'a': 666, diff --git a/scruffy/env.py b/scruffy/env.py index 826493b44e233c011714ef1f834741307797d43a..ae086930f474fbeed9eb634eb920580de3fa9758 100644 --- a/scruffy/env.py +++ b/scruffy/env.py @@ -1,3 +1,10 @@ +""" +Environment +----------- + +Classes for representing the encompassing environment in which your application +runs. +""" import os import yaml import itertools @@ -5,9 +12,11 @@ import errno import logging import logging.config +from six import string_types + from .file import Directory from .plugin import PluginManager -from .config import ConfigNode, Config, ConfigEnv, ConfigApplicator +from .config import ConfigNode, Config, ConfigEnv, ConfigApplicator, ConfigFile class Environment(object): @@ -61,7 +70,7 @@ class Environment(object): # first see if we got a kwarg named 'config', as this guy is special if 'config' in children: - if type(children['config']) == str: + if isinstance(children['config'], string_types): children['config'] = ConfigFile(children['config']) elif isinstance(children['config'], Config): children['config'] = children['config'] @@ -96,7 +105,7 @@ class Environment(object): Add objects to the environment. """ for key in kwargs: - if type(kwargs[key]) == str: + if isinstance(kwargs[key], string_types): self._children[key] = Directory(kwargs[key]) else: self._children[key] = kwargs[key] diff --git a/scruffy/file.py b/scruffy/file.py index cd913f7fc1dd85a1a308879ca5d825330a5012a1..2db91e1db76b8cbb2c44127b6a67810b5aca3b04 100644 --- a/scruffy/file.py +++ b/scruffy/file.py @@ -1,4 +1,12 @@ +""" +File +---- + +Classes for representing and performing operations on files and directories. +""" +from __future__ import unicode_literals import os +from six import string_types import yaml import copy import logging @@ -38,9 +46,9 @@ class File(object): def apply_config(self, applicator): """ - Replace any config tokens with values from the config. + Replace any config tokens in the file's path with values from the config. """ - if type(self._fpath) == str: + if isinstance(self._fpath, string_types): self._fpath = applicator.apply(self._fpath) def create(self): @@ -51,7 +59,7 @@ class File(object): def remove(self): """ - Remove the file. + Remove the file if it exists. """ if self.exists: os.unlink(self.path) @@ -166,7 +174,7 @@ class LogFile(File): # if we got a string for the formatter, assume it's the name of a # formatter in the environment's config - if type(self._formatter) == str: + if isinstance(self._format, string_types): if self._env and self._env.config.logging.dict_config.formatters[self._formatter]: d = self._env.config.logging.dict_config.formatters[self._formatter].to_dict() handler.setFormatter(logging.Formatter(**d)) @@ -237,14 +245,14 @@ class Directory(object): A Scruffy Environment usually encompasses a number of these. For example, the main Directory object may represent `~/.myproject`. - d = Directory({ - path='~/.myproject', - create=True, - cleanup=False, - children=[ - ... - ] - }) + >>> d = Directory({ + ... path='~/.myproject', + ... create=True, + ... cleanup=False, + ... children=[ + ... ... + ... ] + ... }) `path` can be either a string representing the path to the directory, or a nested Directory object. If a Directory object is passed as the `path` @@ -261,7 +269,7 @@ class Directory(object): self._env = None self._parent = parent - if self._path and type(self._path) == str: + if self._path and isinstance(self._path, string_types): self._path = os.path.expanduser(self._path) self.add(**kwargs) @@ -283,7 +291,7 @@ class Directory(object): """ Replace any config tokens with values from the config. """ - if type(self._path) == str: + if isinstance(self._path, string_types): self._path = applicator.apply(self._path) for key in self._children: @@ -390,7 +398,7 @@ class Directory(object): Add objects to the directory. """ for key in kwargs: - if isinstance(kwargs[key], str): + if isinstance(kwargs[key], string_types): self._children[key] = File(kwargs[key]) else: self._children[key] = kwargs[key] @@ -403,7 +411,7 @@ class Directory(object): self._children[arg.name] = arg self._children[arg.name]._parent = self self._children[arg.name]._env = self._env - elif isinstance(arg, str): + elif isinstance(arg, string_types): f = File(arg) added.append(f) self._children[arg] = f diff --git a/scruffy/plugin.py b/scruffy/plugin.py index 267b2d21f66e5721a5174d175389aa9c2c525ed3..9c6853b6fd4d2a4eb94e974e54d9f2551ca51b36 100644 --- a/scruffy/plugin.py +++ b/scruffy/plugin.py @@ -1,3 +1,9 @@ +""" +Plugin +------ + +Classes for representing and loading plugins. +""" import os import imp import six diff --git a/scruffy/state.py b/scruffy/state.py index f73881a3d64019253403a547024b053bf5ae2d6b..3931d4735f02e646d5d5773a1031c1712ce31cac 100644 --- a/scruffy/state.py +++ b/scruffy/state.py @@ -1,6 +1,14 @@ +""" +State +----- + +Classes for storing a program's state. +""" import os +import atexit import yaml + try: from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.ext.declarative import declarative_base @@ -28,6 +36,7 @@ class State(object): self.path = path self.d = {} self.load() + atexit.register(self._exit_handler) def __enter__(self): self.load() @@ -45,6 +54,9 @@ class State(object): def __setitem__(self, key, value): self.d[key] = value + def _exit_handler(self): + self.save() + def save(self): """ Save the state to a file. @@ -58,7 +70,11 @@ class State(object): """ if os.path.exists(self.path): with open(self.path, 'r') as f: - self.d = yaml.safe_load(f.read().replace('\t', ' '*4)) + d = yaml.safe_load(f.read().replace('\t', ' '*4)) + # don't clobber self.d if we successfully opened the state file + # but it was empty + if d: + self.d = d def cleanup(self): """ diff --git a/setup.cfg b/setup.cfg index 6f08d0e3e7d4475804244ad049d476c45fa48356..adf5ed72aa402bde56b499833d162862dae66dc4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,5 +4,4 @@ universal = 1 [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff --git a/setup.py b/setup.py index aef1603b3e36692ee8cd7f3e7ef91802c69f06da..7c63b7ef62cebe4abbd05d11b40f68d689c9a413 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,15 @@ from setuptools import setup +with open("README.rst", "r") as fp: + long_description = fp.read() + setup( name="scruffington", - version="0.3.3", + version="0.3.8.2", author="snare", author_email="snare@ho.ax", + long_description=long_description, + long_description_content_type="text/x-rst", description=("The janitor"), license="MIT", keywords="scruffy",