Skip to main content

Liskov Utility

https://pypip.in/v/liskov/badge.png

Utility to check if your subtypes pass supertypes tests.

Liskov Substitution is related in SOLID principles. It has been formulated by Barbara Liskov and Jeanette Wing in order to define more precisely the notion of subtypes.

For more details read: http://reports-archive.adm.cs.cmu.edu/anon/1999/CMU-CS-99-156.ps

To ensure a certain respect of Liskov Substitution principle in your program, you can simply make your subtypes tests extends your supertypes tests, but importing a supertype test directly in subtype file make the test being recognized by unit tests runners and run it several times.

You can just import it in a function, but to not repeat this every time, I’ve made a small util, wich offers 3 ways to declare a subtype test.

Each solution gives you different expressiveness depending on your preference:
  • “subtype” function which just import and return a class from a module given as string argument.

  • “behave_as” metaclass generator function which returns a metaclass from given modules.

  • “can_substitute” decorator wich returns the class extending modules given as arguments.

Liskov and Wing classified subtype relationships in two broad categories:
  • Extension subtypes: add methods or eventually states to supertypes

  • Constrained subtypes: when supertype enable variations in subtypes

Since version 0.2 you can find some helpers to define constraints.(see example 4)


Table of Contents

Installation

Simply install it from pypi:

pip install liskov

or from sources:

git clone git@github.com:apieum/liskov.git
cd liskov
python setup.py install

Usage

Example 1 - “subtype”:

Use a lambda if too long.

from liskov import subtype

BasicCalc = lambda: subtype('testCalc.BasicCalcTest')
BaseConverter = lambda: subtype('testConvert.BaseConverterTest')
class ScientificCalcTest(BasicCalc(), BaseConverter()):
  def test_it_is_a_subtype_of_BasicCalc(self):
    from testCalc import BasicCalcTest
    assert isinstance(self, BasicCalcTest)

  def test_it_is_a_subtype_of_BaseConverter(self):
    from testConvert import BaseConverterTest
    assert isinstance(self, BaseConverterTest)

Example 2 - “behave_as”:

Python 2 version

from liskov import behave_as

class ScientificCalcTest(object):
  __metaclass__ = behave_as('testCalc.BasicCalcTest', 'testConvert.BaseConverterTest')

  def test_it_is_a_subtype_of_BasicCalc(self):
    from testCalc import BasicCalcTest
    assert isinstance(self, BasicCalcTest)

  def test_it_is_a_subtype_of_BaseConverter(self):
    from testConvert import BaseConverterTest
    assert isinstance(self, BaseConverterTest)

Python 3 version

from liskov import behave_as

metaclass = behave_as('testCalc.BasicCalcTest', 'testConvert.BaseConverterTest')

class ScientificCalcTest(object, metaclass=metaclass):
  def test_it_is_a_subtype_of_BasicCalc(self):
    from testCalc import BasicCalcTest
    assert isinstance(self, BasicCalcTest)

  def test_it_is_a_subtype_of_BaseConverter(self):
    from testConvert import BaseConverterTest
    assert isinstance(self, BaseConverterTest)

Example 3 - “can_substitute”:

from liskov import can_substitute

@can_substitute('testCalc.BasicCalcTest', 'testConvert.BaseConverterTest')
class ScientificCalcTest(object):
  def test_it_is_a_subtype_of_BasicCalc(self):
    from testCalc import BasicCalcTest
    assert isinstance(self, BasicCalcTest)

  def test_it_is_a_subtype_of_BaseConverter(self):
    from testConvert import BaseConverterTest
    assert isinstance(self, BaseConverterTest)

Example 4 - Constraints:

This example follow Liskov and Wing constrained subtypes Elephants hierarchy example from “Behavioural Subtyping using invariants and constraints” (link above)

Elephants can be white, green or blue RoyalElephant is always blue AlbinoElephant is always white

Each instance of Elephant in ElephantTest is made with “new_elephant” ElephantTest test if an Elephant can be white, green or blue.

Declare Constraints with a decorator

from liskov import can_substitute, under_constraint
import elephant

@can_substitute('elephant.ElephantTest')
@under_constraint('test_it_can_be_grey', 'test_it_can_be_white')
class RoyalElephantTest(object):
  def new_elephant(self, *args):
    return elephant.RoyalElephant()

Declare Constraints with metaclass

Python 2 version

from liskov import behave_as
import elephant

class RoyalElephantTest(object):
  __metaclass__ = behave_as('elephant.ElephantTest').except_for('test_it_can_be_grey', 'test_it_can_be_white')
  def new_elephant(self, *args):
    return elephant.RoyalElephant()

Python 3 version

from liskov import behave_as
import elephant

metaclass = behave_as('elephant.ElephantTest').except_for('test_it_can_be_grey', 'test_it_can_be_white')
class RoyalElephantTest(object, metaclass=metaclass):
  def new_elephant(self, *args):
    return elephant.RoyalElephant()
Declare Constraints with subtype function

bind “subtype” to “constrain” with any of these operators: “& | + -”

from liskov import subtype, constrain
import elephant

ConstrainedElephantTest = lambda: subtype('elephant.ElephantTest') & constrain('test_it_can_be_grey', 'test_it_can_be_white')

class RoyalElephantTest(ConstrainedElephantTest()):
  def new_elephant(self, *args):
    return elephant.RoyalElephant()

Development

Fell free to give feedback or improvment.

Launch test:

git clone git@github.com:apieum/liskov.git
cd liskov
nosetests --with-spec --spec-color
https://secure.travis-ci.org/apieum/liskov.png?branch=master

Metadata

Release files for liskov 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for liskov 0.2.2
File Size Uploaded Provenance
liskov-0.2.2.tar.gz 5.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for liskov 0.2.2
File Interpreter ABI Platform
liskov-0.2.2-py2.7.egg Legacy Egg format - - Details

Total release size: 13.8 kB

Release files / liskov-0.2.2.tar.gz

Download URL liskov-0.2.2.tar.gz
Size 5.0 kB
Tags Source
SHA-256 checksum
How to use checksums
bb85833acbec80eab416447c7ce71cae5bfb42098b124b213458ebbf0bb1c648
BLAKE2b-256 checksum
How to use checksums
c1545fc890ec0c1ce4d8e011c264182f8c4dd5ed91751a15fbad21718acff34c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Release files / liskov-0.2.2-py2.7.egg

Download URL liskov-0.2.2-py2.7.egg
Size 8.8 kB
Tags Egg
SHA-256 checksum
How to use checksums
204e865eba84d4aeb1375e814cdf4d9862d6cf46bf57ace0bb62b03cd894cb72
BLAKE2b-256 checksum
How to use checksums
6a74b3dd236f41eab4b1d417893ce781c952a7f00662e202f100af58324e13e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No

Security for liskov 0.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page