diff --git a/HISTORY b/HISTORY
index ddf0574..d00642a 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,3 +1,17 @@
+2.11rc2
+=======
+
+Changes
+-------
+
+- Apply correct type annotations to `annotated_resource.type_resolution.SIMPLE_TYPE_MAP`
+
+Bugfix
+------
+
+- field_type as being overridden when `Annotated[list[str]]` was being used.
+
+
2.11rc1
=======
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6c06be0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,198 @@
+# Odin
+
+Odin provides a declarative framework for defining resources (classes) and their relationships, validation of the fields
+that make up the resources and mapping between objects (either a resource, or other python structures).
+
+Odin also comes with built in serialisation tools for importing and exporting data from resources.
+
+
+
+## Highlights
+
+* Class based declarative style
+* Class based annotations style! ✨ new in 2.0
+* Fields for building composite resources
+* Field and Resource level validation
+* Easy extension to support custom fields
+* Python 3.8+ and PyPy 1 supported
+* Support for documenting resources with [Sphinx](http://sphinx-doc.org/)
+* Minimal dependencies
+
+1 certain contrib items are not supported. Pint is not installable with PyPy.
+
+## Use cases
+
+* Design, document and validate complex (and simple!) data structures
+* Convert structures to and from different formats such as JSON, YAML, MsgPack, CSV, TOML
+* Validate API inputs
+* Define message formats for communications protocols, like an RPC
+* Map API requests to ORM objects
+
+## Quick links
+
+* [Documentation](https://odin.readthedocs.org/)
+* [Project home](https://github.com/python-odin/odin)
+* [Issue tracker](https://github.com/python-odin/odin/issues)
+
+
+## Upcoming features
+
+### In development
+
+* XML Codec (export only)
+* Complete documentation coverage
+* Improvements for CSV Codec (writing, reading multi resource CSV's)
+
+
+## Requires
+
+### Optional
+
+* simplejson - Odin will use simplejson if it is available or fallback to the builtin json library
+* msgpack-python - To enable use of the msgpack codec
+* pyyaml - To enable use of the YAML codec
+* toml - To enable use of the TOML codec
+
+### Contrib
+
+* arrow - Support for Arrow data types.
+* pint - Support for physical quantities using the [Pint](http://pint.readthedocs.org/) library.
+
+### Development
+
+* pytest - Testing
+* pytest-cov - Coverage reporting
+
+## Example
+
+### Definition
+
+```python
+import odin
+
+class Author(odin.Resource):
+ name = odin.StringField()
+
+class Publisher(odin.Resource):
+ name = odin.StringField()
+
+class Book(odin.Resource):
+ title = odin.StringField()
+ authors = odin.ArrayOf(Author)
+ publisher = odin.DictAs(Publisher)
+ genre = odin.StringField()
+ num_pages = odin.IntegerField()
+```
+
+### Using Annotations
+
+```python
+import odin
+
+class Author(odin.AnnotatedResource):
+ name: str
+
+class Publisher(odin.AnnotatedResource):
+ name: str
+ website: odin.Url | None
+
+class Book(odin.AnnotatedResource):
+ title: str
+ authors: list[Author]
+ publisher: Publisher
+ genre: str
+ num_pages: int
+```
+
+### Usage
+
+```pycon
+>>> b = Book(
+ title="Consider Phlebas",
+ genre="Space Opera",
+ publisher=Publisher(name="Macmillan"),
+ num_pages=471
+ )
+>>> b.authors.append(Author(name="Iain M. Banks"))
+>>> from odin.codecs import json_codec
+>>> json_codec.dumps(b, indent=4)
+{
+ "$": "Book",
+ "authors": [
+ {
+ "$": "Author",
+ "name": "Iain M. Banks"
+ }
+ ],
+ "genre": "Space Opera",
+ "num_pages": 471,
+ "publisher": {
+ "$": "Publisher",
+ "name": "Macmillan"
+ },
+ "title": "Consider Phlebas"
+}
+```
diff --git a/README.rst b/README.rst
deleted file mode 100644
index c28e51b..0000000
--- a/README.rst
+++ /dev/null
@@ -1,179 +0,0 @@
-
-####
-Odin
-####
-
-Odin provides a declarative framework for defining resources (classes) and their relationships, validation of the fields
-that make up the resources and mapping between objects (either a resource, or other python structures).
-
-Odin also comes with built in serialisation tools for importing and exporting data from resources.
-
-+---------+-------------------------------------------------------------------------------------------------------------+
-| Docs/ | .. image:: https://readthedocs.org/projects/odin/badge/?version=latest |
-| Help | :target: https://odin.readthedocs.org/ |
-| | :alt: ReadTheDocs |
-| | .. image:: https://img.shields.io/badge/gitterim-timsavage.odin-brightgreen.svg?style=flat |
-| | :target: https://gitter.im/timsavage/odin |
-| | :alt: Gitter.im |
-+---------+-------------------------------------------------------------------------------------------------------------+
-| Build | .. image:: https://github.com/python-odin/odin/actions/workflows/python-package.yml/badge.svg |
-| | :target: https://github.com/python-odin/odin/actions/workflows/python-package.yml |
-| | :alt: Python package |
-+---------+-------------------------------------------------------------------------------------------------------------+
-| Quality | .. image:: https://sonarcloud.io/api/project_badges/measure?project=python-odin_odin&metric=sqale_rating |
-| | :target: https://sonarcloud.io/dashboard?id=python-odin_odin |
-| | :alt: Maintainability |
-| | .. image:: https://sonarcloud.io/api/project_badges/measure?project=python-odin_odin&metric=security_rating |
-| | :target: https://sonarcloud.io/project/security_hotspots |
-| | :alt: Security |
-| | .. image:: https://sonarcloud.io/api/project_badges/measure?project=python-odin_odin&metric=coverage |
-| | :target: https://sonarcloud.io/code?id=python-odin_odin |
-| | :alt: Test Coverage |
-| | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg |
-| | :target: https://github.com/ambv/black |
-| | :alt: Once you go Black... |
-+---------+-------------------------------------------------------------------------------------------------------------+
-| Package | .. image:: https://img.shields.io/pypi/v/odin |
-| | :target: https://pypi.io/pypi/odin/ |
-| | :alt: Latest Version |
-| | .. image:: https://img.shields.io/pypi/pyversions/odin |
-| | :target: https://pypi.io/pypi/odin/ |
-| | .. image:: https://img.shields.io/pypi/l/odin |
-| | :target: https://pypi.io/pypi/odin/ |
-| | .. image:: https://img.shields.io/pypi/wheel/odin |
-| | :alt: PyPI - Wheel |
-| | :target: https://pypi.io/pypi/odin/ |
-+---------+-------------------------------------------------------------------------------------------------------------+
-
-
-Highlights
-**********
-
-* Class based declarative style
-* Class based annotations style! ✨ new in 2.0
-* Fields for building composite resources
-* Field and Resource level validation
-* Easy extension to support custom fields
-* Python 3.8+ and PyPy :sup:`1` supported
-* Support for documenting resources with `Sphinx `_
-* Minimal dependencies
-
-:sup:`1` certain contrib items are not supported. Pint is not installable with PyPy.
-
-Use cases
-*********
-* Design, document and validate complex (and simple!) data structures
-* Convert structures to and from different formats such as JSON, YAML, MsgPack, CSV, TOML
-* Validate API inputs
-* Define message formats for communications protocols, like an RPC
-* Map API requests to ORM objects
-
-Quick links
-***********
-
-* `Documentation `_
-* `Project home `_
-* `Issue tracker `_
-
-
-Upcoming features
-*****************
-
-**In development**
-
-* XML Codec (export only)
-* Complete documentation coverage
-* Improvements for CSV Codec (writing, reading multi resource CSV's)
-
-
-Requires
-********
-
-**Optional**
-
-* simplejson - Odin will use simplejson if it is available or fallback to the builtin json library
-* msgpack-python - To enable use of the msgpack codec
-* pyyaml - To enable use of the YAML codec
-* toml - To enable use of the TOML codec
-
-**Contrib**
-
-* arrow - Support for Arrow data types.
-* pint - Support for physical quantities using the `Pint `_ library.
-
-**Development**
-
-* pytest - Testing
-* pytest-cov - Coverage reporting
-
-Example
-*******
-
-**Definition**
-
-.. code-block:: python
-
- import odin
-
- class Author(odin.Resource):
- name = odin.StringField()
-
- class Publisher(odin.Resource):
- name = odin.StringField()
-
- class Book(odin.Resource):
- title = odin.StringField()
- authors = odin.ArrayOf(Author)
- publisher = odin.DictAs(Publisher)
- genre = odin.StringField()
- num_pages = odin.IntegerField()
-
-**Using Annotations**
-
-.. code-block:: python
-
- import odin
-
- class Author(odin.AnnotatedResource):
- name: str
-
- class Publisher(odin.AnnotatedResource):
- name: str
- website: Optional[odin.Url]
-
- class Book(odin.AnnotatedResource):
- title: str
- authors: List[Author]
- publisher: Publisher
- genre: str
- num_pages: int
-
-**Usage**::
-
- >>> b = Book(
- title="Consider Phlebas",
- genre="Space Opera",
- publisher=Publisher(name="Macmillan"),
- num_pages=471
- )
- >>> b.authors.append(Author(name="Iain M. Banks"))
- >>> from odin.codecs import json_codec
- >>> json_codec.dumps(b, indent=4)
- {
- "$": "Book",
- "authors": [
- {
- "$": "Author",
- "name": "Iain M. Banks"
- }
- ],
- "genre": "Space Opera",
- "num_pages": 471,
- "publisher": {
- "$": "Publisher",
- "name": "Macmillan"
- },
- "title": "Consider Phlebas"
- }
-
-
diff --git a/pyproject.toml b/pyproject.toml
index dc333e9..a6afca8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,11 +4,11 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "odin"
-version = "2.11rc1"
+version = "2.11rc2"
description = "Data-structure definition/validation/traversal, mapping and serialisation toolkit for Python"
authors = ["Tim Savage "]
license = "BSD-3-Clause"
-readme = "README.rst"
+readme = "README.md"
repository = "https://github.com/python-odin/odin"
documentation = "https://odin.readthedocs.org"
keywords = ["data-structure", "validation", "data-mapping"]
diff --git a/src/odin/annotated_resource/type_resolution.py b/src/odin/annotated_resource/type_resolution.py
index ea241d2..96143cb 100644
--- a/src/odin/annotated_resource/type_resolution.py
+++ b/src/odin/annotated_resource/type_resolution.py
@@ -147,7 +147,7 @@ def construct_field(self) -> Field:
raise ResourceDefError(msg)
-SIMPLE_TYPE_MAP = {
+SIMPLE_TYPE_MAP: dict[type, type[Field] | functools.partial] = {
bool: BooleanField,
datetime.date: DateField,
datetime.datetime: DateTimeField,
@@ -269,10 +269,14 @@ def _create_field_via_origin(origin, tp, options: Options) -> BaseField:
options.base_args["value"] = value
return options.construct_field()
- elif issubclass(origin, list):
+ # If type already defined skip lookup
+ if options.is_field_type_valid:
+ return options.construct_field()
+
+ if issubclass(origin, list):
return _create_field_from_list_type(get_args(tp), options)
- elif issubclass(origin, dict):
+ if issubclass(origin, dict):
return _create_field_from_dict_type(get_args(tp), options)
msg = f"Unable to resolve field for sub-scripted type {tp!r}"
@@ -281,16 +285,18 @@ def _create_field_via_origin(origin, tp, options: Options) -> BaseField:
def _create_field_for_type(tp, options: Options) -> Field:
# If type already defined skip lookup
- if not options.is_field_type_valid:
- # Is a basic type
- if isinstance(tp, type):
- _set_options_field_type(options, tp)
- elif tp is Any:
- # For Python 3.10
- options.field_type = AnyField
- else:
- msg = f"Annotation is not a type instance {tp!r}"
- raise ResourceDefError(msg)
+ if options.is_field_type_valid:
+ return options.construct_field()
+
+ # Is a basic type
+ if isinstance(tp, type):
+ _set_options_field_type(options, tp)
+ elif tp is Any:
+ # For Python 3.10
+ options.field_type = AnyField
+ else:
+ msg = f"Annotation is not a type instance {tp!r}"
+ raise ResourceDefError(msg)
return options.construct_field()
diff --git a/tests/annotated_resources/test_type_resolution.py b/tests/annotated_resources/test_type_resolution.py
index 1711b71..ae2e415 100644
--- a/tests/annotated_resources/test_type_resolution.py
+++ b/tests/annotated_resources/test_type_resolution.py
@@ -187,6 +187,18 @@ def test_to_python(self, target, value, expected):
odin.StringField,
odin.NotProvided,
),
+ (
+ Annotated[str, odin.Options(field_type=odin.EmailField)],
+ odin.NotProvided,
+ odin.EmailField,
+ odin.NotProvided,
+ ),
+ (
+ Annotated[list[str], odin.Options(field_type=odin.EmailField)],
+ odin.NotProvided,
+ odin.EmailField,
+ odin.NotProvided,
+ ),
(
Annotated[str, odin.Options(verbose_name="Foo")],
"foo",