djapi-guard 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- djapi_guard-1.0.0/.github/FUNDING.yml +5 -0
- djapi_guard-1.0.0/.github/ISSUE_TEMPLATE/bug_report.md +78 -0
- djapi_guard-1.0.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- djapi_guard-1.0.0/.github/ISSUE_TEMPLATE/documentation.md +53 -0
- djapi_guard-1.0.0/.github/ISSUE_TEMPLATE/feature_request.md +60 -0
- djapi_guard-1.0.0/.github/PULL_REQUEST_TEMPLATE.md +66 -0
- djapi_guard-1.0.0/.github/dependabot.yml +17 -0
- djapi_guard-1.0.0/.github/scripts/bump_version.py +319 -0
- djapi_guard-1.0.0/.github/workflows/ci.yml +174 -0
- djapi_guard-1.0.0/.github/workflows/code-ql.yml +129 -0
- djapi_guard-1.0.0/.github/workflows/container-release.yml +152 -0
- djapi_guard-1.0.0/.github/workflows/docs.yml +193 -0
- djapi_guard-1.0.0/.github/workflows/release.yml +154 -0
- djapi_guard-1.0.0/.github/workflows/scheduled-lint.yml +201 -0
- djapi_guard-1.0.0/.gitignore +226 -0
- djapi_guard-1.0.0/.mike.yml +8 -0
- djapi_guard-1.0.0/.pre-commit-config.yaml +62 -0
- djapi_guard-1.0.0/CHANGELOG.md +29 -0
- djapi_guard-1.0.0/CLAUDE.md +94 -0
- djapi_guard-1.0.0/CODE_OF_CONDUCT.md +162 -0
- djapi_guard-1.0.0/CONTRIBUTING.md +132 -0
- djapi_guard-1.0.0/Dockerfile +32 -0
- djapi_guard-1.0.0/LICENSE +21 -0
- djapi_guard-1.0.0/MANIFEST.in +3 -0
- djapi_guard-1.0.0/Makefile +239 -0
- djapi_guard-1.0.0/PKG-INFO +805 -0
- djapi_guard-1.0.0/README.md +752 -0
- djapi_guard-1.0.0/SECURITY.md +114 -0
- djapi_guard-1.0.0/compose.yml +54 -0
- djapi_guard-1.0.0/djangoapi_guard/__init__.py +42 -0
- djapi_guard-1.0.0/djangoapi_guard/core/__init__.py +5 -0
- djapi_guard-1.0.0/djangoapi_guard/core/behavioral/__init__.py +6 -0
- djapi_guard-1.0.0/djangoapi_guard/core/behavioral/context.py +16 -0
- djapi_guard-1.0.0/djangoapi_guard/core/behavioral/processor.py +109 -0
- djapi_guard-1.0.0/djangoapi_guard/core/bypass/__init__.py +6 -0
- djapi_guard-1.0.0/djangoapi_guard/core/bypass/context.py +20 -0
- djapi_guard-1.0.0/djangoapi_guard/core/bypass/handler.py +67 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/__init__.py +45 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/base.py +107 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/helpers.py +331 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/__init__.py +67 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/authentication.py +65 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/cloud_ip_refresh.py +23 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/cloud_provider.py +91 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/custom_request.py +64 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/custom_validators.py +58 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/emergency_mode.py +63 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/https_enforcement.py +77 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/ip_security.py +149 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/rate_limit.py +225 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/referrer.py +99 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/request_logging.py +17 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/request_size_content.py +110 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/required_headers.py +83 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/route_config.py +27 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/suspicious_activity.py +172 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/time_window.py +74 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/implementations/user_agent.py +91 -0
- djapi_guard-1.0.0/djangoapi_guard/core/checks/pipeline.py +131 -0
- djapi_guard-1.0.0/djangoapi_guard/core/events/__init__.py +9 -0
- djapi_guard-1.0.0/djangoapi_guard/core/events/extension_events.py +164 -0
- djapi_guard-1.0.0/djangoapi_guard/core/events/metrics.py +80 -0
- djapi_guard-1.0.0/djangoapi_guard/core/initialization/__init__.py +10 -0
- djapi_guard-1.0.0/djangoapi_guard/core/initialization/handler_initializer.py +111 -0
- djapi_guard-1.0.0/djangoapi_guard/core/responses/__init__.py +6 -0
- djapi_guard-1.0.0/djangoapi_guard/core/responses/context.py +24 -0
- djapi_guard-1.0.0/djangoapi_guard/core/responses/factory.py +165 -0
- djapi_guard-1.0.0/djangoapi_guard/core/routing/__init__.py +6 -0
- djapi_guard-1.0.0/djangoapi_guard/core/routing/context.py +20 -0
- djapi_guard-1.0.0/djangoapi_guard/core/routing/resolver.py +103 -0
- djapi_guard-1.0.0/djangoapi_guard/core/validation/__init__.py +6 -0
- djapi_guard-1.0.0/djangoapi_guard/core/validation/context.py +14 -0
- djapi_guard-1.0.0/djangoapi_guard/core/validation/validator.py +83 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/__init__.py +59 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/access_control.py +87 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/advanced.py +82 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/authentication.py +65 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/base.py +223 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/behavioral.py +161 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/content_filtering.py +101 -0
- djapi_guard-1.0.0/djangoapi_guard/decorators/rate_limiting.py +47 -0
- djapi_guard-1.0.0/djangoapi_guard/detection_engine/__init__.py +11 -0
- djapi_guard-1.0.0/djangoapi_guard/detection_engine/compiler.py +230 -0
- djapi_guard-1.0.0/djangoapi_guard/detection_engine/monitor.py +547 -0
- djapi_guard-1.0.0/djangoapi_guard/detection_engine/preprocessor.py +440 -0
- djapi_guard-1.0.0/djangoapi_guard/detection_engine/semantic.py +504 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/__init__.py +21 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/behavior_handler.py +377 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/cloud_handler.py +299 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/dynamic_rule_handler.py +345 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/ipban_handler.py +147 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/ipinfo_handler.py +238 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/ratelimit_handler.py +245 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/redis_handler.py +286 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/security_headers_handler.py +552 -0
- djapi_guard-1.0.0/djangoapi_guard/handlers/suspatterns_handler.py +997 -0
- djapi_guard-1.0.0/djangoapi_guard/middleware.py +491 -0
- djapi_guard-1.0.0/djangoapi_guard/models.py +863 -0
- djapi_guard-1.0.0/djangoapi_guard/protocols/__init__.py +9 -0
- djapi_guard-1.0.0/djangoapi_guard/protocols/agent_protocol.py +76 -0
- djapi_guard-1.0.0/djangoapi_guard/protocols/geo_ip_protocol.py +16 -0
- djapi_guard-1.0.0/djangoapi_guard/protocols/redis_protocol.py +18 -0
- djapi_guard-1.0.0/djangoapi_guard/scripts/__init__.py +0 -0
- djapi_guard-1.0.0/djangoapi_guard/scripts/rate_lua.py +23 -0
- djapi_guard-1.0.0/djangoapi_guard/utils.py +848 -0
- djapi_guard-1.0.0/docs/api/behavior-manager.md +48 -0
- djapi_guard-1.0.0/docs/api/cloud-manager.md +38 -0
- djapi_guard-1.0.0/docs/api/core-architecture.md +68 -0
- djapi_guard-1.0.0/docs/api/decorators.md +106 -0
- djapi_guard-1.0.0/docs/api/ipban-manager.md +37 -0
- djapi_guard-1.0.0/docs/api/ipinfo-manager.md +39 -0
- djapi_guard-1.0.0/docs/api/overview.md +66 -0
- djapi_guard-1.0.0/docs/api/ratelimit-manager.md +35 -0
- djapi_guard-1.0.0/docs/api/redis-manager.md +45 -0
- djapi_guard-1.0.0/docs/api/security-headers.md +60 -0
- djapi_guard-1.0.0/docs/api/security-middleware.md +141 -0
- djapi_guard-1.0.0/docs/api/sus-patterns.md +59 -0
- djapi_guard-1.0.0/docs/api/utilities.md +57 -0
- djapi_guard-1.0.0/docs/assets/favicon.png +0 -0
- djapi_guard-1.0.0/docs/assets/logo.svg +9 -0
- djapi_guard-1.0.0/docs/css/custom.css +94 -0
- djapi_guard-1.0.0/docs/index.md +393 -0
- djapi_guard-1.0.0/docs/installation.md +87 -0
- djapi_guard-1.0.0/docs/overrides/partials/analytics.html +10 -0
- djapi_guard-1.0.0/docs/overrides/partials/footer.html +15 -0
- djapi_guard-1.0.0/docs/overrides/partials/version-select.html +14 -0
- djapi_guard-1.0.0/docs/release-notes.md +29 -0
- djapi_guard-1.0.0/docs/tutorial/advanced-customizations.md +238 -0
- djapi_guard-1.0.0/docs/tutorial/configuration/cors.md +103 -0
- djapi_guard-1.0.0/docs/tutorial/configuration/logging.md +239 -0
- djapi_guard-1.0.0/docs/tutorial/configuration/security-config.md +240 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/access-control.md +122 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/advanced.md +67 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/authentication.md +102 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/behavioral.md +87 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/content-filtering.md +106 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/overview.md +217 -0
- djapi_guard-1.0.0/docs/tutorial/decorators/rate-limiting.md +89 -0
- djapi_guard-1.0.0/docs/tutorial/examples/example-app.md +220 -0
- djapi_guard-1.0.0/docs/tutorial/first-steps.md +130 -0
- djapi_guard-1.0.0/docs/tutorial/ip-management/banning.md +64 -0
- djapi_guard-1.0.0/docs/tutorial/ip-management/cloud-providers.md +80 -0
- djapi_guard-1.0.0/docs/tutorial/ip-management/geolocation.md +101 -0
- djapi_guard-1.0.0/docs/tutorial/ip-management/rate-limiter.md +96 -0
- djapi_guard-1.0.0/docs/tutorial/redis-integration/caching.md +84 -0
- djapi_guard-1.0.0/docs/tutorial/security/custom-patterns.md +72 -0
- djapi_guard-1.0.0/docs/tutorial/security/detection-engine/architecture.md +54 -0
- djapi_guard-1.0.0/docs/tutorial/security/detection-engine/components.md +39 -0
- djapi_guard-1.0.0/docs/tutorial/security/detection-engine/configuration.md +65 -0
- djapi_guard-1.0.0/docs/tutorial/security/detection-engine/overview.md +51 -0
- djapi_guard-1.0.0/docs/tutorial/security/detection-engine/performance-tuning.md +48 -0
- djapi_guard-1.0.0/docs/tutorial/security/http-security-headers.md +78 -0
- djapi_guard-1.0.0/docs/tutorial/security/monitoring.md +59 -0
- djapi_guard-1.0.0/docs/tutorial/security/penetration-detection.md +72 -0
- djapi_guard-1.0.0/docs/tutorial/security/proxy-security.md +69 -0
- djapi_guard-1.0.0/docs/versions/versions.json +4 -0
- djapi_guard-1.0.0/examples/__init__.py +0 -0
- djapi_guard-1.0.0/examples/advanced/Dockerfile +40 -0
- djapi_guard-1.0.0/examples/advanced/README.md +78 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/__init__.py +0 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/models.py +80 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/__init__.py +25 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/access_control.py +66 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/admin.py +138 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/advanced.py +63 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/auth.py +67 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/basic.py +72 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/behavioral.py +76 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/content.py +87 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/headers.py +201 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/health.py +21 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/rate_limiting.py +53 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/routes/testing.py +98 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/security.py +106 -0
- djapi_guard-1.0.0/examples/advanced/advanced_app/views.py +0 -0
- djapi_guard-1.0.0/examples/advanced/advanced_project/__init__.py +0 -0
- djapi_guard-1.0.0/examples/advanced/advanced_project/settings.py +28 -0
- djapi_guard-1.0.0/examples/advanced/advanced_project/urls.py +75 -0
- djapi_guard-1.0.0/examples/advanced/advanced_project/wsgi.py +7 -0
- djapi_guard-1.0.0/examples/advanced/compose.yml +59 -0
- djapi_guard-1.0.0/examples/advanced/gunicorn.conf.py +21 -0
- djapi_guard-1.0.0/examples/advanced/manage.py +20 -0
- djapi_guard-1.0.0/examples/advanced/nginx/nginx.conf +65 -0
- djapi_guard-1.0.0/examples/advanced/pyproject.toml +15 -0
- djapi_guard-1.0.0/examples/simple/Dockerfile +17 -0
- djapi_guard-1.0.0/examples/simple/README.md +139 -0
- djapi_guard-1.0.0/examples/simple/__init__.py +0 -0
- djapi_guard-1.0.0/examples/simple/compose.yml +27 -0
- djapi_guard-1.0.0/examples/simple/example_app/__init__.py +0 -0
- djapi_guard-1.0.0/examples/simple/example_app/views.py +840 -0
- djapi_guard-1.0.0/examples/simple/example_project/__init__.py +0 -0
- djapi_guard-1.0.0/examples/simple/example_project/settings.py +106 -0
- djapi_guard-1.0.0/examples/simple/example_project/urls.py +66 -0
- djapi_guard-1.0.0/examples/simple/example_project/wsgi.py +7 -0
- djapi_guard-1.0.0/examples/simple/manage.py +20 -0
- djapi_guard-1.0.0/examples/simple/requirements.txt +3 -0
- djapi_guard-1.0.0/mkdocs.yml +181 -0
- djapi_guard-1.0.0/pyproject.toml +246 -0
- djapi_guard-1.0.0/setup.py +9 -0
- djapi_guard-1.0.0/tests/__init__.py +0 -0
- djapi_guard-1.0.0/tests/conftest.py +77 -0
- djapi_guard-1.0.0/tests/settings.py +27 -0
- djapi_guard-1.0.0/tests/test_agent/__init__.py +1 -0
- djapi_guard-1.0.0/tests/test_agent/conftest.py +265 -0
- djapi_guard-1.0.0/tests/test_agent/test_behavior_agent_integration.py +120 -0
- djapi_guard-1.0.0/tests/test_agent/test_cloud_agent_integration.py +158 -0
- djapi_guard-1.0.0/tests/test_agent/test_decorator_agent_integration.py +386 -0
- djapi_guard-1.0.0/tests/test_agent/test_drh_agent_integration.py +1322 -0
- djapi_guard-1.0.0/tests/test_agent/test_extension_agent_integration.py +888 -0
- djapi_guard-1.0.0/tests/test_agent/test_ipban_agent_integration.py +114 -0
- djapi_guard-1.0.0/tests/test_agent/test_ipinfo_agent_integration.py +298 -0
- djapi_guard-1.0.0/tests/test_agent/test_models_agent_integration.py +134 -0
- djapi_guard-1.0.0/tests/test_agent/test_ratelimit_agent_integration.py +218 -0
- djapi_guard-1.0.0/tests/test_agent/test_redis_agent_integration.py +192 -0
- djapi_guard-1.0.0/tests/test_agent/test_suspatterns_agent_integration.py +142 -0
- djapi_guard-1.0.0/tests/test_agent/test_utils_agent_integration.py +95 -0
- djapi_guard-1.0.0/tests/test_cloud_ips/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_cloud_ips/test_cloud_ips.py +277 -0
- djapi_guard-1.0.0/tests/test_core/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_core/test_behavioral.py +308 -0
- djapi_guard-1.0.0/tests/test_core/test_bypass_handler.py +233 -0
- djapi_guard-1.0.0/tests/test_core/test_check_implementations.py +838 -0
- djapi_guard-1.0.0/tests/test_core/test_checks_base.py +249 -0
- djapi_guard-1.0.0/tests/test_core/test_cloud_provider_edge_cases.py +164 -0
- djapi_guard-1.0.0/tests/test_core/test_edge_cases.py +419 -0
- djapi_guard-1.0.0/tests/test_core/test_https_enforcement_edge_cases.py +530 -0
- djapi_guard-1.0.0/tests/test_core/test_initialization.py +466 -0
- djapi_guard-1.0.0/tests/test_core/test_ip_security_edge_cases.py +189 -0
- djapi_guard-1.0.0/tests/test_core/test_metrics.py +102 -0
- djapi_guard-1.0.0/tests/test_core/test_pipeline.py +266 -0
- djapi_guard-1.0.0/tests/test_core/test_rate_limit_edge_cases.py +327 -0
- djapi_guard-1.0.0/tests/test_core/test_routing_resolver.py +236 -0
- djapi_guard-1.0.0/tests/test_core/test_time_window_edge_cases.py +86 -0
- djapi_guard-1.0.0/tests/test_core/test_validator.py +276 -0
- djapi_guard-1.0.0/tests/test_coverage_gaps.py +1345 -0
- djapi_guard-1.0.0/tests/test_decorators/__init__.py +1 -0
- djapi_guard-1.0.0/tests/test_decorators/test_access_control.py +178 -0
- djapi_guard-1.0.0/tests/test_decorators/test_advanced.py +201 -0
- djapi_guard-1.0.0/tests/test_decorators/test_advanced_edge_cases.py +150 -0
- djapi_guard-1.0.0/tests/test_decorators/test_authentication.py +274 -0
- djapi_guard-1.0.0/tests/test_decorators/test_base.py +163 -0
- djapi_guard-1.0.0/tests/test_decorators/test_behavior_handler.py +503 -0
- djapi_guard-1.0.0/tests/test_decorators/test_behavior_handler_edge_cases.py +118 -0
- djapi_guard-1.0.0/tests/test_decorators/test_behavioral.py +330 -0
- djapi_guard-1.0.0/tests/test_decorators/test_content_filtering.py +222 -0
- djapi_guard-1.0.0/tests/test_decorators/test_decorator_events.py +230 -0
- djapi_guard-1.0.0/tests/test_decorators/test_extension_integration.py +540 -0
- djapi_guard-1.0.0/tests/test_decorators/test_rate_limiting.py +137 -0
- djapi_guard-1.0.0/tests/test_extension/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_extension/test_security_extension.py +627 -0
- djapi_guard-1.0.0/tests/test_features/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_features/test_configurable_cloud_refresh.py +142 -0
- djapi_guard-1.0.0/tests/test_features/test_context_aware_detection.py +143 -0
- djapi_guard-1.0.0/tests/test_features/test_last_updated_cloud.py +84 -0
- djapi_guard-1.0.0/tests/test_features/test_structured_json_logging.py +99 -0
- djapi_guard-1.0.0/tests/test_handlers/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_handlers/test_agent_events.py +357 -0
- djapi_guard-1.0.0/tests/test_handlers/test_dynamic_rule_handler.py +985 -0
- djapi_guard-1.0.0/tests/test_handlers/test_handler_redis_paths.py +750 -0
- djapi_guard-1.0.0/tests/test_ipinfo/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_ipinfo/test_ipinfo.py +289 -0
- djapi_guard-1.0.0/tests/test_middleware.py +1327 -0
- djapi_guard-1.0.0/tests/test_models/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_models/test_models.py +273 -0
- djapi_guard-1.0.0/tests/test_models_legacy.py +78 -0
- djapi_guard-1.0.0/tests/test_redis/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_redis/test_redis.py +235 -0
- djapi_guard-1.0.0/tests/test_security_headers/__init__.py +1 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_agent_integration.py +266 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_cors_integration.py +259 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_csp_validation.py +94 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_edge_cases.py +109 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_extension_integration.py +96 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_headers_core.py +426 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_redis_integration.py +315 -0
- djapi_guard-1.0.0/tests/test_security_headers/test_security_validation.py +203 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/conftest.py +71 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_compiler.py +317 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_detection_engine.py +75 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_extension_integration.py +115 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_monitor.py +717 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_preprocessor.py +459 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_semantic.py +500 -0
- djapi_guard-1.0.0/tests/test_sus_patterns/test_sus_patterns.py +714 -0
- djapi_guard-1.0.0/tests/test_utils/__init__.py +0 -0
- djapi_guard-1.0.0/tests/test_utils/test_ip_ban_manager.py +94 -0
- djapi_guard-1.0.0/tests/test_utils/test_logging.py +399 -0
- djapi_guard-1.0.0/tests/test_utils/test_proxy_handling.py +154 -0
- djapi_guard-1.0.0/tests/test_utils/test_request_checks.py +654 -0
- djapi_guard-1.0.0/tests/test_utils/test_utils_edge_cases.py +265 -0
- djapi_guard-1.0.0/tests/test_utils_legacy.py +112 -0
- djapi_guard-1.0.0/tests/urls.py +21 -0
- djapi_guard-1.0.0/vulture_whitelist.py +128 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
name: Bug report
|
|
4
|
+
about: Create a report to help us improve DjangoAPI Guard
|
|
5
|
+
title: '[BUG] '
|
|
6
|
+
labels: bug
|
|
7
|
+
assignees: ''
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Bug Description
|
|
11
|
+
===============
|
|
12
|
+
|
|
13
|
+
A clear and concise description of what the bug is.
|
|
14
|
+
|
|
15
|
+
___
|
|
16
|
+
|
|
17
|
+
Steps To Reproduce
|
|
18
|
+
------------------
|
|
19
|
+
|
|
20
|
+
Steps to reproduce the behavior:
|
|
21
|
+
|
|
22
|
+
1. Configure DjangoAPI Guard with '...'
|
|
23
|
+
2. Make request to endpoint '....'
|
|
24
|
+
3. See error
|
|
25
|
+
|
|
26
|
+
___
|
|
27
|
+
|
|
28
|
+
Expected Behavior
|
|
29
|
+
-----------------
|
|
30
|
+
|
|
31
|
+
A clear and concise description of what you expected to happen.
|
|
32
|
+
|
|
33
|
+
___
|
|
34
|
+
|
|
35
|
+
Actual Behavior
|
|
36
|
+
---------------
|
|
37
|
+
|
|
38
|
+
What actually happened, including error messages, stack traces, or logs.
|
|
39
|
+
|
|
40
|
+
___
|
|
41
|
+
|
|
42
|
+
Environment
|
|
43
|
+
-----------
|
|
44
|
+
|
|
45
|
+
- DjangoAPI Guard version: [e.g. 1.0.0]
|
|
46
|
+
- Python version: [e.g. 3.11.10]
|
|
47
|
+
- Django version: [e.g. 5.1.0]
|
|
48
|
+
- OS: [e.g. Ubuntu 22.04, Windows 11, MacOS 15.4]
|
|
49
|
+
- Other relevant dependencies:
|
|
50
|
+
|
|
51
|
+
___
|
|
52
|
+
|
|
53
|
+
Configuration
|
|
54
|
+
-------------
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Include your DjangoAPI Guard configuration here
|
|
58
|
+
# settings.py
|
|
59
|
+
MIDDLEWARE = [
|
|
60
|
+
"djangoapi_guard.middleware.DjangoAPIGuardMiddleware",
|
|
61
|
+
# ...
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
DJANGOAPI_GUARD = {
|
|
65
|
+
# Include your config here
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
___
|
|
70
|
+
|
|
71
|
+
Additional Context
|
|
72
|
+
------------------
|
|
73
|
+
|
|
74
|
+
Add any other context about the problem here. For example:
|
|
75
|
+
|
|
76
|
+
- Is this happening in production or development?
|
|
77
|
+
- Does it happen consistently or intermittently?
|
|
78
|
+
- Have you tried any workarounds?
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Django API Guard Documentation
|
|
4
|
+
url: https://rennf93.github.io/djapi-guard/
|
|
5
|
+
about: Check the documentation for usage information and tutorials
|
|
6
|
+
- name: Usage Questions
|
|
7
|
+
url: https://github.com/rennf93/djapi-guard/discussions
|
|
8
|
+
about: Please ask and answer questions in GitHub Discussions
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
name: Documentation
|
|
4
|
+
about: Report issues with the DjangoAPI Guard documentation or suggest improvements
|
|
5
|
+
title: '[DOCS] '
|
|
6
|
+
labels: documentation
|
|
7
|
+
assignees: ''
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Documentation Issue
|
|
11
|
+
===================
|
|
12
|
+
|
|
13
|
+
Describe the issue or improvement needed for the documentation.
|
|
14
|
+
|
|
15
|
+
___
|
|
16
|
+
|
|
17
|
+
Current Documentation
|
|
18
|
+
---------------------
|
|
19
|
+
|
|
20
|
+
Link to the current documentation page or section that needs improvement:
|
|
21
|
+
[Link it here](https://example.com)
|
|
22
|
+
|
|
23
|
+
___
|
|
24
|
+
|
|
25
|
+
Suggested Changes
|
|
26
|
+
-----------------
|
|
27
|
+
|
|
28
|
+
Describe what changes you suggest:
|
|
29
|
+
|
|
30
|
+
- [ ] Clarify existing content
|
|
31
|
+
- [ ] Add missing information
|
|
32
|
+
- [ ] Fix incorrect information
|
|
33
|
+
- [ ] Improve examples
|
|
34
|
+
- [ ] Fix typos/grammar
|
|
35
|
+
- [ ] Other (please specify)
|
|
36
|
+
|
|
37
|
+
___
|
|
38
|
+
|
|
39
|
+
Proposed Text
|
|
40
|
+
-------------
|
|
41
|
+
|
|
42
|
+
If you have a specific suggestion for new text, please provide it here:
|
|
43
|
+
|
|
44
|
+
```markdown
|
|
45
|
+
# Your proposed documentation text here
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
___
|
|
49
|
+
|
|
50
|
+
Additional Context
|
|
51
|
+
------------------
|
|
52
|
+
|
|
53
|
+
Any other information or screenshots about the documentation issue.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
name: Feature request
|
|
4
|
+
about: Suggest an idea for DjangoAPI Guard
|
|
5
|
+
title: '[FEATURE] '
|
|
6
|
+
labels: enhancement
|
|
7
|
+
assignees: ''
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
Is your feature request related to a problem
|
|
11
|
+
=============================================
|
|
12
|
+
|
|
13
|
+
A clear and concise description of what the problem is. Ex. I'm always facing issues when [...]
|
|
14
|
+
|
|
15
|
+
___
|
|
16
|
+
|
|
17
|
+
Describe the solution you'd like
|
|
18
|
+
---------------------------------
|
|
19
|
+
|
|
20
|
+
A clear and concise description of what you want to happen. Include any API design ideas or examples of how you'd like to use this feature.
|
|
21
|
+
|
|
22
|
+
___
|
|
23
|
+
|
|
24
|
+
Describe alternatives you've considered
|
|
25
|
+
---------------------------------------
|
|
26
|
+
|
|
27
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
28
|
+
|
|
29
|
+
___
|
|
30
|
+
|
|
31
|
+
Example Implementation
|
|
32
|
+
----------------------
|
|
33
|
+
|
|
34
|
+
If possible, provide a pseudocode example of how this feature might be implemented or used:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Example code showing how you'd like to use this feature
|
|
38
|
+
# settings.py
|
|
39
|
+
MIDDLEWARE = [
|
|
40
|
+
"djangoapi_guard.middleware.DjangoAPIGuardMiddleware",
|
|
41
|
+
# ...
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
DJANGOAPI_GUARD = {
|
|
45
|
+
"new_feature": {
|
|
46
|
+
# Your feature configuration here
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
___
|
|
52
|
+
|
|
53
|
+
Additional context
|
|
54
|
+
------------------
|
|
55
|
+
|
|
56
|
+
Add any other context or screenshots about the feature request here:
|
|
57
|
+
|
|
58
|
+
- How would this benefit the community?
|
|
59
|
+
- Are there similar implementations in other libraries that could be referenced?
|
|
60
|
+
- Would this require changes to existing APIs?
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Description
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
<!--- Describe your changes in detail -->
|
|
5
|
+
|
|
6
|
+
___
|
|
7
|
+
|
|
8
|
+
Related Issue
|
|
9
|
+
-------------
|
|
10
|
+
|
|
11
|
+
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
|
|
12
|
+
<!--- Please link to the issue here: -->
|
|
13
|
+
Fixes #
|
|
14
|
+
|
|
15
|
+
___
|
|
16
|
+
|
|
17
|
+
Motivation and Context
|
|
18
|
+
----------------------
|
|
19
|
+
|
|
20
|
+
<!--- Why is this change required? What problem does it solve? -->
|
|
21
|
+
<!--- If adding a new feature, please describe the motivation for it -->
|
|
22
|
+
|
|
23
|
+
___
|
|
24
|
+
|
|
25
|
+
Type of change
|
|
26
|
+
--------------
|
|
27
|
+
|
|
28
|
+
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
|
29
|
+
|
|
30
|
+
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
31
|
+
- [ ] New feature (non-breaking change which adds functionality)
|
|
32
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
|
33
|
+
- [ ] Documentation change
|
|
34
|
+
- [ ] Performance improvement
|
|
35
|
+
- [ ] Code cleanup or refactoring
|
|
36
|
+
|
|
37
|
+
___
|
|
38
|
+
|
|
39
|
+
How Has This Been Tested
|
|
40
|
+
-------------------------
|
|
41
|
+
|
|
42
|
+
<!--- Please describe in detail how you tested your changes. -->
|
|
43
|
+
<!--- Include details of your testing environment, and the tests you ran to -->
|
|
44
|
+
<!--- see how your change affects other areas of the code, etc. -->
|
|
45
|
+
|
|
46
|
+
___
|
|
47
|
+
|
|
48
|
+
Screenshots (if appropriate)
|
|
49
|
+
-----------------------------
|
|
50
|
+
|
|
51
|
+
___
|
|
52
|
+
|
|
53
|
+
Checklist
|
|
54
|
+
---------
|
|
55
|
+
|
|
56
|
+
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
|
57
|
+
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
|
58
|
+
|
|
59
|
+
- [ ] My code follows the code style of this project (Mypy, Ruff)
|
|
60
|
+
- [ ] I have added tests to cover my changes
|
|
61
|
+
- [ ] All new and existing tests passed
|
|
62
|
+
- [ ] My change requires a change to the documentation
|
|
63
|
+
- [ ] I have updated the documentation accordingly
|
|
64
|
+
- [ ] I have checked that my changes don't introduce any new warnings or errors
|
|
65
|
+
- [ ] I have updated the version number if necessary
|
|
66
|
+
- [ ] I have added any new dependencies to the appropriate requirements file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
|
|
8
|
+
- package-ecosystem: "pip"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
12
|
+
groups:
|
|
13
|
+
dependencies:
|
|
14
|
+
patterns:
|
|
15
|
+
- "*"
|
|
16
|
+
allow:
|
|
17
|
+
- dependency-type: "all"
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version bump helper script for djapi-guard.
|
|
3
|
+
|
|
4
|
+
Updates the version string across all files that reference it:
|
|
5
|
+
- pyproject.toml
|
|
6
|
+
- .mike.yml
|
|
7
|
+
- docs/versions/versions.json
|
|
8
|
+
- docs/index.md
|
|
9
|
+
- CHANGELOG.md
|
|
10
|
+
- docs/release-notes.md
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python .github/scripts/bump_version.py <version>
|
|
14
|
+
make bump-version VERSION=x.y.z
|
|
15
|
+
|
|
16
|
+
No external dependencies required — stdlib only.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from collections.abc import Callable
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
# Resolve project root relative to this script's location
|
|
29
|
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
|
30
|
+
|
|
31
|
+
VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+$")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def parse_semver(version: str) -> tuple[int, ...]:
|
|
35
|
+
"""Parse a semver string into a comparable tuple."""
|
|
36
|
+
return tuple(int(part) for part in version.split("."))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def is_latest(new_version: str, existing_versions: list[str]) -> bool:
|
|
40
|
+
"""Return True if new_version is >= all existing versions."""
|
|
41
|
+
new_parsed = parse_semver(new_version)
|
|
42
|
+
for v in existing_versions:
|
|
43
|
+
try:
|
|
44
|
+
if parse_semver(v) > new_parsed:
|
|
45
|
+
return False
|
|
46
|
+
except ValueError:
|
|
47
|
+
continue
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def sorted_versions_descending(versions: list[str]) -> list[str]:
|
|
52
|
+
"""Sort version strings in descending semver order."""
|
|
53
|
+
semver_versions = []
|
|
54
|
+
non_semver = []
|
|
55
|
+
for v in versions:
|
|
56
|
+
try:
|
|
57
|
+
semver_versions.append((parse_semver(v), v))
|
|
58
|
+
except ValueError:
|
|
59
|
+
non_semver.append(v)
|
|
60
|
+
semver_versions.sort(reverse=True)
|
|
61
|
+
return [v for _, v in semver_versions] + non_semver
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def update_pyproject_toml(version: str) -> bool:
|
|
65
|
+
"""Update version in pyproject.toml."""
|
|
66
|
+
path = PROJECT_ROOT / "pyproject.toml"
|
|
67
|
+
content = path.read_text()
|
|
68
|
+
pattern = re.compile(r'^(version\s*=\s*)"[^"]*"', re.MULTILINE)
|
|
69
|
+
match = pattern.search(content)
|
|
70
|
+
if not match:
|
|
71
|
+
print(" ERROR: Could not find version field in pyproject.toml")
|
|
72
|
+
return False
|
|
73
|
+
current = re.search(r'"([^"]*)"', match.group(0))
|
|
74
|
+
if current and current.group(1) == version:
|
|
75
|
+
print(f" pyproject.toml: already set to {version}")
|
|
76
|
+
return True
|
|
77
|
+
new_content = pattern.sub(f'{match.group(1)}"{version}"', content)
|
|
78
|
+
path.write_text(new_content)
|
|
79
|
+
print(f" pyproject.toml: updated to {version}")
|
|
80
|
+
return True
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def update_mike_yml(version: str) -> bool:
|
|
84
|
+
"""Update .mike.yml with new version entry and latest alias."""
|
|
85
|
+
path = PROJECT_ROOT / ".mike.yml"
|
|
86
|
+
content = path.read_text()
|
|
87
|
+
lines = content.splitlines()
|
|
88
|
+
|
|
89
|
+
# Extract existing versions from the versions list
|
|
90
|
+
existing_versions: list[str] = []
|
|
91
|
+
versions_start = -1
|
|
92
|
+
versions_end = -1
|
|
93
|
+
in_versions = False
|
|
94
|
+
for i, line in enumerate(lines):
|
|
95
|
+
if line.startswith("versions:"):
|
|
96
|
+
versions_start = i
|
|
97
|
+
in_versions = True
|
|
98
|
+
continue
|
|
99
|
+
if in_versions:
|
|
100
|
+
stripped = line.strip()
|
|
101
|
+
if stripped.startswith("- "):
|
|
102
|
+
entry = stripped[2:].strip()
|
|
103
|
+
existing_versions.append(entry)
|
|
104
|
+
versions_end = i
|
|
105
|
+
else:
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
if versions_start == -1:
|
|
109
|
+
print(" ERROR: Could not find versions list in .mike.yml")
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# Check if version already exists
|
|
113
|
+
if version in existing_versions:
|
|
114
|
+
print(f" .mike.yml: version {version} already present")
|
|
115
|
+
else:
|
|
116
|
+
# Build new versions list: semver versions sorted descending, then "latest"
|
|
117
|
+
semver_versions = [v for v in existing_versions if v != "latest"]
|
|
118
|
+
semver_versions.append(version)
|
|
119
|
+
sorted_versions = sorted_versions_descending(semver_versions)
|
|
120
|
+
if "latest" in existing_versions:
|
|
121
|
+
sorted_versions.append("latest")
|
|
122
|
+
|
|
123
|
+
# Rebuild the versions block
|
|
124
|
+
new_version_lines = [f" - {v}" for v in sorted_versions]
|
|
125
|
+
lines = (
|
|
126
|
+
lines[: versions_start + 1] + new_version_lines + lines[versions_end + 1 :]
|
|
127
|
+
)
|
|
128
|
+
print(f" .mike.yml: added version {version}")
|
|
129
|
+
|
|
130
|
+
# Update the latest alias if this is the newest version
|
|
131
|
+
semver_only = [v for v in existing_versions + [version] if v != "latest"]
|
|
132
|
+
if is_latest(version, semver_only):
|
|
133
|
+
alias_pattern = re.compile(r"^(\s*latest:\s*).+$")
|
|
134
|
+
for i, line in enumerate(lines):
|
|
135
|
+
match = alias_pattern.match(line)
|
|
136
|
+
if match:
|
|
137
|
+
if line.strip() == f"latest: {version}":
|
|
138
|
+
print(f" .mike.yml: latest alias already points to {version}")
|
|
139
|
+
else:
|
|
140
|
+
lines[i] = f" latest: {version}"
|
|
141
|
+
print(f" .mike.yml: updated latest alias to {version}")
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
path.write_text("\n".join(lines) + "\n")
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def update_versions_json(version: str) -> bool:
|
|
149
|
+
"""Update docs/versions/versions.json."""
|
|
150
|
+
path = PROJECT_ROOT / "docs" / "versions" / "versions.json"
|
|
151
|
+
data: dict[str, str] = json.loads(path.read_text())
|
|
152
|
+
|
|
153
|
+
if version in data and data.get("latest") == version:
|
|
154
|
+
print(f" versions.json: already contains {version}")
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
changed = False
|
|
158
|
+
if version not in data:
|
|
159
|
+
data[version] = version
|
|
160
|
+
changed = True
|
|
161
|
+
print(f" versions.json: added {version}")
|
|
162
|
+
|
|
163
|
+
# Determine if this is the latest
|
|
164
|
+
semver_keys = [k for k in data if k != "latest"]
|
|
165
|
+
if is_latest(version, semver_keys):
|
|
166
|
+
if data.get("latest") != version:
|
|
167
|
+
data["latest"] = version
|
|
168
|
+
changed = True
|
|
169
|
+
print(f" versions.json: updated latest to {version}")
|
|
170
|
+
|
|
171
|
+
if not changed:
|
|
172
|
+
print(f" versions.json: already up to date for {version}")
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
# Sort: semver keys descending, then "latest" at the end
|
|
176
|
+
semver_keys = [k for k in data if k != "latest"]
|
|
177
|
+
sorted_keys = sorted_versions_descending(semver_keys)
|
|
178
|
+
if "latest" in data:
|
|
179
|
+
sorted_keys.append("latest")
|
|
180
|
+
|
|
181
|
+
ordered: dict[str, str] = {k: data[k] for k in sorted_keys}
|
|
182
|
+
path.write_text(json.dumps(ordered, indent=4) + "\n")
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def update_index_md(version: str) -> bool:
|
|
187
|
+
"""Update docker pull version tag in docs/index.md."""
|
|
188
|
+
path = PROJECT_ROOT / "docs" / "index.md"
|
|
189
|
+
content = path.read_text()
|
|
190
|
+
|
|
191
|
+
pattern = re.compile(
|
|
192
|
+
r"(docker pull ghcr\.io/rennf93/djapi-guard-example:v)"
|
|
193
|
+
r"[\d]+\.[\d]+\.[\d]+"
|
|
194
|
+
)
|
|
195
|
+
match = pattern.search(content)
|
|
196
|
+
if not match:
|
|
197
|
+
print(" docs/index.md: no docker pull version tag found, skipping")
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
current_version = match.group(0).split(":v")[-1]
|
|
201
|
+
if current_version == version:
|
|
202
|
+
print(f" docs/index.md: already set to v{version}")
|
|
203
|
+
return True
|
|
204
|
+
|
|
205
|
+
# Only update if this is the latest version
|
|
206
|
+
# We check against pyproject.toml's current version as reference
|
|
207
|
+
pyproject = PROJECT_ROOT / "pyproject.toml"
|
|
208
|
+
pyproject_content = pyproject.read_text()
|
|
209
|
+
pyproject_match = re.search(
|
|
210
|
+
r'^version\s*=\s*"([^"]*)"', pyproject_content, re.MULTILINE
|
|
211
|
+
)
|
|
212
|
+
existing_versions = [pyproject_match.group(1)] if pyproject_match else []
|
|
213
|
+
existing_versions.append(current_version)
|
|
214
|
+
|
|
215
|
+
if not is_latest(version, existing_versions):
|
|
216
|
+
print(f" docs/index.md: {version} is not latest, skipping docker tag update")
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
new_content = pattern.sub(f"\\g<1>{version}", content)
|
|
220
|
+
path.write_text(new_content)
|
|
221
|
+
print(f" docs/index.md: updated docker tag to v{version}")
|
|
222
|
+
return True
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _insert_changelog_scaffold(path: Path, version: str, label: str) -> bool:
|
|
226
|
+
"""Insert a version scaffold block into a changelog file."""
|
|
227
|
+
content = path.read_text()
|
|
228
|
+
today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
|
|
229
|
+
header = f"v{version} ({today})"
|
|
230
|
+
|
|
231
|
+
# Check if this version already has an entry
|
|
232
|
+
if f"v{version} (" in content:
|
|
233
|
+
print(f" {label}: v{version} entry already exists")
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
scaffold = (
|
|
237
|
+
f"{header}\n"
|
|
238
|
+
f"-------------------\n"
|
|
239
|
+
f"\n"
|
|
240
|
+
f"TITLE (v{version})\n"
|
|
241
|
+
f"------------\n"
|
|
242
|
+
f"\n"
|
|
243
|
+
f"CONTENT\n"
|
|
244
|
+
f"\n"
|
|
245
|
+
f"___\n"
|
|
246
|
+
f"\n"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Find the first existing version entry to insert before it
|
|
250
|
+
version_header_pattern = re.compile(r"^v\d+\.\d+\.\d+ \(", re.MULTILINE)
|
|
251
|
+
match = version_header_pattern.search(content)
|
|
252
|
+
if match:
|
|
253
|
+
insert_pos = match.start()
|
|
254
|
+
new_content = content[:insert_pos] + scaffold + content[insert_pos:]
|
|
255
|
+
else:
|
|
256
|
+
# No existing entries — append at end
|
|
257
|
+
new_content = content.rstrip() + "\n\n" + scaffold
|
|
258
|
+
|
|
259
|
+
path.write_text(new_content)
|
|
260
|
+
print(f" {label}: added v{version} scaffold")
|
|
261
|
+
return True
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def update_changelogs(version: str) -> bool:
|
|
265
|
+
"""Update CHANGELOG.md and docs/release-notes.md."""
|
|
266
|
+
changelog = PROJECT_ROOT / "CHANGELOG.md"
|
|
267
|
+
release_notes = PROJECT_ROOT / "docs" / "release-notes.md"
|
|
268
|
+
|
|
269
|
+
ok = True
|
|
270
|
+
ok = _insert_changelog_scaffold(changelog, version, "CHANGELOG.md") and ok
|
|
271
|
+
ok = (
|
|
272
|
+
_insert_changelog_scaffold(release_notes, version, "docs/release-notes.md")
|
|
273
|
+
and ok
|
|
274
|
+
)
|
|
275
|
+
return ok
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def main() -> int:
|
|
279
|
+
if len(sys.argv) != 2:
|
|
280
|
+
print("Usage: bump_version.py <version>")
|
|
281
|
+
print(" version must be in X.Y.Z format")
|
|
282
|
+
return 1
|
|
283
|
+
|
|
284
|
+
version = sys.argv[1]
|
|
285
|
+
|
|
286
|
+
if not VERSION_PATTERN.match(version):
|
|
287
|
+
print(f"Error: '{version}' is not a valid version. Expected format: X.Y.Z")
|
|
288
|
+
return 1
|
|
289
|
+
|
|
290
|
+
print(f"Bumping version to {version}...\n")
|
|
291
|
+
|
|
292
|
+
updaters: list[tuple[str, Callable[[str], bool]]] = [
|
|
293
|
+
("pyproject.toml", update_pyproject_toml),
|
|
294
|
+
(".mike.yml", update_mike_yml),
|
|
295
|
+
("docs/versions/versions.json", update_versions_json),
|
|
296
|
+
("docs/index.md", update_index_md),
|
|
297
|
+
("changelogs", update_changelogs),
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
all_ok = True
|
|
301
|
+
for name, updater in updaters:
|
|
302
|
+
try:
|
|
303
|
+
if not updater(version):
|
|
304
|
+
print(f"\n FAILED: {name}")
|
|
305
|
+
all_ok = False
|
|
306
|
+
except Exception as e:
|
|
307
|
+
print(f"\n ERROR updating {name}: {e}")
|
|
308
|
+
all_ok = False
|
|
309
|
+
|
|
310
|
+
print()
|
|
311
|
+
if all_ok:
|
|
312
|
+
print("Version bump complete.")
|
|
313
|
+
else:
|
|
314
|
+
print("Version bump completed with errors.")
|
|
315
|
+
return 0 if all_ok else 1
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
sys.exit(main())
|