{ "info": { "author": "Rafael Oliveira", "author_email": "rafaelbco@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "================\nrbco.caseclasses\n================\n\n.. image:: https://travis-ci.org/rafaelbco/rbco.caseclasses.svg?branch=master\n :target: https://travis-ci.org/rafaelbco/rbco.caseclasses\n :alt: Build status\n\n.. image:: https://coveralls.io/repos/rafaelbco/rbco.caseclasses/badge.png\n :target: https://coveralls.io/r/rafaelbco/rbco.caseclasses\n :alt: Coverage\n\n.. .. image:: https://pypip.in/download/rbco.caseclasses/badge.png\n :target: https://pypi.python.org/pypi/rbco.caseclasses/\n :alt: Downloads\n\n.. image:: https://pypip.in/version/rbco.caseclasses/badge.png\n :target: https://pypi.python.org/pypi/rbco.caseclasses/\n :alt: Latest Version\n\n.. image:: https://pypip.in/license/rbco.caseclasses/badge.png\n :target: https://raw.githubusercontent.com/rafaelbco/rbco.caseclasses/master/docs/LICENSE.txt\n :alt: License\n\n.. contents::\n\nOverview\n========\n\nProvide a compact syntax to define simple \"struct-like\" (or \"record-like\", \"bean-like\") classes.\nThe resulting classes are very similar to namedtuple_, but mutable, with a nicer syntax, more\nflexibility and more features.\n\nHere's a summary of the features:\n\n- It's possible to define default values for fields.\n- Useful ``__repr__`` and ``__str__`` implementations.\n- Structural equality, i.e, useful ``__eq__`` and ``__ne__`` implementations.\n- ``copy`` method (copy-constructor).\n- Conversion from/to dictionary and tuple.\n- `__slots__`_ declaration to improve performance and prevent assignment on unknown fields.\n- It's possible to define custom methods.\n- Supports inheritance.\n\nSee also the motivation_ section for other implementations of the concept, specially MacroPy_\nwhich was the inspiration for this project and uses a very different approach.\n\n\nCompatibility\n=============\n\nCurrently only Python 2.7 is supported.\n\n\nInstalation\n===========\n\nThe usual::\n\n pip install rbco.caseclasses\n\nOr::\n\n easy_install rbco.caseclasses\n\n\nUsage\n=====\n\nBasics\n------\n\nLet's start by creating a simple case class:\n\n.. code:: python\n\n >>> from rbco.caseclasses import case\n >>>\n >>> @case\n ... class Person(object):\n ... \"\"\"Represent a person.\"\"\"\n ... def __init__(self, name, age=None, gender=None): pass\n\nThe declared ``__init__`` is just a stub. The parameters defines which fields the class will have\nand its default values. The ``__init__`` method is replaced by a new one, which takes care of\nassigning the values of the fields.\n\nThe constructor works as expected, according to the provided ``__init__`` stub:\n\n.. code:: python\n\n >>> Person('John')\n Person(name='John', age=None, gender=None)\n >>> Person('John', 30, 'm')\n Person(name='John', age=30, gender='m')\n >>> Person(name='John', age=30, gender='m')\n Person(name='John', age=30, gender='m')\n >>> Person('John', gender='m')\n Person(name='John', age=None, gender='m')\n\nNote that in the string representation the fields are in the same order as defined in the\nconstructor.\n\nThe docstring of the class is preserved:\n\n.. code:: python\n\n >>> Person.__doc__\n 'Represent a person.'\n\nThe signature of the constructor is not preserved. The resulting ``__init__`` method signature\nis a generic one, taking only ``*args`` and ``**kwargs``:\n\n.. code:: python\n\n >>> from inspect import getargspec\n >>> getargspec(Person.__init__)\n ArgSpec(args=['self'], varargs='args', keywords='kwargs', defaults=None)\n\nHowever the docstring contains the original signature:\n\n.. code:: python\n\n >>> Person.__init__.__doc__\n 'Original signature: (self, name, age=None, gender=None)'\n\nIt's not possible to create a case class without a constructor:\n\n.. code:: python\n\n >>> from rbco.caseclasses import case\n >>>\n >>> @case\n ... class Foo(object): pass\n Traceback (most recent call last):\n ...\n RuntimeError: Case class must define a constructor.\n\n\nMutability and __slots__\n------------------------\n\nInstances are mutable:\n\n.. code:: python\n\n >>> p = Person('John')\n >>> p\n Person(name='John', age=None, gender=None)\n >>> p.name = 'Bob'\n >>> p.age = 35\n >>> p\n Person(name='Bob', age=35, gender=None)\n\nHowever it's not possible to assign to unknown attributes:\n\n.. code:: python\n\n >>> p.department = 'sales'\n Traceback (most recent call last):\n ...\n AttributeError: 'Person' object has no attribute 'department'\n\nThis is because of the `__slots__`_ declaration:\n\n.. code:: python\n\n >>> p.__slots__\n ['name', 'age', 'gender']\n\n\nStructural equality\n-------------------\n\nStructural equality is supported:\n\n.. code:: python\n\n >>> p1 = Person('John', 30)\n >>> p2 = Person('Bob', 25)\n >>> p1 == p2\n False\n >>> p1 != p2\n True\n >>> p2.name = 'John'\n >>> p2.age = 30\n >>> p1 == p2\n True\n >>> p1 != p2\n False\n >>> p2.gender = 'm'\n >>> p1 == p2\n False\n\n\nCopy-constructor\n----------------\n\nA copy-constructor is provided:\n\n.. code:: python\n\n >>> p1 = Person('John', 30)\n >>> copy_of_p1 = p1.copy()\n >>> p1\n Person(name='John', age=30, gender=None)\n >>> copy_of_p1\n Person(name='John', age=30, gender=None)\n >>> p1 is copy_of_p1\n False\n >>> p2 = p1.copy(name='Bob', gender='m')\n >>> p2\n Person(name='Bob', age=30, gender='m')\n\n\nConversion from/to dictionary and tuple\n---------------------------------------\n\nConversion from/to dictionary is easy. The ``as_dict`` method return an ``OrderedDict``:\n\n.. code:: python\n\n >>> p1 = Person('Mary', 33)\n >>> p1\n Person(name='Mary', age=33, gender=None)\n >>> p1.as_dict()\n OrderedDict([('name', 'Mary'), ('age', 33), ('gender', None)])\n >>> Person(**p1.as_dict())\n Person(name='Mary', age=33, gender=None)\n\nConversion from/to tuple is also possible:\n\n.. code:: python\n\n >>> p1 = Person('John', 30)\n >>> p1\n Person(name='John', age=30, gender=None)\n >>> p1.as_tuple()\n ('John', 30, None)\n >>> Person(*p1.as_tuple())\n Person(name='John', age=30, gender=None)\n\n\n.. _`custom members`:\n\nCustom members\n--------------\n\nCase classes are very much like regular classes. It's possible to define any kind of custom\nmembers.\n\nThe most common case should be adding a custom instance method:\n\n.. code:: python\n\n >>> import math\n >>> @case\n ... class Point(object):\n ... def __init__(self, x, y): pass\n ...\n ... def distance(self, other):\n ... return math.sqrt((self.x - other.x)**2 + (self.y - other.y)**2)\n >>> p1 = Point(0, 0)\n >>> p2 = Point(10, 0)\n >>> p1.distance(p2)\n 10.0\n\nOther kinds of class members are supported as well:\n\n.. code:: python\n\n >>> @case\n ... class Example(object):\n ... class_attribute = 'some value'\n ...\n ... def __init__(self, field1): pass\n ...\n ... @staticmethod\n ... def static_method():\n ... print 'This is an static method.'\n ...\n ... @classmethod\n ... def class_method(cls):\n ... print 'This is a class method of the class {}.'.format(cls.__name__)\n ...\n >>> e = Example('example')\n >>> Example.class_attribute\n 'some value'\n >>> e.class_attribute\n 'some value'\n >>> Example.static_method()\n This is an static method.\n >>> Example.class_method()\n This is a class method of the class Example.\n\n\nInheritance\n-----------\n\nLet's create a base case class and a derived one:\n\n.. code:: python\n\n >>> @case\n ... class Person(object):\n ... def __init__(self, name, age=None, gender=None): pass\n ...\n ... def present(self):\n ... print \"I'm {}, {} years old and my gender is '{}'.\".format(\n ... self.name,\n ... self.age,\n ... self.gender\n ... )\n ...\n >>> @case\n ... class Employee(Person):\n ... def __init__(self, name, age=None, gender=None, department=None): pass\n\nIt's necessary to repeat the fields of the base class, but you would have to do that anyway if\nyou were implementing the case classes manually.\n\nMethods from the base class are inherited:\n\n.. code:: python\n\n >>> p = Person('John', 30, 'm')\n >>> p.present()\n I'm John, 30 years old and my gender is 'm'.\n >>> e = Employee('Mary', 33, 'f', 'sales')\n >>> e.present()\n I'm Mary, 33 years old and my gender is 'f'.\n\nInstances of ``Person`` and ``Employee`` will always be considered different, since employees\nhave an extra field:\n\n.. code:: python\n\n >>> p = Person('John')\n >>> e = Employee('John')\n >>> p == e\n False\n\nOverriding a base class method works as expected:\n\n.. code:: python\n\n >>> @case\n ... class ImprovedEmployee(Employee):\n ... def present(self):\n ... super(ImprovedEmployee, self).present()\n ... print 'I work at the {} department.'.format(self.department)\n ...\n >>> ie = ImprovedEmployee(name='Mary', department='marketing', age=33, gender='f')\n >>> ie.present()\n I'm Mary, 33 years old and my gender is 'f'.\n I work at the marketing department.\n\n\nOverriding case class behavior\n------------------------------\n\nIt's possible to override the standard case class methods (``__repr__``, ``__eq__``, etc).\nFor example:\n\n.. code:: python\n\n >>> @case\n ... class Foo(object):\n ... def __init__(self, bar): pass\n ...\n ... def __eq__(self, other):\n ... return True # All `Foo`s are equal.\n ...\n >>> Foo('bar') == Foo('baz')\n True\n\nIt's even possible to call the original version on the subclass method:\n\n.. code:: python\n\n >>> @case\n ... class Foo(object):\n ... def __init__(self, bar):\n ... pass\n ...\n ... def __repr__(self):\n ... return 'This is my string representation: ' + super(Foo, self).__repr__()\n ...\n >>> Foo('bar')\n This is my string representation: Foo(bar='bar')\n\nIt's not possible to override the ``__init__`` method, because it's replaced when the ``@case``\ndecorator is applied. If a custom constructor is needed using the CaseClassMixin_ can be\na solution.\n\n\n.. _CaseClassMixin:\n\nUsing ``CaseClassMixin`` for more flexibility\n---------------------------------------------\n\nThe classes created by the ``@case`` decorator inherits from ``CaseClassMixin``.\n\n.. code:: python\n\n >>> from rbco.caseclasses import CaseClassMixin\n >>> issubclass(Person, CaseClassMixin)\n True\n\nThe ``CaseClassMixin`` provides all the \"case class\" behavior, except for the constructor.\nTo use ``CaseClassMixin`` directly the only requirement the subclass must match is to provide a\n``__fields__`` attribute, containing a sequence of field names.\n\nThis can be useful if greater flexibility is required. In the following example we create a case\nclass with a custom constructor:\n\n.. code:: python\n\n >>> class Foo(CaseClassMixin):\n ... __fields__ = ('field1', 'field2')\n ...\n ... def __init__(self, field1, *args):\n ... self.field1 = field1 + '_modified'\n ... self.field2 = list(args)\n ...\n >>> Foo('bar', 1, 2)\n Foo(field1='bar_modified', field2=[1, 2])\n\n\nLimitations\n===========\n\n- The constructor of a case class cannot be customized because it's replaced when the ``@case``\n decorator is applied. See the section about CaseClassMixin_ for an alternative.\n\n- It's not possible to assign to unknow fields because of the ``__slots__`` declaration.\n\n- The constructor cannot take ``*args`` or ``**kwargs``:\n\n .. code:: python\n\n >>> @case\n ... class Foo(object):\n ... def __init__(self, **kwargs): pass\n Traceback (most recent call last):\n ...\n RuntimeError: Case class constructor cannot take *args or **kwargs.\n\n See the section about CaseClassMixin_ for an alternative.\n\n\n.. _motivation:\n\nMotivation, design decisions and other implementations\n======================================================\n\nComparison with MacroPy\n-----------------------\n\nThe idea for this project came from MacroPy_. It provides an implementation of case classes using\nsyntactic macros, which results in a very elegant way to define the case classes.\nThe motivation was to provide similar functionality without resorting to syntactic macros nor\nstring evaluation (`the approach took by namedtuple`__). In other words: to provide the best\nimplementation possible without using much magic.\n\n__ `namedtuple source code`_\n\nThe comparison to MacroPy_ can be summarized as follows:\n\nAdvantages:\n\n- No magic.\n- Allows any kind of `custom members`_, including instance methods.\n- Since case classes are just regular classes, any kind of inheritance is allowed.\n\nDisadvantages:\n\n- MacroPy syntax is much nicer. The ``__init__`` stub thing can be considered kind of ugly\n in comparison.\n- Do not support custom initialization logic. This can be achieved by using CaseClassMixin_ but\n additional work will have to be done by the programmer.\n- Do not support ``*args`` and ``**kwargs`` in the constructor. Again, this can be achieved by\n using CaseClassMixin_ at the expense of doing more work.\n\n\nOther implementations\n---------------------\n\nOther implementations of the \"case class\" concept (or similar) in Python exists:\n\n- The constructor stub mechanism idea was stole from `this implementation`__ by hwiechers.\n\n__ `hwiechers`_\n\n- A simple implementation by Brian Wickman can be found in `this Gist`__.\n\n__ `wickman gist`_\n\n- `This discussion`__ on stackoverflow has some implementation ideas.\n\n__ `stackoverflow discussion`_\n\n\nDiscarded implementation ideas\n------------------------------\n\nSome implementation ideas were considered but discarded afterwards. Here some of them are\ndiscussed.\n\nFunctional syntax\n^^^^^^^^^^^^^^^^^\n\nThis means using a function to generate the class. This would be something like this:\n\n.. code:: python\n\n Person = case_class('Person', 'name', age=None, gender=None)\n\nThe first problem with this idea is that there's no way to preserve the order of the fields.\nThe ``case_class`` function would have to be defined like this:\n\n.. code:: python\n\n def case_class(__name__, *args, **kwargs):\n ...\n\n``**kwargs`` is a unordered dictionary, so the order of the fields is lost.\n\nTo overcome this the following syntax could be used:\n\n.. code:: python\n\n Person = case_class('Person', 'name', 'age', 'gender', age=None, gender=None)\n\nI thinks this syntax is not elegant enough. I don't like the repetition of field names and to have\nfield names represented as both strings and parameter names.\n\nPerhaps something like this would work too:\n\n.. code:: python\n\n Person = case_class('Person', ['name', 'age', 'gender'], {'age': None, 'gender': None})\n\nBut again I think the syntax is not elegant.\n\nAlso, some functionalities would be difficult to support using this syntax, namely:\n\n- *Custom members*. This would mean complicate the signature of the ``case_class`` function or\n add the custom members after the class is created. Like this:\n\n .. code:: python\n\n Person = case_class('Person', ...)\n\n def present(self):\n print ...\n\n Person.present = present\n\n Not very elegant.\n\n- *Inheritance*. This would require a new parameter to the ``case_class`` function, to allow to\n pass in a base class.\n\n\nFields specification as parameters to the class decorator\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThis would end the necessity to define an empty constructor. The syntax would be like this:\n\n.. code:: python\n\n @case(name, age=None, gender=None)\n class Person(object):\n 'Represent a person.'\n\nThe same problem faced by the function syntax arises: field ordering is not preserved, since\nthe ``case`` function would have to accept a ``**kwargs`` argument, which is an unordered dict.\n\nAlternate syntaxes, similar to the ones presented for the functional syntax, could overcome the\nfield ordering problem. However I think the solution using a ``__init__`` stub to define the\nfields is more elegant.\n\n\nFields specification as class attributes\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe syntax would be like this:\n\n.. code:: python\n\n @case\n class Person(object):\n name = NO_DEFAULT_VALUE\n age = None\n gender = None\n\nAgain, there's no way to preserve the order of the fields. The ``case`` function would have to\nretrieve the class attributes from ``Person.__dic__``, which is unordered.\n\nMaybe something like this would work:\n\n.. code:: python\n\n @case\n class Person(object):\n __fields__ = (\n ('name', NO_DEFAULT_VALUE),\n ('age', None),\n ('gender', None)\n )\n\nHowever I think the solution using a ``__init__`` stub to define the fields is more elegant.\n\nContributing\n============\n\nPlease fork this project and submit a pull request if you would like to contribute.\nThanks in advance !\n\n\n.. Refer\u00eancias:\n.. _namedtuple: https://docs.python.org/2/library/collections.html#collections.namedtuple\n.. _`__slots__`: https://docs.python.org/2/reference/datamodel.html?highlight=__slots__#__slots__\n.. _MacroPy: https://github.com/lihaoyi/macropy#case-classes\n.. _`namedtuple source code`: https://github.com/python/cpython/blob/2.7/Lib/collections.py\n.. _`wickman gist`: https://gist.github.com/wickman/857930\n.. _`stackoverflow discussion`: http://stackoverflow.com/questions/1264833/python-class-factory-to-produce-simple-struct-like-classes\n.. _`hwiechers`: http://hwiechers.blogspot.com.br/2010/08/case-classes-in-python.html\n\nChangelog\n=========\n\n1.0.1 (2014-05-09)\n------------------\n\n- Improve documentation.\n- Minor refactoring.\n\n\n1.0.0 (2014-05-09)\n------------------\n\n- First release.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/rafaelbco/rbco.caseclasses", "keywords": "caseclasses", "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "rbco.caseclasses", "package_url": "https://pypi.org/project/rbco.caseclasses/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/rbco.caseclasses/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/rafaelbco/rbco.caseclasses" }, "release_url": "https://pypi.org/project/rbco.caseclasses/1.0.1/", "requires_dist": null, "requires_python": null, "summary": "Provide a compact syntax to define simple \"struct-like\" (or \"record-like\", \"bean-like\") classes.", "version": "1.0.1" }, "last_serial": 1087107, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "2e8786a011da94e47161afadc9d1b940", "sha256": "98c377908daf3896bff4a5995537ac3936d11704917414a5ffd4866898542b32" }, "downloads": -1, "filename": "rbco.caseclasses-1.0.0.zip", "has_sig": false, "md5_digest": "2e8786a011da94e47161afadc9d1b940", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27189, "upload_time": "2014-05-09T17:09:05", "url": "https://files.pythonhosted.org/packages/50/0e/481cee458804ac9ecac53b078c6601ad812d25216826203146359da7f51f/rbco.caseclasses-1.0.0.zip" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "6a6cbdaa52e7b45ca0eae10bee551a22", "sha256": "af329a63598ce578e57412ac037ec5106a111bcf9b5bc4b8fb66c9749f957b62" }, "downloads": -1, "filename": "rbco.caseclasses-1.0.1.zip", "has_sig": false, "md5_digest": "6a6cbdaa52e7b45ca0eae10bee551a22", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27656, "upload_time": "2014-05-09T17:57:12", "url": "https://files.pythonhosted.org/packages/65/11/f2fa8e53933dd9b8b626adcf2b1cf917ddc0bb30c2ba685b0b1227870f20/rbco.caseclasses-1.0.1.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "6a6cbdaa52e7b45ca0eae10bee551a22", "sha256": "af329a63598ce578e57412ac037ec5106a111bcf9b5bc4b8fb66c9749f957b62" }, "downloads": -1, "filename": "rbco.caseclasses-1.0.1.zip", "has_sig": false, "md5_digest": "6a6cbdaa52e7b45ca0eae10bee551a22", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 27656, "upload_time": "2014-05-09T17:57:12", "url": "https://files.pythonhosted.org/packages/65/11/f2fa8e53933dd9b8b626adcf2b1cf917ddc0bb30c2ba685b0b1227870f20/rbco.caseclasses-1.0.1.zip" } ] }