asyncapi-python 0.2.2__tar.gz → 0.2.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 (39) hide show
  1. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/PKG-INFO +98 -1
  2. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/README.md +97 -0
  3. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/pyproject.toml +2 -1
  4. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/utils.py +17 -4
  5. asyncapi_python-0.2.4/src/asyncapi_python_pants/__init__.py +0 -0
  6. asyncapi_python-0.2.4/src/asyncapi_python_pants/py.typed +0 -0
  7. asyncapi_python-0.2.4/src/asyncapi_python_pants/register.py +19 -0
  8. asyncapi_python-0.2.4/src/asyncapi_python_pants/rules.py +93 -0
  9. asyncapi_python-0.2.4/src/asyncapi_python_pants/targets.py +53 -0
  10. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/LICENSE +0 -0
  11. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/__init__.py +0 -0
  12. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/__init__.py +0 -0
  13. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/base_application.py +0 -0
  14. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/connection.py +0 -0
  15. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/endpoint/__init__.py +0 -0
  16. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/endpoint/base.py +0 -0
  17. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/endpoint/receiver.py +0 -0
  18. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/endpoint/sender.py +0 -0
  19. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/error.py +0 -0
  20. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/operation.py +0 -0
  21. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/amqp/utils.py +0 -0
  22. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/py.typed +0 -0
  23. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python/utils.py +0 -0
  24. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/__init__.py +0 -0
  25. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/__init__.py +0 -0
  26. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/base.py +0 -0
  27. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/bindings/__init__.py +0 -0
  28. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/bindings/amqp.py +0 -0
  29. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/components.py +0 -0
  30. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/document.py +0 -0
  31. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/document_context.py +0 -0
  32. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/document/ref.py +0 -0
  33. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/__init__.py +0 -0
  34. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/amqp/__init__.py +0 -0
  35. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/amqp/generate.py +0 -0
  36. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/amqp/templates/__init__.py.j2 +0 -0
  37. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/amqp/templates/application.py.j2 +0 -0
  38. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/generators/amqp/templates/routes.py.j2 +0 -0
  39. {asyncapi_python-0.2.2 → asyncapi_python-0.2.4}/src/asyncapi_python_codegen/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: asyncapi-python
3
- Version: 0.2.2
3
+ Version: 0.2.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
@@ -52,6 +52,7 @@ Easily generate type-safe and async Python applications from AsyncAPI 3 specific
52
52
  - [x] Supports publish-subscribe pattern
53
53
  - [ ] AsyncAPI trait support
54
54
  - [ ] Customizable message encoder/decoder
55
+ - [x] Works as a plugin for [pantsbuild](https://pantsbuild.org) (see [instructions](#usage-as-a-pants-plugin) below)
55
56
 
56
57
  ## Requirements
57
58
 
@@ -82,6 +83,102 @@ pip install asyncapi-python[amqp]
82
83
 
83
84
  You can replace `amqp` with any other supported protocols. For more info, see [Supported Protocols](#supported-protocols--use-cases) section.
84
85
 
86
+ ### Usage as a Pants plugin
87
+
88
+ > The following method was tested with pants version 2.23.1.
89
+ > Pleas note that Pants plugin API is still in development and things might break.
90
+
91
+ This library can act as a plugin for [Pants](https://pantsbuild.org). More specifically, it creates a new target type: `asyncapi_python_service` -- which can be used like:
92
+
93
+ ```python
94
+ # BUILD
95
+ asyncapi_python_service(
96
+ name="asyncapi_app",
97
+ service="app.asyncapi.yaml",
98
+ sources=[
99
+ "app.asyncapi.yaml",
100
+ "lib.asyncapi.yaml",
101
+ "commons.*.asyncapi.yaml"
102
+ ]
103
+ )
104
+ ```
105
+
106
+ This will be generating python module named `asyncapi_app` on `codegen-export` and `export` goals.
107
+ This target can later be used as a dependency of `python_sources`.
108
+
109
+ ```python
110
+ # BUILD
111
+ python_sources(
112
+ dependencies=[
113
+ ":asyncapi_app",
114
+ ":reqs",
115
+ ],
116
+ )
117
+
118
+ python_requirements(
119
+ name="reqs",
120
+ )
121
+ ```
122
+
123
+ Note that this plugin does not do dependency injection, so asyncapi-python must be a dependency
124
+
125
+ ```text
126
+ # requirements.txt
127
+ asyncapi-python[amqp]
128
+ ```
129
+
130
+ ### Deploying this plugin into pants monorepo
131
+
132
+ In order to deploy this plugin into your pants monorepo, create the following structure inside your plugins folder:
133
+
134
+ ```bash
135
+ pants-plugins/
136
+ └── asyncapi_python_plugin
137
+ ├── BUILD
138
+ ├── __init__.py
139
+ ├── register.py
140
+ └── requirements.txt
141
+ ```
142
+
143
+ `requirements.txt` must contain:
144
+
145
+ ```text
146
+ asyncapi-python
147
+ ```
148
+
149
+ `register.py` should have:
150
+
151
+ ```python
152
+ from asyncapi_python_pants.register import *
153
+ ```
154
+
155
+ `BUILD` must include:
156
+
157
+ ```python
158
+ python_sources(
159
+ dependencies=[":reqs"],
160
+ )
161
+
162
+ python_requirements(
163
+ name="reqs",
164
+ )
165
+ ```
166
+
167
+ `__init__.py` can be empty, but it has to exist.
168
+
169
+ Finally, add `pants-plugins` to your `PYTHONPATH`, and add the created folder as a backend package:
170
+
171
+ ```toml
172
+ # pants.toml
173
+ backend_packages = [
174
+ "asyncapi_python_plugin",
175
+ ...
176
+ ]
177
+ pythonpath = ["%(buildroot)s/pants-plugins"]
178
+ ```
179
+
180
+ When everything is done, test if the plugin works by running `pants export-codegen ::`
181
+
85
182
  ## Supported Protocols / Use Cases
86
183
 
87
184
  Below, you may see the table of protocols and the supported use cases. The tick signs (✅) contain links to the examples for each implemented protocol-use case pair, while the hammer signs (🔨) contain links to the Issues tracking the progress for protocol-use case pairs. The list of protocols and use cases is expected to increase over the progress of development.
@@ -25,6 +25,7 @@ Easily generate type-safe and async Python applications from AsyncAPI 3 specific
25
25
  - [x] Supports publish-subscribe pattern
26
26
  - [ ] AsyncAPI trait support
27
27
  - [ ] Customizable message encoder/decoder
28
+ - [x] Works as a plugin for [pantsbuild](https://pantsbuild.org) (see [instructions](#usage-as-a-pants-plugin) below)
28
29
 
29
30
  ## Requirements
30
31
 
@@ -55,6 +56,102 @@ pip install asyncapi-python[amqp]
55
56
 
56
57
  You can replace `amqp` with any other supported protocols. For more info, see [Supported Protocols](#supported-protocols--use-cases) section.
57
58
 
59
+ ### Usage as a Pants plugin
60
+
61
+ > The following method was tested with pants version 2.23.1.
62
+ > Pleas note that Pants plugin API is still in development and things might break.
63
+
64
+ This library can act as a plugin for [Pants](https://pantsbuild.org). More specifically, it creates a new target type: `asyncapi_python_service` -- which can be used like:
65
+
66
+ ```python
67
+ # BUILD
68
+ asyncapi_python_service(
69
+ name="asyncapi_app",
70
+ service="app.asyncapi.yaml",
71
+ sources=[
72
+ "app.asyncapi.yaml",
73
+ "lib.asyncapi.yaml",
74
+ "commons.*.asyncapi.yaml"
75
+ ]
76
+ )
77
+ ```
78
+
79
+ This will be generating python module named `asyncapi_app` on `codegen-export` and `export` goals.
80
+ This target can later be used as a dependency of `python_sources`.
81
+
82
+ ```python
83
+ # BUILD
84
+ python_sources(
85
+ dependencies=[
86
+ ":asyncapi_app",
87
+ ":reqs",
88
+ ],
89
+ )
90
+
91
+ python_requirements(
92
+ name="reqs",
93
+ )
94
+ ```
95
+
96
+ Note that this plugin does not do dependency injection, so asyncapi-python must be a dependency
97
+
98
+ ```text
99
+ # requirements.txt
100
+ asyncapi-python[amqp]
101
+ ```
102
+
103
+ ### Deploying this plugin into pants monorepo
104
+
105
+ In order to deploy this plugin into your pants monorepo, create the following structure inside your plugins folder:
106
+
107
+ ```bash
108
+ pants-plugins/
109
+ └── asyncapi_python_plugin
110
+ ├── BUILD
111
+ ├── __init__.py
112
+ ├── register.py
113
+ └── requirements.txt
114
+ ```
115
+
116
+ `requirements.txt` must contain:
117
+
118
+ ```text
119
+ asyncapi-python
120
+ ```
121
+
122
+ `register.py` should have:
123
+
124
+ ```python
125
+ from asyncapi_python_pants.register import *
126
+ ```
127
+
128
+ `BUILD` must include:
129
+
130
+ ```python
131
+ python_sources(
132
+ dependencies=[":reqs"],
133
+ )
134
+
135
+ python_requirements(
136
+ name="reqs",
137
+ )
138
+ ```
139
+
140
+ `__init__.py` can be empty, but it has to exist.
141
+
142
+ Finally, add `pants-plugins` to your `PYTHONPATH`, and add the created folder as a backend package:
143
+
144
+ ```toml
145
+ # pants.toml
146
+ backend_packages = [
147
+ "asyncapi_python_plugin",
148
+ ...
149
+ ]
150
+ pythonpath = ["%(buildroot)s/pants-plugins"]
151
+ ```
152
+
153
+ When everything is done, test if the plugin works by running `pants export-codegen ::`
154
+
58
155
  ## Supported Protocols / Use Cases
59
156
 
60
157
  Below, you may see the table of protocols and the supported use cases. The tick signs (✅) contain links to the examples for each implemented protocol-use case pair, while the hammer signs (🔨) contain links to the Issues tracking the progress for protocol-use case pairs. The list of protocols and use cases is expected to increase over the progress of development.
@@ -1,11 +1,12 @@
1
1
  [tool.poetry]
2
2
  name = "asyncapi-python"
3
- version = "0.2.2"
3
+ version = "0.2.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>"]
7
7
  readme = "README.md"
8
8
  packages = [
9
+ { include = "asyncapi_python_pants", from = "src" },
9
10
  { include = "asyncapi_python_codegen", from = "src" },
10
11
  { include = "asyncapi_python", from = "src" },
11
12
  ]
@@ -22,10 +22,10 @@ from collections import defaultdict
22
22
 
23
23
 
24
24
  Reference = Union[None, tuple[Path, tuple[str, ...]]]
25
- """A reference type"""
25
+ """A reference type, maps a document path to a set of references that point to it"""
26
26
 
27
27
  ReferenceCounter = defaultdict[Reference, set[Reference]]
28
- """A reference counter"""
28
+ """A reference counter, maps each reference to a set of references that point to it"""
29
29
 
30
30
 
31
31
  def populate_jsonschema_defs(schema: Any) -> Any:
@@ -45,10 +45,17 @@ def populate_jsonschema_defs(schema: Any) -> Any:
45
45
 
46
46
  def _count_references(schema: Any, this: Reference, counter: ReferenceCounter):
47
47
  """Recursively constructs back references within the JsonSchema"""
48
+
49
+ # List case
50
+ if isinstance(schema, list):
51
+ for v in schema:
52
+ _count_references(v, this, counter)
53
+
54
+ # Dict case
48
55
  if not isinstance(schema, dict):
49
56
  return
50
57
 
51
- if "$ref" in schema:
58
+ if "$ref" in schema: # If dict is $ref object
52
59
  ref: Ref[Any] = Ref.model_validate(schema)
53
60
  with set_current_doc_path(ref.filepath):
54
61
  ref = ref.flatten()
@@ -61,7 +68,7 @@ def _count_references(schema: Any, this: Reference, counter: ReferenceCounter):
61
68
  with set_current_doc_path(ref.filepath):
62
69
  return _count_references(doc, child, counter)
63
70
 
64
- for v in schema.values():
71
+ for v in schema.values(): # Recur
65
72
  _count_references(v, this, counter)
66
73
 
67
74
 
@@ -72,6 +79,12 @@ def _populate_jsonschema_recur(
72
79
  ignore_shared: bool = False,
73
80
  ) -> Any:
74
81
  """Recursively populates JsonSchema $defs object"""
82
+
83
+ # List case
84
+ if isinstance(schema, list):
85
+ return [_populate_jsonschema_recur(v, counter, shared_schemas, ignore_shared) for v in schema]
86
+
87
+ # Dict case
75
88
  if not isinstance(schema, dict):
76
89
  return schema
77
90
 
@@ -0,0 +1,19 @@
1
+ from pants.engine.rules import collect_rules
2
+ from pants.engine.unions import UnionRule
3
+ from pants.backend.python.util_rules import pex
4
+ from pants.core.goals.resolves import ExportableTool
5
+
6
+ from .rules import *
7
+ from .targets import *
8
+
9
+
10
+ def rules():
11
+ return [
12
+ *collect_rules(),
13
+ *pex.rules(),
14
+ UnionRule(GenerateSourcesRequest, GeneratePythonFromAsyncapiRequest),
15
+ ]
16
+
17
+
18
+ def target_types():
19
+ return [AsyncapiServiceTarget]
@@ -0,0 +1,93 @@
1
+ from importlib.metadata import version
2
+ from pants.engine.internals.native_engine import (
3
+ Digest,
4
+ MergeDigests,
5
+ RemovePrefix,
6
+ AddPrefix,
7
+ Snapshot,
8
+ )
9
+ from pants.core.util_rules.stripped_source_files import StrippedSourceFiles
10
+ from pants.core.util_rules.source_files import SourceFilesRequest
11
+ from pants.engine.target import (
12
+ GeneratedSources,
13
+ TransitiveTargets,
14
+ TransitiveTargetsRequest,
15
+ )
16
+ from pants.engine.rules import rule, Get, MultiGet
17
+ from pants.engine.process import ProcessResult
18
+ from pants.source.source_root import SourceRoot, SourceRootRequest
19
+ from pants.backend.python.target_types import ConsoleScript
20
+ from pants.backend.python.util_rules.interpreter_constraints import (
21
+ InterpreterConstraints,
22
+ )
23
+ from pants.backend.python.util_rules.pex import (
24
+ Pex,
25
+ PexProcess,
26
+ PexRequest,
27
+ PexRequirements,
28
+ )
29
+ from .targets import *
30
+
31
+
32
+ @rule
33
+ async def generate_python_from_asyncapi(
34
+ request: GeneratePythonFromAsyncapiRequest,
35
+ ) -> GeneratedSources:
36
+ pex = await Get(
37
+ Pex,
38
+ PexRequest(
39
+ output_filename="asyncapi-python-codegen.pex",
40
+ internal_only=True,
41
+ requirements=PexRequirements(
42
+ [f"asyncapi-python[codegen]=={version('asyncapi-python')}"]
43
+ ),
44
+ interpreter_constraints=InterpreterConstraints([">=3.9"]),
45
+ main=ConsoleScript("asyncapi-python-codegen"),
46
+ ),
47
+ )
48
+ transitive_targets = await Get(
49
+ TransitiveTargets,
50
+ TransitiveTargetsRequest([request.protocol_target.address]),
51
+ )
52
+ all_sources_stripped = await Get(
53
+ StrippedSourceFiles,
54
+ SourceFilesRequest(
55
+ (tgt.get(AsyncapiSourcesField) for tgt in transitive_targets.closure),
56
+ for_sources_types=(AsyncapiSourcesField,),
57
+ ),
58
+ )
59
+ input_digest = await Get(
60
+ Digest,
61
+ MergeDigests(
62
+ (
63
+ all_sources_stripped.snapshot.digest,
64
+ pex.digest,
65
+ )
66
+ ),
67
+ )
68
+ output_dir = "_generated_files"
69
+ module_name = request.protocol_target.address.target_name
70
+ result = await Get(
71
+ ProcessResult,
72
+ PexProcess(
73
+ pex,
74
+ argv=[
75
+ request.protocol_target[AsyncapiServiceField].value or "",
76
+ f"{output_dir}/{module_name}",
77
+ ],
78
+ description=f"Generating Python sources from {request.protocol_target.address}.",
79
+ output_directories=(output_dir,),
80
+ input_digest=input_digest,
81
+ ),
82
+ )
83
+ source_root_request = SourceRootRequest.for_target(request.protocol_target)
84
+ normalized_digest, source_root = await MultiGet(
85
+ Get(Digest, RemovePrefix(result.output_digest, output_dir)),
86
+ Get(SourceRoot, SourceRootRequest, source_root_request),
87
+ )
88
+ source_root_restored = (
89
+ await Get(Snapshot, AddPrefix(normalized_digest, source_root.path))
90
+ if source_root.path != "."
91
+ else await Get(Snapshot, Digest, normalized_digest)
92
+ )
93
+ return GeneratedSources(source_root_restored)
@@ -0,0 +1,53 @@
1
+ from pants.engine.target import GenerateSourcesRequest
2
+ from pants.engine.target import (
3
+ COMMON_TARGET_FIELDS,
4
+ Dependencies,
5
+ Target,
6
+ AsyncFieldMixin,
7
+ MultipleSourcesField,
8
+ StringField,
9
+ )
10
+ from pants.backend.python.target_types import PythonSourceField
11
+ from pants.backend.python.target_types import (
12
+ InterpreterConstraintsField,
13
+ PythonResolveField,
14
+ )
15
+
16
+
17
+ class AsyncapiPythonInterpreterConstraints(InterpreterConstraintsField): ...
18
+
19
+
20
+ class AsyncapiPythonResolveField(PythonResolveField): ...
21
+
22
+
23
+ class AsyncapiServiceField(StringField):
24
+ alias = "service"
25
+
26
+
27
+ class AsyncapiSourcesField(MultipleSourcesField, AsyncFieldMixin):
28
+ expected_file_extensions = (".yaml", ".asyncapi.yaml")
29
+
30
+
31
+ class AsyncapiDependencies(Dependencies): ...
32
+
33
+
34
+ class AsyncapiServiceTarget(Target):
35
+ alias = "asyncapi_python_service"
36
+ help = "A single AsyncAPI file."
37
+ core_fields = (
38
+ *COMMON_TARGET_FIELDS,
39
+ AsyncapiDependencies,
40
+ AsyncapiSourcesField,
41
+ AsyncapiServiceField,
42
+ AsyncapiPythonInterpreterConstraints,
43
+ AsyncapiPythonResolveField,
44
+ )
45
+
46
+
47
+ class InjectAsyncapiDependencies(Target):
48
+ inject_for = AsyncapiDependencies
49
+
50
+
51
+ class GeneratePythonFromAsyncapiRequest(GenerateSourcesRequest):
52
+ input = AsyncapiSourcesField
53
+ output = PythonSourceField
File without changes