summaryrefslogtreecommitdiff
path: root/python-marshy.spec
diff options
context:
space:
mode:
Diffstat (limited to 'python-marshy.spec')
-rw-r--r--python-marshy.spec928
1 files changed, 928 insertions, 0 deletions
diff --git a/python-marshy.spec b/python-marshy.spec
new file mode 100644
index 0000000..1f4147b
--- /dev/null
+++ b/python-marshy.spec
@@ -0,0 +1,928 @@
+%global _empty_manifest_terminate_build 0
+Name: python-marshy
+Version: 4.0.3
+Release: 1
+Summary: A convention over configuration approach to object marshalling.
+License: MIT License
+URL: https://github.com/tofarr/marshy
+Source0: https://mirrors.nju.edu.cn/pypi/web/packages/0a/ff/2a578cdd0811414709b46d16f3bd9d4cd98474a48d2393b2f4c9ac878249/marshy-4.0.3.tar.gz
+BuildArch: noarch
+
+Requires: python3-typing-inspect
+Requires: python3-black
+Requires: python3-marshmallow-dataclass
+Requires: python3-pytest
+Requires: python3-pytest-cov
+Requires: python3-pytest-xdist
+Requires: python3-pylint
+
+%description
+# Marshy - Better Marshalling for Python.
+
+This project is a general purpose externalizer for python objects.
+(Like Marshmallow or Pedantic) The guiding philosophy is convention
+over configuration, with the aim of still making customizations as
+pain free as possible, based on python type hints.
+
+Out of the box, it supports primitives, dataclasses, and enums.
+
+## Installation
+
+`pip install marshy`
+
+## General Usage
+
+Given the following dataclass:
+```
+from typing import List, Optional
+import dataclasses
+
+
+@dataclasses.dataclass
+class Doohickey:
+ title: str
+ description: Optional[str] = None
+ tags: List[str] = dataclasses.field(default_factory=list)
+```
+
+Marshall data with:
+```
+import marshy
+result = marshy.dump(Doohickey('Thingy', tags=['a','b']))
+# result == dict(title='Thingy', description=None, tags=['a','b'])
+```
+
+Unmarshall data with:
+```
+result = marshy.load(Doohickey, dict(title='Thingy'))
+# result == Doohickey('Thingy', description=None, tags=[])
+```
+
+## Custom properties
+
+Custom properties are also serialized by default. (If they
+have a setter, it is used when loading):
+
+```
+@dataclass
+class Factorial:
+ value: int
+
+ @property
+ def factorial(self) -> int:
+ return reduce(lambda a, b: a*b, range(1, self.value+1))
+
+factorial = Factorial(4)
+dumped = dump(factorial)
+# dumped == dict(value=4, factorial=24)
+loaded = load(Factorial, dumped)
+# loaded == factorial
+```
+
+## Under The Hood
+
+Internally, API defines 3 core concepts:
+
+* A [Marshaller](marshy/marshaller/marshaller_abc.py): Is
+ responsible for marshalling / unmarshalling a single type of
+ object.
+* A [MarshallerFactory](marshy/factory/marshaller_factory_abc.py): has
+ a `create` method used to create marshallers for types, and has
+ a priority which controls the order in which they are run.
+ (higher first)
+* A [MarshallerContext](marshy/marshaller_context.py): coordinates
+ the activities of Marshallers and Factories
+
+## Creating a Custom Marshaller Context
+
+If you need multiple independent sets of rules for
+marshalling data, then you should create your own marshalling
+contexts and store references to them. The default works well
+otherwise:
+
+```
+# Dump a Doohickey using the default context (Same as marshy.dump...)
+from marshy import get_default_context
+dumped = get_default_context().dump(Doohickey('Thingy'))
+
+# Create a new blank marshaller context - this will fail
+# because there are no preset types or factories.
+from marshy.marshaller_context import MarshallerContext
+my_marshaller_context = MarshallerContext()
+dumped = my_marshaller_context.dump(Doohickey('Thingy'))
+
+# Create a new marshaller context which copies the default rules.
+from marshy.default_context import new_default_context
+my_default_context = new_default_context()
+dumped = my_default_context.dump(Doohickey('Thingy'))
+```
+
+## Creating a Custom Marshaller
+
+To customize marshalling for a type, write a marshaller and then
+register it with your context:
+```
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy.types import ExternalType
+
+
+class MyDoohickeyMarshaller(MarshallerABC[Doohickey]):
+
+ def __init__(self):
+ super().__init__(Doohickey)
+
+ def load(self, item: ExternalType) -> Doohickey:
+ return Doohickey(item[0], item[1], item[2])
+
+ def dump(self, item: Doohickey) -> ExternalType:
+ return [item.title, item.description, item.tags]
+
+my_default_context.register_marshaller(MyDoohickeyMarshaller())
+dumped = my_default_context.dump(Doohickey('Thingy'))
+# dumped == ['Thingy', None, []]
+
+loaded = my_default_context.load(Doohickey, dumped)
+# dumped == Doohickey('Thingy')
+```
+
+## Creating a Custom Marshaller Factory
+
+Sometimes you need to create a marshaller for a while concept of
+object rather than a single type - In this case you need a factory,
+(this is how the default rules work!). Examples:
+* [ListMarshallerFactory](marshy/factory/list_marshaller_factory.py)
+ looks for typed lists (e.g.,: List[str]) and creates marshallers
+ for them - you already saw the results in `Doohickey.tags` above.
+* [OptionalMarshallerFactory](marshy/factory/optional_marshaller_factory.py)
+ looks for optional fields (e.g.,: Optional[str]) and creates
+ marshallers that mean each individual other marshaller does not
+ need to accommodate the case where a value is None - just mark
+ it optional!
+* [DataClassMarshallerFactory](marshy/factory/dataclass_marshaller_factory.py)
+ provides a marshaller for dataclasses assuming they have a standard
+ constructor based on their fields.
+
+## Customizing dataclass attributes:
+
+Taking the doohickey example:
+
+```
+from marshy import dump, get_default_context
+from marshy.marshaller import str_marshaller, bool_marshaller
+from marshy.marshaller.obj_marshaller import ObjMarshaller
+attr_marshallers = dict(title=str_marshaller, tags=bool_marshaller)
+get_default_context().register_marshaller(ObjMarshaller(Doohickey, attr_marshallers, False))
+dumped = dump(Doohickey('Thingy'))
+# dumped == dict(title='Thingy', tags=False)
+```
+
+## Customizing dataclass marshalling
+
+As an alternative to defining a custom marshaller / factory, it is possible to simply
+define a __marshaller_factory__ class method. (Note: this becomes the default for all
+contexts) Imagine a case where you have a dataclass representing a 2D point, which you
+want to be marshalled in the format [x, y] (An array rather than the standard object):
+```
+from dataclasses import dataclass
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy import load, dump
+
+@dataclass
+class Point:
+ x: float
+ y: float
+
+ @classmethod
+ def __marshaller_factory__(cls, marshaller_context):
+ return PointMarshaller()
+
+class PointMarshaller(MarshallerABC):
+
+ def __init__(self):
+ super().__init__(Point)
+
+ def load(self, item):
+ return Point(item[0], item[1])
+
+ def dump(self, item):
+ return [item.x, item.y]
+
+dumped = dump(Point(1.2, 3.4))
+loaded = load(Point, dumped)
+```
+
+## Circular References
+
+Due to the fact that types in the object graph can self reference,
+we defer resolution of most marshaller until as late as possible.
+[DeferredMarshaller](marshy/marshaller/deferred_marshaller.py)
+is responsible for this, and means types can
+[self reference](test/test_marshall_deferred.py).
+
+Circular references within objects will still cause an error.
+(Unless you decide on an error handling protocol for this an
+implement a custom Factory to deal with it!)
+
+## Customizing the default context
+
+The project uses the namespace convention `marshy_config_` to identity configuration packages.
+(https://packaging.python.org/guides/creating-and-discovering-plugins/). Configuration packages should have an integer
+priority attribute, and a `def configure(context: MarshallerContext)` function. e.g.:
+[default_config](marshy_config_default/__init__.py)
+
+## Adding Polymorphic Implementations
+
+Taking the following polymorphic classes where `Pet` has implementations `Cat` and `Dog`:
+
+```
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+@dataclass
+class PetAbc(ABC):
+ name: str
+
+ @abstractmethod
+ def vocalize(self) -> str:
+ """ What sound does this make? """
+
+
+class Cat(PetAbc):
+
+ def vocalize(self):
+ return "Meow!"
+
+
+class Dog(PetAbc):
+
+ def vocalize(self) -> str:
+ return "Woof!"
+```
+
+In order to deserialize a Pet, marshy needs to be informed tha the implementations exist. This can be done at any point
+in the configuration:
+
+```
+from marshy import load
+from marshy.factory.impl_marshaller_factory import register_impl
+register_impl(PetAbc, Cat)
+register_impl(PetAbc, Dog)
+pet = ['Cat', dict(name='Felix')]
+loaded = load(PetAbc, pet)
+```
+
+[Tests for this are here] (test/test_impl_marshaller.py)
+
+## Performance Tests
+
+Basic Tests show performance is approximate with marshmallow:
+
+```
+python -m timeit -s "
+from test.performance.marshy_performance import run
+run(1000)
+"
+```
+
+```
+python -m timeit -s "
+from test.performance.marshmallow_performance import run
+run(1000)
+"
+```
+
+
+## Release Proceedure
+
+![status](https://github.com/tofarr/marshy/actions/workflows/quality.yml/badge.svg?branch=main)
+
+The typical process here is:
+* Create a PR with changes. Merge these to main (The `Quality` workflows make sure that your PR
+ meets the styling, linting, and code coverage standards).
+* New releases created in github are automatically uploaded to pypi
+
+
+%package -n python3-marshy
+Summary: A convention over configuration approach to object marshalling.
+Provides: python-marshy
+BuildRequires: python3-devel
+BuildRequires: python3-setuptools
+BuildRequires: python3-pip
+%description -n python3-marshy
+# Marshy - Better Marshalling for Python.
+
+This project is a general purpose externalizer for python objects.
+(Like Marshmallow or Pedantic) The guiding philosophy is convention
+over configuration, with the aim of still making customizations as
+pain free as possible, based on python type hints.
+
+Out of the box, it supports primitives, dataclasses, and enums.
+
+## Installation
+
+`pip install marshy`
+
+## General Usage
+
+Given the following dataclass:
+```
+from typing import List, Optional
+import dataclasses
+
+
+@dataclasses.dataclass
+class Doohickey:
+ title: str
+ description: Optional[str] = None
+ tags: List[str] = dataclasses.field(default_factory=list)
+```
+
+Marshall data with:
+```
+import marshy
+result = marshy.dump(Doohickey('Thingy', tags=['a','b']))
+# result == dict(title='Thingy', description=None, tags=['a','b'])
+```
+
+Unmarshall data with:
+```
+result = marshy.load(Doohickey, dict(title='Thingy'))
+# result == Doohickey('Thingy', description=None, tags=[])
+```
+
+## Custom properties
+
+Custom properties are also serialized by default. (If they
+have a setter, it is used when loading):
+
+```
+@dataclass
+class Factorial:
+ value: int
+
+ @property
+ def factorial(self) -> int:
+ return reduce(lambda a, b: a*b, range(1, self.value+1))
+
+factorial = Factorial(4)
+dumped = dump(factorial)
+# dumped == dict(value=4, factorial=24)
+loaded = load(Factorial, dumped)
+# loaded == factorial
+```
+
+## Under The Hood
+
+Internally, API defines 3 core concepts:
+
+* A [Marshaller](marshy/marshaller/marshaller_abc.py): Is
+ responsible for marshalling / unmarshalling a single type of
+ object.
+* A [MarshallerFactory](marshy/factory/marshaller_factory_abc.py): has
+ a `create` method used to create marshallers for types, and has
+ a priority which controls the order in which they are run.
+ (higher first)
+* A [MarshallerContext](marshy/marshaller_context.py): coordinates
+ the activities of Marshallers and Factories
+
+## Creating a Custom Marshaller Context
+
+If you need multiple independent sets of rules for
+marshalling data, then you should create your own marshalling
+contexts and store references to them. The default works well
+otherwise:
+
+```
+# Dump a Doohickey using the default context (Same as marshy.dump...)
+from marshy import get_default_context
+dumped = get_default_context().dump(Doohickey('Thingy'))
+
+# Create a new blank marshaller context - this will fail
+# because there are no preset types or factories.
+from marshy.marshaller_context import MarshallerContext
+my_marshaller_context = MarshallerContext()
+dumped = my_marshaller_context.dump(Doohickey('Thingy'))
+
+# Create a new marshaller context which copies the default rules.
+from marshy.default_context import new_default_context
+my_default_context = new_default_context()
+dumped = my_default_context.dump(Doohickey('Thingy'))
+```
+
+## Creating a Custom Marshaller
+
+To customize marshalling for a type, write a marshaller and then
+register it with your context:
+```
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy.types import ExternalType
+
+
+class MyDoohickeyMarshaller(MarshallerABC[Doohickey]):
+
+ def __init__(self):
+ super().__init__(Doohickey)
+
+ def load(self, item: ExternalType) -> Doohickey:
+ return Doohickey(item[0], item[1], item[2])
+
+ def dump(self, item: Doohickey) -> ExternalType:
+ return [item.title, item.description, item.tags]
+
+my_default_context.register_marshaller(MyDoohickeyMarshaller())
+dumped = my_default_context.dump(Doohickey('Thingy'))
+# dumped == ['Thingy', None, []]
+
+loaded = my_default_context.load(Doohickey, dumped)
+# dumped == Doohickey('Thingy')
+```
+
+## Creating a Custom Marshaller Factory
+
+Sometimes you need to create a marshaller for a while concept of
+object rather than a single type - In this case you need a factory,
+(this is how the default rules work!). Examples:
+* [ListMarshallerFactory](marshy/factory/list_marshaller_factory.py)
+ looks for typed lists (e.g.,: List[str]) and creates marshallers
+ for them - you already saw the results in `Doohickey.tags` above.
+* [OptionalMarshallerFactory](marshy/factory/optional_marshaller_factory.py)
+ looks for optional fields (e.g.,: Optional[str]) and creates
+ marshallers that mean each individual other marshaller does not
+ need to accommodate the case where a value is None - just mark
+ it optional!
+* [DataClassMarshallerFactory](marshy/factory/dataclass_marshaller_factory.py)
+ provides a marshaller for dataclasses assuming they have a standard
+ constructor based on their fields.
+
+## Customizing dataclass attributes:
+
+Taking the doohickey example:
+
+```
+from marshy import dump, get_default_context
+from marshy.marshaller import str_marshaller, bool_marshaller
+from marshy.marshaller.obj_marshaller import ObjMarshaller
+attr_marshallers = dict(title=str_marshaller, tags=bool_marshaller)
+get_default_context().register_marshaller(ObjMarshaller(Doohickey, attr_marshallers, False))
+dumped = dump(Doohickey('Thingy'))
+# dumped == dict(title='Thingy', tags=False)
+```
+
+## Customizing dataclass marshalling
+
+As an alternative to defining a custom marshaller / factory, it is possible to simply
+define a __marshaller_factory__ class method. (Note: this becomes the default for all
+contexts) Imagine a case where you have a dataclass representing a 2D point, which you
+want to be marshalled in the format [x, y] (An array rather than the standard object):
+```
+from dataclasses import dataclass
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy import load, dump
+
+@dataclass
+class Point:
+ x: float
+ y: float
+
+ @classmethod
+ def __marshaller_factory__(cls, marshaller_context):
+ return PointMarshaller()
+
+class PointMarshaller(MarshallerABC):
+
+ def __init__(self):
+ super().__init__(Point)
+
+ def load(self, item):
+ return Point(item[0], item[1])
+
+ def dump(self, item):
+ return [item.x, item.y]
+
+dumped = dump(Point(1.2, 3.4))
+loaded = load(Point, dumped)
+```
+
+## Circular References
+
+Due to the fact that types in the object graph can self reference,
+we defer resolution of most marshaller until as late as possible.
+[DeferredMarshaller](marshy/marshaller/deferred_marshaller.py)
+is responsible for this, and means types can
+[self reference](test/test_marshall_deferred.py).
+
+Circular references within objects will still cause an error.
+(Unless you decide on an error handling protocol for this an
+implement a custom Factory to deal with it!)
+
+## Customizing the default context
+
+The project uses the namespace convention `marshy_config_` to identity configuration packages.
+(https://packaging.python.org/guides/creating-and-discovering-plugins/). Configuration packages should have an integer
+priority attribute, and a `def configure(context: MarshallerContext)` function. e.g.:
+[default_config](marshy_config_default/__init__.py)
+
+## Adding Polymorphic Implementations
+
+Taking the following polymorphic classes where `Pet` has implementations `Cat` and `Dog`:
+
+```
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+@dataclass
+class PetAbc(ABC):
+ name: str
+
+ @abstractmethod
+ def vocalize(self) -> str:
+ """ What sound does this make? """
+
+
+class Cat(PetAbc):
+
+ def vocalize(self):
+ return "Meow!"
+
+
+class Dog(PetAbc):
+
+ def vocalize(self) -> str:
+ return "Woof!"
+```
+
+In order to deserialize a Pet, marshy needs to be informed tha the implementations exist. This can be done at any point
+in the configuration:
+
+```
+from marshy import load
+from marshy.factory.impl_marshaller_factory import register_impl
+register_impl(PetAbc, Cat)
+register_impl(PetAbc, Dog)
+pet = ['Cat', dict(name='Felix')]
+loaded = load(PetAbc, pet)
+```
+
+[Tests for this are here] (test/test_impl_marshaller.py)
+
+## Performance Tests
+
+Basic Tests show performance is approximate with marshmallow:
+
+```
+python -m timeit -s "
+from test.performance.marshy_performance import run
+run(1000)
+"
+```
+
+```
+python -m timeit -s "
+from test.performance.marshmallow_performance import run
+run(1000)
+"
+```
+
+
+## Release Proceedure
+
+![status](https://github.com/tofarr/marshy/actions/workflows/quality.yml/badge.svg?branch=main)
+
+The typical process here is:
+* Create a PR with changes. Merge these to main (The `Quality` workflows make sure that your PR
+ meets the styling, linting, and code coverage standards).
+* New releases created in github are automatically uploaded to pypi
+
+
+%package help
+Summary: Development documents and examples for marshy
+Provides: python3-marshy-doc
+%description help
+# Marshy - Better Marshalling for Python.
+
+This project is a general purpose externalizer for python objects.
+(Like Marshmallow or Pedantic) The guiding philosophy is convention
+over configuration, with the aim of still making customizations as
+pain free as possible, based on python type hints.
+
+Out of the box, it supports primitives, dataclasses, and enums.
+
+## Installation
+
+`pip install marshy`
+
+## General Usage
+
+Given the following dataclass:
+```
+from typing import List, Optional
+import dataclasses
+
+
+@dataclasses.dataclass
+class Doohickey:
+ title: str
+ description: Optional[str] = None
+ tags: List[str] = dataclasses.field(default_factory=list)
+```
+
+Marshall data with:
+```
+import marshy
+result = marshy.dump(Doohickey('Thingy', tags=['a','b']))
+# result == dict(title='Thingy', description=None, tags=['a','b'])
+```
+
+Unmarshall data with:
+```
+result = marshy.load(Doohickey, dict(title='Thingy'))
+# result == Doohickey('Thingy', description=None, tags=[])
+```
+
+## Custom properties
+
+Custom properties are also serialized by default. (If they
+have a setter, it is used when loading):
+
+```
+@dataclass
+class Factorial:
+ value: int
+
+ @property
+ def factorial(self) -> int:
+ return reduce(lambda a, b: a*b, range(1, self.value+1))
+
+factorial = Factorial(4)
+dumped = dump(factorial)
+# dumped == dict(value=4, factorial=24)
+loaded = load(Factorial, dumped)
+# loaded == factorial
+```
+
+## Under The Hood
+
+Internally, API defines 3 core concepts:
+
+* A [Marshaller](marshy/marshaller/marshaller_abc.py): Is
+ responsible for marshalling / unmarshalling a single type of
+ object.
+* A [MarshallerFactory](marshy/factory/marshaller_factory_abc.py): has
+ a `create` method used to create marshallers for types, and has
+ a priority which controls the order in which they are run.
+ (higher first)
+* A [MarshallerContext](marshy/marshaller_context.py): coordinates
+ the activities of Marshallers and Factories
+
+## Creating a Custom Marshaller Context
+
+If you need multiple independent sets of rules for
+marshalling data, then you should create your own marshalling
+contexts and store references to them. The default works well
+otherwise:
+
+```
+# Dump a Doohickey using the default context (Same as marshy.dump...)
+from marshy import get_default_context
+dumped = get_default_context().dump(Doohickey('Thingy'))
+
+# Create a new blank marshaller context - this will fail
+# because there are no preset types or factories.
+from marshy.marshaller_context import MarshallerContext
+my_marshaller_context = MarshallerContext()
+dumped = my_marshaller_context.dump(Doohickey('Thingy'))
+
+# Create a new marshaller context which copies the default rules.
+from marshy.default_context import new_default_context
+my_default_context = new_default_context()
+dumped = my_default_context.dump(Doohickey('Thingy'))
+```
+
+## Creating a Custom Marshaller
+
+To customize marshalling for a type, write a marshaller and then
+register it with your context:
+```
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy.types import ExternalType
+
+
+class MyDoohickeyMarshaller(MarshallerABC[Doohickey]):
+
+ def __init__(self):
+ super().__init__(Doohickey)
+
+ def load(self, item: ExternalType) -> Doohickey:
+ return Doohickey(item[0], item[1], item[2])
+
+ def dump(self, item: Doohickey) -> ExternalType:
+ return [item.title, item.description, item.tags]
+
+my_default_context.register_marshaller(MyDoohickeyMarshaller())
+dumped = my_default_context.dump(Doohickey('Thingy'))
+# dumped == ['Thingy', None, []]
+
+loaded = my_default_context.load(Doohickey, dumped)
+# dumped == Doohickey('Thingy')
+```
+
+## Creating a Custom Marshaller Factory
+
+Sometimes you need to create a marshaller for a while concept of
+object rather than a single type - In this case you need a factory,
+(this is how the default rules work!). Examples:
+* [ListMarshallerFactory](marshy/factory/list_marshaller_factory.py)
+ looks for typed lists (e.g.,: List[str]) and creates marshallers
+ for them - you already saw the results in `Doohickey.tags` above.
+* [OptionalMarshallerFactory](marshy/factory/optional_marshaller_factory.py)
+ looks for optional fields (e.g.,: Optional[str]) and creates
+ marshallers that mean each individual other marshaller does not
+ need to accommodate the case where a value is None - just mark
+ it optional!
+* [DataClassMarshallerFactory](marshy/factory/dataclass_marshaller_factory.py)
+ provides a marshaller for dataclasses assuming they have a standard
+ constructor based on their fields.
+
+## Customizing dataclass attributes:
+
+Taking the doohickey example:
+
+```
+from marshy import dump, get_default_context
+from marshy.marshaller import str_marshaller, bool_marshaller
+from marshy.marshaller.obj_marshaller import ObjMarshaller
+attr_marshallers = dict(title=str_marshaller, tags=bool_marshaller)
+get_default_context().register_marshaller(ObjMarshaller(Doohickey, attr_marshallers, False))
+dumped = dump(Doohickey('Thingy'))
+# dumped == dict(title='Thingy', tags=False)
+```
+
+## Customizing dataclass marshalling
+
+As an alternative to defining a custom marshaller / factory, it is possible to simply
+define a __marshaller_factory__ class method. (Note: this becomes the default for all
+contexts) Imagine a case where you have a dataclass representing a 2D point, which you
+want to be marshalled in the format [x, y] (An array rather than the standard object):
+```
+from dataclasses import dataclass
+from marshy.marshaller.marshaller_abc import MarshallerABC
+from marshy import load, dump
+
+@dataclass
+class Point:
+ x: float
+ y: float
+
+ @classmethod
+ def __marshaller_factory__(cls, marshaller_context):
+ return PointMarshaller()
+
+class PointMarshaller(MarshallerABC):
+
+ def __init__(self):
+ super().__init__(Point)
+
+ def load(self, item):
+ return Point(item[0], item[1])
+
+ def dump(self, item):
+ return [item.x, item.y]
+
+dumped = dump(Point(1.2, 3.4))
+loaded = load(Point, dumped)
+```
+
+## Circular References
+
+Due to the fact that types in the object graph can self reference,
+we defer resolution of most marshaller until as late as possible.
+[DeferredMarshaller](marshy/marshaller/deferred_marshaller.py)
+is responsible for this, and means types can
+[self reference](test/test_marshall_deferred.py).
+
+Circular references within objects will still cause an error.
+(Unless you decide on an error handling protocol for this an
+implement a custom Factory to deal with it!)
+
+## Customizing the default context
+
+The project uses the namespace convention `marshy_config_` to identity configuration packages.
+(https://packaging.python.org/guides/creating-and-discovering-plugins/). Configuration packages should have an integer
+priority attribute, and a `def configure(context: MarshallerContext)` function. e.g.:
+[default_config](marshy_config_default/__init__.py)
+
+## Adding Polymorphic Implementations
+
+Taking the following polymorphic classes where `Pet` has implementations `Cat` and `Dog`:
+
+```
+from abc import ABC, abstractmethod
+from dataclasses import dataclass
+
+@dataclass
+class PetAbc(ABC):
+ name: str
+
+ @abstractmethod
+ def vocalize(self) -> str:
+ """ What sound does this make? """
+
+
+class Cat(PetAbc):
+
+ def vocalize(self):
+ return "Meow!"
+
+
+class Dog(PetAbc):
+
+ def vocalize(self) -> str:
+ return "Woof!"
+```
+
+In order to deserialize a Pet, marshy needs to be informed tha the implementations exist. This can be done at any point
+in the configuration:
+
+```
+from marshy import load
+from marshy.factory.impl_marshaller_factory import register_impl
+register_impl(PetAbc, Cat)
+register_impl(PetAbc, Dog)
+pet = ['Cat', dict(name='Felix')]
+loaded = load(PetAbc, pet)
+```
+
+[Tests for this are here] (test/test_impl_marshaller.py)
+
+## Performance Tests
+
+Basic Tests show performance is approximate with marshmallow:
+
+```
+python -m timeit -s "
+from test.performance.marshy_performance import run
+run(1000)
+"
+```
+
+```
+python -m timeit -s "
+from test.performance.marshmallow_performance import run
+run(1000)
+"
+```
+
+
+## Release Proceedure
+
+![status](https://github.com/tofarr/marshy/actions/workflows/quality.yml/badge.svg?branch=main)
+
+The typical process here is:
+* Create a PR with changes. Merge these to main (The `Quality` workflows make sure that your PR
+ meets the styling, linting, and code coverage standards).
+* New releases created in github are automatically uploaded to pypi
+
+
+%prep
+%autosetup -n marshy-4.0.3
+
+%build
+%py3_build
+
+%install
+%py3_install
+install -d -m755 %{buildroot}/%{_pkgdocdir}
+if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi
+if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi
+if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi
+if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi
+pushd %{buildroot}
+if [ -d usr/lib ]; then
+ find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/lib64 ]; then
+ find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/bin ]; then
+ find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+if [ -d usr/sbin ]; then
+ find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst
+fi
+touch doclist.lst
+if [ -d usr/share/man ]; then
+ find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst
+fi
+popd
+mv %{buildroot}/filelist.lst .
+mv %{buildroot}/doclist.lst .
+
+%files -n python3-marshy -f filelist.lst
+%dir %{python3_sitelib}/*
+
+%files help -f doclist.lst
+%{_docdir}/*
+
+%changelog
+* Wed May 10 2023 Python_Bot <Python_Bot@openeuler.org> - 4.0.3-1
+- Package Spec generated