Skip to content

Commit

Permalink
More tidy up of README.rst.
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-mitchell committed Nov 15, 2023
1 parent fea3b59 commit 601ee59
Showing 1 changed file with 45 additions and 24 deletions.
69 changes: 45 additions & 24 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ Features
attributes and methods defined by ``Interface``.
* Supports python 3.8+

A note on the name
------------------
**A note on the name**

The phrase *pure interface* applies only to the first design goal - a class that defines only an interface with no
implementation is a pure interface [*]_.
In every other respect the zen of 'practicality beats purity' applies.
Expand All @@ -33,7 +33,7 @@ or you can grab the source code from GitHub_.
Defining an Interface
=====================

For simplicity in these examples we assume that the entire pure_interface namespace has been imported ::
For simplicity in these examples we assume that the entire ``pure_interface`` namespace has been imported ::

from pure_interface import *

Expand Down Expand Up @@ -75,8 +75,8 @@ ABC-style property definitions are also supported (and equivalent)::
Again, the height property is removed from the class dictionary, but, as with the other syntaxes,
all concrete subclasses will be required to have a ``height`` attribute.

However the ``abstractmethod`` decorator is optional as **ALL** methods and properties on a ``Interface`` subclass are abstract.
In the examples above, both ``height`` and ``speak`` are considered abstract and must be overridden by subclasses.
Note that the ``abstractmethod`` decorator is optional as **ALL** methods and properties on a ``Interface`` subclass are abstract.
All the examples above are equivalent, both ``height`` and ``speak`` are considered abstract and must be overridden by subclasses.

Including ``abstractmethod`` decorators in your code can be useful for reminding yourself (and telling your IDE) that you need
to override those methods. Another common way of informing an IDE that a method needs to be overridden is for
Expand Down Expand Up @@ -218,8 +218,8 @@ When all your imports are complete you can check if this list is empty.::

Note that missing properties are NOT checked for as they may be provided by instance attributes.

Partial-Interfaces
------------------
Interface Subsets
-----------------
Sometimes your code only uses a small part of a large interface. It can be useful (eg. for test mocking) to specify
the sub part of the interface that your code requires. This can be done with the ``sub_interface_of`` decorator.::

Expand Down Expand Up @@ -360,7 +360,7 @@ Structural_ type checking checks if an object has the attributes and methods def
As interfaces are inherited, you can usually use ``isinstance(obj, MyInterface)`` to check if an interface is provided.
An alternative to ``isinstance()`` is the ``Interface.provided_by(obj)`` classmethod which will fall back to structural type
checking if the instance is not an actual subclass. The structural type-checking does not check function signatures.
Pure interface is stricter than ``Protocol`` in that it differentiates between attributes and methods.::
Pure interface is stricter than a ``runtime_checkable`` decorated ``Protocol`` in that it differentiates between attributes and methods.::

class Parrot(object):
def __init__(self):
Expand Down Expand Up @@ -397,18 +397,20 @@ Adaption also supports structural typing by passing ``allow_implicit=True`` (but
speaker = ISpeaker.adapt(Parrot(), allow_implicit=True)
ISpeaker.provided_by(speaker) --> True

When using ``provided_by()`` or ``adapt()`` with ``allow_implicit=True``, a warning may be issued informing you that
When using ``adapt()`` with ``allow_implicit=True``, a warning may be issued informing you that
the structurally typed object should inherit the interface. The warning is only issued if the interface is implemented by the
class (and not by instance attributes as in the DTO case above) and the warning is only issued once for each
class, interface pair. For example::

s = ISpeaker.adapt(Parrot())
s = ISpeaker.adapt(Parrot(), allow_implicit=True)
UserWarning: Class Parrot implements ISpeaker.
Consider inheriting ISpeaker or using ISpeaker.register(Parrot)

This warning is issued because ``provided_by`` first does an isinstance check and will be faster in this situation.

Dataclass Support
=================
``Interfaces`` can be decorated with the built-in ``dataclasses.dataclass`` decorator.
``Interfaces`` can be decorated with the standard library ``dataclasses.dataclass`` decorator.
This will create a dataclass that implements an interface. For example::

class IAnimal2(Interface):
Expand All @@ -431,22 +433,22 @@ Interface Type Information
The ``pure_interface`` module provides these functions for returning information about interface types.

type_is_interface(cls)
Return True if cls is a pure interface, False otherwise or if cls is not a class.
Return ``True`` if ``cls`` is a pure interface, ``False`` otherwise or if ``cls`` is not a class.

get_type_interfaces(cls)
Returns all interfaces in the cls mro including cls itself if it is an interface
Returns all interfaces in the ``cls`` mro including ``cls`` itself if it is an interface

get_interface_names(cls)
Returns a ``frozenset`` of names (methods and attributes) defined by the interface.
if interface is not a ``Interface`` subtype then an empty set is returned.
If ``interface`` is not a ``Interface`` subtype then an empty set is returned.

get_interface_method_names(interface)
Returns a ``frozenset`` of names of methods defined by the interface.
if interface is not a ``Interface`` subtype then an empty set is returned
If ``interface`` is not a ``Interface`` subtype then an empty set is returned

get_interface_attribute_names(interface)
Returns a ``frozenset`` of names of attributes defined by the interface.
if interface is not a ``Interface`` subtype then an empty set is returned
If ``interface`` is not a ``Interface`` subtype then an empty set is returned


Automatic Adaption
Expand All @@ -458,17 +460,17 @@ For example::
def my_func(foo, bar=None):
pass

In Python 3.5 and later the types can be taken from the argument annotations.::
The types can also be taken from the argument annotations.::

@adapt_args
def my_func(foo: IFoo, bar: IBar=None):
def my_func(foo: IFoo, bar: IBar | None = None):
pass

This would adapt the ``foo`` parameter to ``IFoo`` (with ``IFoo.optional_adapt(foo))`` and ``bar`` to ``IBar
(using ``IBar.optional_adapt(bar)``)
before passing them to my_func. ``None`` values are never adapted, so ``my_func(foo, None)`` will work, otherwise
``AdaptionError`` is raised if the parameter is not adaptable.
All arguments must be specified as keyword arguments::
All arguments to ``adapt_args`` must be specified as keyword arguments::

@adapt_args(IFoo, IBar) # NOT ALLOWED
def other_func(foo, bar):
Expand Down Expand Up @@ -511,7 +513,7 @@ For example suppose we want to extend an Animal with a new method ``price``::
ea.speak() -> 'hello' # speak is in IAnimal and routed to 'ea.a.speak()'
ea.price() -> 'lots'

The following code is equivalent but more verbose and won't update with changes to IAnimal::
The following code is equivalent but won't update with changes to IAnimal::

class ExtendedAnimal(Delegate):
pi_attr_delegates = {'a': ['height', 'speak']}
Expand Down Expand Up @@ -551,8 +553,8 @@ if there is no delegate, mapping or implementation for that attribute. Again, th
def price(self):
return 'lots'

Note that method and attribute names for all ``Interface`` classes in ``ExtendAnimal.mro()`` are routed to ``a``.
Methods and properties defined on the delegate class itself take precedence (as one would expect)::
Note that method and attribute names for all interface classes in ``ExtendAnimal.mro()`` are routed to ``a``.
Methods and properties defined on the delegating class itself take precedence (as one would expect)::

class MyDelegate(Delegate, IAnimal):
pi_attr_delegates = {'impl': IAnimal}
Expand Down Expand Up @@ -583,7 +585,7 @@ instead. If you want to override an interface attribute using an instance attri
self.height = 10

If you supply more than one delegation rule (e.g. both ``pi_attr_mapping`` and ``pi_attr_fallack``) then
``pi_attr_delegates`` delegation rules have priority over ``pi_attr_mapping`` delegation rules have priority over ``pi_attr_fallback``.
``pi_attr_delegates`` delegation rules have priority over ``pi_attr_mapping`` delegation rules which have priority over ``pi_attr_fallback``.

Type Composition
----------------
Expand Down Expand Up @@ -643,6 +645,24 @@ argument provides all the interfaces in the type (even if the argument is not a
AT.provided_by(X()) -> True
TA.provided_by(X()) -> True

MyPy
----

``pure_interface`` does some things that mypy does not understand. For example mypy does not understand that
all methods in an interface are abstract and will complain about incorrect return types.
For this reason ``pure_interface`` has a mypy plugin. Unfortunately this plugin does not completely cover all
the capabilities of ``pure_interface`` and some `# type: ignore` comments will be required to get a clean mypy run.

To use the ``pure_interface`` plugin add the following to your `mypy configuration`_ file.::

[mypy]
plugins = pure_interface.mypy_plugin

Or your pyproject.toml file::

[tool.mypy]
plugins = "pure_interface.mypy_plugin"

Development Flag
================

Expand Down Expand Up @@ -863,6 +883,7 @@ Exceptions
.. _dataclasses: https://docs.python.org/3/library/dataclasses.html
.. _mock_protocol: https://pypi.org/project/mock-protocol/
.. _Structural: https://en.wikipedia.org/wiki/Structural_type_system
.. _mypy configuration: https://mypy.readthedocs.io/en/stable/config_file.html#config-file

.. [*] We don't talk about the methods on the base ``Interface`` class. In earlier versions they
were all on the meta class but then practicality got in the way.
were all on the meta class but then practicality (mainly type-hinting) got in the way.

0 comments on commit 601ee59

Please sign in to comment.