chanx 2.2.2__tar.gz → 2.3.0__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 (69) hide show
  1. {chanx-2.2.2 → chanx-2.3.0}/PKG-INFO +1 -1
  2. chanx-2.3.0/chanx/channels/management/__init__.py +1 -0
  3. chanx-2.3.0/chanx/channels/management/commands/__init__.py +1 -0
  4. chanx-2.3.0/chanx/channels/management/commands/generate_asyncapi_schema.py +259 -0
  5. {chanx-2.2.2 → chanx-2.3.0}/pyproject.toml +1 -1
  6. {chanx-2.2.2 → chanx-2.3.0}/.gitignore +0 -0
  7. {chanx-2.2.2 → chanx-2.3.0}/AUTHORS.rst +0 -0
  8. {chanx-2.2.2 → chanx-2.3.0}/LICENSE +0 -0
  9. {chanx-2.2.2 → chanx-2.3.0}/README.rst +0 -0
  10. {chanx-2.2.2 → chanx-2.3.0}/chanx/__init__.py +0 -0
  11. {chanx-2.2.2 → chanx-2.3.0}/chanx/asyncapi/__init__.py +0 -0
  12. {chanx-2.2.2 → chanx-2.3.0}/chanx/asyncapi/constants.py +0 -0
  13. {chanx-2.2.2 → chanx-2.3.0}/chanx/asyncapi/generator.py +0 -0
  14. {chanx-2.2.2 → chanx-2.3.0}/chanx/asyncapi/type_defs.py +0 -0
  15. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/__init__.py +0 -0
  16. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/apps.py +0 -0
  17. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/authenticator.py +0 -0
  18. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/discovery.py +0 -0
  19. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/routing.py +0 -0
  20. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/settings.py +0 -0
  21. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/templates/channels/asyncapi_docs.html +0 -0
  22. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/testing.py +0 -0
  23. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/urls.py +0 -0
  24. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/utils/__init__.py +0 -0
  25. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/utils/asgi.py +0 -0
  26. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/utils/request.py +0 -0
  27. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/utils/settings.py +0 -0
  28. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/views.py +0 -0
  29. {chanx-2.2.2 → chanx-2.3.0}/chanx/channels/websocket.py +0 -0
  30. {chanx-2.2.2 → chanx-2.3.0}/chanx/cli/__init__.py +0 -0
  31. {chanx-2.2.2 → chanx-2.3.0}/chanx/cli/main.py +0 -0
  32. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/__init__.py +0 -0
  33. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/analyzer.py +0 -0
  34. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/base/__init__.py +0 -0
  35. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/base/client.py +0 -0
  36. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/codegen.py +0 -0
  37. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/generator.py +0 -0
  38. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/loader.py +0 -0
  39. {chanx-2.2.2 → chanx-2.3.0}/chanx/client_generator/templates.py +0 -0
  40. {chanx-2.2.2 → chanx-2.3.0}/chanx/constants.py +0 -0
  41. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/__init__.py +0 -0
  42. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/authenticator.py +0 -0
  43. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/check.py +0 -0
  44. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/config.py +0 -0
  45. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/decorators.py +0 -0
  46. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/registry.py +0 -0
  47. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/testing.py +0 -0
  48. {chanx-2.2.2 → chanx-2.3.0}/chanx/core/websocket.py +0 -0
  49. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/__init__.py +0 -0
  50. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/constants.py +0 -0
  51. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/discovery.py +0 -0
  52. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/testing.py +0 -0
  53. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/type_defs.py +0 -0
  54. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/utils.py +0 -0
  55. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/views.py +0 -0
  56. {chanx-2.2.2 → chanx-2.3.0}/chanx/fast_channels/websocket.py +0 -0
  57. {chanx-2.2.2 → chanx-2.3.0}/chanx/messages/__init__.py +0 -0
  58. {chanx-2.2.2 → chanx-2.3.0}/chanx/messages/base.py +0 -0
  59. {chanx-2.2.2 → chanx-2.3.0}/chanx/messages/incoming.py +0 -0
  60. {chanx-2.2.2 → chanx-2.3.0}/chanx/messages/outgoing.py +0 -0
  61. {chanx-2.2.2 → chanx-2.3.0}/chanx/py.typed +0 -0
  62. {chanx-2.2.2 → chanx-2.3.0}/chanx/routing/__init__.py +0 -0
  63. {chanx-2.2.2 → chanx-2.3.0}/chanx/routing/discovery.py +0 -0
  64. {chanx-2.2.2 → chanx-2.3.0}/chanx/routing/patterns.py +0 -0
  65. {chanx-2.2.2 → chanx-2.3.0}/chanx/routing/traversal.py +0 -0
  66. {chanx-2.2.2 → chanx-2.3.0}/chanx/type_defs.py +0 -0
  67. {chanx-2.2.2 → chanx-2.3.0}/chanx/utils/__init__.py +0 -0
  68. {chanx-2.2.2 → chanx-2.3.0}/chanx/utils/asyncio.py +0 -0
  69. {chanx-2.2.2 → chanx-2.3.0}/chanx/utils/logging.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chanx
3
- Version: 2.2.2
3
+ Version: 2.3.0
4
4
  Summary: The missing toolkit for Channels — auth, logging, consumers, and more.
5
5
  Project-URL: Documentation, https://chanx.readthedocs.io/
6
6
  Project-URL: Homepage, https://github.com/huynguyengl99/chanx
@@ -0,0 +1 @@
1
+ """Management commands for Chanx Channels."""
@@ -0,0 +1 @@
1
+ """Management commands for Chanx Channels."""
@@ -0,0 +1,259 @@
1
+ """
2
+ Django management command to generate AsyncAPI schema file.
3
+
4
+ This command generates an AsyncAPI 3.0-compliant schema for your WebSocket API
5
+ without requiring an HTTP request-response flow.
6
+ """
7
+
8
+ import json
9
+ from textwrap import dedent
10
+ from types import ModuleType
11
+ from typing import Any, cast
12
+
13
+ from django.core.management.base import BaseCommand, CommandError
14
+ from django.test import RequestFactory
15
+ from django.utils.module_loading import import_string
16
+
17
+ from chanx.asyncapi.generator import AsyncAPIGenerator
18
+ from chanx.channels.discovery import DjangoRouteDiscovery
19
+ from chanx.channels.settings import chanx_settings
20
+
21
+ try:
22
+ import yaml
23
+
24
+ yaml_available = True
25
+ except ImportError: # pragma: no cover
26
+ yaml = cast(ModuleType, None)
27
+ yaml_available = False
28
+
29
+
30
+ class SchemaGenerationError(CommandError):
31
+ """Exception raised when schema generation fails."""
32
+
33
+ pass
34
+
35
+
36
+ class Command(BaseCommand):
37
+ """Management command to generate AsyncAPI schema."""
38
+
39
+ help = dedent(
40
+ """
41
+ Generate an AsyncAPI 3.0-compliant schema for your WebSocket API.
42
+
43
+ This command introspects your Django Channels routing configuration and
44
+ generates a complete AsyncAPI specification file that can be used for
45
+ documentation, code generation, and API testing.
46
+
47
+ The command supports both JSON and YAML output formats and can write
48
+ to a file or stdout.
49
+
50
+ Example usage:
51
+ # Generate YAML schema and write to file
52
+ python manage.py generate_asyncapi_schema --format yaml --file schema.yaml
53
+
54
+ # Generate JSON schema to stdout
55
+ python manage.py generate_asyncapi_schema --format json
56
+
57
+ # Use custom settings and base URL
58
+ python manage.py generate_asyncapi_schema --base-url ws://example.com --format yaml
59
+ """
60
+ )
61
+
62
+ def add_arguments(self, parser: Any) -> None:
63
+ """Add command arguments."""
64
+ parser.add_argument(
65
+ "--format",
66
+ dest="format",
67
+ choices=["json", "yaml"],
68
+ default="yaml",
69
+ type=str,
70
+ help="Output format (json or yaml)",
71
+ )
72
+ parser.add_argument(
73
+ "--file",
74
+ dest="file",
75
+ default=None,
76
+ type=str,
77
+ help="Output file path (if not specified, outputs to stdout)",
78
+ )
79
+ parser.add_argument(
80
+ "--base-url",
81
+ dest="base_url",
82
+ default=None,
83
+ type=str,
84
+ help="WebSocket base URL (e.g., ws://localhost:8000)",
85
+ )
86
+ parser.add_argument(
87
+ "--title",
88
+ dest="title",
89
+ default=None,
90
+ type=str,
91
+ help="API title for AsyncAPI documentation",
92
+ )
93
+ parser.add_argument(
94
+ "--api-version",
95
+ dest="api_version",
96
+ default=None,
97
+ type=str,
98
+ help="API version for AsyncAPI documentation",
99
+ )
100
+ parser.add_argument(
101
+ "--description",
102
+ dest="description",
103
+ default=None,
104
+ type=str,
105
+ help="API description for AsyncAPI documentation",
106
+ )
107
+ parser.add_argument(
108
+ "--discovery-class",
109
+ dest="discovery_class",
110
+ default=None,
111
+ type=str,
112
+ help="Custom route discovery class path",
113
+ )
114
+
115
+ def handle(self, *args: Any, **options: Any) -> None:
116
+ """Execute the command."""
117
+ # Check if YAML is required but not available
118
+ if options["format"] == "yaml" and not yaml_available:
119
+ raise SchemaGenerationError(
120
+ "YAML format requested but PyYAML is not installed. "
121
+ "Please install it: pip install PyYAML"
122
+ )
123
+
124
+ try:
125
+ # Generate the schema
126
+ schema = self._generate_schema(options)
127
+
128
+ # Render the schema in the requested format
129
+ output = self._render_schema(schema, options["format"])
130
+
131
+ # Write to file or stdout
132
+ if options["file"]:
133
+ with open(options["file"], "wb") as f:
134
+ f.write(output)
135
+ self.stdout.write(
136
+ self.style.SUCCESS(
137
+ f"AsyncAPI schema successfully generated: {options['file']}"
138
+ )
139
+ )
140
+ else:
141
+ self.stdout.write(output.decode())
142
+
143
+ except Exception as e:
144
+ raise SchemaGenerationError(f"Failed to generate schema: {str(e)}") from e
145
+
146
+ def _generate_schema(self, options: dict[str, Any]) -> dict[str, Any]:
147
+ """
148
+ Generate AsyncAPI schema.
149
+
150
+ Args:
151
+ options: Command options
152
+
153
+ Returns:
154
+ AsyncAPI schema dictionary
155
+ """
156
+ # Get or create route discovery instance
157
+ if options.get("discovery_class"):
158
+ discovery_class = import_string(options["discovery_class"])
159
+ discovery = discovery_class()
160
+ else:
161
+ discovery = DjangoRouteDiscovery()
162
+
163
+ # Determine base URL
164
+ base_url = options.get("base_url") or self._get_default_base_url()
165
+
166
+ # Discover routes
167
+ routes = discovery.discover_routes(base_url)
168
+
169
+ if not routes:
170
+ self.stderr.write(
171
+ self.style.WARNING(
172
+ "No WebSocket routes found. Make sure your ASGI application "
173
+ "is properly configured."
174
+ )
175
+ )
176
+
177
+ # Create a fake request for settings that need it
178
+ request_factory = RequestFactory()
179
+ request_factory.get("/")
180
+
181
+ # Parse base URL for server configuration
182
+ server_url, server_protocol = self._parse_base_url(base_url)
183
+
184
+ # Use custom settings or fall back to defaults
185
+ title = options.get("title") or chanx_settings.ASYNCAPI_TITLE
186
+ version = options.get("api_version") or chanx_settings.ASYNCAPI_VERSION
187
+ description = options.get("description") or chanx_settings.ASYNCAPI_DESCRIPTION
188
+
189
+ # Generate AsyncAPI schema using the generator
190
+ generator = AsyncAPIGenerator(
191
+ routes,
192
+ title=title,
193
+ version=version,
194
+ description=description,
195
+ server_url=server_url,
196
+ server_protocol=server_protocol,
197
+ camelize=chanx_settings.CAMELIZE,
198
+ )
199
+
200
+ return generator.generate()
201
+
202
+ def _get_default_base_url(self) -> str:
203
+ """
204
+ Get default base URL from settings or use localhost.
205
+
206
+ Returns:
207
+ Default WebSocket base URL
208
+ """
209
+ if chanx_settings.WEBSOCKET_BASE_URL:
210
+ return chanx_settings.WEBSOCKET_BASE_URL
211
+ return "ws://localhost:8000"
212
+
213
+ def _parse_base_url(self, base_url: str) -> tuple[str, str]:
214
+ """
215
+ Parse base URL into server URL and protocol.
216
+
217
+ Args:
218
+ base_url: Full WebSocket URL (e.g., ws://localhost:8000)
219
+
220
+ Returns:
221
+ Tuple of (server_url, protocol)
222
+ """
223
+ # Extract protocol (ws or wss)
224
+ if base_url.startswith("wss://"):
225
+ protocol = "wss"
226
+ server_url = base_url[6:] # Remove 'wss://'
227
+ elif base_url.startswith("ws://"):
228
+ protocol = "ws"
229
+ server_url = base_url[5:] # Remove 'ws://'
230
+ else:
231
+ # Default to ws if no protocol specified
232
+ protocol = "ws"
233
+ server_url = base_url
234
+
235
+ return server_url, protocol
236
+
237
+ def _render_schema(self, schema: dict[str, Any], format_type: str) -> bytes:
238
+ """
239
+ Render schema in the requested format.
240
+
241
+ Args:
242
+ schema: AsyncAPI schema dictionary
243
+ format_type: Output format ('json' or 'yaml')
244
+
245
+ Returns:
246
+ Rendered schema as bytes
247
+ """
248
+ if format_type == "yaml":
249
+ yaml_content = yaml.dump(
250
+ schema,
251
+ default_flow_style=False,
252
+ sort_keys=False,
253
+ indent=2,
254
+ allow_unicode=True,
255
+ )
256
+ return yaml_content.encode("utf-8")
257
+ else:
258
+ json_content = json.dumps(schema, indent=2, ensure_ascii=False)
259
+ return json_content.encode("utf-8")
@@ -106,7 +106,7 @@ license = {file = "LICENSE"}
106
106
  name = "chanx"
107
107
  readme = "README.rst"
108
108
  requires-python = ">=3.11,<4.0"
109
- version = "2.2.2"
109
+ version = "2.3.0"
110
110
 
111
111
  [project.optional-dependencies]
112
112
  channels = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes