From 23dd42e6e4c56cee845390efefdf0317771e6819 Mon Sep 17 00:00:00 2001 From: Colin McDonnell Date: Thu, 20 Oct 2022 12:34:38 -0700 Subject: [PATCH] Add codegen docs (#380) Also drop pydantic target: It is not really a code generation target. Instead, new options are added: --skip-pydantic-validation and --no-skip-pydantic-validation. Also added help text in codegen CLI. Co-authored-by: Fantix King --- docs/api/codegen.rst | 96 +++++++++++++++++++ docs/index.rst | 1 + edgedb/codegen/cli.py | 36 ++++++- edgedb/codegen/generator.py | 3 +- .../generated_async_edgeql.py.assert | 2 +- .../select_scalar_edgeql.py.assert | 2 +- .../argnames/query_one_edgeql.py.assert | 2 +- .../generated_async_edgeql.py.assert | 2 +- .../object/link_prop_edgeql.py.assert | 2 +- .../object/select_object_edgeql.py.assert | 2 +- .../object/select_objects_edgeql.py.assert | 2 +- .../parpkg/select_args_edgeql.py.assert | 2 +- .../parpkg/subpkg/my_query_edgeql.py.assert | 2 +- .../scalar/select_scalar_edgeql.py.assert | 2 +- .../scalar/select_scalars_edgeql.py.assert | 2 +- tests/test_codegen.py | 2 + 16 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 docs/api/codegen.rst diff --git a/docs/api/codegen.rst b/docs/api/codegen.rst new file mode 100644 index 00000000..c62abccf --- /dev/null +++ b/docs/api/codegen.rst @@ -0,0 +1,96 @@ +.. _edgedb-python-codegen: + +=============== +Code Generation +=============== + +.. py:currentmodule:: edgedb + +The ``edgedb-python`` package exposes a command-line tool to generate +typesafe functions from ``*.edgeql`` files, using :py:mod:`dataclasses` for +objects primarily. + +.. code-block:: bash + + $ edgedb-py + +Or alternatively: + +.. code-block:: bash + + $ python -m edgedb.codegen + +Consider a simple query that lives in a file called ``get_number.edgeql``: + +.. code-block:: edgeql + + select $arg; + +Running the code generator will generate a new file called +``get_number_async_edgeql.py`` containing the following code (roughly): + +.. code-block:: python + + from __future__ import annotations + import edgedb + + + async def get_number( + client: edgedb.AsyncIOClient, + *, + arg: int, + ) -> int: + return await client.query_single( + """\ + select $arg\ + """, + arg=arg, + ) + +Target +~~~~~~ + +By default, the generated code uses an ``async`` API. The generator supports +additional targets via the ``--target`` flag. + +.. code-block:: bash + + $ edgedb-py --target async # generate async function (default) + $ edgedb-py --target blocking # generate blocking code + +The names of the generated files will differ accordingly: +``{query_filename}_{target}_edgeql.py``. + +Single-file mode +~~~~~~~~~~~~~~~~ + +It may be preferable to generate a single file containing all the generated +functions. This can be done by passing the ``--file`` flag. + +.. code-block:: bash + + $ edgedb-py --file + +This generates a single file called ``generated_{target}_edgeql.py`` in the +root of your project. + +Connection +~~~~~~~~~~ + +The ``edgedb-py`` command supports the same set of :ref:`connection options +` as the ``edgedb`` CLI. + +.. code-block:: + + -I, --instance + --dsn + --credentials-file + -H, --host + -P, --port + -d, --database + -u, --user + --password + --password-from-stdin + --tls-ca-file + --tls-security + diff --git a/docs/index.rst b/docs/index.rst index 41ca1684..1b04d26e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,4 +46,5 @@ and :ref:`asyncio ` implementations. api/asyncio_client api/blocking_client api/types + api/codegen api/advanced diff --git a/edgedb/codegen/cli.py b/edgedb/codegen/cli.py index 88ae916a..1a229bae 100644 --- a/edgedb/codegen/cli.py +++ b/edgedb/codegen/cli.py @@ -18,6 +18,7 @@ import argparse +import sys from . import generator @@ -39,15 +40,44 @@ "--tls-security", choices=["default", "strict", "no_host_verification", "insecure"], ) -parser.add_argument("--file", action="store_true") +parser.add_argument( + "--file", + action="store_true", + help="Generate a single file instead of one per .edgeql file.", +) parser.add_argument( "--target", - choices=["blocking", "async", "pydantic"], + choices=["blocking", "async"], nargs="*", - default=["async", "pydantic"], + default=["async"], + help="Choose one or more targets to generate code (default is async)." ) +if sys.version_info[:2] >= (3, 9): + parser.add_argument( + "--skip-pydantic-validation", + action=argparse.BooleanOptionalAction, + default=argparse.SUPPRESS, # override the builtin help for default + help="Add a mixin to generated dataclasses " + "to skip Pydantic validation (default is to add the mixin).", + ) +else: + parser.add_argument( + "--skip-pydantic-validation", + action="store_true", + default=True, + ) + parser.add_argument( + "--no-skip-pydantic-validation", + dest="skip_pydantic_validation", + action="store_false", + default=False, + help="Add a mixin to generated dataclasses " + "to skip Pydantic validation (default is to add the mixin).", + ) def main(): args = parser.parse_args() + if not hasattr(args, "skip_pydantic_validation"): + args.skip_pydantic_validation = True generator.Generator(args).run() diff --git a/edgedb/codegen/generator.py b/edgedb/codegen/generator.py index 7620ed94..07e29033 100644 --- a/edgedb/codegen/generator.py +++ b/edgedb/codegen/generator.py @@ -122,6 +122,7 @@ class Generator: def __init__(self, args: argparse.Namespace): self._default_module = "default" self._targets = args.target + self._skip_pydantic_validation = args.skip_pydantic_validation self._async = False try: self._project_dir = pathlib.Path( @@ -407,7 +408,7 @@ def _generate_code( buf = io.StringIO() self._imports.add("dataclasses") print("@dataclasses.dataclass", file=buf) - if "pydantic" in self._targets: + if self._skip_pydantic_validation: print(f"class {rv}(NoPydanticValidation):", file=buf) self._use_pydantic = True else: diff --git a/tests/codegen/test-project1/generated_async_edgeql.py.assert b/tests/codegen/test-project1/generated_async_edgeql.py.assert index 3f10ec50..9a25de46 100644 --- a/tests/codegen/test-project1/generated_async_edgeql.py.assert +++ b/tests/codegen/test-project1/generated_async_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'select_scalar.edgeql' WITH: -# $ edgedb-py --target async --file +# $ edgedb-py --target async --file --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project1/select_scalar_edgeql.py.assert b/tests/codegen/test-project1/select_scalar_edgeql.py.assert index 52de1a44..227c30d2 100644 --- a/tests/codegen/test-project1/select_scalar_edgeql.py.assert +++ b/tests/codegen/test-project1/select_scalar_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'select_scalar.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/argnames/query_one_edgeql.py.assert b/tests/codegen/test-project2/argnames/query_one_edgeql.py.assert index d8a944a8..3e874faa 100644 --- a/tests/codegen/test-project2/argnames/query_one_edgeql.py.assert +++ b/tests/codegen/test-project2/argnames/query_one_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'argnames/query_one.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/generated_async_edgeql.py.assert b/tests/codegen/test-project2/generated_async_edgeql.py.assert index 7b7bc3dd..2dfbabba 100644 --- a/tests/codegen/test-project2/generated_async_edgeql.py.assert +++ b/tests/codegen/test-project2/generated_async_edgeql.py.assert @@ -8,7 +8,7 @@ # 'scalar/select_scalar.edgeql' # 'scalar/select_scalars.edgeql' # WITH: -# $ edgedb-py --target async --file +# $ edgedb-py --target async --file --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/object/link_prop_edgeql.py.assert b/tests/codegen/test-project2/object/link_prop_edgeql.py.assert index f2f56514..0d7de44e 100644 --- a/tests/codegen/test-project2/object/link_prop_edgeql.py.assert +++ b/tests/codegen/test-project2/object/link_prop_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'object/link_prop.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/object/select_object_edgeql.py.assert b/tests/codegen/test-project2/object/select_object_edgeql.py.assert index 9bda7ddb..3786486d 100644 --- a/tests/codegen/test-project2/object/select_object_edgeql.py.assert +++ b/tests/codegen/test-project2/object/select_object_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'object/select_object.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/object/select_objects_edgeql.py.assert b/tests/codegen/test-project2/object/select_objects_edgeql.py.assert index a1d1d483..1c6cd231 100644 --- a/tests/codegen/test-project2/object/select_objects_edgeql.py.assert +++ b/tests/codegen/test-project2/object/select_objects_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'object/select_objects.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/parpkg/select_args_edgeql.py.assert b/tests/codegen/test-project2/parpkg/select_args_edgeql.py.assert index 3176dfd5..b5e961b0 100644 --- a/tests/codegen/test-project2/parpkg/select_args_edgeql.py.assert +++ b/tests/codegen/test-project2/parpkg/select_args_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'parpkg/select_args.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/parpkg/subpkg/my_query_edgeql.py.assert b/tests/codegen/test-project2/parpkg/subpkg/my_query_edgeql.py.assert index f0d22935..1506b2af 100644 --- a/tests/codegen/test-project2/parpkg/subpkg/my_query_edgeql.py.assert +++ b/tests/codegen/test-project2/parpkg/subpkg/my_query_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'parpkg/subpkg/my_query.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/scalar/select_scalar_edgeql.py.assert b/tests/codegen/test-project2/scalar/select_scalar_edgeql.py.assert index 673b004f..e2c7abe5 100644 --- a/tests/codegen/test-project2/scalar/select_scalar_edgeql.py.assert +++ b/tests/codegen/test-project2/scalar/select_scalar_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'scalar/select_scalar.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/codegen/test-project2/scalar/select_scalars_edgeql.py.assert b/tests/codegen/test-project2/scalar/select_scalars_edgeql.py.assert index 62e138f1..cfedc1ce 100644 --- a/tests/codegen/test-project2/scalar/select_scalars_edgeql.py.assert +++ b/tests/codegen/test-project2/scalar/select_scalars_edgeql.py.assert @@ -1,5 +1,5 @@ # AUTOGENERATED FROM 'scalar/select_scalars.edgeql' WITH: -# $ edgedb-py --target blocking +# $ edgedb-py --target blocking --no-skip-pydantic-validation from __future__ import annotations diff --git a/tests/test_codegen.py b/tests/test_codegen.py index c524a8d1..cdceb130 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -72,6 +72,7 @@ async def run(*args, extra_env=None): "edgedb-py", "--target", "blocking", + "--no-skip-pydantic-validation", extra_env={"EDGEDB_PYTHON_CODEGEN_PY_VER": "3.9.2"}, ) await run( @@ -79,6 +80,7 @@ async def run(*args, extra_env=None): "--target", "async", "--file", + "--no-skip-pydantic-validation", extra_env={"EDGEDB_PYTHON_CODEGEN_PY_VER": "3.10.3"}, )