django-cfg 1.4.62__py3-none-any.whl → 1.4.64__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +582 -0
- django_cfg/apps/centrifugo/services/client/config.py +236 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +380 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Code generated by django-cfg/django_client - DO NOT EDIT.
|
|
2
|
+
// Generated at: {{ generated_at }}
|
|
3
|
+
|
|
4
|
+
package {{ package_name }}
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"fmt"
|
|
9
|
+
"net/http"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// APIError represents an API error response with full context.
|
|
13
|
+
type APIError struct {
|
|
14
|
+
// HTTP status code (e.g., 404, 500)
|
|
15
|
+
StatusCode int `json:"status_code"`
|
|
16
|
+
|
|
17
|
+
// HTTP status text (e.g., "Not Found")
|
|
18
|
+
Status string `json:"status"`
|
|
19
|
+
|
|
20
|
+
// Error message from API
|
|
21
|
+
Message string `json:"message"`
|
|
22
|
+
|
|
23
|
+
// Detailed error information (can be string, object, or array)
|
|
24
|
+
Detail interface{} `json:"detail,omitempty"`
|
|
25
|
+
|
|
26
|
+
// Field-specific validation errors
|
|
27
|
+
Errors map[string]interface{} `json:"errors,omitempty"`
|
|
28
|
+
|
|
29
|
+
// Raw response body
|
|
30
|
+
Body []byte `json:"-"`
|
|
31
|
+
|
|
32
|
+
// Request method (GET, POST, etc.)
|
|
33
|
+
Method string `json:"method"`
|
|
34
|
+
|
|
35
|
+
// Request URL
|
|
36
|
+
URL string `json:"url"`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Error implements the error interface.
|
|
40
|
+
func (e *APIError) Error() string {
|
|
41
|
+
if e.Detail != nil {
|
|
42
|
+
return fmt.Sprintf("API error: %s %s -> %d %s: %s (detail: %v)",
|
|
43
|
+
e.Method, e.URL, e.StatusCode, e.Status, e.Message, e.Detail)
|
|
44
|
+
}
|
|
45
|
+
if e.Message != "" {
|
|
46
|
+
return fmt.Sprintf("API error: %s %s -> %d %s: %s",
|
|
47
|
+
e.Method, e.URL, e.StatusCode, e.Status, e.Message)
|
|
48
|
+
}
|
|
49
|
+
return fmt.Sprintf("API error: %s %s -> %d %s",
|
|
50
|
+
e.Method, e.URL, e.StatusCode, e.Status)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// IsNotFound returns true if the error is a 404 Not Found.
|
|
54
|
+
func (e *APIError) IsNotFound() bool {
|
|
55
|
+
return e.StatusCode == http.StatusNotFound
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// IsUnauthorized returns true if the error is a 401 Unauthorized.
|
|
59
|
+
func (e *APIError) IsUnauthorized() bool {
|
|
60
|
+
return e.StatusCode == http.StatusUnauthorized
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// IsForbidden returns true if the error is a 403 Forbidden.
|
|
64
|
+
func (e *APIError) IsForbidden() bool {
|
|
65
|
+
return e.StatusCode == http.StatusForbidden
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// IsBadRequest returns true if the error is a 400 Bad Request.
|
|
69
|
+
func (e *APIError) IsBadRequest() bool {
|
|
70
|
+
return e.StatusCode == http.StatusBadRequest
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// IsValidationError returns true if the error is a 422 Unprocessable Entity (validation error).
|
|
74
|
+
func (e *APIError) IsValidationError() bool {
|
|
75
|
+
return e.StatusCode == http.StatusUnprocessableEntity
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// IsServerError returns true if the error is a 5xx server error.
|
|
79
|
+
func (e *APIError) IsServerError() bool {
|
|
80
|
+
return e.StatusCode >= 500 && e.StatusCode < 600
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// IsClientError returns true if the error is a 4xx client error.
|
|
84
|
+
func (e *APIError) IsClientError() bool {
|
|
85
|
+
return e.StatusCode >= 400 && e.StatusCode < 500
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// GetFieldError returns the error message for a specific field, if any.
|
|
89
|
+
func (e *APIError) GetFieldError(field string) (string, bool) {
|
|
90
|
+
if e.Errors == nil {
|
|
91
|
+
return "", false
|
|
92
|
+
}
|
|
93
|
+
if err, ok := e.Errors[field]; ok {
|
|
94
|
+
return fmt.Sprintf("%v", err), true
|
|
95
|
+
}
|
|
96
|
+
return "", false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// HasFieldErrors returns true if there are field-specific validation errors.
|
|
100
|
+
func (e *APIError) HasFieldErrors() bool {
|
|
101
|
+
return len(e.Errors) > 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ParseAPIError attempts to parse an error response from the API.
|
|
105
|
+
func ParseAPIError(statusCode int, status, method, url string, body []byte) error {
|
|
106
|
+
apiErr := &APIError{
|
|
107
|
+
StatusCode: statusCode,
|
|
108
|
+
Status: status,
|
|
109
|
+
Method: method,
|
|
110
|
+
URL: url,
|
|
111
|
+
Body: body,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Try to parse JSON error response
|
|
115
|
+
var errorResponse struct {
|
|
116
|
+
Message string `json:"message"`
|
|
117
|
+
Detail interface{} `json:"detail"`
|
|
118
|
+
Errors map[string]interface{} `json:"errors"`
|
|
119
|
+
Error string `json:"error"`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if err := json.Unmarshal(body, &errorResponse); err == nil {
|
|
123
|
+
// Use message field if available
|
|
124
|
+
if errorResponse.Message != "" {
|
|
125
|
+
apiErr.Message = errorResponse.Message
|
|
126
|
+
} else if errorResponse.Error != "" {
|
|
127
|
+
apiErr.Message = errorResponse.Error
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
apiErr.Detail = errorResponse.Detail
|
|
131
|
+
apiErr.Errors = errorResponse.Errors
|
|
132
|
+
} else {
|
|
133
|
+
// If JSON parsing fails, use body as message
|
|
134
|
+
apiErr.Message = string(body)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return apiErr
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// NetworkError represents a network-level error (connection failed, timeout, etc.).
|
|
141
|
+
type NetworkError struct {
|
|
142
|
+
// Underlying error
|
|
143
|
+
Err error
|
|
144
|
+
|
|
145
|
+
// Request method
|
|
146
|
+
Method string
|
|
147
|
+
|
|
148
|
+
// Request URL
|
|
149
|
+
URL string
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Error implements the error interface.
|
|
153
|
+
func (e *NetworkError) Error() string {
|
|
154
|
+
return fmt.Sprintf("network error: %s %s: %v", e.Method, e.URL, e.Err)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Unwrap returns the underlying error.
|
|
158
|
+
func (e *NetworkError) Unwrap() error {
|
|
159
|
+
return e.Err
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// UnmarshalError represents an error unmarshaling the response.
|
|
163
|
+
type UnmarshalError struct {
|
|
164
|
+
// Underlying error
|
|
165
|
+
Err error
|
|
166
|
+
|
|
167
|
+
// Response body
|
|
168
|
+
Body []byte
|
|
169
|
+
|
|
170
|
+
// Expected type
|
|
171
|
+
Type string
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Error implements the error interface.
|
|
175
|
+
func (e *UnmarshalError) Error() string {
|
|
176
|
+
return fmt.Sprintf("failed to unmarshal response into %s: %v (body: %s)", e.Type, e.Err, string(e.Body))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Unwrap returns the underlying error.
|
|
180
|
+
func (e *UnmarshalError) Unwrap() error {
|
|
181
|
+
return e.Err
|
|
182
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Code generated by django-cfg/django_client - DO NOT EDIT.
|
|
2
|
+
// Generated at: {{ generated_at }}
|
|
3
|
+
|
|
4
|
+
package {{ package_name }}
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"net/http"
|
|
8
|
+
{% for subclient in subclients %}
|
|
9
|
+
"{{ module_name }}/{{ subclient.package }}"
|
|
10
|
+
{% endfor %}
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
// Client is the main API client with sub-clients for each module.
|
|
14
|
+
type Client struct {
|
|
15
|
+
BaseURL string
|
|
16
|
+
HTTPClient *http.Client
|
|
17
|
+
Token string
|
|
18
|
+
|
|
19
|
+
{% for subclient in subclients %}
|
|
20
|
+
// {{ subclient.name }} provides access to {{ subclient.name }} operations
|
|
21
|
+
{{ subclient.name }} *{{ subclient.package }}.Client
|
|
22
|
+
{% endfor %}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// NewClient creates a new API client.
|
|
26
|
+
func NewClient(baseURL string, options ...Option) *Client {
|
|
27
|
+
client := &Client{
|
|
28
|
+
BaseURL: baseURL,
|
|
29
|
+
HTTPClient: &http.Client{},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Apply options
|
|
33
|
+
for _, opt := range options {
|
|
34
|
+
opt(client)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Initialize sub-clients
|
|
38
|
+
{% for subclient in subclients %}
|
|
39
|
+
client.{{ subclient.name }} = {{ subclient.package }}.NewClient(baseURL, client.HTTPClient, client.Token)
|
|
40
|
+
{% endfor %}
|
|
41
|
+
|
|
42
|
+
return client
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Option is a functional option for configuring the Client.
|
|
46
|
+
type Option func(*Client)
|
|
47
|
+
|
|
48
|
+
// WithHTTPClient sets a custom HTTP client.
|
|
49
|
+
func WithHTTPClient(httpClient *http.Client) Option {
|
|
50
|
+
return func(c *Client) {
|
|
51
|
+
c.HTTPClient = httpClient
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// WithToken sets the authentication token.
|
|
56
|
+
func WithToken(token string) Option {
|
|
57
|
+
return func(c *Client) {
|
|
58
|
+
c.Token = token
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
// Code generated by django-cfg/django_client - DO NOT EDIT.
|
|
2
|
+
// Generated at: {{ generated_at }}
|
|
3
|
+
|
|
4
|
+
package {{ package_name }}
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"context"
|
|
8
|
+
"log"
|
|
9
|
+
"math"
|
|
10
|
+
"net/http"
|
|
11
|
+
"net/http/httputil"
|
|
12
|
+
"time"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// RetryConfig configures retry behavior.
|
|
16
|
+
type RetryConfig struct {
|
|
17
|
+
// Maximum number of retry attempts (default: 3)
|
|
18
|
+
MaxRetries int
|
|
19
|
+
|
|
20
|
+
// Initial backoff duration (default: 1s)
|
|
21
|
+
InitialBackoff time.Duration
|
|
22
|
+
|
|
23
|
+
// Maximum backoff duration (default: 30s)
|
|
24
|
+
MaxBackoff time.Duration
|
|
25
|
+
|
|
26
|
+
// Backoff multiplier (default: 2.0)
|
|
27
|
+
BackoffMultiplier float64
|
|
28
|
+
|
|
29
|
+
// Retry on these status codes (default: [429, 500, 502, 503, 504])
|
|
30
|
+
RetryableStatusCodes []int
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// DefaultRetryConfig returns default retry configuration.
|
|
34
|
+
func DefaultRetryConfig() *RetryConfig {
|
|
35
|
+
return &RetryConfig{
|
|
36
|
+
MaxRetries: 3,
|
|
37
|
+
InitialBackoff: 1 * time.Second,
|
|
38
|
+
MaxBackoff: 30 * time.Second,
|
|
39
|
+
BackoffMultiplier: 2.0,
|
|
40
|
+
RetryableStatusCodes: []int{429, 500, 502, 503, 504},
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// RetryTransport implements automatic retry with exponential backoff.
|
|
45
|
+
type RetryTransport struct {
|
|
46
|
+
Base http.RoundTripper
|
|
47
|
+
Config *RetryConfig
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// NewRetryTransport creates a new retry transport.
|
|
51
|
+
func NewRetryTransport(base http.RoundTripper, config *RetryConfig) *RetryTransport {
|
|
52
|
+
if base == nil {
|
|
53
|
+
base = http.DefaultTransport
|
|
54
|
+
}
|
|
55
|
+
if config == nil {
|
|
56
|
+
config = DefaultRetryConfig()
|
|
57
|
+
}
|
|
58
|
+
return &RetryTransport{
|
|
59
|
+
Base: base,
|
|
60
|
+
Config: config,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// RoundTrip executes a single HTTP transaction with retry logic.
|
|
65
|
+
func (t *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
66
|
+
var resp *http.Response
|
|
67
|
+
var err error
|
|
68
|
+
|
|
69
|
+
for attempt := 0; attempt <= t.Config.MaxRetries; attempt++ {
|
|
70
|
+
// Clone request for retry (body can only be read once)
|
|
71
|
+
reqClone := req.Clone(req.Context())
|
|
72
|
+
|
|
73
|
+
resp, err = t.Base.RoundTrip(reqClone)
|
|
74
|
+
|
|
75
|
+
// Success - no retry needed
|
|
76
|
+
if err == nil && !t.shouldRetry(resp.StatusCode) {
|
|
77
|
+
return resp, nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Last attempt - return error
|
|
81
|
+
if attempt == t.Config.MaxRetries {
|
|
82
|
+
return resp, err
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Calculate backoff
|
|
86
|
+
backoff := t.calculateBackoff(attempt)
|
|
87
|
+
|
|
88
|
+
// Check context cancellation
|
|
89
|
+
select {
|
|
90
|
+
case <-req.Context().Done():
|
|
91
|
+
return resp, req.Context().Err()
|
|
92
|
+
case <-time.After(backoff):
|
|
93
|
+
// Continue to next attempt
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Close previous response body if exists
|
|
97
|
+
if resp != nil {
|
|
98
|
+
resp.Body.Close()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return resp, err
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// shouldRetry determines if a status code should trigger a retry.
|
|
106
|
+
func (t *RetryTransport) shouldRetry(statusCode int) bool {
|
|
107
|
+
for _, code := range t.Config.RetryableStatusCodes {
|
|
108
|
+
if statusCode == code {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// calculateBackoff calculates exponential backoff duration.
|
|
116
|
+
func (t *RetryTransport) calculateBackoff(attempt int) time.Duration {
|
|
117
|
+
backoff := float64(t.Config.InitialBackoff) * math.Pow(t.Config.BackoffMultiplier, float64(attempt))
|
|
118
|
+
maxBackoff := float64(t.Config.MaxBackoff)
|
|
119
|
+
|
|
120
|
+
if backoff > maxBackoff {
|
|
121
|
+
backoff = maxBackoff
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return time.Duration(backoff)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// LoggingTransport logs HTTP requests and responses.
|
|
128
|
+
type LoggingTransport struct {
|
|
129
|
+
Base http.RoundTripper
|
|
130
|
+
Logger *log.Logger
|
|
131
|
+
Verbose bool // If true, logs request/response bodies
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// NewLoggingTransport creates a new logging transport.
|
|
135
|
+
func NewLoggingTransport(base http.RoundTripper, logger *log.Logger, verbose bool) *LoggingTransport {
|
|
136
|
+
if base == nil {
|
|
137
|
+
base = http.DefaultTransport
|
|
138
|
+
}
|
|
139
|
+
return &LoggingTransport{
|
|
140
|
+
Base: base,
|
|
141
|
+
Logger: logger,
|
|
142
|
+
Verbose: verbose,
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// RoundTrip executes a single HTTP transaction with logging.
|
|
147
|
+
func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
148
|
+
start := time.Now()
|
|
149
|
+
|
|
150
|
+
// Log request
|
|
151
|
+
if t.Logger != nil {
|
|
152
|
+
if t.Verbose {
|
|
153
|
+
reqDump, _ := httputil.DumpRequestOut(req, true)
|
|
154
|
+
t.Logger.Printf("→ Request:\n%s\n", string(reqDump))
|
|
155
|
+
} else {
|
|
156
|
+
t.Logger.Printf("→ %s %s", req.Method, req.URL.String())
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Execute request
|
|
161
|
+
resp, err := t.Base.RoundTrip(req)
|
|
162
|
+
duration := time.Since(start)
|
|
163
|
+
|
|
164
|
+
// Log response
|
|
165
|
+
if t.Logger != nil {
|
|
166
|
+
if err != nil {
|
|
167
|
+
t.Logger.Printf("← Error after %v: %v", duration, err)
|
|
168
|
+
} else {
|
|
169
|
+
if t.Verbose {
|
|
170
|
+
respDump, _ := httputil.DumpResponse(resp, true)
|
|
171
|
+
t.Logger.Printf("← Response after %v:\n%s\n", duration, string(respDump))
|
|
172
|
+
} else {
|
|
173
|
+
t.Logger.Printf("← %d %s after %v", resp.StatusCode, resp.Status, duration)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return resp, err
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// RateLimitTransport implements client-side rate limiting.
|
|
182
|
+
type RateLimitTransport struct {
|
|
183
|
+
Base http.RoundTripper
|
|
184
|
+
Limiter *time.Ticker
|
|
185
|
+
requests chan struct{}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// NewRateLimitTransport creates a new rate limit transport.
|
|
189
|
+
// requestsPerSecond: maximum number of requests per second
|
|
190
|
+
func NewRateLimitTransport(base http.RoundTripper, requestsPerSecond int) *RateLimitTransport {
|
|
191
|
+
if base == nil {
|
|
192
|
+
base = http.DefaultTransport
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interval := time.Second / time.Duration(requestsPerSecond)
|
|
196
|
+
limiter := time.NewTicker(interval)
|
|
197
|
+
requests := make(chan struct{}, requestsPerSecond)
|
|
198
|
+
|
|
199
|
+
return &RateLimitTransport{
|
|
200
|
+
Base: base,
|
|
201
|
+
Limiter: limiter,
|
|
202
|
+
requests: requests,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// RoundTrip executes a single HTTP transaction with rate limiting.
|
|
207
|
+
func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
208
|
+
// Wait for rate limiter
|
|
209
|
+
select {
|
|
210
|
+
case <-req.Context().Done():
|
|
211
|
+
return nil, req.Context().Err()
|
|
212
|
+
case <-t.Limiter.C:
|
|
213
|
+
// Proceed with request
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return t.Base.RoundTrip(req)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Close stops the rate limiter.
|
|
220
|
+
func (t *RateLimitTransport) Close() {
|
|
221
|
+
if t.Limiter != nil {
|
|
222
|
+
t.Limiter.Stop()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// TimeoutTransport adds request-level timeout.
|
|
227
|
+
type TimeoutTransport struct {
|
|
228
|
+
Base http.RoundTripper
|
|
229
|
+
Timeout time.Duration
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// NewTimeoutTransport creates a new timeout transport.
|
|
233
|
+
func NewTimeoutTransport(base http.RoundTripper, timeout time.Duration) *TimeoutTransport {
|
|
234
|
+
if base == nil {
|
|
235
|
+
base = http.DefaultTransport
|
|
236
|
+
}
|
|
237
|
+
return &TimeoutTransport{
|
|
238
|
+
Base: base,
|
|
239
|
+
Timeout: timeout,
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// RoundTrip executes a single HTTP transaction with timeout.
|
|
244
|
+
func (t *TimeoutTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
245
|
+
ctx, cancel := context.WithTimeout(req.Context(), t.Timeout)
|
|
246
|
+
defer cancel()
|
|
247
|
+
|
|
248
|
+
return t.Base.RoundTrip(req.WithContext(ctx))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ChainTransports chains multiple http.RoundTrippers together.
|
|
252
|
+
// The order matters: first transport wraps second, second wraps third, etc.
|
|
253
|
+
//
|
|
254
|
+
// Example:
|
|
255
|
+
// transport := ChainTransports(
|
|
256
|
+
// NewLoggingTransport(nil, logger, false), // Outermost
|
|
257
|
+
// NewRetryTransport(nil, nil), // Middle
|
|
258
|
+
// http.DefaultTransport, // Innermost (base)
|
|
259
|
+
// )
|
|
260
|
+
func ChainTransports(transports ...http.RoundTripper) http.RoundTripper {
|
|
261
|
+
if len(transports) == 0 {
|
|
262
|
+
return http.DefaultTransport
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Start from the innermost (last) transport
|
|
266
|
+
base := transports[len(transports)-1]
|
|
267
|
+
|
|
268
|
+
// Wrap each transport from right to left
|
|
269
|
+
for i := len(transports) - 2; i >= 0; i-- {
|
|
270
|
+
switch t := transports[i].(type) {
|
|
271
|
+
case *RetryTransport:
|
|
272
|
+
t.Base = base
|
|
273
|
+
base = t
|
|
274
|
+
case *LoggingTransport:
|
|
275
|
+
t.Base = base
|
|
276
|
+
base = t
|
|
277
|
+
case *RateLimitTransport:
|
|
278
|
+
t.Base = base
|
|
279
|
+
base = t
|
|
280
|
+
case *TimeoutTransport:
|
|
281
|
+
t.Base = base
|
|
282
|
+
base = t
|
|
283
|
+
default:
|
|
284
|
+
// For custom transports, just use as-is
|
|
285
|
+
base = t
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return base
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// HTTPClientWithMiddleware creates an HTTP client with middleware.
|
|
293
|
+
//
|
|
294
|
+
// Example:
|
|
295
|
+
// client := HTTPClientWithMiddleware(
|
|
296
|
+
// WithRetry(3),
|
|
297
|
+
// WithLogging(logger, false),
|
|
298
|
+
// WithRateLimit(10), // 10 requests per second
|
|
299
|
+
// WithTimeout(30 * time.Second),
|
|
300
|
+
// )
|
|
301
|
+
func HTTPClientWithMiddleware(options ...MiddlewareOption) *http.Client {
|
|
302
|
+
config := &middlewareConfig{
|
|
303
|
+
baseTransport: http.DefaultTransport,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Apply options
|
|
307
|
+
for _, option := range options {
|
|
308
|
+
option(config)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Build transport chain
|
|
312
|
+
var transports []http.RoundTripper
|
|
313
|
+
|
|
314
|
+
if config.loggingTransport != nil {
|
|
315
|
+
transports = append(transports, config.loggingTransport)
|
|
316
|
+
}
|
|
317
|
+
if config.retryTransport != nil {
|
|
318
|
+
transports = append(transports, config.retryTransport)
|
|
319
|
+
}
|
|
320
|
+
if config.rateLimitTransport != nil {
|
|
321
|
+
transports = append(transports, config.rateLimitTransport)
|
|
322
|
+
}
|
|
323
|
+
if config.timeoutTransport != nil {
|
|
324
|
+
transports = append(transports, config.timeoutTransport)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
transports = append(transports, config.baseTransport)
|
|
328
|
+
|
|
329
|
+
return &http.Client{
|
|
330
|
+
Transport: ChainTransports(transports...),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// middlewareConfig holds middleware configuration.
|
|
335
|
+
type middlewareConfig struct {
|
|
336
|
+
baseTransport http.RoundTripper
|
|
337
|
+
retryTransport *RetryTransport
|
|
338
|
+
loggingTransport *LoggingTransport
|
|
339
|
+
rateLimitTransport *RateLimitTransport
|
|
340
|
+
timeoutTransport *TimeoutTransport
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// MiddlewareOption configures middleware.
|
|
344
|
+
type MiddlewareOption func(*middlewareConfig)
|
|
345
|
+
|
|
346
|
+
// WithRetry adds retry middleware.
|
|
347
|
+
func WithRetry(maxRetries int) MiddlewareOption {
|
|
348
|
+
return func(c *middlewareConfig) {
|
|
349
|
+
config := DefaultRetryConfig()
|
|
350
|
+
config.MaxRetries = maxRetries
|
|
351
|
+
c.retryTransport = NewRetryTransport(nil, config)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// WithRetryConfig adds retry middleware with custom config.
|
|
356
|
+
func WithRetryConfig(config *RetryConfig) MiddlewareOption {
|
|
357
|
+
return func(c *middlewareConfig) {
|
|
358
|
+
c.retryTransport = NewRetryTransport(nil, config)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// WithLogging adds logging middleware.
|
|
363
|
+
func WithLogging(logger *log.Logger, verbose bool) MiddlewareOption {
|
|
364
|
+
return func(c *middlewareConfig) {
|
|
365
|
+
c.loggingTransport = NewLoggingTransport(nil, logger, verbose)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// WithRateLimit adds rate limiting middleware.
|
|
370
|
+
func WithRateLimit(requestsPerSecond int) MiddlewareOption {
|
|
371
|
+
return func(c *middlewareConfig) {
|
|
372
|
+
c.rateLimitTransport = NewRateLimitTransport(nil, requestsPerSecond)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// WithTimeout adds timeout middleware.
|
|
377
|
+
func WithTimeout(timeout time.Duration) MiddlewareOption {
|
|
378
|
+
return func(c *middlewareConfig) {
|
|
379
|
+
c.timeoutTransport = NewTimeoutTransport(nil, timeout)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// WithBaseTransport sets custom base transport.
|
|
384
|
+
func WithBaseTransport(transport http.RoundTripper) MiddlewareOption {
|
|
385
|
+
return func(c *middlewareConfig) {
|
|
386
|
+
c.baseTransport = transport
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Code generated by django-cfg/django_client - DO NOT EDIT.
|
|
2
|
+
// Generated at: {{ generated_at }}
|
|
3
|
+
|
|
4
|
+
package {{ package_name }}
|
|
5
|
+
|
|
6
|
+
{% if imports %}
|
|
7
|
+
import (
|
|
8
|
+
{% for import_path in imports %}
|
|
9
|
+
"{{ import_path }}"
|
|
10
|
+
{% endfor %}
|
|
11
|
+
)
|
|
12
|
+
{% endif %}
|
|
13
|
+
|
|
14
|
+
{% for struct in structs %}
|
|
15
|
+
// {{ struct.doc | replace('\n', '\n// ') }}
|
|
16
|
+
type {{ struct.name }} struct {
|
|
17
|
+
{% for field in struct.fields %}
|
|
18
|
+
{% if field.description %}
|
|
19
|
+
// {{ field.description | replace('\n', '\n\t// ') }}
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% if field.deprecated %}
|
|
22
|
+
// Deprecated: This field is deprecated.
|
|
23
|
+
{% endif %}
|
|
24
|
+
{{ field.name }} {{ field.type }} {{ field.json_tag }}
|
|
25
|
+
{% endfor %}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
{% endfor %}
|