Metadata-Version: 1.2
Name: compose-struct
Version: 0.6
Summary: yet another namedtuple alternative
Home-page: https://github.com/ninjaaron/compose-struct
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: compose
        =======
        yet another namedtuple alternative for Python
        
        ``compose.struct`` is something like an alternative to namedtuple,
        attrs_ and now dataclasses_ in Python 3.7.
        
        .. _attrs: https://github.com/python-attrs/attrs
        .. _dataclasses: https://docs.python.org/3/library/dataclasses.html
        
        to create a new struct, you simply:
        
        .. code:: Python
        
          @compose.struct
          class Foo:
              bar = ...
              baz = 'spam'
        
        This generate a class like this:
        
        .. code:: Python
        
          class Foo:
              __slots__ = 'bar', 'baz'
        
              def __init__(self, bar, baz='spam'):
                  self.bar = bar
                  self.baz = spam
        
        You can, naturally, implement any other methods you wish.
        
        You can also use type annotation syntax for positional arguments:
        
        .. code:: Python
        
          @compose.struct
          class Foo:
              bar: int
              baz: str = 'spam'
        
        If the ``name = ...`` syntax is used in combination with type annotation
        syntax for positional arguments, all positional arguments with
        annotations will come before positional arguments without. However, this
        should be considered an implementation detail. best practice is to not
        mix the two styles. Use ``typing.Any`` if you are using type
        annotations and don't want one of the arguments to care about type.
        
        How's this different from attrs_ and dataclasses_? A few ways. Aside
        from the use of ellipsis to create positional parameters, another
        difference that can be seen here is that everything is based on
        ``__slots__``, which means your attribute lookup will be faster and your
        instances more compact in memory. attrs_ allows you to use slots, but
        ``struct`` defaults to using slots. This means that attributes cannot be
        dynamically created. If a class needs private attributes, you may define
        additional slots with the usual method of defining ``__slots__`` inside
        the class body.
        
        Another important distinction is ``compose.struct`` doesn't define a
        bunch of random dunder methods. You get your ``__init__`` method and
        your ``__repr__`` and that's it. It is the opinion of the author that
        sticking all attributes in a tuple and comparing them usually is *not*
        what you want when defining a new type. However, it is still easy to get
        more dunder methods, as you will see in the following section.
        
        Interfaces
        ----------
        Perhaps the most significant difference between our structs and
        alternatives is that we emphasize composition over inheritance. A
        ``struct`` isn't even able to inherit! It's an outrage! What about
        interfaces!? What about polymorphism!? Well, what ``compose`` provides
        is a simple way to generate pass-through methods to attributes.
        
        .. code:: Python
        
          from compose import struct, Provider
        
          @struct
          class ListWrapper:
              data = Provider('__getitem__', '__iter__')
              metadata = None
        
        
        So this will generate pass-through methods for ``__getitem__`` and
        ``__iter__`` to the ``data`` attribute. Certain python keywords and
        operators can be used as shorthand for adding dunder methods as well.
        
        .. code:: Python
        
          @struct
          class ListWrapper:
              data = Provider('[]', 'for')
              metadata = None
        
        Here, ``[]`` is shorthand for item access and implements
        ``__getitem__``, ``__setitem__`` and ``__delitem__``. ``for`` implements
        the ``__iter__`` method. A full list of these abbreviations can be found
        below in the `Pre-Defined Interfaces`_ section.
        
        Going even deeper, interfaces can be specified as classes. Wrapper
        methods will be created for any method attached to a class which is
        given as an argument to ``Provider``. The following code is more or less
        equivalent to subclassing ``collections.UserList``, but no inheritance
        is used.
        
        .. code:: Python
        
          from collections import abc
        
          @struct
          class ListWrapper:
              data = Provider(abc.MutableSequence)
              metadata = None
        
        An instances of this class tested with ``isinstance(instance,
        abc.MutableSequence)`` will return ``True`` because wrapper methods
        have been generated on ``self.data`` for all the methods in
        ``abc.MutableSequence``. *Note that ``abc.MutableSequence`` does not
        actually provide all of the methods a real list does. If you want ALL
        of them, you can use ``Provides(list)``.*
        
        You cannot implicitly make pass-through methods for ``__setattr__`` and
        ``__getattribute__`` by passing in a class that implements them, since
        they have some rather strange behaviors. You can, however, pass them
        explicitly to ``Provider`` to force the issue.  In the case of
        ``__setattr__``, This invokes special behavior. See `__setattr__ hacks`_
        for details.
        
        All methods defined with a provider can be overridden in the body of the
        class as desired. Methods can also be overridden by other providers.
        It's first-come, first-serve in that case. The Provider you want to
        define the methods has to be placed *above* any other interfaces that
        implement the same method.
        
        You can use ``@struct(frozen=True)`` to make the instances more-or-less
        immutable after it initializes. It will raise an exception if you try
        to change it using the normal means.
        
        If you need a ``struct`` to look like a child of another class, I
        suggest using the abc_ module to define abstract classes. This allows
        classes to look like children for the purposes of type-checking, but
        without actually using inheritance.
        
        .. _abc: https://docs.python.org/3/library/abc.html
        
        
        ``*args`` and ``**kwargs``
        ------------------------
        Though it is not especially recommended, it is possible to implement
        ``*args`` and ``**kwargs`` for your constructor.
        
        .. code:: Python
        
          >>> from compose import struct, Provider, args, kwargs
          >>> @struct
          ... class Foo:
          ...     items = args
          ...     mapping = kwargs
          ...
          >>> f = Foo('bar', 'baz', spam='eggs')
          >>> f
          Foo(*items=('bar', 'baz'), **mapping={'spam': 'eggs'})
        
        This breaks the principle that the object's repr can be used to
        instantiate an identical instance, but it does at least give the option
        and still makes the internal structure of the class transparent. With
        ``Provider`` parameters, simply pass in ``compose.args`` or
        ``compose.kwargs`` as arguments the constructor.
        
        .. code:: Python
        
          >>> @struct
          ... class MySequence:
          ...     data = Provider('__getitem__', '__iter__', args)
          ...
          >>> s = MySequence('foo', 'bar', 'baz')
          >>> s
          MySequence(*data=('foo', 'bar', 'baz'))
          >>> for i in s:
          ...     print(i)
          ...
          foo
          bar
          baz
        
        Caveats
        -------
        This library uses code generation at class-creation time. The intent is
        to optimize performance of instances at the cost of slowing class
        creation. If you're dynamically creating huge numbers of classes, using
        ``compose.struct`` might be a bad idea. FYI, ``namedtuple`` does the
        same. I haven't looked at the source for attrs_ too much, but I did see
        some strings with sourcecode there as well.
        
        Pre-Defined Interfaces
        ----------------------
        This is the code that implements the expansion of interface
        abbreviations for dunder methods. Any key in the ``interfaces``
        dictionary may be used to implement the corresponding dunder methods on
        an attribute with the ``Provides()`` constructor.
        
        .. code:: Python
        
          interfaces = {
              '+': 'add radd',
              '-': 'sub rsub',
              '*': 'mul rmul',
              '@': 'matmul rmatmul',
              '/': 'truediv rtruediv',
              '//': 'floordiv rfloordiv',
              '%': 'mod rmod',
              '**': 'pow rpow',
              '<<': 'lshift rlshift',
              '>>': 'rshift rrshift',
              '&': 'and rand',
              '^': 'xor rxor',
              '|': 'or ror',
              '~': 'invert',
              '==': 'eq',
              '!=': 'ne',
              '>': 'gt',
              '<': 'lt',
              '>=': 'ge',
              '<=': 'le',
              '()': 'call',
              '[]': 'getitem setitem delitem',
              '.': 'get set delete set_name',
              'in': 'contains',
              'for': 'iter',
              'with': 'enter exit',
              'del': 'del',
              'await': 'await'
          }
          interfaces = {k: ['__%s__' % n for n in v.split()]
                        for k, v in interfaces.items()}
        
        __setattr__ hacks
        -----------------
        If you choose to create an attribute
        wrapper for ``__setattr__``, the default will look like this so you
        won't hit a recursion error while accessing pre-defined attributes:
        
        .. code:: Python
        
            def __setattr__(self, attribute, value):
                if attr in self.__slots__:
                    object.__setattr__(self, attribute, value)
                else:
                    setattr(self.{wrapped_attribute}, attribute, value)
        
        If you want to override ``__setattr__`` with a more, eh, "exotic"
        method, you may want to build your struct with the ``escape_setattr``
        argument.
        
        .. code:: Python
        
            @struct(escape_setattr=True)
            class Foo:
                 bar = ...
                 baz = ...
        
             def __setattr__(self, attribute, value):
                  setattr(self.bar, attribute, value)
        
        This allows attributes to be set when the object is initialized, but
        will use your method at all other times, *including in other methods,
        which may break your stuff*. Definiting a ``__setattr__`` method like
        this together with the default ``__getattr__`` wrapper will cause a
        recursion error durring initialization of you don't use
        ``escape_setattr``.
        
Platform: UNKNOWN
Requires-Python: >=3.5
