├── 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 |
--------------------------------------------------------------------------------