Git Product home page Git Product logo

characteristic's Introduction

characteristic: Python attributes without boilerplate.

No Maintenance Intended

Warning

Characteristic is unmaintained. Please have a look at its successor attrs.

characteristic's People

Contributors

adamtheturtle avatar ashfall avatar babbageclunk avatar felixonmars avatar hynek avatar julian avatar nossralf avatar paulie-g avatar tomprince avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

characteristic's Issues

with_init support for positional arguments

with_init doesn't currently seem to support positional arguments. Maybe that's deliberate, but it's not explained in the documentation and the error message isn't particularly helpful.

I personally prefer keyword arguments, because it makes the code more readable, but sometimes the name of the variable is clue enough eg

In [13]: from characteristic import attributes

In [14]: class Foo(object):
    pass
   ....: 

In [15]: Foo = attributes(['reactor','url'])(Foo)

In [16]: dummyReactor = object()

In [17]: url = b'http://www.example.com'

In [18]: Foo(dummyReactor, url)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-181d7803e7c2> in <module>()
----> 1 Foo(dummyReactor, url)

/home/richard/.virtualenvs/flocker-30/lib/python2.7/site-packages/characteristic.pyc in init(self, *args, **kw)
    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)

ValueError: Missing value for 'reactor'.

So I propose adding support for positional arguments, but if not, add some better error handling and some documentation.

inscrutable error when passing too many parameters

If I do this:

from characteristic import attributes

@attributes(["x"])
class X(object):
    pass

X(x=1, y=2)

I get this error:

Traceback (most recent call last):
  File "c.py", line 8, in <module>
    X(x=1, y=2)
  File ".../characteristic.py", line 128, in init
    self.__original_init__(*args, **kw)
TypeError: object.__init__() takes no parameters

This isn't a great way of saying parameter "y" not expected.

Split with_cmp into with_eq_hash and with_ordering?

Quite often I want to define equality on objects by all or most of the attributes, but ordering by a different, smaller subset. Or, similarly, sometimes I want equality + hashing so that I can use them for set membership, but I don't want ordering, which is often meaningless.

Thoughts?

Feature request: Immutable records

One of the nice things about namedtuple is that it creates immutable objects. I very much like the approach that characteristic takes, but it'd be wonderful if it could be used to create immutable records.

Add type checks

Something along of instance_of=int.

Needs a settled with_init though #9.

14.0.0: Tests failed with Python 2.7.8/3.4.1

Hi, I'm getting tests failures on Arch Linux x86_64, with either Python 2.7.8 or 3.4.1.

Failures with Python 3.4.1:

============================= test session starts ==============================
platform linux -- Python 3.4.1 -- py-1.4.23 -- pytest-2.6.1
collected 61 items

test_characteristic.py ...........................................F........F........

=================================== FAILURES ===================================
____________________ TestWithInit.test_deprecation_defaults ____________________

self = <test_characteristic.TestWithInit object at 0x7fdb2307f978>

    def test_deprecation_defaults(self):
        """
            Emits a DeprecationWarning if `defaults` is used.
            """
        with warnings.catch_warnings(record=True) as w:
            @attributes(["a"], create_init=False)
            class C(object):
                pass
>       assert (
            '`create_init` has been deprecated in 14.0, please use '
            '`apply_with_init`.'
        ) == w[0].message.args[0]
E       IndexError: list index out of range

test_characteristic.py:480: IndexError
_________________ TestAttributes.test_deprecation_create_init __________________

self = <test_characteristic.TestAttributes object at 0x7fdb23718cf8>

    def test_deprecation_create_init(self):
        """
            Emits a DeprecationWarning if `create_init` is used.
            """
        with warnings.catch_warnings(record=True) as w:
            @attributes(["a"], create_init=False)
            class C(object):
                pass
>       assert (
            '`create_init` has been deprecated in 14.0, please use '
            '`apply_with_init`.'
        ) == w[0].message.args[0]
E       IndexError: list index out of range

test_characteristic.py:595: IndexError
===================== 2 failed, 59 passed in 0.13 seconds ======================

With Python 2.7.8:

============================= test session starts ==============================
platform linux2 -- Python 2.7.8 -- py-1.4.23 -- pytest-2.6.1
collected 61 items

test_characteristic.py ...........................................F........F........

=================================== FAILURES ===================================
____________________ TestWithInit.test_deprecation_defaults ____________________

self = <test_characteristic.TestWithInit object at 0x7f05c8345690>

    def test_deprecation_defaults(self):
        """
            Emits a DeprecationWarning if `defaults` is used.
            """
        with warnings.catch_warnings(record=True) as w:
            @attributes(["a"], create_init=False)
            class C(object):
                pass
>       assert (
            '`create_init` has been deprecated in 14.0, please use '
            '`apply_with_init`.'
        ) == w[0].message.args[0]
E       IndexError: list index out of range

test_characteristic.py:480: IndexError
_________________ TestAttributes.test_deprecation_create_init __________________

self = <test_characteristic.TestAttributes object at 0x7f05c80e4dd0>

    def test_deprecation_create_init(self):
        """
            Emits a DeprecationWarning if `create_init` is used.
            """
        with warnings.catch_warnings(record=True) as w:
            @attributes(["a"], create_init=False)
            class C(object):
                pass
>       assert (
            '`create_init` has been deprecated in 14.0, please use '
            '`apply_with_init`.'
        ) == w[0].message.args[0]
E       IndexError: list index out of range

test_characteristic.py:595: IndexError
===================== 2 failed, 59 passed in 0.11 seconds ======================

characteristic_vars or .from_characteristic_attributes?

So, since we have .characteristic_attributes, it's now possible to get the characteristic attributes of a characteristically-defined class. Hooray. But getting their values is very slightly nontrivial.

E.g., a few times I've written:

@attributes(
     [...]
)
class Foo(object):
    pass

attributes = {attr.name : getattr(foo_instance) for attr in foo_instance.characteristics}
new_foo = Foo(**attributes)

but this is subtly wrong, because you need to actually also call init_aliaser, which only possibly shows up once you add a private attribute if you're using the default implementation.

Do you think it's worthwhile adding an API for this? Two ideas for one would be to add characteristic.characteristic_vars to replace that dict comprehension, in which case the new way to do this would be Foo(**characteristic.characteristic_vars(...)). Alternatively, another idea would be to just add a classmethod to characteristic classes to construct them out of dictionaries produced by characteristic_attributes. Either of these sound like decent ideas to you?

Update docs with mention of how @with_cmp affects .__hash__()

I have run into this use case a few times now: I want all of characteristic's goodness for the entire set of attributes, but it only makes sense for .hash() to reference some subset of of them, because the rest are not meaningful in that context. Characteristic already has a facility to make this distinction for compares (with_cmp/exclude_from_cmp) and I'd like to submit a PR that does it for hashes as well in an analogous manner.

Any objections @hynek ? ๐Ÿ‘

Use of `with_init` with inhitence fails.

Trying to use with_init in both a base class and a derived class fails. It appears that the generated base class' __init__ is getting called twice. It looks like this is due to a confusion with both classes trying to control __original_init__.

from characteristic import attributes

@attributes(["base"])
def Base(Exception):
    pass

@attributes(["derived"])
def Derived(Base):
    pass

Derived(base="base", derived="dervied")

fails with

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    Derived(base="base", derived="dervied")
TypeError: Derived() got an unexpected keyword argument 'base

14.3.0: pytest warnings

Just normal build, install and test cycle used on building package from non-root account:

  • "setup.py build":
  • "setup.py install --root </install/prefix>"
  • "pytest with PYTHONPATH pointing to setearch and sitelib inside </install/prefix>
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-characteristic-14.3.0-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-characteristic-14.3.0-2.fc35.x86_64/usr/lib/python3.8/site-packages
+ PYTHONDONTWRITEBYTECODE=1
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.9, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
benchmark: 3.4.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/tkloczko/rpmbuild/BUILD/characteristic-14.3.0
plugins: forked-1.3.0, shutil-1.7.0, virtualenv-1.7.0, expect-1.1.0, httpbin-1.0.0, xdist-2.2.1, flake8-1.0.7, timeout-1.4.2, betamax-0.8.1, freezegun-0.4.2, case-1.5.3, isort-1.3.0, aspectlib-1.5.2, asyncio-0.15.1, toolbox-0.5, xprocess-0.17.1, aiohttp-0.3.0, checkdocs-2.7.0, mock-3.6.1, rerunfailures-9.1.1, requests-mock-1.9.3, cov-2.12.1, pyfakefs-4.5.0, cases-3.6.1, flaky-3.7.0, hypothesis-6.14.0, benchmark-3.4.1, Faker-8.8.0
collected 89 items

. .                                                                                                                                                                  [  1%]
test_characteristic.py .......................................................................................                                                       [100%]

============================================================================= warnings summary =============================================================================
test_characteristic.py::TestWithInit::test_defaults
  /home/tkloczko/rpmbuild/BUILD/characteristic-14.3.0/test_characteristic.py:395: DeprecationWarning: `defaults` has been deprecated in 14.0, please use the `Attribute` class instead.
    @with_init(["a", "b"], defaults={"b": 2})

test_characteristic.py::TestWithInit::test_defaults_conflict
  /home/tkloczko/rpmbuild/BUILD/characteristic-14.3.0/test_characteristic.py:414: DeprecationWarning: `defaults` has been deprecated in 14.0, please use the `Attribute` class instead.
    @with_init([Attribute("a")], defaults={"a": 42})

test_characteristic.py::TestAttributes::test_leaves_init_alone
  /home/tkloczko/rpmbuild/BUILD/characteristic-14.3.0/test_characteristic.py:586: DeprecationWarning: `create_init` has been deprecated in 14.0, please use `apply_with_init`.
    @attributes(["a"], create_init=False)

test_characteristic.py::TestEnsureAttributes::test_leaves_attribute_alone
test_characteristic.py::TestEnsureAttributes::test_converts_rest
test_characteristic.py::TestEnsureAttributes::test_defaults
test_characteristic.py::TestEnsureAttributes::test_defaults_Attribute
  /usr/lib/python3.8/site-packages/_pytest/python.py:183: DeprecationWarning: `defaults` has been deprecated in 14.0, please use the `Attribute` class instead.
    result = testfunction(**testargs)

-- Docs: https://docs.pytest.org/en/stable/warnings.html
====================================================================== 88 passed, 7 warnings in 7.18s ======================================================================

editor support? (i.e. pycharm)

One of the primary benefits I get from having types is that my editor can help suggest appropriate completions and let me know when I'm doing something stupid.

But my editor currently doesn't understand that a class decorated with @attributes has a constructor that takes those named parameters.

What would it take for a python editor to be able to recognize Characteristic attributes?

Could it be done with static analysis, or would it be easier to find out by some run-time introspection?

Would it require code specific to Characteristic, or could it be done with more general python introspection and/or type annotations?

Allow initialiser arguments to be mapped to alternative (private) attribute names

In Twisted we try hard to limit the public API in new classes so it would be nice if with_init had some way to map an argument name to an alternative private attribute name.

In fact it might even be enough to have a rule that argument names beginning with _ would be stored as private attributes but supplied without the _ prefix.

twisted.python.util.FancyStrMixin has a horrible system where you can supply a tuple of (attr_name, display_name). I suppose you could do something like that here.

Or add a new Attribute class with an argument_name option...and then allow that to be supplied to the characteristic decorators...if you see what I mean.

Store attribute names on the class

It'd be nice if there were a way to retrieve the names of the attributes passed to @attributes off the class. This allows things like being able to write:

def to_json(self):
    return dict((attr, getattr(self, attr)) for attr in self.characteristic_attributes)

Support distinct mutable values as defaults

I wanted to replace a use of epsilon.structlike.record with characteristic. The original use looks like this:

class Link(record("source target")):
    def __init__(self, *a, **k):
        super(Link, self).__init__(*a, **k)
        self.annotations = []

I started typing and got this far before I realized my mistake:

@attributes(["source", "target", "annotations"], {"annotations": []})
class Link(object):

I'll probably still use characteristic but I'm slightly sad to not be able to hoist the initialization of annotations (and therefore be able to skip writing an __init__).

It would be neat if characteristic offered support for this use.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.