├── MANIFEST.in ├── dev-requirements.txt ├── setup.cfg ├── docs ├── contributing.rst ├── changelog.rst ├── license.rst ├── examples.rst ├── index.rst ├── why.rst ├── api.rst ├── Makefile └── conf.py ├── docs-requirements.txt ├── .travis.yml ├── tox.ini ├── .gitignore ├── AUTHORS.rst ├── LICENSE ├── README.rst ├── setup.py ├── CONTRIBUTING.rst ├── characteristic.py └── test_characteristic.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst LICENSE 2 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -e . 2 | pytest 3 | pytest-cov 4 | tox 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | # we're pure-python 3 | universal = 1 4 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | .. include:: ../CONTRIBUTING.rst 4 | -------------------------------------------------------------------------------- /docs-requirements.txt: -------------------------------------------------------------------------------- 1 | -r dev-requirements.txt 2 | releases 3 | sphinx 4 | sphinx_rtd_theme 5 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | - :release:`0.1.0 <2014-05-11>` 5 | - :feature:`-` Initial work. 6 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License and Hall of Fame 2 | ======================== 3 | 4 | ``characteristic`` is licensed under the permissive `MIT `_ license. 5 | The full license text can be also found in the `source code repository `_. 6 | 7 | .. _authors: 8 | 9 | .. include:: ../AUTHORS.rst 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 2.7 3 | env: 4 | - TOX_ENV=py26 5 | - TOX_ENV=py27 6 | - TOX_ENV=py33 7 | - TOX_ENV=py34 8 | - TOX_ENV=pypy 9 | - TOX_ENV=doctests 10 | - TOX_ENV=flake8 11 | 12 | install: 13 | - pip install tox coveralls 14 | 15 | script: 16 | - tox --hashseed 0 -e $TOX_ENV 17 | 18 | after_success: 19 | - coveralls 20 | 21 | notifications: 22 | email: false 23 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34, pypy, flake8, doctests 3 | 4 | [testenv] 5 | deps = 6 | pytest 7 | pytest-cov 8 | commands = 9 | py.test --cov characteristic --cov-report term-missing test_characteristic.py 10 | [testenv:doctests] 11 | deps = 12 | pytest 13 | commands = 14 | py.test --doctest-glob='*.rst' 15 | 16 | [testenv:flake8] 17 | basepython = python2.7 18 | deps = 19 | flake8 20 | commands = flake8 characteristic.py test_characteristic.py 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ------- 3 | 4 | ``characteristic`` is written and maintained by `Hynek Schlawack `_. 5 | 6 | The development is kindly supported by `Variomedia AG `_. 7 | 8 | It’s inspired by Twisted’s `FancyEqMixin `_ but is implemented using class decorators because `sub-classing is bad for you `_, m’kay? 9 | 10 | 11 | The following folks helped forming ``characteristic`` into what it is now: 12 | 13 | - `Adam Dangoor `_ 14 | - `Glyph `_ 15 | - `Itamar Turner-Trauring `_ 16 | - `Jean-Paul Calderone `_ 17 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. _examples: 2 | 3 | Examples 4 | ======== 5 | 6 | 7 | ``@attributes([attr1, attr2, …])`` enhances your class by: 8 | 9 | - a nice ``__repr__``, 10 | - comparison methods that compare instances as if they were tuples of their attributes, 11 | - and – optionally but by default – an initializer that uses the keyword arguments to initialize the specified attributes before running the class’ own initializer (you just write the validator!). 12 | 13 | 14 | .. doctest:: 15 | 16 | >>> from characteristic import attributes 17 | >>> @attributes(["a", "b",]) 18 | ... class C(object): 19 | ... pass 20 | >>> obj1 = C(a=1, b="abc") 21 | >>> obj1 22 | 23 | >>> obj2 = C(a=2, b="abc") 24 | >>> obj1 == obj2 25 | False 26 | >>> obj1 < obj2 27 | True 28 | >>> obj3 = C(a=1, b="bca") 29 | >>> obj3 > obj1 30 | True 31 | >>> @attributes(["a", "b", "c",], defaults={"c": 3}) 32 | ... class CWithDefaults(object): 33 | ... pass 34 | >>> obj4 = CWithDefaults(a=1, b=2) 35 | >>> obj5 = CWithDefaults(a=1, b=2, c=3) 36 | >>> obj4 == obj5 37 | True 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Hynek Schlawack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | characteristic: Say ‘yes’ to types but ‘no’ to typing! 2 | ====================================================== 3 | 4 | .. image:: https://travis-ci.org/hynek/characteristic.svg 5 | :target: https://travis-ci.org/hynek/characteristic 6 | 7 | .. image:: https://coveralls.io/repos/hynek/characteristic/badge.png?branch=master 8 | :target: https://coveralls.io/r/hynek/characteristic?branch=master 9 | 10 | .. begin 11 | 12 | 13 | ``characteristic`` is an `MIT `_-licensed Python package with class decorators that ease the chores of implementing the most common attribute-related object protocols. 14 | 15 | You just specify the attributes to work with and ``characteristic`` gives you: 16 | 17 | - a nice human-readable ``__repr__``, 18 | - a complete set of comparison methods, 19 | - and a kwargs-based initializer (that cooperates with your existing one) 20 | 21 | *without* writing dull boilerplate code again and again. 22 | 23 | So put down that type-less data structures and welcome some class into your life! 24 | 25 | ``characteristic``\ ’s documentation lives at `Read the Docs `_, the code on `GitHub `_. 26 | It’s rigorously tested on Python 2.6, 2.7, 3.3+, and PyPy. 27 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | characteristic: Say 'yes' to types but 'no' to typing! 2 | ====================================================== 3 | 4 | Release v\ |release| (:doc:`What's new? `). 5 | 6 | 7 | .. include:: ../README.rst 8 | :start-after: begin 9 | 10 | 11 | Teaser 12 | ------ 13 | 14 | .. doctest:: 15 | 16 | >>> from characteristic import attributes 17 | >>> @attributes(["a", "b"]) 18 | ... class AClass(object): 19 | ... pass 20 | >>> @attributes(["a", "b"], defaults={"b": "abc"}) 21 | ... class AnotherClass(object): 22 | ... pass 23 | >>> obj1 = AClass(a=1, b="abc") 24 | >>> obj2 = AnotherClass(a=1, b="abc") 25 | >>> obj3 = AnotherClass(a=1) 26 | >>> print obj1, obj2, obj3 27 | 28 | >>> obj1 == obj2 29 | False 30 | >>> obj2 == obj3 31 | True 32 | 33 | 34 | User's Guide 35 | ------------ 36 | 37 | .. toctree:: 38 | :maxdepth: 1 39 | 40 | why 41 | api 42 | examples 43 | 44 | Project Information 45 | ^^^^^^^^^^^^^^^^^^^ 46 | 47 | .. toctree:: 48 | :maxdepth: 1 49 | 50 | license 51 | contributing 52 | changelog 53 | 54 | 55 | 56 | Indices and tables 57 | ================== 58 | 59 | * :ref:`genindex` 60 | * :ref:`search` 61 | 62 | -------------------------------------------------------------------------------- /docs/why.rst: -------------------------------------------------------------------------------- 1 | .. _why: 2 | 3 | Why? 4 | ==== 5 | 6 | The difference between namedtuple_\ s and classes decorated by ``characteristic`` is that the latter are type-sensitive and less typing aside regular classes: 7 | 8 | 9 | .. doctest:: 10 | 11 | >>> from characteristic import attributes 12 | >>> @attributes(["a",]) 13 | ... class C1(object): 14 | ... def __init__(self): 15 | ... if not isinstance(self.a, int): 16 | ... raise ValueError("'a' must be an integer.") 17 | ... def print_a(self): 18 | ... print self.a 19 | >>> @attributes(["a",]) 20 | ... class C2(object): 21 | ... pass 22 | >>> c1 = C1(a=1) 23 | >>> c2 = C2(a=1) 24 | >>> c1 == c2 25 | False 26 | >>> c1.print_a() 27 | 1 28 | >>> C1(a="hello") 29 | Traceback (most recent call last): 30 | ... 31 | ValueError: 'a' must be an integer. 32 | 33 | 34 | …while namedtuple’s purpose is *explicitly* to behave like tuples: 35 | 36 | 37 | .. doctest:: 38 | 39 | >>> from collections import namedtuple 40 | >>> NT1 = namedtuple("NT1", "a") 41 | >>> NT2 = namedtuple("NT2", "b") 42 | >>> t1 = NT1._make([1,]) 43 | >>> t2 = NT2._make([1,]) 44 | >>> t1 == t2 == (1,) 45 | True 46 | 47 | 48 | This can easily lead to surprising and unintended behaviors. 49 | 50 | .. _namedtuple: https://docs.python.org/2/library/collections.html#collections.namedtuple 51 | .. _tuple: https://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences 52 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os 3 | import re 4 | 5 | from setuptools import setup 6 | 7 | 8 | def read(*parts): 9 | """ 10 | Build an absolute path from *parts* and and return the contents of the 11 | resulting file. Assume UTF-8 encoding. 12 | """ 13 | here = os.path.abspath(os.path.dirname(__file__)) 14 | with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: 15 | return f.read() 16 | 17 | 18 | def find_version(*file_paths): 19 | """ 20 | Build a path from *file_paths* and search for a ``__version__`` 21 | string inside. 22 | """ 23 | version_file = read(*file_paths) 24 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 25 | version_file, re.M) 26 | if version_match: 27 | return version_match.group(1) 28 | raise RuntimeError("Unable to find version string.") 29 | 30 | 31 | setup( 32 | name="characteristic", 33 | version=find_version("characteristic.py"), 34 | description="Say 'yes' to types but 'no' to typing!", 35 | long_description=(read("README.rst") + "\n\n" + 36 | read("AUTHORS.rst")), 37 | url="https://characteristic.readthedocs.org/", 38 | license="MIT", 39 | author="Hynek Schlawack", 40 | author_email="hs@ox.cx", 41 | py_modules=["characteristic", "test_characteristic"], 42 | classifiers=[ 43 | "Development Status :: 5 - Production/Stable", 44 | "Intended Audience :: Developers", 45 | "Natural Language :: English", 46 | "License :: OSI Approved :: MIT License", 47 | "Operating System :: OS Independent", 48 | "Programming Language :: Python", 49 | "Programming Language :: Python :: 2", 50 | "Programming Language :: Python :: 2.6", 51 | "Programming Language :: Python :: 2.7", 52 | "Programming Language :: Python :: 3", 53 | "Programming Language :: Python :: 3.3", 54 | "Programming Language :: Python :: 3.4", 55 | "Programming Language :: Python :: Implementation :: CPython", 56 | "Programming Language :: Python :: Implementation :: PyPy", 57 | "Topic :: Software Development :: Libraries :: Python Modules", 58 | ], 59 | install_requires=[ 60 | ], 61 | ) 62 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | How To Contribute 2 | ================= 3 | 4 | Every open source project lives from the generous help by contributors that sacrifice their time and ``characteristic`` is no different. 5 | 6 | To make participation as pleasant as possible, this project adheres to the `Code of Conduct`_ by the Python Software Foundation. 7 | 8 | Here are a few guidelines to get you started: 9 | 10 | - Add yourself to the AUTHORS.rst_ file in an alphabetical fashion. 11 | Every contribution is valuable and shall be credited. 12 | - If your change is noteworthy, add an entry to the changelog_. 13 | - No contribution is too small; please submit as many fixes for typos and grammar bloopers as you can! 14 | - Don’t *ever* break backward compatibility. 15 | If it ever *has* to happen for higher reasons, ``characteristic`` will follow the proven procedures_ of the Twisted project. 16 | - *Always* add tests and docs for your code. 17 | This is a hard rule; patches with missing tests or documentation won’t be merged. 18 | If a feature is not tested or documented, it doesn’t exist. 19 | - Obey `PEP 8`_ and `PEP 257`_. 20 | - Write `good commit messages`_. 21 | - Ideally, squash_ your commits, i.e. make your pull requests just one commit. 22 | 23 | .. note:: 24 | If you have something great but aren’t sure whether it adheres -- or even can adhere -- to the rules above: **please submit a pull request anyway**! 25 | 26 | In the best case, we can mold it into something, in the worst case the pull request gets politely closed. 27 | There’s absolutely nothing to fear. 28 | 29 | Thank you for considering to contribute to ``characteristic``! 30 | If you have any question or concerns, feel free to reach out to me. 31 | 32 | 33 | .. _squash: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html 34 | .. _`PEP 8`: http://www.python.org/dev/peps/pep-0008/ 35 | .. _`PEP 257`: http://www.python.org/dev/peps/pep-0257/ 36 | .. _`good commit messages`: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 37 | .. _`Code of Conduct`: https://www.python.org/psf/codeofconduct/ 38 | .. _changelog: https://github.com/hynek/characteristic/blob/master/docs/changelog.rst 39 | .. _AUTHORS.rst: https://github.com/hynek/characteristic/blob/master/AUTHORS.rst 40 | .. _procedures: http://twistedmatrix.com/trac/wiki/CompatibilityPolicy 41 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | ``characteristic`` consists of class decorators that add features to your classes. 7 | There are three that start with ``@with_`` that add *one* feature to your class based on a list of attributes. 8 | Then there's the helper ``@attributes`` that combines them all into one decorator so you don't have to repeat the attribute list multiple times. 9 | 10 | .. currentmodule:: characteristic 11 | 12 | 13 | .. decorator:: with_repr(attrs) 14 | 15 | A class decorator that adds a human readable ``__repr__`` method to your class using *attrs*. 16 | 17 | .. doctest:: 18 | 19 | >>> from characteristic import with_repr 20 | >>> @with_repr(["a", "b"]) 21 | ... class RClass(object): 22 | ... def __init__(self, a, b): 23 | ... self.a = a 24 | ... self.b = b 25 | >>> c = RClass(42, "abc") 26 | >>> print c 27 | 28 | 29 | 30 | :param attrs: Attributes to work with. 31 | :type attrs: `list` of native strings 32 | 33 | 34 | .. decorator:: with_cmp(attrs) 35 | 36 | A class decorator that adds comparison methods based on *attrs*. 37 | 38 | For that, each class is treated like a `tuple` of the values of *attrs*. 39 | 40 | .. doctest:: 41 | 42 | >>> from characteristic import with_cmp 43 | >>> @with_cmp(["a", "b"]) 44 | ... class CClass(object): 45 | ... def __init__(self, a, b): 46 | ... self.a = a 47 | ... self.b = b 48 | >>> o1 = CClass(1, "abc") 49 | >>> o2 = CClass(1, "abc") 50 | >>> o1 == o2 # o1.a == o2.a and o1.b == o2.b 51 | True 52 | >>> o1.c = 23 53 | >>> o2.c = 42 54 | >>> o1 == o2 # attributes that are not passed to with_cmp are ignored 55 | True 56 | >>> o3 = CClass(2, "abc") 57 | >>> o1 < o3 # because 1 < 2 58 | True 59 | >>> o4 = CClass(1, "bca") 60 | >>> o1 < o4 # o1.a == o4.a, but o1.b < o4.b 61 | True 62 | 63 | 64 | :param attrs: Attributes to work with. 65 | :type attrs: `list` of native strings 66 | 67 | 68 | .. decorator:: with_init(attrs, defaults=None) 69 | 70 | A class decorator that wraps the ``__init__`` method of a class and sets 71 | *attrs* using passed keyword arguments before calling the original 72 | ``__init__``. 73 | 74 | Those keyword arguments that are used, are removed from the `kwargs` that is passed into your original ``__init__``. 75 | Optionally, a dictionary of default values for some of *attrs* can be passed too. 76 | 77 | .. doctest:: 78 | 79 | >>> from characteristic import with_init 80 | >>> @with_init(["a", "b"], defaults={"b": 2}) 81 | ... class IClass(object): 82 | ... def __init__(self): 83 | ... if self.b != 2: 84 | ... raise ValueError("'b' must be 2!") 85 | >>> o1 = IClass(a=1, b=2) 86 | >>> o2 = IClass(a=1) 87 | >>> o1.a == o2.a 88 | True 89 | >>> o1.b == o2.b 90 | True 91 | >>> IClass() 92 | Traceback (most recent call last): 93 | ... 94 | ValueError: Missing value for 'a'. 95 | >>> IClass(a=1, b=3) # the custom __init__ is called after the attributes are initialized 96 | Traceback (most recent call last): 97 | ... 98 | ValueError: 'b' must be 2! 99 | 100 | 101 | :param attrs: Attributes to work with. 102 | :type attrs: `list` of native strings 103 | 104 | :param defaults: Default values if attributes are omitted on instantiation. 105 | :type defaults: `dict` or `None` 106 | 107 | :raises ValueError: If the value for a non-optional attribute hasn't been passed. 108 | 109 | 110 | .. decorator:: attributes(attrs, defaults=None, create_init=True) 111 | 112 | A convenience class decorator that combines :func:`with_cmp`, 113 | :func:`with_repr`, and optionally :func:`with_init` to avoid code 114 | duplication. 115 | 116 | 117 | See :doc:`examples` for ``@attributes`` in action! 118 | 119 | :param attrs: Attributes to work with. 120 | :type attrs: Iterable of native strings. 121 | 122 | :param defaults: Default values if attributes are omitted on instantiation. 123 | :type defaults: `dict` or `None` 124 | 125 | :param create_init: Also apply :func:`with_init` (default: `True`) 126 | :type create_init: `bool` 127 | 128 | :raises ValueError: If the value for a non-optional attribute hasn't been passed. 129 | -------------------------------------------------------------------------------- /characteristic.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | """ 4 | Say 'yes' to types but 'no' to typing! 5 | """ 6 | 7 | 8 | __version__ = "0.2.0dev" 9 | __author__ = "Hynek Schlawack" 10 | __license__ = "MIT" 11 | __copyright__ = "Copyright 2014 Hynek Schlawack" 12 | 13 | 14 | def with_cmp(attrs): 15 | """ 16 | A class decorator that adds comparison methods based on *attrs*. 17 | 18 | Two instances are compared as if the respective values of *attrs* were 19 | tuples. 20 | 21 | :param attrs: Attributes to work with. 22 | :type attrs: `list` of native strings 23 | """ 24 | def attrs_to_tuple(obj): 25 | """ 26 | Create a tuple of all values of *obj*'s *attrs*. 27 | """ 28 | return tuple(getattr(obj, a) for a in attrs) 29 | 30 | def eq(self, other): 31 | if isinstance(other, self.__class__): 32 | return attrs_to_tuple(self) == attrs_to_tuple(other) 33 | else: 34 | return NotImplemented 35 | 36 | def ne(self, other): 37 | result = eq(self, other) 38 | if result is NotImplemented: 39 | return NotImplemented 40 | else: 41 | return not result 42 | 43 | def lt(self, other): 44 | if isinstance(other, self.__class__): 45 | return attrs_to_tuple(self) < attrs_to_tuple(other) 46 | else: 47 | return NotImplemented 48 | 49 | def le(self, other): 50 | if isinstance(other, self.__class__): 51 | return attrs_to_tuple(self) <= attrs_to_tuple(other) 52 | else: 53 | return NotImplemented 54 | 55 | def gt(self, other): 56 | if isinstance(other, self.__class__): 57 | return attrs_to_tuple(self) > attrs_to_tuple(other) 58 | else: 59 | return NotImplemented 60 | 61 | def ge(self, other): 62 | if isinstance(other, self.__class__): 63 | return attrs_to_tuple(self) >= attrs_to_tuple(other) 64 | else: 65 | return NotImplemented 66 | 67 | def hash_(self): 68 | return hash(attrs_to_tuple(self)) 69 | 70 | def wrap(cl): 71 | cl.__eq__ = eq 72 | cl.__ne__ = ne 73 | cl.__lt__ = lt 74 | cl.__le__ = le 75 | cl.__gt__ = gt 76 | cl.__ge__ = ge 77 | cl.__hash__ = hash_ 78 | 79 | return cl 80 | return wrap 81 | 82 | 83 | def with_repr(attrs): 84 | """ 85 | A class decorator that adds a human-friendly ``__repr__`` method that 86 | returns a sensible representation based on *attrs*. 87 | 88 | :param attrs: Attributes to work with. 89 | :type attrs: Iterable of native strings. 90 | """ 91 | def repr_(self): 92 | return "<{0}({1})>".format( 93 | self.__class__.__name__, 94 | ", ".join(a + "=" + repr(getattr(self, a)) for a in attrs) 95 | ) 96 | 97 | def wrap(cl): 98 | cl.__repr__ = repr_ 99 | return cl 100 | 101 | return wrap 102 | 103 | 104 | def with_init(attrs, defaults=None): 105 | """ 106 | A class decorator that wraps the __init__ method of a class and sets 107 | *attrs* first using keyword arguments. 108 | 109 | :param attrs: Attributes to work with. 110 | :type attrs: Iterable of native strings. 111 | 112 | :param defaults: Default values if attributes are omitted on instantiation. 113 | :type defaults: `dict` or `None` 114 | """ 115 | if defaults is None: 116 | defaults = {} 117 | 118 | def init(self, *args, **kw): 119 | for a in attrs: 120 | try: 121 | v = kw.pop(a) 122 | except KeyError: 123 | try: 124 | v = defaults[a] 125 | except KeyError: 126 | raise ValueError("Missing value for '{0}'.".format(a)) 127 | setattr(self, a, v) 128 | self.__original_init__(*args, **kw) 129 | 130 | def wrap(cl): 131 | cl.__original_init__ = cl.__init__ 132 | cl.__init__ = init 133 | return cl 134 | 135 | return wrap 136 | 137 | 138 | def attributes(attrs, defaults=None, create_init=True): 139 | """ 140 | A convenience class decorator that combines :func:`with_cmp`, 141 | :func:`with_repr`, and optionally :func:`with_init` to avoid code 142 | duplication. 143 | 144 | :param attrs: Attributes to work with. 145 | :type attrs: Iterable of native strings. 146 | 147 | :param defaults: Default values if attributes are omitted on instantiation. 148 | :type defaults: `dict` or `None` 149 | 150 | :param create_init: Also apply :func:`with_init` (default: `True`) 151 | :type create_init: `bool` 152 | """ 153 | def wrap(cl): 154 | cl = with_cmp(attrs)(with_repr(attrs)(cl)) 155 | if create_init is True: 156 | return with_init(attrs, defaults=defaults)(cl) 157 | else: 158 | return cl 159 | return wrap 160 | -------------------------------------------------------------------------------- /test_characteristic.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | import pytest 4 | 5 | from characteristic import ( 6 | attributes, 7 | with_cmp, 8 | with_init, 9 | with_repr, 10 | ) 11 | 12 | 13 | @with_cmp(["a", "b"]) 14 | class CmpC(object): 15 | def __init__(self, a, b): 16 | self.a = a 17 | self.b = b 18 | 19 | 20 | class TestWithCmp(object): 21 | def test_equal(self): 22 | """ 23 | Equal objects are detected as equal. 24 | """ 25 | assert CmpC(1, 2) == CmpC(1, 2) 26 | assert not (CmpC(1, 2) != CmpC(1, 2)) 27 | 28 | def test_unequal_same_class(self): 29 | """ 30 | Unequal objects of correct type are detected as unequal. 31 | """ 32 | assert CmpC(1, 2) != CmpC(2, 1) 33 | assert not (CmpC(1, 2) == CmpC(2, 1)) 34 | 35 | def test_unequal_different_class(self): 36 | """ 37 | Unequal objects of differnt type are detected even if their attributes 38 | match. 39 | """ 40 | class NotCmpC(object): 41 | a = 1 42 | b = 2 43 | assert CmpC(1, 2) != NotCmpC() 44 | assert not (CmpC(1, 2) == NotCmpC()) 45 | 46 | def test_lt(self): 47 | """ 48 | __lt__ compares objects as tuples of attribute values. 49 | """ 50 | for a, b in [ 51 | ((1, 2), (2, 1)), 52 | ((1, 2), (1, 3)), 53 | (("a", "b"), ("b", "a")), 54 | ]: 55 | assert CmpC(*a) < CmpC(*b) 56 | 57 | def test_lt_unordable(self): 58 | """ 59 | __lt__ returns NotImplemented if classes differ. 60 | """ 61 | assert NotImplemented == (CmpC(1, 2).__lt__(42)) 62 | 63 | def test_le(self): 64 | """ 65 | __le__ compares objects as tuples of attribute values. 66 | """ 67 | for a, b in [ 68 | ((1, 2), (2, 1)), 69 | ((1, 2), (1, 3)), 70 | ((1, 1), (1, 1)), 71 | (("a", "b"), ("b", "a")), 72 | (("a", "b"), ("a", "b")), 73 | ]: 74 | assert CmpC(*a) <= CmpC(*b) 75 | 76 | def test_le_unordable(self): 77 | """ 78 | __le__ returns NotImplemented if classes differ. 79 | """ 80 | assert NotImplemented == (CmpC(1, 2).__le__(42)) 81 | 82 | def test_gt(self): 83 | """ 84 | __gt__ compares objects as tuples of attribute values. 85 | """ 86 | for a, b in [ 87 | ((2, 1), (1, 2)), 88 | ((1, 3), (1, 2)), 89 | (("b", "a"), ("a", "b")), 90 | ]: 91 | assert CmpC(*a) > CmpC(*b) 92 | 93 | def test_gt_unordable(self): 94 | """ 95 | __gt__ returns NotImplemented if classes differ. 96 | """ 97 | assert NotImplemented == (CmpC(1, 2).__gt__(42)) 98 | 99 | def test_ge(self): 100 | """ 101 | __ge__ compares objects as tuples of attribute values. 102 | """ 103 | for a, b in [ 104 | ((2, 1), (1, 2)), 105 | ((1, 3), (1, 2)), 106 | ((1, 1), (1, 1)), 107 | (("b", "a"), ("a", "b")), 108 | (("a", "b"), ("a", "b")), 109 | ]: 110 | assert CmpC(*a) >= CmpC(*b) 111 | 112 | def test_ge_unordable(self): 113 | """ 114 | __ge__ returns NotImplemented if classes differ. 115 | """ 116 | assert NotImplemented == (CmpC(1, 2).__ge__(42)) 117 | 118 | def test_hash(self): 119 | """ 120 | __hash__ returns different hashes for different values. 121 | """ 122 | assert hash(CmpC(1, 2)) != hash(CmpC(1, 1)) 123 | 124 | 125 | @with_repr(["a", "b"]) 126 | class ReprC(object): 127 | def __init__(self, a, b): 128 | self.a = a 129 | self.b = b 130 | 131 | 132 | class TestReprAttrs(object): 133 | def test_repr(self): 134 | """ 135 | Test repr returns a sensible value. 136 | """ 137 | assert "" == repr(ReprC(1, 2)) 138 | 139 | 140 | @with_init(["a", "b"]) 141 | class InitC(object): 142 | def __init__(self): 143 | if self.a == self.b: 144 | raise ValueError 145 | 146 | 147 | class TestWithInit(object): 148 | def test_sets_attributes(self): 149 | """ 150 | The attributes are initialized using the passed keywords. 151 | """ 152 | obj = InitC(a=1, b=2) 153 | assert 1 == obj.a 154 | assert 2 == obj.b 155 | 156 | def test_custom_init(self): 157 | """ 158 | The class initializer is called too. 159 | """ 160 | with pytest.raises(ValueError): 161 | InitC(a=1, b=1) 162 | 163 | def test_passes_args(self): 164 | """ 165 | All positional parameters are passed to the original initializer. 166 | """ 167 | @with_init(["a"]) 168 | class InitWithArg(object): 169 | def __init__(self, arg): 170 | self.arg = arg 171 | 172 | obj = InitWithArg(42, a=1) 173 | assert 42 == obj.arg 174 | assert 1 == obj.a 175 | 176 | def test_passes_remaining_kw(self): 177 | """ 178 | Keyword arguments that aren't used for attributes are passed to the 179 | original initializer. 180 | """ 181 | @with_init(["a"]) 182 | class InitWithKWArg(object): 183 | def __init__(self, kw_arg=None): 184 | self.kw_arg = kw_arg 185 | 186 | obj = InitWithKWArg(a=1, kw_arg=42) 187 | assert 42 == obj.kw_arg 188 | assert 1 == obj.a 189 | 190 | def test_does_not_pass_attrs(self): 191 | """ 192 | The attributes are removed from the keyword arguments before they are 193 | passed to the original initializer. 194 | """ 195 | @with_init(["a"]) 196 | class InitWithKWArgs(object): 197 | def __init__(self, **kw): 198 | assert "a" not in kw 199 | assert "b" in kw 200 | InitWithKWArgs(a=1, b=42) 201 | 202 | def test_defaults(self): 203 | """ 204 | If defaults are passed, they are used as fallback. 205 | """ 206 | @with_init(["a", "b"], defaults={"b": 2}) 207 | class InitWithDefaults(object): 208 | pass 209 | obj = InitWithDefaults(a=1) 210 | assert 2 == obj.b 211 | 212 | def test_missing_arg(self): 213 | """ 214 | Raises `ValueError` if a value isn't passed. 215 | """ 216 | with pytest.raises(ValueError): 217 | InitC(a=1) 218 | 219 | 220 | @attributes(["a", "b"], create_init=True) 221 | class MagicWithInitC(object): 222 | pass 223 | 224 | 225 | @attributes(["a", "b"], create_init=False) 226 | class MagicWithoutInitC(object): 227 | pass 228 | 229 | 230 | class TestAttributes(object): 231 | def test_leaves_init_alone(self): 232 | """ 233 | If *create_init* is `False`, leave __init__ alone. 234 | """ 235 | obj = MagicWithoutInitC() 236 | with pytest.raises(AttributeError): 237 | obj.a 238 | with pytest.raises(AttributeError): 239 | obj.b 240 | 241 | def test_wraps_init(self): 242 | """ 243 | If *create_init* is `True`, build initializer. 244 | """ 245 | obj = MagicWithInitC(a=1, b=2) 246 | assert 1 == obj.a 247 | assert 2 == obj.b 248 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(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/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/characteristic.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/characteristic.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/characteristic" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/characteristic" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # characteristic documentation build configuration file, created by 4 | # sphinx-quickstart on Sun May 11 16:17:15 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import codecs 16 | import datetime 17 | import os 18 | import re 19 | 20 | try: 21 | import sphinx_rtd_theme 22 | except ImportError: 23 | sphinx_rtd_theme = None 24 | 25 | 26 | def read(*parts): 27 | """ 28 | Build an absolute path from *parts* and and return the contents of the 29 | resulting file. Assume UTF-8 encoding. 30 | """ 31 | here = os.path.abspath(os.path.dirname(__file__)) 32 | with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: 33 | return f.read() 34 | 35 | 36 | def find_version(*file_paths): 37 | """ 38 | Build a path from *file_paths* and search for a ``__version__`` 39 | string inside. 40 | """ 41 | version_file = read(*file_paths) 42 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 43 | version_file, re.M) 44 | if version_match: 45 | return version_match.group(1) 46 | raise RuntimeError("Unable to find version string.") 47 | 48 | # If extensions (or modules to document with autodoc) are in another directory, 49 | # add these directories to sys.path here. If the directory is relative to the 50 | # documentation root, use os.path.abspath to make it absolute, like shown here. 51 | #sys.path.insert(0, os.path.abspath('.')) 52 | 53 | # -- General configuration ------------------------------------------------ 54 | 55 | # If your documentation needs a minimal Sphinx version, state it here. 56 | #needs_sphinx = '1.0' 57 | 58 | # Add any Sphinx extension module names here, as strings. They can be 59 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 60 | # ones. 61 | extensions = [ 62 | 'releases', 63 | 'sphinx.ext.doctest', 64 | 'sphinx.ext.intersphinx', 65 | 'sphinx.ext.todo', 66 | ] 67 | 68 | # 'releases' (changelog) settings 69 | releases_issue_uri = "https://github.com/hynek/characteristic/issues/%s" 70 | releases_release_uri = "https://github.com/hynek/characteristic/tree/%s" 71 | 72 | 73 | # Add any paths that contain templates here, relative to this directory. 74 | templates_path = ['_templates'] 75 | 76 | # The suffix of source filenames. 77 | source_suffix = '.rst' 78 | 79 | # The encoding of source files. 80 | #source_encoding = 'utf-8-sig' 81 | 82 | # The master toctree document. 83 | master_doc = 'index' 84 | 85 | # General information about the project. 86 | project = u'characteristic' 87 | year = datetime.date.today().year 88 | copyright = u'2014{0}, Hynek Schlawack'.format( 89 | u'-{0}'.format(year) if year != 2014 else u"" 90 | ) 91 | 92 | # The version info for the project you're documenting, acts as replacement for 93 | # |version| and |release|, also used in various other places throughout the 94 | # built documents. 95 | # 96 | # The short X.Y version. 97 | release = find_version("../characteristic.py") 98 | version = release.rsplit(u".", 1)[0] 99 | # The full version, including alpha/beta/rc tags. 100 | 101 | # The language for content autogenerated by Sphinx. Refer to documentation 102 | # for a list of supported languages. 103 | #language = None 104 | 105 | # There are two options for replacing |today|: either, you set today to some 106 | # non-false value, then it is used: 107 | #today = '' 108 | # Else, today_fmt is used as the format for a strftime call. 109 | #today_fmt = '%B %d, %Y' 110 | 111 | # List of patterns, relative to source directory, that match files and 112 | # directories to ignore when looking for source files. 113 | exclude_patterns = ['_build'] 114 | 115 | # The reST default role (used for this markup: `text`) to use for all 116 | # documents. 117 | #default_role = None 118 | 119 | # If true, '()' will be appended to :func: etc. cross-reference text. 120 | #add_function_parentheses = True 121 | 122 | # If true, the current module name will be prepended to all description 123 | # unit titles (such as .. function::). 124 | #add_module_names = True 125 | 126 | # If true, sectionauthor and moduleauthor directives will be shown in the 127 | # output. They are ignored by default. 128 | #show_authors = False 129 | 130 | # The name of the Pygments (syntax highlighting) style to use. 131 | pygments_style = 'sphinx' 132 | 133 | # A list of ignored prefixes for module index sorting. 134 | #modindex_common_prefix = [] 135 | 136 | # If true, keep warnings as "system message" paragraphs in the built documents. 137 | #keep_warnings = False 138 | 139 | 140 | # -- Options for HTML output ---------------------------------------------- 141 | 142 | # The theme to use for HTML and HTML Help pages. See the documentation for 143 | # a list of builtin themes. 144 | 145 | if sphinx_rtd_theme: 146 | html_theme = "sphinx_rtd_theme" 147 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 148 | else: 149 | html_theme = "default" 150 | 151 | # Theme options are theme-specific and customize the look and feel of a theme 152 | # further. For a list of options available for each theme, see the 153 | # documentation. 154 | #html_theme_options = {} 155 | 156 | # Add any paths that contain custom themes here, relative to this directory. 157 | #html_theme_path = [] 158 | 159 | # The name for this set of Sphinx documents. If None, it defaults to 160 | # " v documentation". 161 | #html_title = None 162 | 163 | # A shorter title for the navigation bar. Default is the same as html_title. 164 | #html_short_title = None 165 | 166 | # The name of an image file (relative to this directory) to place at the top 167 | # of the sidebar. 168 | #html_logo = None 169 | 170 | # The name of an image file (within the static path) to use as favicon of the 171 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 172 | # pixels large. 173 | #html_favicon = None 174 | 175 | # Add any paths that contain custom static files (such as style sheets) here, 176 | # relative to this directory. They are copied after the builtin static files, 177 | # so a file named "default.css" will overwrite the builtin "default.css". 178 | # html_static_path = ['_static'] 179 | 180 | # Add any extra paths that contain custom files (such as robots.txt or 181 | # .htaccess) here, relative to this directory. These files are copied 182 | # directly to the root of the documentation. 183 | #html_extra_path = [] 184 | 185 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 186 | # using the given strftime format. 187 | #html_last_updated_fmt = '%b %d, %Y' 188 | 189 | # If true, SmartyPants will be used to convert quotes and dashes to 190 | # typographically correct entities. 191 | #html_use_smartypants = True 192 | 193 | # Custom sidebar templates, maps document names to template names. 194 | #html_sidebars = {} 195 | 196 | # Additional templates that should be rendered to pages, maps page names to 197 | # template names. 198 | #html_additional_pages = {} 199 | 200 | # If false, no module index is generated. 201 | #html_domain_indices = True 202 | 203 | # If false, no index is generated. 204 | #html_use_index = True 205 | 206 | # If true, the index is split into individual pages for each letter. 207 | #html_split_index = False 208 | 209 | # If true, links to the reST sources are added to the pages. 210 | #html_show_sourcelink = True 211 | 212 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 213 | #html_show_sphinx = True 214 | 215 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 216 | #html_show_copyright = True 217 | 218 | # If true, an OpenSearch description file will be output, and all pages will 219 | # contain a tag referring to it. The value of this option must be the 220 | # base URL from which the finished HTML is served. 221 | #html_use_opensearch = '' 222 | 223 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 224 | #html_file_suffix = None 225 | 226 | # Output file base name for HTML help builder. 227 | htmlhelp_basename = 'characteristicdoc' 228 | 229 | 230 | # -- Options for LaTeX output --------------------------------------------- 231 | 232 | latex_elements = { 233 | # The paper size ('letterpaper' or 'a4paper'). 234 | #'papersize': 'letterpaper', 235 | 236 | # The font size ('10pt', '11pt' or '12pt'). 237 | #'pointsize': '10pt', 238 | 239 | # Additional stuff for the LaTeX preamble. 240 | #'preamble': '', 241 | } 242 | 243 | # Grouping the document tree into LaTeX files. List of tuples 244 | # (source start file, target name, title, 245 | # author, documentclass [howto, manual, or own class]). 246 | latex_documents = [ 247 | ('index', 'characteristic.tex', u'characteristic Documentation', 248 | u'Hynek Schlawack', 'manual'), 249 | ] 250 | 251 | # The name of an image file (relative to this directory) to place at the top of 252 | # the title page. 253 | #latex_logo = None 254 | 255 | # For "manual" documents, if this is true, then toplevel headings are parts, 256 | # not chapters. 257 | #latex_use_parts = False 258 | 259 | # If true, show page references after internal links. 260 | #latex_show_pagerefs = False 261 | 262 | # If true, show URL addresses after external links. 263 | #latex_show_urls = False 264 | 265 | # Documents to append as an appendix to all manuals. 266 | #latex_appendices = [] 267 | 268 | # If false, no module index is generated. 269 | #latex_domain_indices = True 270 | 271 | 272 | # -- Options for manual page output --------------------------------------- 273 | 274 | # One entry per manual page. List of tuples 275 | # (source start file, name, description, authors, manual section). 276 | man_pages = [ 277 | ('index', 'characteristic', u'characteristic Documentation', 278 | [u'Hynek Schlawack'], 1) 279 | ] 280 | 281 | # If true, show URL addresses after external links. 282 | #man_show_urls = False 283 | 284 | 285 | # -- Options for Texinfo output ------------------------------------------- 286 | 287 | # Grouping the document tree into Texinfo files. List of tuples 288 | # (source start file, target name, title, author, 289 | # dir menu entry, description, category) 290 | texinfo_documents = [ 291 | ('index', 'characteristic', u'characteristic Documentation', 292 | u'Hynek Schlawack', 'characteristic', 'One line description of project.', 293 | 'Miscellaneous'), 294 | ] 295 | 296 | # Documents to append as an appendix to all manuals. 297 | #texinfo_appendices = [] 298 | 299 | # If false, no module index is generated. 300 | #texinfo_domain_indices = True 301 | 302 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 303 | #texinfo_show_urls = 'footnote' 304 | 305 | # If true, do not generate a @detailmenu in the "Top" node's menu. 306 | #texinfo_no_detailmenu = False 307 | 308 | 309 | # Example configuration for intersphinx: refer to the Python standard library. 310 | intersphinx_mapping = {'http://docs.python.org/': None} 311 | --------------------------------------------------------------------------------