Skip to content
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

Generics #1

Merged
merged 79 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
07dbfcc
attrs: Use attrs.has
delfick Dec 11, 2022
7a3ff7f
cattrs: Use the more "modern" GenConverter
delfick Dec 11, 2022
c45c25c
hints: improve how the hint resolver works
delfick Jan 2, 2023
4471268
meta: make the InstanceCheck issubclassable
delfick Jan 2, 2023
6261b89
types: container to represent a type
delfick Jan 2, 2023
6b85eb5
types: switch to new Type class
delfick Jan 2, 2023
c54b8a1
generics: add tests
delfick Jan 2, 2023
5ea928e
structure hook: cleanup object passthrough
delfick Jan 2, 2023
d0456de
types: improve extracting optional/annotated
delfick Jan 4, 2023
deb5a87
types: cleanup testing equivalency
delfick Jan 4, 2023
97b7ee0
disassemble: Add disassembler
delfick Jan 7, 2023
4561c98
disassemble: use in annotations, hints and meta
delfick Jan 7, 2023
40aa2f0
disassemble: Make Type use it
delfick Jan 7, 2023
e70eb48
imports: move Convert definitions in decorators
delfick Jan 4, 2023
9db5029
disassembled: Making only one Field class
delfick Jan 7, 2023
ac09ca7
helpers: move out memoized_property
delfick Jan 8, 2023
be47777
disassemble: use a memoizing cache
delfick Jan 8, 2023
9f28dbc
disassemble: move is_equivalent_type_for directly into here
delfick Jan 8, 2023
14439a1
Type: make Type a subclass of Disassembled
delfick Jan 8, 2023
24225b2
Type: merge disassembled back into Type
delfick Jan 8, 2023
878283f
disassemble: simplify checkable_union
delfick Jan 8, 2023
0811974
type: pass around type cache everywhere
delfick Jan 8, 2023
be1f0ab
tools: update dev requirements
delfick Jan 28, 2023
c66f0bc
helpers: improve memoized property
delfick Feb 5, 2023
943cca4
disassemble: introduce common shortcuts
delfick Apr 24, 2023
d163559
disassemble: Adding owner to Field objects
delfick Apr 24, 2023
fd5c6f1
disassemble: Making sure fields_from_class doesn't choke when no sign…
delfick Apr 24, 2023
a00baa2
register: Only accept checking against type or Type
delfick Apr 24, 2023
45da507
disassemble: renaming origin_or_type to just origin
delfick Apr 24, 2023
0bb75c5
disassemble: return Type instances when finding generic subtypes
delfick Apr 24, 2023
5ccc63f
disassemble: more info for checkable instances
delfick Apr 24, 2023
1628bc1
disassemble: improving checking equality
delfick Apr 24, 2023
1fc5417
disassemble: Introducing skeleton for MRO class
delfick Jun 24, 2023
8d6a91d
mro: Ability to get all vars from type tree
delfick Aug 30, 2023
667306b
mro: ability to display Type and TypeTree
delfick Aug 30, 2023
c215dea
hints: move these tests below the helper they use
delfick Aug 31, 2023
600acad
hints: Ensure type cache is cleared after resolving types
delfick Aug 31, 2023
c9e0b27
mro: Ability to get fields
delfick Aug 31, 2023
f92b0c0
mro: Ability to determine subtype
delfick Aug 31, 2023
8cf35bf
sorting: Ability to sort type annotations
delfick Aug 31, 2023
2108f9d
score: ability to display the score
delfick Sep 1, 2023
cff4a20
disassemble: extracting InstanceCheck logic
delfick Sep 2, 2023
453b97c
matching: make func_from more efficiently match by descending complexity
delfick Sep 2, 2023
91a2e21
disassemble: remove need for typed_fields property
delfick Sep 2, 2023
d226645
structure: tests for creation
delfick Sep 2, 2023
998b70e
disassemble: moving the main logic into a folder
delfick Sep 2, 2023
76b7556
disassemble: moving more logic into separate folder
delfick Sep 2, 2023
1fe2789
dev: updating dev reqs
delfick Sep 2, 2023
dbcb3c6
packaging: convert to hatchling
delfick Sep 2, 2023
297f93b
docs: use sphinx-autobuild for local
delfick Sep 3, 2023
3ce0396
annotations: Clean up names for annotations
delfick Sep 2, 2023
a600838
creators: ensure value and want must be positional only
delfick Sep 3, 2023
c554780
creators: shuffling how the creator decorator works
delfick Sep 3, 2023
26558fb
docs: creators
delfick Sep 3, 2023
d6524cf
docs: memoized_property and strcs.standard
delfick Sep 3, 2023
40977ab
docs: register
delfick Sep 3, 2023
3db8652
docs: hints
delfick Sep 10, 2023
5535857
hints: move out of the disassemble package
delfick Sep 10, 2023
824cd3f
docs: creation
delfick Sep 10, 2023
09a0e9c
docs: InstanceCheck
delfick Sep 10, 2023
2f1904b
docs: MRO
delfick Sep 10, 2023
9101f68
disassemble: extract score and fields
delfick Sep 15, 2023
bef2f14
disassemble: no longer need generic_super
delfick Sep 15, 2023
4bf3db3
disassemble: extract TypeCache
delfick Sep 15, 2023
d885402
disassembled: Creating convenience imports
delfick Sep 15, 2023
1d109f3
imports: prefer accessing from modules
delfick Sep 15, 2023
334859d
disassemble: Make individual parts explicitly private
delfick Sep 15, 2023
542c9b4
docs: disassemble._extract
delfick Sep 17, 2023
8806db8
docs: disassembled._fields
delfick Sep 17, 2023
eb21c28
docs: score
delfick Sep 17, 2023
200fba3
docs: type cache
delfick Sep 17, 2023
5ffcbeb
docs: use reference roles
delfick Sep 17, 2023
51f385c
disassemble: simplify fields_from
delfick Sep 17, 2023
d407b73
docs: Type
delfick Sep 17, 2023
83ce310
docs: fix indentation in changelog
delfick Sep 17, 2023
24eec0f
disassemble: represent annotation values as a sequence
delfick Sep 17, 2023
3a55d42
deps: allow later versions of attrs/cattrs
delfick Sep 17, 2023
4d1c45a
tests: fix failure in CI
delfick Sep 17, 2023
336f0f5
docs: changelog 0.3.0
delfick Sep 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 14 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
on:
- workflow_dispatch

name: Release packages
name: Release Packages

jobs:
build:
Expand All @@ -15,39 +15,28 @@ jobs:
with:
python-version: "3.11"

- id: sdist
run: python setup.py sdist
- id: build
run: pip install hatch==1.6.3 && hatch build

- id: version
run: |
printf "::set-output name=version::%s\n" $(python -c "import runpy; print(runpy.run_path('strcs/version.py')['VERSION'])")
printf "::set-output name=versiondash::%s\n" $(python -c "import runpy; print(runpy.run_path('strcs/version.py')['VERSION'].replace('.', '-'))")
version=$(python -c "import runpy; print(runpy.run_path('strcs/version.py')['VERSION'])")
versiondash=$(python -c "import runpy; print(runpy.run_path('strcs/version.py')['VERSION'].replace('.', '-'))")

- id: package
run: >
printf "::set-output name=package::strcs-${{ steps.version.outputs.version}}.tar.gz"

- id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
echo "version=$version" >> $GITHUB_OUTPUT
echo "versiondash=$versiondash" >> $GITHUB_OUTPUT

- id: create-release
uses: softprops/action-gh-release@v1
with:
tag_name: "release-${{ steps.version.outputs.version }}"
release_name: strcs ${{ steps.version.outputs.version }}
name: strcs ${{ steps.version.outputs.version }}
body: "https://strcs.readthedocs.io/en/latest/strcs/changelog.html#release-${{ steps.version.outputs.versiondash }}"
tag_name: "release-${{ steps.version.outputs.version }}"
token: ${{ secrets.GITHUB_TOKEN }}
fail_on_unmatched_files: true
draft: false
prerelease: false

- id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: "dist/${{ steps.package.outputs.package }}"
asset_name: ${{ steps.package.outputs.package }}
asset_content_type: application/tar+gzip
files: "dist/*"

- uses: pypa/gh-action-pypi-publish@v1.4.1
with:
Expand Down
14 changes: 13 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
extensions = ["sphinx.ext.autodoc", "sphinx_rtd_theme"]
import pathlib
import sys

sys.path.append(str(pathlib.Path(__file__).parent))

extensions = [
"sphinx.ext.autodoc",
"sphinx_rtd_theme",
"sphinx_toolbox.more_autodoc.autoprotocol",
"strcs_sphinx_ext",
]

html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
Expand All @@ -16,3 +26,5 @@

version = "0.1"
release = "0.1"

autodoc_preserve_defaults = True
3 changes: 2 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
strcs/features/meta
strcs/features/creators
strcs/features/annotations
strcs/features/disassembled

.. _strcs:

Expand Down Expand Up @@ -132,7 +133,7 @@ Here is a contrived example that shows a couple features. Read the

# The meta object may also be given a custom cattrs Converter. In this case
# we do not and a blank one is made for us.
meta = strcs.Meta({"renderer": renderer})
meta = reg.meta({"renderer": renderer})

# First pass has no excluded authors
images = reg.create(Images, configuration, meta=meta)
Expand Down
32 changes: 22 additions & 10 deletions docs/strcs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,44 @@
Changelog
---------

.. _release-0.3.0:

0.3.0 - TBD
* Introduced a number of helpers for introspecting type annotations
* Introduced new ``strcs.Type`` class for representing types and creators
now take these objects.
* Can now create and use creators for generics so that when using the register
to create an object, the filled type vars of the provided type are
understood and respected.
* Updated dependencies
* Converted packaging to hatchling

.. _release-0.2.0:

0.2.0 - 30 October 2022
* Renamed toggle for auto resolution of string annotations
* Fixed structuring object as a type
* Renamed toggle for auto resolution of string annotations
* Fixed structuring object as a type

.. _release-0.1.3:

0.1.3 - 29 October 2022
* Improved error messages from creators failing
* Improved error messages from creators failing

.. _release-0.1.2:

0.1.2 - 29 October 2022
* Added py.typed file to the distribution
* Removed need for the recursed option
* Added resolution of string type annotations on attrs/dataclass/normal
classes
* Added py.typed file to the distribution
* Removed need for the recursed option
* Added resolution of string type annotations on attrs/dataclass/normal
classes

.. _release-0.1.1:

0.1.1 - 26 September 2022
* Fix a bunch of typing problems
* Fix a bunch of typing problems

.. _release-0.1.0:

0.1.0 - 21 August 2022
* Initial release
* Note this code is not actively used by anything yet
* Initial release
* Note this code is not actively used by anything yet
117 changes: 9 additions & 108 deletions docs/strcs/features/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@
Annotations
===========

Python has a ``typing.Annotated`` since Python 3.9 that lets the developer attach
information to a type and ``strcs`` will understand these annotations to get
an object it uses to modify the meta and/or creator:
.. automodule:: strcs.annotations

.. code-block:: python

from attrs import define, asdict
import attrs
import typing as tp
import strcs

reg = strcs.CreateRegister()
creator = reg.make_decorator()


@define(frozen=True)
class MathsAnnotation(strcs.MergedAnnotation):
@attrs.define(frozen=True)
class MathsAnnotation(strcs.MergedMetaAnnotation):
addition: int | None = None
multiplication: int | None = None

Expand All @@ -27,12 +25,12 @@ an object it uses to modify the meta and/or creator:
return (value + addition) * multiplication


@define
@attrs.define
class Thing:
val: tp.Annotated[int, strcs.Ann(MathsAnnotation(addition=20), do_maths)]


@define
@attrs.define
class Holder:
once: Thing
twice: tp.Annotated[Thing, MathsAnnotation(multiplication=2)]
Expand All @@ -55,111 +53,14 @@ an object it uses to modify the meta and/or creator:

holder = reg.create(Holder, 33)
assert isinstance(holder, Holder)
assert asdict(holder) == {"once": {"val": 53}, "twice": {"val": 106}, "thrice": {"val": 159}}
assert attrs.asdict(holder) == {"once": {"val": 53}, "twice": {"val": 106}, "thrice": {"val": 159}}

.. note:: it is a good idea to set a default value when retrieving multiple values
from meta that have the same type. In the example above ``addition`` and
``multiplication`` are both ints and to force ``strcs`` to match by name a
default is specified. Otherwise if only addition or multiplication are in meta
then they will both be set to the value of the one that is found.

An annotation may either be an instance of ``strcs.Ann``, an instance of
``strcs.Annotation`` or a callable object. When a value is supplied that isn't
``strcs.Ann`` then one is created from that value.
.. autoclass:: strcs.Ann

So if an ``strcs.Annotation`` is provided then it will create
``strcs.Ann(meta=found)``, otherwise if the value is a callable then
``strcs.Ann(creator=found)``.

``strcs`` will use the ``adjusted_meta`` and ``adjusted_creator`` on the ``Ann``
object to find a new meta or new creator to use for that field.

New Meta will persist for any transformation that occurs below that field, but
a new creator will only be used for that field.

When providing a meta object to ``Ann``, there are two default strategies to
choose from: ``strcs.Annotation`` and ``strcs.MergedAnnotation``. A custom
strategy may be provided by implementing ``adjusted_meta`` on the ``Annotation``.

``strcs.Annotation``
Will return a cloned meta containing ``__call_defined_annotation__`` so that
the creator may retrieve the entire ``Annotation`` using the type of that
annotation.

For example:

.. code-block:: python

@define(frozen=True)
class MyAnnotation(strcs.Annotation):
one: int
two: int

@creator(MyKls)
def create_mykls(value: object, /, annotation: MyAnnotation) -> dict | None:
if not isinstance(value, str):
return None
return {"key": f"{value}-{annotation.one}-{annotation.two}"}

``strcs.MergedAnnotation``
Will add the keys from the annotation into the meta. This would mean
the above example becomes:

.. code-block:: python

@define(frozen=True)
class MyAnnotation(strcs.MergedAnnotation):
one: int
two: int

@creator(MyKls)
def create_mykls(value: object, /, one: int = 0, two: int = 0) -> dict | None:
if not isinstance(value, str):
return None
return {"key": f"{value}-{one}-{two}"}

Optional keys are not added to meta if they are not set:

.. code-block:: python

@define(frozen=True)
class MyAnnotation(strcs.MergedAnnotation):
one: int | None = None
two: int | None = None

@creator(MyKls)
def create_mykls(value: object, /, one: int = 0, two: int = 0) -> dict | None:
if not isinstance(value, str):
return None
# one and two will be zero each instead of None when MyKls
# is annotated with either of those not set respectively
return {"key": f"{value}-{one}-{two}"}

Injecting data from meta
------------------------

Sometimes it is desirable to set a value straight from what is found in the Meta
object and this may be achieved via ``strcs.FromMeta``:

.. code-block:: python

from attrs import define
import typing as tp
import strcs

reg = strcs.CreateRegister()
creator = reg.make_decorator()


class Magic:
def incantation(self) -> str:
return "abracadabra!"


@define
class Wizard:
magic: tp.Annotated[Magic, strcs.FromMeta("magic")]


wizard = reg.create(Wizard, meta=strcs.Meta({"magic": Magic()}))
assert wizard.magic.incantation() == "abracadabra!"
.. autoclass:: strcs.FromMeta
Loading