ccproxy-api 0.1.6__py3-none-any.whl → 0.2.0__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.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +439 -212
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +145 -176
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +402 -530
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +558 -0
- ccproxy/data/codex_headers_fallback.json +121 -0
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +63 -107
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +346 -314
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +95 -342
- ccproxy/utils/version_checker.py +279 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1231
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -269
- ccproxy/services/codex_detection_service.py +0 -263
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.6.dist-info/METADATA +0 -615
- ccproxy_api-0.1.6.dist-info/RECORD +0 -189
- ccproxy_api-0.1.6.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.6.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Auth manager registry for plugin system."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ccproxy.core.logging import get_logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuthManagerRegistry:
|
|
13
|
+
"""Registry for auth managers that can be referenced by name.
|
|
14
|
+
|
|
15
|
+
This registry uses Any types to avoid circular imports with the auth module.
|
|
16
|
+
The actual auth managers are expected to have a 'create' class method for
|
|
17
|
+
async instantiation.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self._factories: dict[str, Callable[[], Awaitable[Any]]] = {}
|
|
22
|
+
self._instances: dict[str, Any] = {}
|
|
23
|
+
|
|
24
|
+
def register_class(self, name: str, auth_manager_class: type[Any]) -> None:
|
|
25
|
+
"""Register an auth manager class that will be instantiated on demand.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
name: Name to register the auth manager under
|
|
29
|
+
auth_manager_class: Auth manager class to instantiate
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
async def create_instance() -> Any:
|
|
33
|
+
# Use the async create class method if available
|
|
34
|
+
if hasattr(auth_manager_class, "create"):
|
|
35
|
+
instance = await auth_manager_class.create()
|
|
36
|
+
return instance
|
|
37
|
+
else:
|
|
38
|
+
# Fallback to direct instantiation (requires storage parameter)
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
f"Auth manager class {auth_manager_class.__name__} must have a 'create' class method"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self._factories[name] = create_instance
|
|
44
|
+
logger.debug(
|
|
45
|
+
"auth_manager_class_registered",
|
|
46
|
+
name=name,
|
|
47
|
+
class_name=auth_manager_class.__name__,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def register_factory(
|
|
51
|
+
self, name: str, factory: Callable[[], Awaitable[Any]]
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Register a factory function for creating auth managers.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name: Name to register the auth manager under
|
|
57
|
+
factory: Factory function that returns an auth manager instance
|
|
58
|
+
"""
|
|
59
|
+
self._factories[name] = factory
|
|
60
|
+
logger.debug("auth_manager_factory_registered", name=name)
|
|
61
|
+
|
|
62
|
+
def register_instance(self, name: str, instance: Any) -> None:
|
|
63
|
+
"""Register an existing auth manager instance.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
name: Name to register the auth manager under
|
|
67
|
+
instance: Auth manager instance
|
|
68
|
+
"""
|
|
69
|
+
self._instances[name] = instance
|
|
70
|
+
logger.debug(
|
|
71
|
+
"auth_manager_instance_registered",
|
|
72
|
+
name=name,
|
|
73
|
+
instance_type=type(instance).__name__,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def unregister(self, name: str) -> None:
|
|
77
|
+
"""Unregister an auth manager.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
name: Name of auth manager to unregister
|
|
81
|
+
"""
|
|
82
|
+
if name in self._factories:
|
|
83
|
+
del self._factories[name]
|
|
84
|
+
if name in self._instances:
|
|
85
|
+
del self._instances[name]
|
|
86
|
+
logger.debug("auth_manager_unregistered", name=name)
|
|
87
|
+
|
|
88
|
+
async def get(self, name: str) -> Any | None:
|
|
89
|
+
"""Get an auth manager by name.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
name: Name of the auth manager to retrieve
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Auth manager instance or None if not found
|
|
96
|
+
"""
|
|
97
|
+
# Check for existing instance first
|
|
98
|
+
if name in self._instances:
|
|
99
|
+
return self._instances[name]
|
|
100
|
+
|
|
101
|
+
# Check for factory
|
|
102
|
+
if name in self._factories:
|
|
103
|
+
try:
|
|
104
|
+
instance = await self._factories[name]()
|
|
105
|
+
# Cache the instance for future use
|
|
106
|
+
self._instances[name] = instance
|
|
107
|
+
logger.debug(
|
|
108
|
+
"auth_manager_created",
|
|
109
|
+
name=name,
|
|
110
|
+
instance_type=type(instance).__name__,
|
|
111
|
+
)
|
|
112
|
+
return instance
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(
|
|
115
|
+
"auth_manager_creation_failed", name=name, error=str(e), exc_info=e
|
|
116
|
+
)
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
def has(self, name: str) -> bool:
|
|
122
|
+
"""Check if an auth manager is registered under the given name.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
name: Name to check
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
True if auth manager is registered, False otherwise
|
|
129
|
+
"""
|
|
130
|
+
return name in self._factories or name in self._instances
|
|
131
|
+
|
|
132
|
+
def list(self) -> dict[str, str]:
|
|
133
|
+
"""List all registered auth managers.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Dictionary mapping auth manager names to their types
|
|
137
|
+
"""
|
|
138
|
+
result = {}
|
|
139
|
+
|
|
140
|
+
# Add factories
|
|
141
|
+
for name in self._factories:
|
|
142
|
+
result[name] = "factory"
|
|
143
|
+
|
|
144
|
+
# Add instances
|
|
145
|
+
for name, instance in self._instances.items():
|
|
146
|
+
result[name] = type(instance).__name__
|
|
147
|
+
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
def get_class(self, name: str) -> type[Any] | None:
|
|
151
|
+
"""Get the auth manager class if registered via register_class.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
name: Name of the auth manager
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Auth manager class or None if not found or not registered as class
|
|
158
|
+
"""
|
|
159
|
+
# This is a bit hacky but works: we inspect the factory closure
|
|
160
|
+
# to extract the class that was registered
|
|
161
|
+
if name not in self._factories:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
factory = self._factories[name]
|
|
165
|
+
# Check if this factory was created by register_class
|
|
166
|
+
if hasattr(factory, "__closure__") and factory.__closure__:
|
|
167
|
+
for cell in factory.__closure__:
|
|
168
|
+
try:
|
|
169
|
+
obj = cell.cell_contents
|
|
170
|
+
if isinstance(obj, type):
|
|
171
|
+
return obj
|
|
172
|
+
except (AttributeError, ValueError):
|
|
173
|
+
continue
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
def clear(self) -> None:
|
|
177
|
+
"""Clear all registered auth managers."""
|
|
178
|
+
self._factories.clear()
|
|
179
|
+
self._instances.clear()
|
|
180
|
+
logger.debug("auth_manager_registry_cleared")
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Response caching for API requests."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = structlog.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class CacheEntry:
|
|
17
|
+
"""A cached response entry."""
|
|
18
|
+
|
|
19
|
+
key: str
|
|
20
|
+
data: Any
|
|
21
|
+
timestamp: float
|
|
22
|
+
ttl: float
|
|
23
|
+
|
|
24
|
+
def is_expired(self) -> bool:
|
|
25
|
+
"""Check if the cache entry has expired."""
|
|
26
|
+
return time.time() - self.timestamp > self.ttl
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ResponseCache:
|
|
30
|
+
"""In-memory response cache with TTL support."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, default_ttl: float = 300.0, max_size: int = 1000) -> None:
|
|
33
|
+
"""Initialize the response cache.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
default_ttl: Default time-to-live in seconds (5 minutes)
|
|
37
|
+
max_size: Maximum number of cached entries
|
|
38
|
+
"""
|
|
39
|
+
self.default_ttl = default_ttl
|
|
40
|
+
self.max_size = max_size
|
|
41
|
+
self._cache: dict[str, CacheEntry] = {}
|
|
42
|
+
self._access_order: list[str] = []
|
|
43
|
+
self.logger = logger
|
|
44
|
+
|
|
45
|
+
def _generate_key(
|
|
46
|
+
self,
|
|
47
|
+
method: str,
|
|
48
|
+
url: str,
|
|
49
|
+
body: bytes | None = None,
|
|
50
|
+
headers: dict[str, str] | None = None,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Generate a cache key for the request.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
method: HTTP method
|
|
56
|
+
url: Request URL
|
|
57
|
+
body: Request body
|
|
58
|
+
headers: Request headers
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Cache key string
|
|
62
|
+
"""
|
|
63
|
+
# Include important headers in cache key
|
|
64
|
+
cache_headers = {}
|
|
65
|
+
if headers:
|
|
66
|
+
for header in ["authorization", "x-api-key", "content-type"]:
|
|
67
|
+
if header in headers:
|
|
68
|
+
cache_headers[header] = headers[header]
|
|
69
|
+
|
|
70
|
+
key_parts = [
|
|
71
|
+
method,
|
|
72
|
+
url,
|
|
73
|
+
body.decode("utf-8") if body else "",
|
|
74
|
+
json.dumps(cache_headers, sort_keys=True),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
key_string = "|".join(key_parts)
|
|
78
|
+
return hashlib.sha256(key_string.encode()).hexdigest()
|
|
79
|
+
|
|
80
|
+
def get(
|
|
81
|
+
self,
|
|
82
|
+
method: str,
|
|
83
|
+
url: str,
|
|
84
|
+
body: bytes | None = None,
|
|
85
|
+
headers: dict[str, str] | None = None,
|
|
86
|
+
) -> Any | None:
|
|
87
|
+
"""Get a cached response if available and not expired.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
method: HTTP method
|
|
91
|
+
url: Request URL
|
|
92
|
+
body: Request body
|
|
93
|
+
headers: Request headers
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Cached response data or None
|
|
97
|
+
"""
|
|
98
|
+
key = self._generate_key(method, url, body, headers)
|
|
99
|
+
|
|
100
|
+
if key in self._cache:
|
|
101
|
+
entry = self._cache[key]
|
|
102
|
+
|
|
103
|
+
if entry.is_expired():
|
|
104
|
+
# Remove expired entry
|
|
105
|
+
del self._cache[key]
|
|
106
|
+
if key in self._access_order:
|
|
107
|
+
self._access_order.remove(key)
|
|
108
|
+
self.logger.debug("cache_entry_expired", key=key[:8])
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
# Update access order (LRU)
|
|
112
|
+
if key in self._access_order:
|
|
113
|
+
self._access_order.remove(key)
|
|
114
|
+
self._access_order.append(key)
|
|
115
|
+
|
|
116
|
+
self.logger.debug("cache_hit", key=key[:8])
|
|
117
|
+
return entry.data
|
|
118
|
+
|
|
119
|
+
self.logger.debug("cache_miss", key=key[:8])
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
def set(
|
|
123
|
+
self,
|
|
124
|
+
method: str,
|
|
125
|
+
url: str,
|
|
126
|
+
data: Any,
|
|
127
|
+
body: bytes | None = None,
|
|
128
|
+
headers: dict[str, str] | None = None,
|
|
129
|
+
ttl: float | None = None,
|
|
130
|
+
) -> None:
|
|
131
|
+
"""Cache a response.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
method: HTTP method
|
|
135
|
+
url: Request URL
|
|
136
|
+
data: Response data to cache
|
|
137
|
+
body: Request body
|
|
138
|
+
headers: Request headers
|
|
139
|
+
ttl: Time-to-live in seconds (uses default if None)
|
|
140
|
+
"""
|
|
141
|
+
# Don't cache streaming responses
|
|
142
|
+
if hasattr(data, "__aiter__"):
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
key = self._generate_key(method, url, body, headers)
|
|
146
|
+
ttl = ttl or self.default_ttl
|
|
147
|
+
|
|
148
|
+
# Enforce max size with LRU eviction
|
|
149
|
+
if (
|
|
150
|
+
len(self._cache) >= self.max_size
|
|
151
|
+
and key not in self._cache
|
|
152
|
+
and self._access_order
|
|
153
|
+
):
|
|
154
|
+
oldest_key = self._access_order.pop(0)
|
|
155
|
+
del self._cache[oldest_key]
|
|
156
|
+
self.logger.debug("cache_evicted", key=oldest_key[:8])
|
|
157
|
+
|
|
158
|
+
# Store the entry
|
|
159
|
+
self._cache[key] = CacheEntry(
|
|
160
|
+
key=key,
|
|
161
|
+
data=data,
|
|
162
|
+
timestamp=time.time(),
|
|
163
|
+
ttl=ttl,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Update access order
|
|
167
|
+
if key in self._access_order:
|
|
168
|
+
self._access_order.remove(key)
|
|
169
|
+
self._access_order.append(key)
|
|
170
|
+
|
|
171
|
+
self.logger.debug("cache_set", key=key[:8], ttl=ttl)
|
|
172
|
+
|
|
173
|
+
def invalidate(
|
|
174
|
+
self,
|
|
175
|
+
method: str | None = None,
|
|
176
|
+
url: str | None = None,
|
|
177
|
+
pattern: str | None = None,
|
|
178
|
+
) -> int:
|
|
179
|
+
"""Invalidate cached entries.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
method: HTTP method to match (None for any)
|
|
183
|
+
url: URL to match (None for any)
|
|
184
|
+
pattern: URL pattern to match (None for any)
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Number of entries invalidated
|
|
188
|
+
"""
|
|
189
|
+
keys_to_remove = []
|
|
190
|
+
|
|
191
|
+
for key, entry in self._cache.items():
|
|
192
|
+
should_remove = False
|
|
193
|
+
|
|
194
|
+
# Check if entry matches invalidation criteria
|
|
195
|
+
if pattern and pattern in str(entry.data.get("url", "")):
|
|
196
|
+
should_remove = True
|
|
197
|
+
elif method and url:
|
|
198
|
+
test_key = self._generate_key(method, url)
|
|
199
|
+
if key == test_key:
|
|
200
|
+
should_remove = True
|
|
201
|
+
|
|
202
|
+
if should_remove:
|
|
203
|
+
keys_to_remove.append(key)
|
|
204
|
+
|
|
205
|
+
# Remove matched entries
|
|
206
|
+
for key in keys_to_remove:
|
|
207
|
+
del self._cache[key]
|
|
208
|
+
if key in self._access_order:
|
|
209
|
+
self._access_order.remove(key)
|
|
210
|
+
|
|
211
|
+
if keys_to_remove:
|
|
212
|
+
self.logger.info(
|
|
213
|
+
"cache_invalidated",
|
|
214
|
+
count=len(keys_to_remove),
|
|
215
|
+
method=method,
|
|
216
|
+
url=url,
|
|
217
|
+
pattern=pattern,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return len(keys_to_remove)
|
|
221
|
+
|
|
222
|
+
def clear(self) -> None:
|
|
223
|
+
"""Clear all cached entries."""
|
|
224
|
+
count = len(self._cache)
|
|
225
|
+
self._cache.clear()
|
|
226
|
+
self._access_order.clear()
|
|
227
|
+
self.logger.info("cache_cleared", count=count)
|
|
228
|
+
|
|
229
|
+
def cleanup_expired(self) -> int:
|
|
230
|
+
"""Remove all expired entries.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Number of entries removed
|
|
234
|
+
"""
|
|
235
|
+
expired_keys = [key for key, entry in self._cache.items() if entry.is_expired()]
|
|
236
|
+
|
|
237
|
+
for key in expired_keys:
|
|
238
|
+
del self._cache[key]
|
|
239
|
+
if key in self._access_order:
|
|
240
|
+
self._access_order.remove(key)
|
|
241
|
+
|
|
242
|
+
if expired_keys:
|
|
243
|
+
self.logger.debug("cache_cleanup", removed=len(expired_keys))
|
|
244
|
+
|
|
245
|
+
return len(expired_keys)
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def size(self) -> int:
|
|
249
|
+
"""Get the current cache size."""
|
|
250
|
+
return len(self._cache)
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def stats(self) -> dict[str, Any]:
|
|
254
|
+
"""Get cache statistics."""
|
|
255
|
+
return {
|
|
256
|
+
"size": self.size,
|
|
257
|
+
"max_size": self.max_size,
|
|
258
|
+
"default_ttl": self.default_ttl,
|
|
259
|
+
"oldest_entry": self._access_order[0][:8] if self._access_order else None,
|
|
260
|
+
"newest_entry": self._access_order[-1][:8] if self._access_order else None,
|
|
261
|
+
}
|