diff --git a/README.md b/README.md index 61e4263..ec33ed0 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,12 @@ def public_function( a: str, /, # support for positional-only arguments # support for type unions - b: Union[int, float], # or from Python 3.10 `int | float` + b: int | float, # or Python 3.9 `Union[int, float]` # validate type of container items - c: dict[str, Union[int, float]], # dict[str, int | float] + c: dict[str, int | float], # dict[str, Union[int, float]] # coerce input to a specific type d: Annotated[ - Union[int, float, str], # int | float | str + int | float | str, # Union[int, float, str] Coerce(int) ], # parse input with reference to earlier inputs... @@ -34,7 +34,7 @@ def public_function( ], # coerce and parse input... f: Annotated[ - Union[str, int], # str | int + str | int, # Union[str, int] Coerce(str), Parser(lambda name, obj, _: obj + f"_{name}") ], @@ -43,21 +43,21 @@ def public_function( # validate input is subclass of a specific class (or that class itself) ... h: type[int], # ... or of specific classes... - i: type[Union[int, str]], # type[int | str] + i: type[int | str], # type[Union[int, str]] # support for packing extra arguments if required, can be optionally typed... *args: Annotated[ - Union[int, float, str], # int | float | str + int | float | str, # Union[int, float, str] Coerce(int) ], # support for optional types - j: Optional[str], # str | None + j: str | None, # Optional[str] # define default values dynamically with reference to earlier inputs k: Annotated[ - Optional[float], # float | None + float | None, # Optional[float] Parser(lambda _, obj, params: params["b"] if obj is None else obj) ] = None, # support for packing excess kwargs if required, can be optionally typed... - # **kwargs: Union[int, float] + # **kwargs: int | float # Union[int, float] ) -> dict[str, Any]: return {"a":a, "b":b, "c":c, "d":d, "e":e, "f":f, "g":g, "h":h, "i":i, "args":args, "j":j, "k":k} @@ -86,9 +86,9 @@ returns: 'd': 3, 'e': 'four_e_zero', 'f': '5_f', - 'g': , - 'h': , - 'i': , + 'g': str, + 'h': bool, + 'i': int, 'args': (10, 20), 'j': 'keyword_arg_j', 'k': 1.0} @@ -171,7 +171,7 @@ class ADataclass: a: str b: Annotated[ - Union[str, int], + str | int, # Union[str, int] Coerce(str), Parser(lambda name, obj, params: obj + f" {name} {params['a']}") ] diff --git a/docs/readme_examples.ipynb b/docs/readme_examples.ipynb new file mode 100644 index 0000000..5aca10f --- /dev/null +++ b/docs/readme_examples.ipynb @@ -0,0 +1,275 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "00d19d9e-5259-49a3-ab98-4d09c87d80b3", + "metadata": {}, + "source": [ + "# README example" + ] + }, + { + "cell_type": "markdown", + "id": "ded58180-f6eb-4830-a939-3399f7bb1bf6", + "metadata": {}, + "source": [ + "#### As at **2026/06/03**" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a4148dbd-b3fe-4c0d-bbcd-b76b38b2d157", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a': 'zero',\n", + " 'b': 1.0,\n", + " 'c': {'two': 2},\n", + " 'd': 3,\n", + " 'e': 'four_e_zero',\n", + " 'f': '5_f',\n", + " 'g': str,\n", + " 'h': bool,\n", + " 'i': int,\n", + " 'args': (10, 20),\n", + " 'j': 'keyword_arg_j',\n", + " 'k': 1.0}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from valimp import parse, Parser, Coerce\n", + "from typing import Annotated, Union, Optional, Any\n", + "\n", + "@parse # add the `valimp.parse`` decorator to a public function or method\n", + "def public_function(\n", + " # validate against built-in or custom types\n", + " a: str,\n", + " /, # support for positional-only arguments\n", + " # support for type unions\n", + " b: int | float, # or Python 3.9 `Union[int, float]`\n", + " # validate type of container items\n", + " c: dict[str, int | float], # dict[str, Union[int, float]]\n", + " # coerce input to a specific type\n", + " d: Annotated[\n", + " int | float | str, # Union[int, float, str]\n", + " Coerce(int)\n", + " ],\n", + " # parse input with reference to earlier inputs...\n", + " e: Annotated[\n", + " str,\n", + " Parser(lambda name, obj, params: obj + f\"_{name}_{params['a']}\")\n", + " ],\n", + " # coerce and parse input...\n", + " f: Annotated[\n", + " str | int, # Union[str, int]\n", + " Coerce(str),\n", + " Parser(lambda name, obj, _: obj + f\"_{name}\")\n", + " ],\n", + " # validate input is a class (rather than an instance)\n", + " g: type,\n", + " # validate input is subclass of a specific class (or that class itself) ...\n", + " h: type[int],\n", + " # ... or of specific classes...\n", + " i: type[int | str], # type[Union[int, str]]\n", + " # support for packing extra arguments if required, can be optionally typed...\n", + " *args: Annotated[\n", + " int | float | str, # Union[int, float, str]\n", + " Coerce(int)\n", + " ],\n", + " # support for optional types\n", + " j: str | None, # Optional[str]\n", + " # define default values dynamically with reference to earlier inputs\n", + " k: Annotated[\n", + " float | None, # Optional[float]\n", + " Parser(lambda _, obj, params: params[\"b\"] if obj is None else obj)\n", + " ] = None,\n", + " # support for packing excess kwargs if required, can be optionally typed...\n", + " # **kwargs: int | float # Union[int, float]\n", + ") -> dict[str, Any]:\n", + " return {\"a\":a, \"b\":b, \"c\":c, \"d\":d, \"e\":e, \"f\":f, \"g\":g, \"h\":h, \"i\":i, \"args\":args, \"j\":j, \"k\":k}\n", + "\n", + "public_function(\n", + " # NB 'a' must be passed positionally, 'b' through 'i' can be passed positionally\n", + " \"zero\", # a\n", + " 1.0, # b\n", + " {\"two\": 2}, # c\n", + " 3.3, # d, will be coerced from float to int, i.e. to 3\n", + " \"four\", # e, will be parsed to \"four_e_zero\"\n", + " 5, # f, will be coerced to str and then parsed to \"5_f\"\n", + " str, # g\n", + " bool, # h, a subclass of int\n", + " int, # i, one of the subscripted classes\n", + " \"10\", # extra arg, will be coerced to int and packed\n", + " 20, # extra arg, will be packed\n", + " j=\"keyword_arg_j\",\n", + " # k, not passed, will be assigned dynamically as parameter b (i.e. 1.0)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "adf51369-0f5d-47f4-a476-8ade7ebb951c", + "metadata": {}, + "outputs": [], + "source": [ + "public_function(\n", + " [\"not a string\"], # INVALID\n", + " b=\"not an int or a float\", # INVALID\n", + " c={2: \"two\"}, # INVALID, key not a str and value not an int or float\n", + " d=3.2, # valid input\n", + " e=\"valid input\",\n", + " f=5.0, # INVALID, not a str or an int\n", + " g=str, # valid input\n", + " h=str, # INVALID, str is not int or a subclass of int\n", + " i=bool, # valid input\n", + " j=\"valid input\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "b58808cf-5a46-46fc-bf0f-8edcf5687d57", + "metadata": {}, + "source": [ + "```\n", + "InputsError: The following inputs to 'public_function' do not conform with the corresponding type annotation:\n", + "\n", + "a\n", + "\tTakes type although received '['not a string']' of type .\n", + "\n", + "b\n", + "\tTakes input that conforms with <(, )> although received 'not an int or a float' of type .\n", + "\n", + "c\n", + "\tTakes type with keys that conform to the first argument and values that conform to the second argument of , although the received dictionary contains an item with key '2' of type and value 'two' of type .\n", + "\n", + "f\n", + "\tTakes input that conforms with <(, )> although received '5.0' of type .\n", + "\n", + "h\n", + "\tTakes a subclass of although received ''.\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b848919c-d7f3-46af-ab83-4e8b95e4bc30", + "metadata": {}, + "outputs": [], + "source": [ + "public_function(\n", + " \"zero\",\n", + " \"invalid input\", # invalid (not int or float), included in errors\n", + " {\"two\": 2},\n", + " 3.2,\n", + " # no argument passed for required positional args 'e', 'f', 'g', 'h' and 'i'\n", + " a=\"a again\", # 'a' is positional-only: cannot be passed as a kwarg unless sig has **kwargs\n", + " c={\"three\": 3}, # passing multiple values for 'c'\n", + " not_a_kwarg=\"not a kwarg\", # including an unexpected kwarg\n", + " # no argument passed for required keyword arg 'j'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "7291a496-2716-43bd-b1e3-9a13e54a7dd2", + "metadata": {}, + "source": [ + "```\n", + "InputsError: Inputs to 'public_function' do not conform with the function signature:\n", + "\n", + "Got multiple values for argument: 'c'.\n", + "\n", + "Got unexpected keyword argument: 'not_a_kwarg'.\n", + "\n", + "Got positional-only argument as keyword argument (and signature makes no provision for **kwargs that would otherwise receive it): 'a'.\n", + "\n", + "Missing 5 positional arguments: 'e', 'f', 'g', 'h' and 'i'.\n", + "\n", + "Missing 1 keyword-only argument: 'j'.\n", + "\n", + "The following inputs to 'public_function' do not conform with the corresponding type annotation:\n", + "\n", + "b\n", + "\tTakes input that conforms with <(, )> although received 'invalid input' of type .\n", + " ```" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "c21342f6-d9de-4e8f-9058-9833027b45c0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'a': \"I'm a and will appear at the end of b\",\n", + " 'b': \"33 b I'm a and will appear at the end of b\"}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from valimp import parse_cls\n", + "import dataclasses\n", + "\n", + "@parse_cls # place valimp decorator above the dataclass decorator\n", + "@dataclasses.dataclass\n", + "class ADataclass:\n", + " \n", + " a: str\n", + " b: Annotated[\n", + " str | int, # Union[str, int]\n", + " Coerce(str),\n", + " Parser(lambda name, obj, params: obj + f\" {name} {params['a']}\")\n", + " ]\n", + "\n", + "rtrn = ADataclass(\"I'm a and will appear at the end of b\", 33)\n", + "dataclasses.asdict(rtrn)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "946003b7-29fe-4ee9-8c2f-3ec2e432ed93", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "inv_working_313", + "language": "python", + "name": "inv_working_313" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}