django-cfg 1.4.62__py3-none-any.whl → 1.4.63__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.

Files changed (181) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +577 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +228 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +374 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.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,6 @@
1
+ // Code generated by django-cfg/django_client - DO NOT EDIT.
2
+ // Generated at: {{ generated_at }}
3
+
4
+ module {{ module_name }}
5
+
6
+ go {{ go_version }}
@@ -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 %}