Skip to content

Commit

Permalink
import 0.5 release
Browse files Browse the repository at this point in the history
  • Loading branch information
xflr6 committed Apr 29, 2014
1 parent d99ebd5 commit 07b6efd
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 52 deletions.
8 changes: 8 additions & 0 deletions CHANGES
Expand Up @@ -2,6 +2,14 @@ Fileconfig Changelog
====================


Version 0.5
-----------

Added Python 3.3+ support.

Documented sys._getframe dependency.


Version 0.4.1
-------------

Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
@@ -1,3 +1,4 @@
include README.rst LICENSE CHANGES
include dev-requirements.txt .gitignore
include run-tests.py
recursive-include docs *
31 changes: 21 additions & 10 deletions README.rst
Expand Up @@ -12,6 +12,8 @@ will return the instance with the parameters specified in the given section.
Installation
------------

This package runs under Python 2.7 and 3.3+, use pip_ to install:

.. code:: bash
$ pip install fileconfig
Expand All @@ -36,9 +38,6 @@ directory if its file not happens do be there).
... def __init__(self, key, **kwargs):
... self.key = key
... self.__dict__.update(kwargs)
... def __str__(self):
... items = (' %r: %r' % (k, v) for k, v in sorted(self.__dict__.iteritems()))
... return '{\n%s\n}' % ',\n'.join(items)
On instance creation, the ``__init__`` method will be called with the section
name (``key``) and the keyword parameters from the given section of the
Expand All @@ -60,7 +59,7 @@ To retrieve this instance, call the class with its section name.
>>> c = Cfg('parrot')
>>> print c
>>> print(c)
{
'can_talk': 'yes',
'characteristics': 'beautiful plumage, pining for the fjords',
Expand Down Expand Up @@ -153,7 +152,7 @@ Specified keys override inherited ones:

.. code:: python
>>> print Cfg('Polly')
>>> print(Cfg('Polly'))
{
'can_talk': 'no',
'characteristics': 'dead, totally stiff, ceased to exist',
Expand Down Expand Up @@ -207,7 +206,7 @@ Use the ``__init__`` method to process the other parameters to fit your needs.
... self.characteristics = [c.strip() for c in characteristics.split(',')]
... super(Pet, self).__init__(**kwargs)
>>> print Pet('Polly')
>>> print(Pet('Polly'))
{
'can_talk': False,
'characteristics': ['dead', 'totally stiff', 'ceased to exist'],
Expand Down Expand Up @@ -235,7 +234,6 @@ location of the default config.
>>> class Settings(fileconfig.Stacked):
... filename = 'docs/pet-shop.ini'
... __str__ = Cfg.__str__.__func__
Use the ``add`` method to load an overriding config file on top of that:
Expand All @@ -250,7 +248,7 @@ You can access the sections from all files:
.. code:: python
>>> print Settings('Bevis')
>>> print(Settings('Bevis'))
{
'can_talk': 'yes',
'characteristics': "sleeps all night, works all day, puts on women's clothing",
Expand All @@ -262,7 +260,7 @@ As long as they have *different* names:
.. code:: python
>>> print Settings('Polly')
>>> print(Settings('Polly'))
{
'can_talk': 'no',
'characteristics': 'dead, totally stiff, ceased to exist',
Expand All @@ -277,7 +275,7 @@ from previous files:
.. code:: python
>>> print Settings('parrot')
>>> print(Settings('parrot'))
{
'characteristics': 'unsolved problem',
'key': 'parrot'
Expand Down Expand Up @@ -306,12 +304,25 @@ Fileconfig raises an error, if the config file is not found. If you want this
subclass to ``True``.
Potential issues
----------------
This package uses ``sys._getframe`` (which is almost the same as
``inspect.currentframe``, see__ docs__). Under IronPython this might require
enabling the ``FullFrames`` option of the interpreter.
.. __: http://docs.python.org/2/library/sys.html#sys._getframe
.. __: http://docs.python.org/2/library/inspect.html#inspect.currentframe
License
-------
Fileconfig is distributed under the `MIT license`_.
.. _pip: http://pip.readthedocs.org
.. _MIT license: http://opensource.org/licenses/MIT
Expand Down
4 changes: 2 additions & 2 deletions fileconfig/__init__.py
Expand Up @@ -3,11 +3,11 @@
"""Parse config file and return class instances for each section."""

__title__ = 'fileconfig'
__version__ = '0.4.1'
__version__ = '0.5'
__author__ = 'Sebastian Bank <sebastian.bank@uni-leipzig.de>'
__license__ = 'MIT, see LICENSE'
__copyright__ = 'Copyright (c) 2014 Sebastian Bank'

from bases import Config, Stacked
from .bases import Config, Stacked

__all__ = ['Config', 'Stacked']
55 changes: 55 additions & 0 deletions fileconfig/_compat.py
@@ -0,0 +1,55 @@
# _compat.py - Python 2/3 compatibility

import sys

PY2 = sys.version_info[0] == 2


if PY2:
text_type = unicode
integer_types = (int, long)

def iteritems(d):
return d.iteritems()

def try_encode(chars, encoding='ascii'):
"""Return encoded chars, leave unchanged if encoding fails.
>>> try_encode(u'spam')
'spam'
>>> assert try_encode(u'm\xf8\xf8se') == u'm\xf8\xf8se'
"""
try:
return chars.encode(encoding)
except UnicodeEncodeError:
return chars

from ConfigParser import SafeConfigParser as ConfigParser


else:
text_type = str
integer_types = (int, )

def iteritems(d):
return iter(d.items())

try_encode = lambda chars, encoding='ascii': chars

from configparser import ConfigParser


def with_metaclass(meta, *bases):
"""From Jinja2 (BSD licensed).
http://github.com/mitsuhiko/jinja2/blob/master/jinja2/_compat.py
"""
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
15 changes: 7 additions & 8 deletions fileconfig/bases.py
@@ -1,20 +1,21 @@
# bases.py - to be subclassed by client code

import meta
from ._compat import iteritems, with_metaclass

from . import meta

__all__ = ['Config', 'Stacked']


class Config(object):
class Config(with_metaclass(meta.ConfigMeta, object)):
"""Return section by name from filename as instance."""

__metaclass__ = meta.ConfigMeta

def __init__(self, **kwargs):
self.__dict__.update(kwargs)

def __str__(self):
return repr(self.__dict__)
items = (' %r: %r' % (k, v) for k, v in sorted(iteritems(self.__dict__)))
return '{\n%s\n}' % ',\n'.join(items)

def __repr__(self):
if getattr(self, 'key', None) is None:
Expand All @@ -30,11 +31,9 @@ def names(self):
return [self.key] + self.aliases


class Stacked(Config):
class Stacked(with_metaclass(meta.StackedMeta, Config)):
"""Return section by name from first matching file as instance."""

__metaclass__ = meta.StackedMeta

def __repr__(self):
if getattr(self, 'key', None) is None:
return '<%s.%s[%r] object at %#x>' % (self.__module__,
Expand Down
36 changes: 22 additions & 14 deletions fileconfig/meta.py
@@ -1,11 +1,11 @@
# meta.py - parse config, collect arguments, create instances

import os
import codecs
import ConfigParser
import io

import stack
import tools
from ._compat import PY2, try_encode, ConfigParser

from . import stack, tools

__all__ = ['ConfigMeta']

Expand All @@ -19,35 +19,43 @@ class ConfigMeta(type):

_pass_notfound = False

_parser = ConfigParser.SafeConfigParser
_parser = ConfigParser

_encoding = None

_enc = staticmethod(lambda s: s)

@staticmethod
def _split_aliases(aliases):
return aliases.replace(',', ' ').split()

def __init__(self, name, bases, dct):
if self.__module__ == '__builtin__': # workaround nose doctest issue
self.__module__ = '__main__'

if self.filename is None:
return

# work around nose doctest issue
if self.__module__ in ('__builtin__', 'builtins'):
self.__module__ = '__main__'

if not os.path.isabs(self.filename):
self.filename = os.path.join(tools.class_path(self), self.filename)

if not self._pass_notfound and not os.path.exists(self.filename):
open(self.filename)

parser = self._parser()
if self._encoding is None:
parser.read(self.filename)
enc = lambda s: s
enc = self._enc

if PY2:
if self._encoding is None:
parser.read(self.filename)
else:
with io.open(self.filename, encoding=self._encoding) as fd:
parser.readfp(fd)
enc = try_encode
else:
with codecs.open(self.filename, encoding=self._encoding) as fd:
with io.open(self.filename, encoding=self._encoding) as fd:
parser.readfp(fd)
enc = tools.try_encode

self._keys = []
self._kwargs = {}
Expand Down Expand Up @@ -101,7 +109,7 @@ def __iter__(self):

def pprint_all(self):
for c in self:
print '%s\n' % c
print('%s\n' % c)


class StackedMeta(ConfigMeta):
Expand Down
4 changes: 3 additions & 1 deletion fileconfig/stack.py
Expand Up @@ -2,6 +2,8 @@

__all__ = ['ConfigStack']

from ._compat import integer_types


class ConfigStack(object):
"""Ordered and filename-indexed collection of Config classes."""
Expand All @@ -21,7 +23,7 @@ def insert(self, index, filename):
self._classes.insert(index, cls)

def __getitem__(self, filename):
if isinstance(filename, (int, long)):
if isinstance(filename, integer_types):
return self._classes[filename]

return self._map[filename]
Expand Down
20 changes: 4 additions & 16 deletions fileconfig/tools.py
@@ -1,9 +1,10 @@
# tools.py - runtime path inspection

import sys
import os
import inspect

__all__ = ['class_path', 'caller_path', 'try_encode']
__all__ = ['class_path', 'caller_path']


def class_path(cls):
Expand All @@ -21,7 +22,8 @@ def class_path(cls):

def caller_path(steps=1):
"""Return the path to the source file of the current frames' caller."""
frame = inspect.currentframe(steps + 1)

frame = sys._getframe(steps + 1)

try:
path = os.path.dirname(frame.f_code.co_filename)
Expand All @@ -32,17 +34,3 @@ def caller_path(steps=1):
path = os.getcwd()

return os.path.realpath(path)


def try_encode(chars, encoding='ascii'):
"""Return encoded chars, leave unchanged if encoding fails.
>>> try_encode(u'spam')
'spam'
>>> assert try_encode(u'm\xf8\xf8se') == u'm\xf8\xf8se'
"""
try:
return chars.encode(encoding)
except UnicodeEncodeError:
return chars
3 changes: 3 additions & 0 deletions setup.cfg
@@ -1,6 +1,9 @@
[bdist_wheel]
universal = 1

[sdist]
formats = zip

[nosetests]
tests = fileconfig, README.rst
verbosity = 1
Expand Down

0 comments on commit 07b6efd

Please sign in to comment.