asyncapi-python 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/PKG-INFO +9 -2
  2. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/README.md +7 -0
  3. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/pyproject.toml +2 -2
  4. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/consumer.py +2 -2
  5. asyncapi_python-0.1.4/src/asyncapi_python_codegen/document/utils.py +101 -0
  6. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/amqp/generate.py +24 -9
  7. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/LICENSE +0 -0
  8. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/__init__.py +0 -0
  9. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/__init__.py +0 -0
  10. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/base_application.py +0 -0
  11. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/connection.py +0 -0
  12. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/message_handler.py +0 -0
  13. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/message_handler_params.py +0 -0
  14. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/producer.py +0 -0
  15. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/amqp/utils.py +0 -0
  16. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python/py.typed +0 -0
  17. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/__init__.py +0 -0
  18. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/__init__.py +0 -0
  19. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/base.py +0 -0
  20. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/bindings/__init__.py +0 -0
  21. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/bindings/amqp.py +0 -0
  22. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/components.py +0 -0
  23. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/document.py +0 -0
  24. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/document_context.py +0 -0
  25. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/document/ref.py +0 -0
  26. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/__init__.py +0 -0
  27. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/amqp/__init__.py +0 -0
  28. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/amqp/templates/__init__.py.j2 +0 -0
  29. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/amqp/templates/application.py.j2 +0 -0
  30. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/generators/amqp/utils.py +0 -0
  31. {asyncapi_python-0.1.2 → asyncapi_python-0.1.4}/src/asyncapi_python_codegen/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: asyncapi-python
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: Easily generate type-safe and async Python applications from AsyncAPI 3 specifications.
5
5
  License: Apache-2.0
6
6
  Author: Yaroslav Petrov
@@ -16,7 +16,7 @@ Provides-Extra: amqp
16
16
  Provides-Extra: codegen
17
17
  Requires-Dist: aio-pika ; extra == "amqp"
18
18
  Requires-Dist: black ; extra == "codegen"
19
- Requires-Dist: datamodel-code-generator[http] (>=0.26.1,<0.27.0) ; extra == "codegen"
19
+ Requires-Dist: datamodel-code-generator[http] (>=0.26.4,<0.27.0) ; extra == "codegen"
20
20
  Requires-Dist: jinja2 (>=3.1.4,<4.0.0) ; extra == "codegen"
21
21
  Requires-Dist: pydantic (>=2)
22
22
  Requires-Dist: pytz
@@ -25,6 +25,13 @@ Requires-Dist: typer[all] (>=0.12.5,<0.13.0) ; extra == "codegen"
25
25
  Description-Content-Type: text/markdown
26
26
 
27
27
  # AsyncAPI Python Code Generator
28
+ > [!IMPORTANT]
29
+ > Although commits to dev branch might seem infrequent, the project is under active development **as of December 2024**.
30
+ >
31
+ > We currently produce only those changes that are required to satisfy our personal use cases.
32
+ >
33
+ > The number of commits will grow as we see increase in popularity of it, so do not hesitate
34
+ > to star this repo, open issues, and pull requests.
28
35
 
29
36
  [Link to this github repository](https://github.com/G-USI/asyncapi-python)
30
37
 
@@ -1,4 +1,11 @@
1
1
  # AsyncAPI Python Code Generator
2
+ > [!IMPORTANT]
3
+ > Although commits to dev branch might seem infrequent, the project is under active development **as of December 2024**.
4
+ >
5
+ > We currently produce only those changes that are required to satisfy our personal use cases.
6
+ >
7
+ > The number of commits will grow as we see increase in popularity of it, so do not hesitate
8
+ > to star this repo, open issues, and pull requests.
2
9
 
3
10
  [Link to this github repository](https://github.com/G-USI/asyncapi-python)
4
11
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "asyncapi-python"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  license = "Apache-2.0"
5
5
  description = "Easily generate type-safe and async Python applications from AsyncAPI 3 specifications."
6
6
  authors = ["Yaroslav Petrov <yaroslav.v.petrov@gmail.com>"]
@@ -32,7 +32,7 @@ jinja2 = { version = "^3.1.4", optional = true }
32
32
  typer = { extras = ["all"], version = "^0.12.5", optional = true }
33
33
  datamodel-code-generator = { extras = [
34
34
  "http",
35
- ], version = "^0.26.1", optional = true }
35
+ ], version = "^0.26.4", optional = true }
36
36
  aio-pika = { version = "*", optional = true }
37
37
  pyyaml = { version = "*", optional = true }
38
38
  black = { version = "*", optional = true }
@@ -68,7 +68,7 @@ class Consumer:
68
68
  name=params.root.name,
69
69
  callback=callback,
70
70
  decode_message=lambda x: decode_message(
71
- x, union_model(input_types)
71
+ x, union_model(tuple(input_types))
72
72
  ).root,
73
73
  )
74
74
  else:
@@ -78,7 +78,7 @@ class Consumer:
78
78
  reply_callback=self._reply_callback,
79
79
  encode_message=encode_message,
80
80
  decode_message=lambda x: decode_message(
81
- x, union_model(input_types)
81
+ x, union_model(tuple(input_types))
82
82
  ).root,
83
83
  )
84
84
  self._handlers[params] = handler
@@ -0,0 +1,101 @@
1
+ # Copyright 2024 Yaroslav Petrov <yaroslav.v.petrov@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pathlib import Path
16
+ from typing import Any, Union
17
+ import yaml
18
+
19
+ from .document_context import set_current_doc_path
20
+ from .ref import Ref
21
+ from collections import defaultdict
22
+
23
+
24
+ Reference = Union[None, tuple[Path, tuple[str, ...]]]
25
+ """A reference type"""
26
+
27
+ ReferenceCounter = defaultdict[Reference, set[Reference]]
28
+ """A reference counter"""
29
+
30
+
31
+ def populate_jsonschema_defs(schema: Any) -> Any:
32
+ """Given a $defs element of the JsonSchema
33
+ 1. Constructs back references map for all links
34
+ 2. Populates types by copying its body into parent $def (if there is only one reference)
35
+ 3. Adds a new $defs object (if there is more than one reference), and rewrites $refs
36
+ 4. Returns a huge jsonschema $defs object containing all structs that have been referenced by the structs
37
+ from the original schema
38
+ """
39
+ counter: ReferenceCounter = defaultdict(lambda: set())
40
+ shared_schemas: dict[str, Any] = {}
41
+ _count_references(schema, None, counter)
42
+ res = _populate_jsonschema_recur(schema, counter, shared_schemas)
43
+ return {**res, **shared_schemas}
44
+
45
+
46
+ def _count_references(schema: Any, this: Reference, counter: ReferenceCounter):
47
+ """Recursively constructs back references within the JsonSchema"""
48
+ if not isinstance(schema, dict):
49
+ return
50
+
51
+ if "$ref" in schema:
52
+ ref: Ref[Any] = Ref.model_validate(schema)
53
+ with set_current_doc_path(ref.filepath):
54
+ ref = ref.flatten()
55
+ with ref.filepath.open() as f:
56
+ doc = yaml.safe_load(f)
57
+ for p in ref.doc_path:
58
+ doc = doc[p]
59
+ child = (ref.filepath, ref.doc_path)
60
+ counter[child].add(this)
61
+ with set_current_doc_path(ref.filepath):
62
+ return _count_references(doc, child, counter)
63
+
64
+ for v in schema.values():
65
+ _count_references(v, this, counter)
66
+
67
+
68
+ def _populate_jsonschema_recur(
69
+ schema: Any,
70
+ counter: ReferenceCounter,
71
+ shared_schemas: dict[str, Any],
72
+ ignore_shared: bool = False,
73
+ ) -> Any:
74
+ """Recursively populates JsonSchema $defs object"""
75
+ if not isinstance(schema, dict):
76
+ return schema
77
+
78
+ if "$ref" in schema:
79
+ ref: Ref[Any] = Ref.model_validate(schema)
80
+ with set_current_doc_path(ref.filepath):
81
+ ref = ref.flatten()
82
+
83
+ back_refs = counter[(ref.filepath, ref.doc_path)]
84
+ if len(back_refs) > 1 and not ignore_shared:
85
+ ref_struct_name = ref.doc_path[-1]
86
+ shared_schemas[ref_struct_name] = _populate_jsonschema_recur(
87
+ schema, counter, shared_schemas, True
88
+ )
89
+ return {"$ref": f"#/$defs/{ref_struct_name}"}
90
+
91
+ with ref.filepath.open() as f:
92
+ doc = yaml.safe_load(f)
93
+ for p in ref.doc_path:
94
+ doc = doc[p]
95
+ with set_current_doc_path(ref.filepath):
96
+ return _populate_jsonschema_recur(doc, counter, shared_schemas)
97
+
98
+ return {
99
+ k: _populate_jsonschema_recur(v, counter, shared_schemas)
100
+ for k, v in schema.items()
101
+ }
@@ -24,6 +24,8 @@ from asyncapi_python_codegen import document as d
24
24
  from itertools import chain
25
25
  from datamodel_code_generator.__main__ import main as datamodel_codegen
26
26
 
27
+ from asyncapi_python_codegen.document.utils import populate_jsonschema_defs
28
+
27
29
  from .utils import snake_case
28
30
 
29
31
 
@@ -47,7 +49,10 @@ def generate(
47
49
  doc.info.description,
48
50
  doc.info.version,
49
51
  ).items()
50
- } | {output_path / "models.py": generate_models(ops, doc.filepath.parent)}
52
+ } | {
53
+ output_path / "models.py": generate_models(ops, doc.filepath.parent),
54
+ output_path / "py.typed": "",
55
+ }
51
56
 
52
57
 
53
58
  def generate_application(
@@ -69,14 +74,16 @@ def generate_application(
69
74
  def generate_models(schemas: list[Operation], cwd: Path) -> str:
70
75
  inp = {
71
76
  "$schema": "http://json-schema.org/draft-07/schema#",
72
- "$defs": {
73
- type_name: {"$ref": type_schema}
74
- for s in schemas
75
- for type_name, type_schema in chain(
76
- zip(s["input_types"], s["input_schemas"]),
77
- zip(s["output_types"], s["output_schemas"]),
78
- )
79
- },
77
+ "$defs": populate_jsonschema_defs(
78
+ {
79
+ type_name: {"$ref": type_schema}
80
+ for s in schemas
81
+ for type_name, type_schema in chain(
82
+ zip(s["input_types"], s["input_schemas"]),
83
+ zip(s["output_types"], s["output_schemas"]),
84
+ )
85
+ }
86
+ ),
80
87
  }
81
88
 
82
89
  with tempfile.TemporaryDirectory() as dir:
@@ -88,6 +95,14 @@ def generate_models(schemas: list[Operation], cwd: Path) -> str:
88
95
  --output { str(models_path.absolute()) }
89
96
  --output-model-type pydantic_v2.BaseModel
90
97
  --input-file-type jsonschema
98
+ --reuse-model
99
+ --allow-extra-fields
100
+ --collapse-root-models
101
+ --target-python-version 3.9
102
+ --use-title-as-name
103
+ --capitalize-enum-members
104
+ --snake-case-field
105
+ --allow-population-by-field-name
91
106
  """.split()
92
107
 
93
108
  with schema_path.open("w") as schema:
File without changes