-
Notifications
You must be signed in to change notification settings - Fork 4
Readme tools samples #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,95 +1,210 @@ | ||
| ========= | ||
| ######### | ||
| I Promise | ||
| ========= | ||
| ######### | ||
|
|
||
| .. image:: https://badge.fury.io/py/ipromise.svg | ||
| :target: https://badge.fury.io/py/ipromise | ||
| :alt: ipromise badge | ||
|
|
||
| **I Promise** provides a Python base class, and decorators for | ||
| specifying promises relating to inheritance. | ||
|
|
||
| This repository provides a Python base class, and various decorators for specifying promises relating to inheritance. | ||
| It provides three inheritance patterns: | ||
|
|
||
| * implementing, | ||
| * overriding, and | ||
| * augmenting. | ||
|
|
||
| Base class | ||
| ========== | ||
| Checking promises depends on inheritance from the base class ``AbstractBaseClass``. Unlike the standard library's similar class ``abc.ABCMeta``, ``AbstractBaseClass`` does not bring in any metaclasses. This is thanks to Python 3.6's PEP 487, which added ``__init_subclass__``. | ||
| Using the inheritance patterns can ensure an inheritance hierarchy | ||
| is used as intended by forcing a run-time failure when not. | ||
|
|
||
| ---- | ||
|
|
||
| .. contents:: | ||
|
|
||
| ---- | ||
|
|
||
| ********** | ||
| Installing | ||
| ********** | ||
|
|
||
| .. code-block:: shell | ||
|
|
||
| pip install ipromise | ||
|
|
||
| *** | ||
| Use | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this section add anything?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It helps to distinguish installation. Also, the table of contents is more sensible. I can remove if you don't like it.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't see the table of contents, but okay let's just keep it then. I'll remove it finally if it doesn't look good.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the statement https://docutils.sourceforge.io/docs/ref/rst/directives.html#table-of-contents I added VS Code extension reStructuredText to view the README.rst.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome, thanks. |
||
| *** | ||
|
|
||
| Base class ``AbstractBaseClass`` | ||
| ================================ | ||
|
|
||
| Checking promises depends on inheritance from the base class | ||
| ``AbstractBaseClass``. Unlike the standard library's similar class | ||
| ``abc.ABCMeta``, the ``AbstractBaseClass`` does not bring in any metaclasses. | ||
| This is thanks to Python 3.6 `PEP 487 | ||
| <https://peps.python.org/pep-0487/>`_ | ||
| which added ``__init_subclass__``. | ||
|
|
||
| Implementing | ||
| ============ | ||
| *Implementing* is the pattern whereby an inheriting class's method implements an abstract method from a base class method. | ||
|
|
||
| *Implementing* is the pattern whereby an inheriting class's method implements an | ||
| abstract method from a base class method. | ||
|
|
||
| It is declared using the decorators: | ||
|
|
||
| * ``abc.abstractmethod`` from the standard library, and | ||
| * ``implements``, which indicates that a method implements an abstract method in a base class | ||
| * ``implements``, which indicates that a method implements an abstract method in | ||
| a base class. | ||
|
|
||
| For example: | ||
| From ``samples/implements.py`` : | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| class HasAbstractMethod(AbstractBaseClass): | ||
| @abstractmethod | ||
| def f(self): | ||
| raise NotImplementedError | ||
| from ipromise import AbstractBaseClass, implements | ||
| import abc | ||
|
|
||
| class MyInterface(AbstractBaseClass): | ||
| @abc.abstractmethod | ||
| def f(self): | ||
| raise NotImplementedError("You forgot to implement f()") | ||
|
|
||
| class ImplementsAbstractMethod(HasAbstractMethod): | ||
| @implements(HasAbstractMethod) | ||
| class MyImplementation(MyInterface): | ||
NeilGirdhar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @implements(MyInterface) | ||
| def f(self): | ||
| print("MyImplementation().f()") | ||
| return 0 | ||
|
|
||
| MyImplementation().f() | ||
|
|
||
|
|
||
| Overriding | ||
| ========== | ||
| *Overriding* is the pattern whereby an inheriting class's method replaces the implementation of a base class method. | ||
| It is declared using the decorator ``overrides``, which marks the overriding method. | ||
|
|
||
| An overriding method could call super, but does not have to: | ||
| *Overriding* is the pattern whereby an inheriting class's method replaces the | ||
| implementation of a base class method. | ||
| It is declared using the decorator ``overrides``, which marks the overriding | ||
| method. | ||
|
|
||
| An overriding method could call ``super`` but it is not required. | ||
|
|
||
| From ``samples/overrides.py`` : | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| class HasRegularMethod(AbstractBaseClass): | ||
| from ipromise import AbstractBaseClass, overrides | ||
|
|
||
| class MyClass(AbstractBaseClass): | ||
| def f(self): | ||
| print("MyClass().f()") | ||
| return 1 | ||
|
|
||
|
|
||
| class OverridesRegularMethod(HasRegularMethod): | ||
| @overrides(HasRegularMethod) | ||
| class MyClassButBetter(MyClass): | ||
| @overrides(MyClass) | ||
| def f(self): | ||
| print("MyClassButBetter().f()") | ||
| return 2 | ||
|
|
||
| MyClass().f() | ||
| MyClassButBetter().f() | ||
|
|
||
| Augmenting | ||
| ========== | ||
| *Augmenting* is a special case of *overriding* whereby the inheriting class's method not only *overrides* the base class method, but *extends* its functionality. | ||
|
|
||
| *Augmenting* is a special case of *overriding* whereby the inheriting class's | ||
| method not only *overrides* the base class method, but *extends* its | ||
| functionality. | ||
| This means that it must delegate to *super* in all code paths. | ||
| This pattern is typical in multiple inheritance. | ||
|
|
||
| We hope that Python linters will be able to check for the super call. | ||
|
|
||
| Augmenting is declared using two decorators: | ||
|
|
||
| * ``augments`` indicates that this method must call super within its definition and thus augments the behavior of the base class method, and | ||
| * ``must_agugment`` indicates that child classes that define this method must decorate their method overriddes with ``augments``. | ||
| * ``augments`` indicates that this method must call super within its definition | ||
| and thus augments the behavior of the base class method, and | ||
| * ``must_augment`` indicates that child classes that define this method must | ||
| decorate their method overriddes with ``augments``. | ||
|
|
||
| For example: | ||
| From ``samples/augments.py`` : | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| class HasMustAugmentMethod(AbstractBaseClass): | ||
| from ipromise import AbstractBaseClass, must_augment, augments | ||
| import abc | ||
|
|
||
| class MyClass(AbstractBaseClass): | ||
| @must_augment | ||
| def f(self): | ||
| # must_augment prevents this behavior from being lost. | ||
| print("MyClass().f()") | ||
| self.times_f_called += 1 | ||
| return 0 | ||
|
|
||
|
|
||
| class AugmentsMethod(HasMustAugmentMethod): | ||
| @augments(HasMustAugmentMethod) | ||
| class MyClassAgumentedOnce(MyClass): | ||
| @augments(MyClass) | ||
| def f(self, extra=0, **kwargs): | ||
| print("MyClassAgumentedOnce().f()") | ||
| return super().f(**kwargs) + extra | ||
|
|
||
|
|
||
| class AugmentsMethodFurther(AugmentsMethod): | ||
| @augments(HasMustAugmentMethod) | ||
| class MyClassAgumentedOnceAgain(MyClassAgumentedOnce): | ||
| @augments(MyClass) | ||
| def f(self, **kwargs): | ||
| print("f has been called") | ||
| print("MyClassAgumentedOnceAgain().f()") | ||
| return super().f(**kwargs) | ||
|
|
||
| MyClassAgumentedOnce().f() | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure these lines add anything?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example call is more important in the For consistency, I added calls in the other examples. Some users may be new to inheritance concepts. So they may not understand what can and cannot be called. I wanted to make the examples explicit and consistent, if a little redundant.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay that makes sense. |
||
| MyClassAgumentedOnceAgain().f() | ||
|
|
||
| *********** | ||
| Development | ||
| *********** | ||
NeilGirdhar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| Pull Requests can be submitted on github. | ||
|
|
||
| poetry | ||
| ====== | ||
|
|
||
| The poetry development environment can be started with the typical | ||
| poetry commands: | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| poetry install | ||
| poetry shell | ||
|
|
||
| Tools | ||
| ===== | ||
|
|
||
| Tool commands should be run at the project top-level directory. | ||
|
|
||
| mypy | ||
| ---- | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| mypy ipromise test | ||
|
|
||
| flake8 | ||
| ------ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| pflake8 ipromise test | ||
|
|
||
| pytest | ||
| ------ | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| pytest -c pyproject.toml test | ||
|
|
||
| rst-lint | ||
| -------- | ||
|
|
||
| Only necessary for ``README.rst`` changes. | ||
|
|
||
| .. code-block:: text | ||
|
|
||
| rst-lint --level info README.rst | ||
Uh oh!
There was an error while loading. Please reload this page.