django-cfg 1.4.9__py3-none-any.whl → 1.4.11__py3-none-any.whl
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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/middleware/api_access.py +6 -2
- django_cfg/apps/payments/middleware/rate_limiting.py +2 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +5 -1
- django_cfg/apps/payments/models/managers/api_key_managers.py +0 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +0 -1
- django_cfg/apps/payments/services/core/balance_service.py +5 -5
- django_cfg/apps/payments/services/core/subscription_service.py +1 -2
- django_cfg/apps/payments/views/api/balances.py +8 -7
- django_cfg/apps/payments/views/api/base.py +10 -6
- django_cfg/apps/payments/views/api/currencies.py +53 -10
- django_cfg/apps/payments/views/api/payments.py +3 -1
- django_cfg/apps/payments/views/api/subscriptions.py +2 -5
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +82 -41
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/dashboard/sections/documentation.py +391 -0
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -265
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +238 -0
- django_cfg/models/django/{revolution.py → revolution_legacy.py} +8 -0
- django_cfg/modules/django_admin/management/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +188 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +767 -0
- django_cfg/modules/django_client/core/generator/python.py +751 -0
- django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
- django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
- django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
- django_cfg/modules/django_client/core/generator/typescript.py +872 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/spectacular/__init__.py +9 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +16 -5
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/modules/django_unfold/dashboard.py +1 -1
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/RECORD +192 -71
- django_cfg/management/commands/generate.py +0 -107
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.9.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
class API:
|
2
|
+
"""
|
3
|
+
API Client wrapper with JWT token management.
|
4
|
+
|
5
|
+
This class provides:
|
6
|
+
- Thread-safe JWT token storage
|
7
|
+
- Automatic Authorization header injection
|
8
|
+
- Context manager support for async operations
|
9
|
+
|
10
|
+
Example:
|
11
|
+
>>> api = API('https://api.example.com')
|
12
|
+
>>> api.set_token('jwt-token')
|
13
|
+
>>> async with api:
|
14
|
+
... users = await api.users.list()
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, base_url: str, **kwargs: Any):
|
18
|
+
"""
|
19
|
+
Initialize API client.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
base_url: Base API URL (e.g., 'https://api.example.com')
|
23
|
+
**kwargs: Additional httpx.AsyncClient kwargs
|
24
|
+
"""
|
25
|
+
self.base_url = base_url.rstrip('/')
|
26
|
+
self._kwargs = kwargs
|
27
|
+
self._token: str | None = None
|
28
|
+
self._refresh_token: str | None = None
|
29
|
+
self._lock = threading.Lock()
|
30
|
+
self._client: APIClient | None = None
|
31
|
+
self._init_clients()
|
32
|
+
|
33
|
+
def _init_clients(self) -> None:
|
34
|
+
"""Initialize API client with current token."""
|
35
|
+
# Create httpx client with auth header if token exists
|
36
|
+
headers = {}
|
37
|
+
if self._token:
|
38
|
+
headers['Authorization'] = f'Bearer {self._token}'
|
39
|
+
|
40
|
+
kwargs = {**self._kwargs}
|
41
|
+
if headers:
|
42
|
+
kwargs['headers'] = headers
|
43
|
+
|
44
|
+
# Create new APIClient
|
45
|
+
self._client = APIClient(self.base_url, **kwargs)
|
46
|
+
|
47
|
+
{% for prop in properties %}
|
48
|
+
@property
|
49
|
+
def {{ prop.property }}(self) -> {{ prop.class_name }}:
|
50
|
+
"""Access {{ prop.tag }} endpoints."""
|
51
|
+
return self._client.{{ prop.property }}
|
52
|
+
|
53
|
+
{% endfor %}
|
54
|
+
def get_token(self) -> str | None:
|
55
|
+
"""Get current JWT token."""
|
56
|
+
with self._lock:
|
57
|
+
return self._token
|
58
|
+
|
59
|
+
def get_refresh_token(self) -> str | None:
|
60
|
+
"""Get current refresh token."""
|
61
|
+
with self._lock:
|
62
|
+
return self._refresh_token
|
63
|
+
|
64
|
+
def set_token(self, token: str, refresh_token: str | None = None) -> None:
|
65
|
+
"""
|
66
|
+
Set JWT token and refresh token.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
token: JWT access token
|
70
|
+
refresh_token: JWT refresh token (optional)
|
71
|
+
"""
|
72
|
+
with self._lock:
|
73
|
+
self._token = token
|
74
|
+
if refresh_token:
|
75
|
+
self._refresh_token = refresh_token
|
76
|
+
|
77
|
+
# Reinitialize clients with new token
|
78
|
+
self._init_clients()
|
79
|
+
|
80
|
+
def clear_tokens(self) -> None:
|
81
|
+
"""Clear all tokens."""
|
82
|
+
with self._lock:
|
83
|
+
self._token = None
|
84
|
+
self._refresh_token = None
|
85
|
+
|
86
|
+
# Reinitialize clients without token
|
87
|
+
self._init_clients()
|
88
|
+
|
89
|
+
def is_authenticated(self) -> bool:
|
90
|
+
"""Check if user is authenticated."""
|
91
|
+
return self.get_token() is not None
|
92
|
+
|
93
|
+
def set_base_url(self, url: str) -> None:
|
94
|
+
"""
|
95
|
+
Update base URL and reinitialize clients.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
url: New base URL
|
99
|
+
"""
|
100
|
+
self.base_url = url.rstrip('/')
|
101
|
+
self._init_clients()
|
102
|
+
|
103
|
+
def get_base_url(self) -> str:
|
104
|
+
"""Get current base URL."""
|
105
|
+
return self.base_url
|
106
|
+
|
107
|
+
def get_schema(self) -> dict[str, Any]:
|
108
|
+
"""
|
109
|
+
Get OpenAPI schema.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
Complete OpenAPI specification for this API
|
113
|
+
"""
|
114
|
+
return OPENAPI_SCHEMA
|
115
|
+
|
116
|
+
async def __aenter__(self) -> 'API':
|
117
|
+
"""Async context manager entry."""
|
118
|
+
if self._client:
|
119
|
+
await self._client.__aenter__()
|
120
|
+
return self
|
121
|
+
|
122
|
+
async def __aexit__(self, *args: Any) -> None:
|
123
|
+
"""Async context manager exit."""
|
124
|
+
if self._client:
|
125
|
+
await self._client.__aexit__(*args)
|
126
|
+
|
127
|
+
async def close(self) -> None:
|
128
|
+
"""Close HTTP client."""
|
129
|
+
if self._client:
|
130
|
+
await self._client.close()
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from .models import *
|
6
|
+
|
7
|
+
|
8
|
+
class {{ class_name }}:
|
9
|
+
"""API endpoints for {{ tag }}."""
|
10
|
+
|
11
|
+
def __init__(self, client: httpx.AsyncClient):
|
12
|
+
"""Initialize sub-client with shared httpx client."""
|
13
|
+
self._client = client
|
14
|
+
|
15
|
+
{% for operation in operations %}
|
16
|
+
{{ operation | indent(4, first=True) }}
|
17
|
+
|
18
|
+
{% endfor %}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class APIClient:
|
2
|
+
"""
|
3
|
+
Async API client for {{ api_title }}.
|
4
|
+
|
5
|
+
Usage:
|
6
|
+
>>> async with APIClient(base_url='https://api.example.com') as client:
|
7
|
+
... users = await client.users_list()
|
8
|
+
"""
|
9
|
+
|
10
|
+
def __init__(self, base_url: str, **kwargs: Any):
|
11
|
+
"""
|
12
|
+
Initialize API client.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
base_url: Base API URL (e.g., 'https://api.example.com')
|
16
|
+
**kwargs: Additional httpx.AsyncClient kwargs
|
17
|
+
"""
|
18
|
+
self.base_url = base_url.rstrip('/')
|
19
|
+
self._client = httpx.AsyncClient(
|
20
|
+
base_url=self.base_url,
|
21
|
+
timeout=30.0,
|
22
|
+
**kwargs,
|
23
|
+
)
|
24
|
+
|
25
|
+
async def __aenter__(self) -> 'APIClient':
|
26
|
+
return self
|
27
|
+
|
28
|
+
async def __aexit__(self, *args: Any) -> None:
|
29
|
+
await self._client.aclose()
|
30
|
+
|
31
|
+
async def close(self) -> None:
|
32
|
+
"""Close HTTP client."""
|
33
|
+
await self._client.aclose()
|
34
|
+
|
35
|
+
{% for operation in operations %}
|
36
|
+
{{ operation }}
|
37
|
+
|
38
|
+
{% endfor %}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class APIClient:
|
2
|
+
"""
|
3
|
+
Async API client for {{ api_title }}.
|
4
|
+
|
5
|
+
Usage:
|
6
|
+
>>> async with APIClient(base_url='https://api.example.com') as client:
|
7
|
+
... users = await client.users.list()
|
8
|
+
... post = await client.posts.create(data=new_post)
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
base_url: str,
|
14
|
+
logger_config: Optional[LoggerConfig] = None,
|
15
|
+
**kwargs: Any,
|
16
|
+
):
|
17
|
+
"""
|
18
|
+
Initialize API client.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
base_url: Base API URL (e.g., 'https://api.example.com')
|
22
|
+
logger_config: Logger configuration (None to disable logging)
|
23
|
+
**kwargs: Additional httpx.AsyncClient kwargs
|
24
|
+
"""
|
25
|
+
self.base_url = base_url.rstrip('/')
|
26
|
+
self._client = httpx.AsyncClient(
|
27
|
+
base_url=self.base_url,
|
28
|
+
timeout=30.0,
|
29
|
+
**kwargs,
|
30
|
+
)
|
31
|
+
|
32
|
+
# Initialize logger
|
33
|
+
self.logger: Optional[APILogger] = None
|
34
|
+
if logger_config is not None:
|
35
|
+
self.logger = APILogger(logger_config)
|
36
|
+
|
37
|
+
# Initialize sub-clients
|
38
|
+
{% for tag in tags %}
|
39
|
+
self.{{ tag.property }} = {{ tag.class_name }}(self._client)
|
40
|
+
{% endfor %}
|
41
|
+
|
42
|
+
async def __aenter__(self) -> 'APIClient':
|
43
|
+
return self
|
44
|
+
|
45
|
+
async def __aexit__(self, *args: Any) -> None:
|
46
|
+
await self._client.aclose()
|
47
|
+
|
48
|
+
async def close(self) -> None:
|
49
|
+
"""Close HTTP client."""
|
50
|
+
await self._client.aclose()
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class {{ class_name }}:
|
2
|
+
"""API endpoints for {{ tag }}."""
|
3
|
+
|
4
|
+
def __init__(self, client: httpx.AsyncClient):
|
5
|
+
"""Initialize sub-client with shared httpx client."""
|
6
|
+
self._client = client
|
7
|
+
|
8
|
+
{% for operation in operations %}
|
9
|
+
{{ operation }}
|
10
|
+
|
11
|
+
{% endfor %}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
"""
|
2
|
+
{{ api_title }} - API Client with JWT Management
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
>>> from api import API
|
6
|
+
>>>
|
7
|
+
>>> api = API('https://api.example.com')
|
8
|
+
>>>
|
9
|
+
>>> # Set JWT token
|
10
|
+
>>> api.set_token('your-jwt-token', 'refresh-token')
|
11
|
+
>>>
|
12
|
+
>>> # Use API
|
13
|
+
>>> async with api:
|
14
|
+
... posts = await api.posts.list()
|
15
|
+
... user = await api.users.retrieve(1)
|
16
|
+
>>>
|
17
|
+
>>> # Check authentication
|
18
|
+
>>> if api.is_authenticated():
|
19
|
+
... # ...
|
20
|
+
>>>
|
21
|
+
>>> # Get OpenAPI schema
|
22
|
+
>>> schema = api.get_schema()
|
23
|
+
"""
|
24
|
+
|
25
|
+
from __future__ import annotations
|
26
|
+
|
27
|
+
import threading
|
28
|
+
from typing import Any
|
29
|
+
|
30
|
+
import httpx
|
31
|
+
|
32
|
+
from .client import APIClient
|
33
|
+
from .schema import OPENAPI_SCHEMA
|
34
|
+
{% for tag in tags %}
|
35
|
+
from .{{ tag.slug }} import {{ tag.class_name }}
|
36
|
+
{% endfor %}
|
37
|
+
{% if has_enums %}
|
38
|
+
from . import enums
|
39
|
+
from .enums import {{ enum_names | join(', ') }}
|
40
|
+
{% endif %}
|
41
|
+
|
42
|
+
TOKEN_KEY = "auth_token"
|
43
|
+
REFRESH_TOKEN_KEY = "refresh_token"
|
44
|
+
|
45
|
+
{{ api_class }}
|
46
|
+
|
47
|
+
__all__ = [
|
48
|
+
"API",
|
49
|
+
"APIClient",
|
50
|
+
]
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
7
|
+
{% if has_enums and enum_names %}
|
8
|
+
|
9
|
+
from ..enums import {{ enum_names | join(', ') }}
|
10
|
+
{% endif %}
|
11
|
+
|
12
|
+
|
13
|
+
{% for schema in schemas %}
|
14
|
+
{{ schema }}
|
15
|
+
|
16
|
+
|
17
|
+
{% endfor %}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class {{ name }}({{ base_class }}):
|
2
|
+
{% if docstring %}
|
3
|
+
"""{{ docstring }}"""
|
4
|
+
{% endif %}
|
5
|
+
{% if members %}
|
6
|
+
{% if docstring %}
|
7
|
+
|
8
|
+
{% endif %}
|
9
|
+
{% for member in members %}
|
10
|
+
{{ member }}
|
11
|
+
{% endfor %}
|
12
|
+
{% else %}
|
13
|
+
|
14
|
+
pass
|
15
|
+
{% endif %}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from datetime import datetime
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
7
|
+
{% if has_enums %}
|
8
|
+
|
9
|
+
from .enums import *
|
10
|
+
{% endif %}
|
11
|
+
|
12
|
+
|
13
|
+
{% for schema in schemas %}
|
14
|
+
{{ schema }}
|
15
|
+
|
16
|
+
|
17
|
+
{% endfor %}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class {{ name }}(BaseModel):
|
2
|
+
{% if docstring %}
|
3
|
+
"""{{ docstring }}"""
|
4
|
+
{% endif %}
|
5
|
+
|
6
|
+
model_config = ConfigDict(
|
7
|
+
validate_assignment=True,
|
8
|
+
extra="forbid",
|
9
|
+
frozen=False,
|
10
|
+
)
|
11
|
+
{% if fields %}
|
12
|
+
|
13
|
+
{% for field in fields %}
|
14
|
+
{{ field }}
|
15
|
+
{% endfor %}
|
16
|
+
{% else %}
|
17
|
+
|
18
|
+
pass
|
19
|
+
{% endif %}
|
@@ -0,0 +1,255 @@
|
|
1
|
+
"""
|
2
|
+
API Logger with Rich
|
3
|
+
Beautiful console logging for API requests and responses
|
4
|
+
|
5
|
+
Installation:
|
6
|
+
pip install rich
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
import time
|
12
|
+
from dataclasses import dataclass, field
|
13
|
+
from typing import Any, Dict, Optional
|
14
|
+
|
15
|
+
from rich.console import Console
|
16
|
+
from rich.panel import Panel
|
17
|
+
from rich.table import Table
|
18
|
+
from rich.text import Text
|
19
|
+
|
20
|
+
|
21
|
+
@dataclass
|
22
|
+
class RequestLog:
|
23
|
+
"""Request log data."""
|
24
|
+
|
25
|
+
method: str
|
26
|
+
url: str
|
27
|
+
headers: Optional[Dict[str, str]] = None
|
28
|
+
body: Optional[Any] = None
|
29
|
+
timestamp: float = field(default_factory=time.time)
|
30
|
+
|
31
|
+
|
32
|
+
@dataclass
|
33
|
+
class ResponseLog:
|
34
|
+
"""Response log data."""
|
35
|
+
|
36
|
+
status: int
|
37
|
+
status_text: str
|
38
|
+
data: Optional[Any] = None
|
39
|
+
duration: float = 0.0
|
40
|
+
timestamp: float = field(default_factory=time.time)
|
41
|
+
|
42
|
+
|
43
|
+
@dataclass
|
44
|
+
class ErrorLog:
|
45
|
+
"""Error log data."""
|
46
|
+
|
47
|
+
message: str
|
48
|
+
status_code: Optional[int] = None
|
49
|
+
field_errors: Optional[Dict[str, list[str]]] = None
|
50
|
+
duration: float = 0.0
|
51
|
+
timestamp: float = field(default_factory=time.time)
|
52
|
+
|
53
|
+
|
54
|
+
@dataclass
|
55
|
+
class LoggerConfig:
|
56
|
+
"""Logger configuration."""
|
57
|
+
|
58
|
+
enabled: bool = True
|
59
|
+
log_requests: bool = True
|
60
|
+
log_responses: bool = True
|
61
|
+
log_errors: bool = True
|
62
|
+
log_bodies: bool = True
|
63
|
+
log_headers: bool = False
|
64
|
+
console: Optional[Console] = None
|
65
|
+
|
66
|
+
|
67
|
+
# Sensitive header names to filter out
|
68
|
+
SENSITIVE_HEADERS = [
|
69
|
+
"authorization",
|
70
|
+
"cookie",
|
71
|
+
"set-cookie",
|
72
|
+
"x-api-key",
|
73
|
+
"x-csrf-token",
|
74
|
+
]
|
75
|
+
|
76
|
+
|
77
|
+
class APILogger:
|
78
|
+
"""API Logger class."""
|
79
|
+
|
80
|
+
def __init__(self, config: Optional[LoggerConfig] = None):
|
81
|
+
"""Initialize logger."""
|
82
|
+
self.config = config or LoggerConfig()
|
83
|
+
self.console = self.config.console or Console()
|
84
|
+
|
85
|
+
def enable(self) -> None:
|
86
|
+
"""Enable logging."""
|
87
|
+
self.config.enabled = True
|
88
|
+
|
89
|
+
def disable(self) -> None:
|
90
|
+
"""Disable logging."""
|
91
|
+
self.config.enabled = False
|
92
|
+
|
93
|
+
def set_config(self, **kwargs: Any) -> None:
|
94
|
+
"""Update configuration."""
|
95
|
+
for key, value in kwargs.items():
|
96
|
+
if hasattr(self.config, key):
|
97
|
+
setattr(self.config, key, value)
|
98
|
+
|
99
|
+
def _filter_headers(self, headers: Optional[Dict[str, str]]) -> Dict[str, str]:
|
100
|
+
"""Filter sensitive headers."""
|
101
|
+
if not headers:
|
102
|
+
return {}
|
103
|
+
|
104
|
+
filtered = {}
|
105
|
+
for key, value in headers.items():
|
106
|
+
if key.lower() in SENSITIVE_HEADERS:
|
107
|
+
filtered[key] = "***"
|
108
|
+
else:
|
109
|
+
filtered[key] = value
|
110
|
+
|
111
|
+
return filtered
|
112
|
+
|
113
|
+
def log_request(self, request: RequestLog) -> None:
|
114
|
+
"""Log request."""
|
115
|
+
if not self.config.enabled or not self.config.log_requests:
|
116
|
+
return
|
117
|
+
|
118
|
+
# Create request info
|
119
|
+
text = Text()
|
120
|
+
text.append("→ ", style="bold blue")
|
121
|
+
text.append(request.method, style="bold yellow")
|
122
|
+
text.append(" ", style="")
|
123
|
+
text.append(request.url, style="cyan")
|
124
|
+
|
125
|
+
self.console.print(text)
|
126
|
+
|
127
|
+
if self.config.log_headers and request.headers:
|
128
|
+
headers = self._filter_headers(request.headers)
|
129
|
+
self.console.print(" Headers:", style="dim")
|
130
|
+
for key, value in headers.items():
|
131
|
+
self.console.print(f" {key}: {value}", style="dim")
|
132
|
+
|
133
|
+
if self.config.log_bodies and request.body:
|
134
|
+
self.console.print(" Body:", style="dim")
|
135
|
+
self.console.print(request.body, style="dim")
|
136
|
+
|
137
|
+
def log_response(self, request: RequestLog, response: ResponseLog) -> None:
|
138
|
+
"""Log response."""
|
139
|
+
if not self.config.enabled or not self.config.log_responses:
|
140
|
+
return
|
141
|
+
|
142
|
+
# Determine color based on status
|
143
|
+
if response.status >= 500:
|
144
|
+
status_style = "bold red"
|
145
|
+
elif response.status >= 400:
|
146
|
+
status_style = "bold yellow"
|
147
|
+
elif response.status >= 300:
|
148
|
+
status_style = "bold cyan"
|
149
|
+
else:
|
150
|
+
status_style = "bold green"
|
151
|
+
|
152
|
+
# Create response info
|
153
|
+
text = Text()
|
154
|
+
text.append("← ", style="bold green")
|
155
|
+
text.append(request.method, style="bold yellow")
|
156
|
+
text.append(" ", style="")
|
157
|
+
text.append(request.url, style="cyan")
|
158
|
+
text.append(" ", style="")
|
159
|
+
text.append(str(response.status), style=status_style)
|
160
|
+
text.append(" ", style="")
|
161
|
+
text.append(response.status_text, style=status_style)
|
162
|
+
text.append(f" ({response.duration:.0f}ms)", style="dim")
|
163
|
+
|
164
|
+
self.console.print(text)
|
165
|
+
|
166
|
+
if self.config.log_bodies and response.data:
|
167
|
+
self.console.print(" Response:", style="dim")
|
168
|
+
self.console.print(response.data, style="dim")
|
169
|
+
|
170
|
+
def log_error(self, request: RequestLog, error: ErrorLog) -> None:
|
171
|
+
"""Log error."""
|
172
|
+
if not self.config.enabled or not self.config.log_errors:
|
173
|
+
return
|
174
|
+
|
175
|
+
# Create error header
|
176
|
+
text = Text()
|
177
|
+
text.append("✗ ", style="bold red")
|
178
|
+
text.append(request.method, style="bold yellow")
|
179
|
+
text.append(" ", style="")
|
180
|
+
text.append(request.url, style="cyan")
|
181
|
+
text.append(" ", style="")
|
182
|
+
text.append(
|
183
|
+
str(error.status_code) if error.status_code else "Network",
|
184
|
+
style="bold red",
|
185
|
+
)
|
186
|
+
text.append(" Error", style="bold red")
|
187
|
+
text.append(f" ({error.duration:.0f}ms)", style="dim")
|
188
|
+
|
189
|
+
self.console.print(text)
|
190
|
+
self.console.print(f" Message: {error.message}", style="red")
|
191
|
+
|
192
|
+
if error.field_errors:
|
193
|
+
self.console.print(" Field Errors:", style="red")
|
194
|
+
for field, errors in error.field_errors.items():
|
195
|
+
for err in errors:
|
196
|
+
self.console.print(f" • {field}: {err}", style="red dim")
|
197
|
+
|
198
|
+
def info(self, message: str, **kwargs: Any) -> None:
|
199
|
+
"""Log info message."""
|
200
|
+
if not self.config.enabled:
|
201
|
+
return
|
202
|
+
self.console.print(f"ℹ {message}", style="blue", **kwargs)
|
203
|
+
|
204
|
+
def warn(self, message: str, **kwargs: Any) -> None:
|
205
|
+
"""Log warning message."""
|
206
|
+
if not self.config.enabled:
|
207
|
+
return
|
208
|
+
self.console.print(f"⚠ {message}", style="yellow", **kwargs)
|
209
|
+
|
210
|
+
def error(self, message: str, **kwargs: Any) -> None:
|
211
|
+
"""Log error message."""
|
212
|
+
if not self.config.enabled:
|
213
|
+
return
|
214
|
+
self.console.print(f"✗ {message}", style="red", **kwargs)
|
215
|
+
|
216
|
+
def success(self, message: str, **kwargs: Any) -> None:
|
217
|
+
"""Log success message."""
|
218
|
+
if not self.config.enabled:
|
219
|
+
return
|
220
|
+
self.console.print(f"✓ {message}", style="green", **kwargs)
|
221
|
+
|
222
|
+
def debug(self, message: str, **kwargs: Any) -> None:
|
223
|
+
"""Log debug message."""
|
224
|
+
if not self.config.enabled:
|
225
|
+
return
|
226
|
+
self.console.print(f"🔍 {message}", style="dim", **kwargs)
|
227
|
+
|
228
|
+
def panel(self, content: Any, title: str, style: str = "blue") -> None:
|
229
|
+
"""Log content in a panel."""
|
230
|
+
if not self.config.enabled:
|
231
|
+
return
|
232
|
+
self.console.print(Panel(content, title=title, border_style=style))
|
233
|
+
|
234
|
+
def table(
|
235
|
+
self,
|
236
|
+
headers: list[str],
|
237
|
+
rows: list[list[Any]],
|
238
|
+
title: Optional[str] = None,
|
239
|
+
) -> None:
|
240
|
+
"""Log data in a table."""
|
241
|
+
if not self.config.enabled:
|
242
|
+
return
|
243
|
+
|
244
|
+
table = Table(title=title)
|
245
|
+
for header in headers:
|
246
|
+
table.add_column(header, style="cyan")
|
247
|
+
|
248
|
+
for row in rows:
|
249
|
+
table.add_row(*[str(cell) for cell in row])
|
250
|
+
|
251
|
+
self.console.print(table)
|
252
|
+
|
253
|
+
|
254
|
+
# Default logger instance
|
255
|
+
default_logger = APILogger()
|