django-cfg 1.4.3__py3-none-any.whl → 1.4.5__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.
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.2.25] - 2025-09-24
9
+
10
+ ### Added
11
+ - **Payment System Enhancements**: Unified payment provider configurations
12
+ - New `PaymentsConfig` model with provider-specific settings
13
+ - Enhanced validation utilities for API keys and subscription access
14
+ - Improved webhook handling and reliability
15
+ - Support for multiple payment providers (NowPayments, Cryptomus, etc.)
16
+ - **Template System**: Enhanced project template management
17
+ - Improved template extraction and project name replacement
18
+ - Better integration with CLI `create-project` command
19
+ - More reliable template archiving system
20
+
21
+ ### Changed
22
+ - **Project Structure**: Reorganized template location
23
+ - Moved Django sample project to `examples/django_sample`
24
+ - Improved template packaging for better distribution
25
+ - **Dependencies**: Updated dependency management
26
+ - Better version constraint handling
27
+ - Improved package compatibility
28
+
29
+ ### Fixed
30
+ - **Payment Validation**: Enhanced security for payment processing
31
+ - Improved API key validation
32
+ - Better webhook verification
33
+ - Fixed subscription access control issues
34
+ - **CLI Tools**: Improved reliability of project creation
35
+ - Fixed template extraction issues
36
+ - Better error handling for project setup
37
+ - Improved project name replacement logic
38
+
39
+ ### Security
40
+ - **Payment Processing**: Enhanced security measures
41
+ - Stronger API key validation
42
+ - Improved webhook verification
43
+ - Better access control for subscription features
44
+
45
+ ## [Previous Versions]
46
+
47
+ ### [1.2.24] and earlier
48
+ - Core Django-CFG functionality
49
+ - Basic payment provider support
50
+ - Configuration management system
51
+ - CLI tools for project creation
52
+ - Health monitoring modules
53
+ - Database and Redis integration
54
+
55
+ ---
56
+
57
+ **Note**: This changelog focuses on user-facing features and API changes in the Django-CFG package.
@@ -0,0 +1,145 @@
1
+ # 🤝 Contributing to Django-CFG
2
+
3
+ Thank you for your interest in contributing to Django-CFG! This project aims to make Django configuration simple, type-safe, and developer-friendly.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ### Development Setup
8
+
9
+ 1. **Clone the repository**
10
+ ```bash
11
+ git clone https://github.com/markolofsen/django-cfg.git
12
+ cd django-cfg
13
+ ```
14
+
15
+ 2. **Install dependencies**
16
+ ```bash
17
+ poetry install --extras dev
18
+ ```
19
+
20
+ 3. **Run tests**
21
+ ```bash
22
+ poetry run pytest
23
+ ```
24
+
25
+ ## 📋 Development Workflow
26
+
27
+ ### Making Changes
28
+
29
+ 1. **Create a feature branch**
30
+ ```bash
31
+ git checkout -b feature/your-feature-name
32
+ ```
33
+
34
+ 2. **Make your changes**
35
+ - Follow the existing code style
36
+ - Add tests for new features
37
+ - Update documentation if needed
38
+
39
+ 3. **Test your changes**
40
+ ```bash
41
+ # Run all tests
42
+ poetry run pytest
43
+
44
+ # Run with coverage
45
+ poetry run pytest --cov=django_cfg
46
+
47
+ # Run linting
48
+ poetry run black src/ tests/
49
+ poetry run isort src/ tests/
50
+ poetry run flake8 src/ tests/
51
+ ```
52
+
53
+ 4. **Update version and generate requirements**
54
+ ```bash
55
+ # Use our development CLI
56
+ poetry run python scripts/dev_cli.py
57
+
58
+ # Or manually bump version
59
+ poetry run python scripts/version_manager.py bump --bump-type patch
60
+ ```
61
+
62
+ ### Code Style
63
+
64
+ - **Python**: Follow PEP 8, use Black for formatting
65
+ - **Type Hints**: Required for all public APIs
66
+ - **Documentation**: Add docstrings for public methods
67
+ - **Tests**: Write tests for new features and bug fixes
68
+
69
+ ## 🧪 Testing
70
+
71
+ ### Running Tests
72
+
73
+ ```bash
74
+ # All tests
75
+ poetry run pytest
76
+
77
+ # Specific test file
78
+ poetry run pytest tests/test_basic_config.py
79
+
80
+ # With coverage report
81
+ poetry run pytest --cov=django_cfg --cov-report=html
82
+ ```
83
+
84
+ ### Writing Tests
85
+
86
+ - Place tests in the `tests/` directory
87
+ - Use descriptive test names
88
+ - Test both success and error cases
89
+ - Mock external dependencies
90
+
91
+ ## 📝 Pull Request Process
92
+
93
+ 1. **Ensure tests pass**
94
+ ```bash
95
+ poetry run pytest
96
+ ```
97
+
98
+ 2. **Update documentation** if your change affects the public API
99
+
100
+ 3. **Create a pull request** with:
101
+ - Clear description of changes
102
+ - Link to any related issues
103
+ - Screenshots for UI changes
104
+
105
+ 4. **Code review** - address any feedback from maintainers
106
+
107
+ ## 🐛 Bug Reports
108
+
109
+ When reporting bugs, please include:
110
+
111
+ - Django-CFG version
112
+ - Django version
113
+ - Python version
114
+ - Minimal code example
115
+ - Full error traceback
116
+
117
+ ## 💡 Feature Requests
118
+
119
+ For new features:
120
+
121
+ - Check existing issues first
122
+ - Describe the use case clearly
123
+ - Provide code examples if possible
124
+ - Consider backward compatibility
125
+
126
+ ## 📚 Documentation
127
+
128
+ Documentation improvements are always welcome:
129
+
130
+ - Fix typos and grammar
131
+ - Add examples and use cases
132
+ - Improve API documentation
133
+ - Update README with new features
134
+
135
+ ## ⚖️ License
136
+
137
+ By contributing, you agree that your contributions will be licensed under the MIT License.
138
+
139
+ ## 🙏 Recognition
140
+
141
+ All contributors will be recognized in our README and release notes.
142
+
143
+ ---
144
+
145
+ **Questions?** Open an issue or start a discussion. We're here to help! 🚀
django_cfg/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ReformsAI Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
django_cfg/__init__.py CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.3"
35
+ __version__ = "1.4.4"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
@@ -0,0 +1,5 @@
1
+ """
2
+ Django CFG Endpoints Status Check
3
+
4
+ Provides endpoints monitoring and status checking functionality.
5
+ """
@@ -0,0 +1,406 @@
1
+ """
2
+ Endpoints Status Checker
3
+
4
+ Utility for checking all registered Django URL endpoints.
5
+ """
6
+
7
+ import time
8
+ import re
9
+ from typing import List, Dict, Any, Optional
10
+ from django.urls import get_resolver, URLPattern, URLResolver
11
+ from django.test import Client
12
+ from django.utils import timezone
13
+ from django.contrib.auth import get_user_model
14
+
15
+
16
+ def get_url_group(url_pattern: str, depth: int = 3) -> str:
17
+ """
18
+ Extract group from URL pattern up to specified depth.
19
+
20
+ Examples:
21
+ /api/accounts/profile/ → api/accounts
22
+ /api/payments/webhook/status/ → api/payments/webhook
23
+ /cfg/health/drf/ → cfg/health/drf
24
+ /admin/auth/user/ → admin/auth/user
25
+
26
+ Args:
27
+ url_pattern: URL pattern string
28
+ depth: Maximum depth for grouping (default: 3)
29
+
30
+ Returns:
31
+ Group name as string
32
+ """
33
+ # Remove leading/trailing slashes and split
34
+ parts = [p for p in url_pattern.strip('/').split('/') if p and '<' not in p]
35
+
36
+ # Take up to depth parts
37
+ group_parts = parts[:depth]
38
+
39
+ return '/'.join(group_parts) if group_parts else 'root'
40
+
41
+
42
+ def should_check_endpoint(url_pattern: str, url_name: Optional[str] = None) -> bool:
43
+ """
44
+ Determine if endpoint should be checked.
45
+
46
+ Excludes:
47
+ - Health check endpoints (to avoid recursion)
48
+ - Admin endpoints
49
+ - Static/media files
50
+ - Django internal endpoints
51
+ - Schema/Swagger/Redoc documentation endpoints
52
+
53
+ Args:
54
+ url_pattern: URL pattern string
55
+ url_name: Optional URL name
56
+
57
+ Returns:
58
+ True if endpoint should be checked
59
+ """
60
+ # Exclude patterns
61
+ exclude_patterns = [
62
+ r'^/?static/',
63
+ r'^/?media/',
64
+ r'^/?admin/',
65
+ r'^/?cfg/health/', # Exclude health endpoints (recursion prevention)
66
+ r'^/?cfg/api/endpoints/', # Exclude ourselves
67
+ r'^/__debug__/',
68
+ r'^/__reload__/',
69
+ r'^/?schema/', # Exclude schema/swagger/redoc documentation endpoints
70
+ ]
71
+
72
+ for pattern in exclude_patterns:
73
+ if re.match(pattern, url_pattern):
74
+ return False
75
+
76
+ # Exclude URL names
77
+ exclude_names = [
78
+ 'django_cfg_health',
79
+ 'django_cfg_quick_health',
80
+ 'django_cfg_drf_health',
81
+ 'django_cfg_drf_quick_health',
82
+ 'endpoints_status',
83
+ 'endpoints_status_drf',
84
+ ]
85
+
86
+ if url_name in exclude_names:
87
+ return False
88
+
89
+ return True
90
+
91
+
92
+ def collect_endpoints(
93
+ urlpatterns=None,
94
+ prefix: str = '',
95
+ namespace: str = '',
96
+ include_unnamed: bool = True
97
+ ) -> List[Dict[str, Any]]:
98
+ """
99
+ Recursively collect all URL endpoints.
100
+
101
+ Args:
102
+ urlpatterns: URL patterns to process (default: root resolver)
103
+ prefix: Current URL prefix
104
+ namespace: Current URL namespace
105
+ include_unnamed: Include endpoints without names
106
+
107
+ Returns:
108
+ List of endpoint dictionaries
109
+ """
110
+ if urlpatterns is None:
111
+ resolver = get_resolver()
112
+ urlpatterns = resolver.url_patterns
113
+
114
+ endpoints = []
115
+
116
+ for pattern in urlpatterns:
117
+ if isinstance(pattern, URLResolver):
118
+ # This is an include() - recurse
119
+ new_prefix = prefix + str(pattern.pattern)
120
+ new_namespace = namespace
121
+
122
+ if hasattr(pattern, 'namespace') and pattern.namespace:
123
+ new_namespace = (
124
+ f"{namespace}:{pattern.namespace}"
125
+ if namespace
126
+ else pattern.namespace
127
+ )
128
+
129
+ # Recursively collect nested patterns
130
+ endpoints.extend(
131
+ collect_endpoints(
132
+ pattern.url_patterns,
133
+ new_prefix,
134
+ new_namespace,
135
+ include_unnamed
136
+ )
137
+ )
138
+
139
+ elif isinstance(pattern, URLPattern):
140
+ # Regular URL pattern
141
+ full_pattern = prefix + str(pattern.pattern)
142
+
143
+ # Clean up the pattern
144
+ clean_pattern = re.sub(r'\^|\$', '', full_pattern)
145
+ clean_pattern = re.sub(r'\\/', '/', clean_pattern)
146
+
147
+ # Ensure leading slash
148
+ if not clean_pattern.startswith('/'):
149
+ clean_pattern = '/' + clean_pattern
150
+
151
+ url_name = getattr(pattern, 'name', None)
152
+
153
+ # Skip unnamed if requested
154
+ if not include_unnamed and not url_name:
155
+ continue
156
+
157
+ # Check if should include this endpoint
158
+ if not should_check_endpoint(clean_pattern, url_name):
159
+ continue
160
+
161
+ # Skip patterns with required parameters (for now)
162
+ if '<' in clean_pattern:
163
+ endpoints.append({
164
+ 'url': clean_pattern,
165
+ 'url_name': url_name,
166
+ 'namespace': namespace,
167
+ 'group': get_url_group(clean_pattern),
168
+ 'status': 'skipped',
169
+ 'reason': 'requires_parameters',
170
+ })
171
+ continue
172
+
173
+ # Get view info
174
+ view_name = 'unknown'
175
+ if hasattr(pattern, 'callback'):
176
+ callback = pattern.callback
177
+ if hasattr(callback, 'view_class'):
178
+ view_name = callback.view_class.__name__
179
+ elif hasattr(callback, '__name__'):
180
+ view_name = callback.__name__
181
+
182
+ endpoints.append({
183
+ 'url': clean_pattern,
184
+ 'url_name': url_name,
185
+ 'namespace': namespace,
186
+ 'group': get_url_group(clean_pattern),
187
+ 'view': view_name,
188
+ 'status': 'pending',
189
+ })
190
+
191
+ return endpoints
192
+
193
+
194
+ def create_test_user_and_get_token() -> Optional[str]:
195
+ """
196
+ Create test user and generate JWT token.
197
+
198
+ Returns:
199
+ JWT access token or None if JWT not available
200
+ """
201
+ try:
202
+ from rest_framework_simplejwt.tokens import RefreshToken
203
+
204
+ User = get_user_model()
205
+
206
+ # Create or get test user
207
+ username = 'endpoint_test_user'
208
+ email = 'endpoint_test@test.com'
209
+
210
+ user, created = User.objects.get_or_create(
211
+ username=username,
212
+ defaults={'email': email, 'is_active': True}
213
+ )
214
+
215
+ if created:
216
+ user.set_password('testpass123')
217
+ user.save()
218
+
219
+ # Generate JWT token
220
+ refresh = RefreshToken.for_user(user)
221
+ access_token = str(refresh.access_token)
222
+
223
+ return access_token
224
+
225
+ except ImportError:
226
+ # JWT not installed
227
+ return None
228
+ except Exception:
229
+ # Any other error
230
+ return None
231
+
232
+
233
+ def check_endpoint(
234
+ endpoint: Dict[str, Any],
235
+ client: Optional[Client] = None,
236
+ timeout: int = 5,
237
+ auth_token: Optional[str] = None,
238
+ auto_auth: bool = True
239
+ ) -> tuple[Dict[str, Any], Optional[str]]:
240
+ """
241
+ Check a single endpoint health.
242
+
243
+ Automatically creates test user and retries with JWT if endpoint returns 401/403.
244
+
245
+ Args:
246
+ endpoint: Endpoint dictionary from collect_endpoints()
247
+ client: Django test client (creates new if None)
248
+ timeout: Request timeout in seconds
249
+ auth_token: JWT token (created automatically on first 401/403)
250
+ auto_auth: Auto-retry with JWT on 401/403 (default: True)
251
+
252
+ Returns:
253
+ Tuple of (updated endpoint dictionary, auth_token if created)
254
+ """
255
+ if client is None:
256
+ client = Client()
257
+
258
+ # Skip if already marked as skipped
259
+ if endpoint.get('status') == 'skipped':
260
+ return endpoint, auth_token
261
+
262
+ url = endpoint['url']
263
+ token_created = False
264
+
265
+ try:
266
+ start_time = time.time()
267
+
268
+ # First attempt - without auth
269
+ extra_headers = {'SERVER_NAME': 'localhost'}
270
+ response = client.get(url, timeout=timeout, **extra_headers)
271
+ response_time = (time.time() - start_time) * 1000 # Convert to ms
272
+ status_code = response.status_code
273
+
274
+ # If unauthorized and auto_auth enabled, retry with token
275
+ requires_auth = False
276
+ if status_code in [401, 403] and auto_auth:
277
+ requires_auth = True
278
+
279
+ # Create token if not provided (only once!)
280
+ if auth_token is None:
281
+ auth_token = create_test_user_and_get_token()
282
+ token_created = True
283
+
284
+ if auth_token:
285
+ start_time = time.time()
286
+ extra_headers['HTTP_AUTHORIZATION'] = f'Bearer {auth_token}'
287
+ response = client.get(url, timeout=timeout, **extra_headers)
288
+ response_time = (time.time() - start_time) * 1000 # Convert to ms
289
+ status_code = response.status_code
290
+
291
+ # Determine if healthy
292
+ # 200-299: Success
293
+ # 300-399: Redirects (OK)
294
+ # 401, 403: Auth required (expected, still healthy)
295
+ # 404: Not found (might be OK if endpoint exists but has no data)
296
+ # 405: Method not allowed (endpoint exists, just wrong method)
297
+ # 500+: Server errors (unhealthy)
298
+
299
+ is_healthy = status_code in [
300
+ 200, 201, 204, # Success
301
+ 301, 302, 303, 307, 308, # Redirects
302
+ 401, 403, # Auth required (expected)
303
+ 405, # Method not allowed (endpoint exists)
304
+ ]
305
+
306
+ # Special handling for 404
307
+ if status_code == 404:
308
+ # 404 might be OK for some endpoints (e.g., detail views with no data)
309
+ # Mark as warning rather than unhealthy
310
+ is_healthy = None # Will be marked as 'warning'
311
+
312
+ endpoint.update({
313
+ 'status_code': status_code,
314
+ 'response_time_ms': round(response_time, 2),
315
+ 'is_healthy': is_healthy,
316
+ 'status': 'healthy' if is_healthy else ('warning' if is_healthy is None else 'unhealthy'),
317
+ 'last_checked': timezone.now().isoformat(),
318
+ })
319
+
320
+ if requires_auth:
321
+ endpoint['required_auth'] = True
322
+
323
+ except Exception as e:
324
+ endpoint.update({
325
+ 'status_code': None,
326
+ 'response_time_ms': None,
327
+ 'is_healthy': False,
328
+ 'status': 'error',
329
+ 'error': str(e)[:200], # Truncate long errors
330
+ 'last_checked': timezone.now().isoformat(),
331
+ })
332
+
333
+ # Return endpoint and token (if it was created)
334
+ return endpoint, (auth_token if token_created else None)
335
+
336
+
337
+ def check_all_endpoints(
338
+ include_unnamed: bool = False,
339
+ timeout: int = 5,
340
+ auto_auth: bool = True
341
+ ) -> Dict[str, Any]:
342
+ """
343
+ Check all registered endpoints.
344
+
345
+ Args:
346
+ include_unnamed: Include endpoints without names
347
+ timeout: Request timeout in seconds
348
+ auto_auth: Automatically retry with JWT auth on 401/403 (default: True)
349
+
350
+ Returns:
351
+ Dictionary with overall status and all endpoints
352
+ """
353
+ # Collect endpoints
354
+ endpoints = collect_endpoints(include_unnamed=include_unnamed)
355
+
356
+ # Create client once
357
+ client = Client()
358
+
359
+ # Token will be created lazily on first 401/403
360
+ auth_token = None
361
+
362
+ # Check each endpoint
363
+ checked_endpoints = []
364
+ for endpoint in endpoints:
365
+ # Check endpoint (will auto-retry with JWT if needed)
366
+ checked, new_token = check_endpoint(
367
+ endpoint,
368
+ client=client,
369
+ timeout=timeout,
370
+ auth_token=auth_token,
371
+ auto_auth=auto_auth
372
+ )
373
+
374
+ # If token was created on first 401/403, save it for ALL subsequent endpoints
375
+ if new_token and auth_token is None:
376
+ auth_token = new_token
377
+
378
+ checked_endpoints.append(checked)
379
+
380
+ # Calculate statistics
381
+ total = len(checked_endpoints)
382
+ healthy = sum(1 for e in checked_endpoints if e.get('status') == 'healthy')
383
+ unhealthy = sum(1 for e in checked_endpoints if e.get('status') == 'unhealthy')
384
+ warnings = sum(1 for e in checked_endpoints if e.get('status') == 'warning')
385
+ errors = sum(1 for e in checked_endpoints if e.get('status') == 'error')
386
+ skipped = sum(1 for e in checked_endpoints if e.get('status') == 'skipped')
387
+
388
+ # Determine overall status
389
+ if errors > 0 or unhealthy > 0:
390
+ overall_status = 'unhealthy'
391
+ elif warnings > 0:
392
+ overall_status = 'degraded'
393
+ else:
394
+ overall_status = 'healthy'
395
+
396
+ return {
397
+ 'status': overall_status,
398
+ 'timestamp': timezone.now().isoformat(),
399
+ 'total_endpoints': total,
400
+ 'healthy': healthy,
401
+ 'unhealthy': unhealthy,
402
+ 'warnings': warnings,
403
+ 'errors': errors,
404
+ 'skipped': skipped,
405
+ 'endpoints': checked_endpoints,
406
+ }
@@ -0,0 +1,52 @@
1
+ """
2
+ Django CFG Endpoints Status DRF Views
3
+
4
+ DRF browsable API views with Tailwind theme support.
5
+ """
6
+
7
+ from rest_framework.views import APIView
8
+ from rest_framework.response import Response
9
+ from rest_framework import status
10
+ from rest_framework.permissions import AllowAny
11
+
12
+ from .checker import check_all_endpoints
13
+ from .serializers import EndpointsStatusSerializer
14
+
15
+
16
+ class DRFEndpointsStatusView(APIView):
17
+ """
18
+ Django CFG endpoints status check with DRF Browsable API.
19
+
20
+ Checks all registered URL endpoints and returns their health status.
21
+ Excludes health check endpoints and admin to avoid recursion.
22
+
23
+ Query Parameters:
24
+ - include_unnamed: Include endpoints without names (default: false)
25
+ - timeout: Request timeout in seconds (default: 5)
26
+
27
+ This endpoint uses DRF Browsable API with Tailwind CSS theme! 🎨
28
+ """
29
+
30
+ permission_classes = [AllowAny] # Public endpoint
31
+ serializer_class = EndpointsStatusSerializer # For schema generation
32
+
33
+ def get(self, request):
34
+ """Return endpoints status data."""
35
+ # Get query parameters
36
+ include_unnamed = request.query_params.get('include_unnamed', 'false').lower() == 'true'
37
+ timeout = int(request.query_params.get('timeout', 5))
38
+
39
+ # Check all endpoints
40
+ status_data = check_all_endpoints(
41
+ include_unnamed=include_unnamed,
42
+ timeout=timeout
43
+ )
44
+
45
+ # Return appropriate HTTP status
46
+ http_status = status.HTTP_200_OK
47
+ if status_data["status"] == "unhealthy":
48
+ http_status = status.HTTP_503_SERVICE_UNAVAILABLE
49
+ elif status_data["status"] == "degraded":
50
+ http_status = status.HTTP_200_OK # Still operational
51
+
52
+ return Response(status_data, status=http_status)