From 95f3bb566b79b6d7e3371c14d18d8336b462ad71 Mon Sep 17 00:00:00 2001 From: nstarman Date: Wed, 4 Dec 2024 15:11:59 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20style:=20use=20pycon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 +++++++++++++++++- conftest.py | 21 ++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c73a07c..05700ae 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ interface for object manipulation. 🕶️ For example, -```pycon +```python from dataclassish import replace # New object, replacing select fields d1 = {"a": 1, "b": 2.0, "c": "3"} @@ -79,6 +79,7 @@ Point(x=1.0, y=2.0) >>> p2 = replace(p, x=3.0) >>> p2 Point(x=3.0, y=2.0) + ``` #### Replacing a `dict` @@ -104,6 +105,7 @@ objects, but with `dataclassish` it's easy! ... except ValueError as e: ... print(e) invalid keys {'z'}. + ``` #### Replacing via the `__replace__` Method @@ -167,6 +169,7 @@ MyClass(a=1,b=2,c=3) >>> obj2 = replace(obj, c=4.0) >>> obj2 MyClass(a=1,b=2,c=4.0) + ``` ### Nested Replacement @@ -177,6 +180,7 @@ Point objects: ```pycon >>> p = {"a": Point(1, 2), "b": Point(3, 4), "c": Point(5, 6)} + ``` With `replace` the nested structure can be updated via: @@ -184,6 +188,7 @@ With `replace` the nested structure can be updated via: ```pycon >>> replace(p, {"a": {"x": 1.5}, "b": {"y": 4.5}, "c": {"x": 5.5}}) {'a': Point(x=1.5, y=2), 'b': Point(x=3, y=4.5), 'c': Point(x=5.5, y=6)} + ``` In contrast in pure Python this would be very challenging. Expand the example @@ -201,6 +206,7 @@ This is a bad approach, updating the frozen dataclasses in place: >>> object.__setattr__(newp["a"], "x", 1.5) >>> object.__setattr__(newp["b"], "y", 4.5) >>> object.__setattr__(newp["c"], "x", 5.5) + ``` A better way might be to create an entirely new object! @@ -209,6 +215,7 @@ A better way might be to create an entirely new object! >>> newp = {"a": Point(1.5, p["a"].y), ... "b": Point(p["b"].x, 4.5), ... "c": Point(5.5, p["c"].y)} + ``` This isn't so good either. @@ -242,6 +249,7 @@ Collection(a=Object(x=1.0, y=2.0), b=Object(x=3.0, y=4.0)) >>> replace(p, {"a": {"x": 5.0}, "b": {"y": 6.0}}) Collection(a=Object(x=5.0, y=2.0), b=Object(x=3.0, y=6.0)) + ``` With `replace` this remains a one-liner. Replace pieces of any structure, @@ -255,6 +263,7 @@ To disambiguate dictionary fields from nested structures, use the `F` marker. >>> replace(p, {"a": {"x": F({"thing": 5.0})}}) Collection(a=Object(x={'thing': 5.0}, y=2.0), b=Object(x=3.0, y=4.0)) + ``` ### dataclass tools @@ -276,6 +285,7 @@ these functions. >>> astuple(p) (1.0, 2.0) + ``` `dataclassish` extends these functions to [`dict`][dict-link]'s: @@ -291,6 +301,7 @@ these functions. >>> astuple(p) (1, 2.0) + ``` Support for custom objects can be implemented similarly to `replace`. @@ -321,6 +332,7 @@ utilities. >>> field_items(p) (('x', 1.0), ('y', 2.0)) + ``` These functions work on any object that has been registered in, not just @@ -340,6 +352,7 @@ dict_values([1, 2.0]) >>> field_items(p) dict_items([('x', 1), ('y', 2.0)]) + ``` ### Converters @@ -380,6 +393,7 @@ None >>> obj = Class2("1") >>> obj.attr 1.0 + ``` ### Flags @@ -395,6 +409,7 @@ consideration by the functions in `dataclassish`. >>> from dataclassish import flags >>> flags.__all__ ['FlagConstructionError', 'AbstractFlag', 'NoFlag'] + ``` Where `AbstractFlag` is the base class for flags, and `NoFlag` is a flag that @@ -407,6 +422,7 @@ As a quick example, we'll show how to use `NoFlag`. >>> from dataclassish import field_keys >>> tuple(field_keys(flags.NoFlag, p)) ('x', 'y') + ``` ## Citation diff --git a/conftest.py b/conftest.py index 8af1483..75a7fb4 100644 --- a/conftest.py +++ b/conftest.py @@ -5,23 +5,30 @@ from sybil import Document, Region, Sybil from sybil.parsers.myst import ( - DocTestDirectiveParser as MarkdownDocTestDirectiveParser, - PythonCodeBlockParser as MarkdownPythonCodeBlockParser, - SkipParser as MarkdownSkipParser, + DocTestDirectiveParser as MystDocTestDirectiveParser, + PythonCodeBlockParser as MystPythonCodeBlockParser, + SkipParser as MystSkipParser, ) from sybil.parsers.rest import DocTestParser as ReSTDocTestParser +from sybil.sybil import SybilCollection optionflags = ELLIPSIS | NORMALIZE_WHITESPACE parsers: Sequence[Callable[[Document], Iterable[Region]]] = [ - MarkdownDocTestDirectiveParser(optionflags=optionflags), - MarkdownPythonCodeBlockParser(doctest_optionflags=optionflags), - MarkdownSkipParser(), + MystDocTestDirectiveParser(optionflags=optionflags), + MystPythonCodeBlockParser(doctest_optionflags=optionflags), + MystSkipParser(), ] +# TODO: figure out native parser for `pycon` that doesn't require a new line at +# the end. +readme = Sybil( + parsers=[ReSTDocTestParser(optionflags=optionflags)], + patterns=["README.md"], +) docs = Sybil(parsers=parsers, patterns=["*.md"]) python = Sybil( parsers=[ReSTDocTestParser(optionflags=optionflags), *parsers], patterns=["*.py"] ) -pytest_collect_file = (docs + python).pytest() +pytest_collect_file = SybilCollection([docs, readme, python]).pytest()