identity-plan-kit 0.3.1__tar.gz → 0.3.2__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.
Files changed (179) hide show
  1. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/Makefile +25 -2
  2. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/PKG-INFO +1 -1
  3. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/pyproject.toml +2 -2
  4. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/cache/plan_cache.py +87 -0
  5. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/services/plan_service.py +8 -4
  6. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/test_plan_cache_concurrent.py +105 -0
  7. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/uv.lock +1 -1
  8. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/.gitignore +0 -0
  9. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/README.md +0 -0
  10. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/alembic/env.py +0 -0
  11. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/alembic/script.py.mako +0 -0
  12. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/alembic/versions/20250124_000000_initial_schema.py +0 -0
  13. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/alembic/versions/20250127_000000_add_password_hash.py +0 -0
  14. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/alembic.ini +0 -0
  15. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/integration/__init__.py +0 -0
  16. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/integration/fastapi_integration.py +0 -0
  17. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/integration/sample_alembic_env.py +0 -0
  18. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/integration/webhook_integration.py +0 -0
  19. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/playing/admin_usage.py +0 -0
  20. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/playing/basic_usage.py +0 -0
  21. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/playing/custom_error_formats.py +0 -0
  22. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/playing/extending_models.html +0 -0
  23. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/playing/extension_example.py +0 -0
  24. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/.env.production.example +0 -0
  25. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/Dockerfile +0 -0
  26. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/docker-compose.yml +0 -0
  27. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/nginx.conf +0 -0
  28. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/production_setup.py +0 -0
  29. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/examples/prod/prometheus.yml +0 -0
  30. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/README.md +0 -0
  31. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/__init__.py +0 -0
  32. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/config.py +0 -0
  33. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/locustfile.py +0 -0
  34. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_auth.py +0 -0
  35. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_database_stress.py +0 -0
  36. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_mixed_scenarios.py +0 -0
  37. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_plans_quota.py +0 -0
  38. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_quota_direct.py +0 -0
  39. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/test_rbac_cache.py +0 -0
  40. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/loadtests/utils.py +0 -0
  41. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/__init__.py +0 -0
  42. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/admin/__init__.py +0 -0
  43. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/admin/auth.py +0 -0
  44. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/admin/views.py +0 -0
  45. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/__init__.py +0 -0
  46. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/dependencies.py +0 -0
  47. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/domain/__init__.py +0 -0
  48. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/domain/entities.py +0 -0
  49. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/domain/exceptions.py +0 -0
  50. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/dto/__init__.py +0 -0
  51. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/dto/requests.py +0 -0
  52. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/dto/responses.py +0 -0
  53. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/handlers/__init__.py +0 -0
  54. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/handlers/oauth_routes.py +0 -0
  55. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/models/__init__.py +0 -0
  56. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/models/refresh_token.py +0 -0
  57. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/models/user.py +0 -0
  58. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/models/user_provider.py +0 -0
  59. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/repositories/__init__.py +0 -0
  60. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/repositories/token_repo.py +0 -0
  61. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/repositories/user_repo.py +0 -0
  62. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/services/__init__.py +0 -0
  63. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/services/auth_service.py +0 -0
  64. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/services/oauth_service.py +0 -0
  65. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/auth/uow.py +0 -0
  66. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/cli.py +0 -0
  67. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/config.py +0 -0
  68. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/kit.py +0 -0
  69. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/migrations.py +0 -0
  70. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/__init__.py +0 -0
  71. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/cache/__init__.py +0 -0
  72. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/cache/user_plan_cache.py +0 -0
  73. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/dependencies.py +0 -0
  74. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/domain/__init__.py +0 -0
  75. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/domain/entities.py +0 -0
  76. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/domain/exceptions.py +0 -0
  77. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/dto/__init__.py +0 -0
  78. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/dto/responses.py +0 -0
  79. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/dto/usage.py +0 -0
  80. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/handlers/__init__.py +0 -0
  81. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/handlers/plan_routes.py +0 -0
  82. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/__init__.py +0 -0
  83. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/feature.py +0 -0
  84. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/feature_usage.py +0 -0
  85. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/plan.py +0 -0
  86. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/plan_limit.py +0 -0
  87. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/plan_permission.py +0 -0
  88. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/models/user_plan.py +0 -0
  89. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/repositories/__init__.py +0 -0
  90. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/repositories/plan_repo.py +0 -0
  91. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/repositories/usage_repo.py +0 -0
  92. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/services/__init__.py +0 -0
  93. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/plans/uow.py +0 -0
  94. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/__init__.py +0 -0
  95. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/cache/__init__.py +0 -0
  96. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/cache/permission_cache.py +0 -0
  97. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/dependencies.py +0 -0
  98. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/domain/__init__.py +0 -0
  99. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/domain/entities.py +0 -0
  100. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/domain/exceptions.py +0 -0
  101. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/models/__init__.py +0 -0
  102. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/models/permission.py +0 -0
  103. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/models/role.py +0 -0
  104. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/models/role_permission.py +0 -0
  105. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/repositories/__init__.py +0 -0
  106. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/repositories/rbac_repo.py +0 -0
  107. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/services/__init__.py +0 -0
  108. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/services/rbac_service.py +0 -0
  109. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/rbac/uow.py +0 -0
  110. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/README.md +0 -0
  111. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/__init__.py +0 -0
  112. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/audit.py +0 -0
  113. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/circuit_breaker.py +0 -0
  114. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/cleanup_scheduler.py +0 -0
  115. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/database.py +0 -0
  116. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/error_formatter.py +0 -0
  117. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/exception_handlers.py +0 -0
  118. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/exceptions.py +0 -0
  119. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/graceful_shutdown.py +0 -0
  120. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/health.py +0 -0
  121. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/http_utils.py +0 -0
  122. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/lockout.py +0 -0
  123. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/logging.py +0 -0
  124. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/metrics.py +0 -0
  125. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/models.py +0 -0
  126. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/rate_limiter.py +0 -0
  127. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/request_id.py +0 -0
  128. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/schemas.py +0 -0
  129. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/security.py +0 -0
  130. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/state_store.py +0 -0
  131. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/uow.py +0 -0
  132. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/src/identity_plan_kit/shared/uuid7.py +0 -0
  133. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/__init__.py +0 -0
  134. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/__init__.py +0 -0
  135. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_auth_service.py +0 -0
  136. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_circuit_breaker.py +0 -0
  137. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_csrf_state.py +0 -0
  138. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_dependencies.py +0 -0
  139. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_domain_entities.py +0 -0
  140. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_http_utils.py +0 -0
  141. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_lockout.py +0 -0
  142. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_lockout_concurrent.py +0 -0
  143. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_oauth_service_resilience.py +0 -0
  144. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_security_comprehensive.py +0 -0
  145. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/auth/test_token_security.py +0 -0
  146. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/conftest.py +0 -0
  147. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/__init__.py +0 -0
  148. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/conftest.py +0 -0
  149. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_concurrent_limit_merge.py +0 -0
  150. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_concurrent_plan_operations.py +0 -0
  151. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_concurrent_token_operations.py +0 -0
  152. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_concurrent_user_creation.py +0 -0
  153. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_concurrent_user_deactivation.py +0 -0
  154. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_limit_boundaries.py +0 -0
  155. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_plan_cache_race.py +0 -0
  156. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_quota_concurrency.py +0 -0
  157. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_token_cleanup.py +0 -0
  158. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_token_repository.py +0 -0
  159. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_usage_periods.py +0 -0
  160. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/integration/test_user_repository.py +0 -0
  161. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/__init__.py +0 -0
  162. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/test_dependencies.py +0 -0
  163. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/test_domain_entities.py +0 -0
  164. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/test_period_edge_cases.py +0 -0
  165. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/plans/test_plan_service.py +0 -0
  166. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/rbac/__init__.py +0 -0
  167. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/rbac/test_permission_cache.py +0 -0
  168. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/rbac/test_permission_cache_concurrent.py +0 -0
  169. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/rbac/test_rbac_cache_stampede.py +0 -0
  170. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/rbac/test_rbac_service.py +0 -0
  171. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_audit_pii_masking.py +0 -0
  172. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_circuit_breaker_concurrent.py +0 -0
  173. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_cleanup_scheduler_concurrent.py +0 -0
  174. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_config_validation.py +0 -0
  175. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_exception_handlers.py +0 -0
  176. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_http_utils_security.py +0 -0
  177. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_rate_limiter.py +0 -0
  178. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_rate_limiter_concurrent.py +0 -0
  179. {identity_plan_kit-0.3.1 → identity_plan_kit-0.3.2}/tests/shared/test_state_store_concurrent.py +0 -0
@@ -20,6 +20,9 @@ ENV_FILE ?= $(shell \
20
20
  else echo ""; \
21
21
  fi)
22
22
 
23
+ # PyPI config file (override with: make publish PYPIRC=path/to/.pypirc)
24
+ PYPIRC ?= $(HOME)/.pypirc
25
+
23
26
  help: ## Show this help message
24
27
  @echo "Usage: make [target]"
25
28
  @echo ""
@@ -116,13 +119,33 @@ build-wheels: ## Build binary wheels for distribution
116
119
  @echo "✓ Wheels built in dist/"
117
120
  @ls -lh dist/*.whl
118
121
 
119
- publish-test: build ## Publish package to TestPyPI
122
+ publish-test: build ## Publish package to TestPyPI (uses ~/.pypirc if present)
120
123
  @echo "Publishing to TestPyPI..."
124
+ ifeq ($(wildcard $(PYPIRC)),)
121
125
  $(UV) publish --index testpypi
126
+ else
127
+ @echo "Using credentials from $(PYPIRC)"
128
+ @TOKEN=$$(awk '/^\[testpypi\]/{found=1} found && /^password/{print $$3; exit}' $(PYPIRC)); \
129
+ if [ -n "$$TOKEN" ]; then \
130
+ $(UV) publish --index testpypi --token "$$TOKEN"; \
131
+ else \
132
+ $(UV) publish --index testpypi; \
133
+ fi
134
+ endif
122
135
 
123
- publish: build ## Publish package to PyPI
136
+ publish: build ## Publish package to PyPI (uses ~/.pypirc if present)
124
137
  @echo "Publishing to PyPI..."
138
+ ifeq ($(wildcard $(PYPIRC)),)
125
139
  $(UV) publish
140
+ else
141
+ @echo "Using credentials from $(PYPIRC)"
142
+ @TOKEN=$$(awk '/^\[pypi\]/{found=1} found && /^password/{print $$3; exit}' $(PYPIRC)); \
143
+ if [ -n "$$TOKEN" ]; then \
144
+ $(UV) publish --token "$$TOKEN"; \
145
+ else \
146
+ $(UV) publish; \
147
+ fi
148
+ endif
126
149
 
127
150
  ci: check test-cov ## Run all CI checks (lint, format, typecheck, tests with coverage)
128
151
  @echo "✓ All CI checks passed!"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: identity-plan-kit
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Modern FastAPI library for authentication, RBAC, subscription plans, and usage tracking
5
5
  Author-email: harut <harut.avetisyan2002@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "identity-plan-kit"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "Modern FastAPI library for authentication, RBAC, subscription plans, and usage tracking"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -140,7 +140,7 @@ testpaths = ["tests"]
140
140
  addopts = "-v --tb=short"
141
141
 
142
142
  [tool.bumpversion]
143
- current_version = "0.3.1"
143
+ current_version = "0.3.2"
144
144
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
145
145
  serialize = ["{major}.{minor}.{patch}"]
146
146
  tag = true
@@ -27,6 +27,20 @@ class PlanCacheEntry:
27
27
  return datetime.now(UTC) > self.expires_at
28
28
 
29
29
 
30
+ @dataclass
31
+ class AllPlansCacheEntry:
32
+ """Cache entry for the complete list of all plans."""
33
+
34
+ plans: list[Plan]
35
+ expires_at: datetime
36
+ fetched_at: float = field(default_factory=time.monotonic)
37
+
38
+ @property
39
+ def is_expired(self) -> bool:
40
+ """Check if entry has expired."""
41
+ return datetime.now(UTC) > self.expires_at
42
+
43
+
30
44
  class PlanCache:
31
45
  """
32
46
  In-memory plan cache.
@@ -69,6 +83,8 @@ class PlanCache:
69
83
  self._invalidated_at: dict[str, float] = {}
70
84
  # Global invalidation timestamp for invalidate_all()
71
85
  self._global_invalidated_at: float = 0.0
86
+ # Cache for the complete list of all plans
87
+ self._all_plans_cache: AllPlansCacheEntry | None = None
72
88
 
73
89
  async def get(self, plan_code: str) -> Plan | None:
74
90
  """
@@ -174,12 +190,80 @@ class PlanCache:
174
190
  self._cache[plan_code] = entry
175
191
  return True
176
192
 
193
+ async def get_all(self) -> list[Plan] | None:
194
+ """
195
+ Get cached list of all plans.
196
+
197
+ This operation is lock-free for better performance under high concurrency.
198
+
199
+ Returns:
200
+ List of all Plan entities or None if not cached/expired
201
+ """
202
+ if not self._enabled:
203
+ return None
204
+
205
+ entry = self._all_plans_cache
206
+ if entry is None:
207
+ return None
208
+
209
+ if entry.is_expired:
210
+ return None
211
+
212
+ logger.debug("all_plans_cache_hit", count=len(entry.plans))
213
+ return entry.plans
214
+
215
+ async def set_all(
216
+ self,
217
+ plans: list[Plan],
218
+ fetched_at: float | None = None,
219
+ ) -> bool:
220
+ """
221
+ Cache the complete list of all plans.
222
+
223
+ Also populates individual plan entries for single-plan lookups.
224
+
225
+ Args:
226
+ plans: List of all Plan entities to cache
227
+ fetched_at: Monotonic timestamp when plans were fetched from DB.
228
+ If provided, rejects stale writes after invalidation.
229
+
230
+ Returns:
231
+ True if cached, False if rejected as stale
232
+ """
233
+ if not self._enabled:
234
+ return False
235
+
236
+ # Check for stale write if fetched_at is provided
237
+ if fetched_at is not None and fetched_at < self._global_invalidated_at:
238
+ logger.debug(
239
+ "all_plans_cache_stale_write_rejected",
240
+ reason="global_invalidation",
241
+ fetched_at=fetched_at,
242
+ invalidated_at=self._global_invalidated_at,
243
+ )
244
+ return False
245
+
246
+ # Cache the all-plans list
247
+ self._all_plans_cache = AllPlansCacheEntry(
248
+ plans=plans,
249
+ expires_at=datetime.now(UTC) + self._ttl,
250
+ fetched_at=fetched_at or time.monotonic(),
251
+ )
252
+
253
+ # Also populate individual plan entries
254
+ for plan in plans:
255
+ await self.set(plan.code, plan, fetched_at=fetched_at)
256
+
257
+ logger.debug("all_plans_cached", count=len(plans))
258
+ return True
259
+
177
260
  async def invalidate(self, plan_code: str) -> None:
178
261
  """
179
262
  Invalidate cached plan by code.
180
263
 
181
264
  Records the invalidation timestamp to prevent stale writes from
182
265
  concurrent requests that fetched data before invalidation.
266
+ Also invalidates the all-plans cache since it contains stale data.
183
267
 
184
268
  Args:
185
269
  plan_code: Plan code to invalidate
@@ -189,6 +273,8 @@ class PlanCache:
189
273
  self._invalidated_at[plan_code] = time.monotonic()
190
274
  # dict.pop() is atomic in Python
191
275
  self._cache.pop(plan_code, None)
276
+ # Also invalidate the all-plans cache since it now contains stale data
277
+ self._all_plans_cache = None
192
278
  logger.debug("plan_cache_invalidated", plan_code=plan_code)
193
279
 
194
280
  async def invalidate_all(self) -> None:
@@ -202,6 +288,7 @@ class PlanCache:
202
288
  # Record global invalidation timestamp BEFORE clearing
203
289
  self._global_invalidated_at = time.monotonic()
204
290
  self._cache.clear()
291
+ self._all_plans_cache = None
205
292
  # Also clear key-specific invalidation timestamps to prevent memory growth
206
293
  self._invalidated_at.clear()
207
294
  logger.info("plan_cache_cleared")
@@ -1150,7 +1150,7 @@ class PlanService:
1150
1150
  This is an optimized method that loads all plans with their
1151
1151
  nested relationships in a minimal number of queries.
1152
1152
 
1153
- Plans are cached individually after being fetched for subsequent
1153
+ Plans are cached as a complete list and individually for subsequent
1154
1154
  single-plan lookups.
1155
1155
 
1156
1156
  Args:
@@ -1159,15 +1159,19 @@ class PlanService:
1159
1159
  Returns:
1160
1160
  List of all Plan entities with permissions and limits
1161
1161
  """
1162
+ # Check cache first
1163
+ cached_plans = await self._plan_cache.get_all()
1164
+ if cached_plans is not None:
1165
+ return cached_plans
1166
+
1162
1167
  # Get fetch timestamp BEFORE DB query for stale write prevention
1163
1168
  fetch_ts = self._plan_cache.get_fetch_timestamp()
1164
1169
 
1165
1170
  async with self._create_uow(session=session) as uow:
1166
1171
  plans = await uow.plans.get_all_plans()
1167
1172
 
1168
- # Cache each plan for future single-plan lookups
1169
- for plan in plans:
1170
- await self._plan_cache.set(plan.code, plan, fetched_at=fetch_ts)
1173
+ # Cache the complete list and individual plans
1174
+ await self._plan_cache.set_all(plans, fetched_at=fetch_ts)
1171
1175
 
1172
1176
  logger.debug(
1173
1177
  "all_plans_loaded",
@@ -585,3 +585,108 @@ class TestStaleWritePrevention:
585
585
  result = await cache.set("pro", plan) # No fetched_at
586
586
  assert result is True
587
587
  assert await cache.get("pro") == plan
588
+
589
+
590
+ class TestAllPlansCache:
591
+ """Tests for the all-plans cache (get_all/set_all)."""
592
+
593
+ async def test_get_all_returns_none_when_empty(self):
594
+ """get_all returns None when cache is empty."""
595
+ cache = PlanCache(ttl_seconds=60)
596
+ result = await cache.get_all()
597
+ assert result is None
598
+
599
+ async def test_set_all_and_get_all(self):
600
+ """set_all caches plans, get_all returns them."""
601
+ cache = PlanCache(ttl_seconds=60)
602
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
603
+
604
+ result = await cache.set_all(plans)
605
+ assert result is True
606
+
607
+ cached = await cache.get_all()
608
+ assert cached is not None
609
+ assert len(cached) == 3
610
+ assert all(p.code in [f"plan_{i}" for i in range(3)] for p in cached)
611
+
612
+ async def test_set_all_populates_individual_cache(self):
613
+ """set_all also populates individual plan cache entries."""
614
+ cache = PlanCache(ttl_seconds=60)
615
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
616
+
617
+ await cache.set_all(plans)
618
+
619
+ # Individual plans should be cached
620
+ for i in range(3):
621
+ cached = await cache.get(f"plan_{i}")
622
+ assert cached is not None
623
+ assert cached.code == f"plan_{i}"
624
+
625
+ async def test_invalidate_clears_all_plans_cache(self):
626
+ """Invalidating a single plan clears the all-plans cache."""
627
+ cache = PlanCache(ttl_seconds=60)
628
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
629
+
630
+ await cache.set_all(plans)
631
+ assert await cache.get_all() is not None
632
+
633
+ # Invalidate a single plan
634
+ await cache.invalidate("plan_1")
635
+
636
+ # All-plans cache should be cleared
637
+ assert await cache.get_all() is None
638
+
639
+ # Other individual plans should still be cached
640
+ assert await cache.get("plan_0") is not None
641
+ assert await cache.get("plan_2") is not None
642
+
643
+ async def test_invalidate_all_clears_all_plans_cache(self):
644
+ """invalidate_all clears the all-plans cache."""
645
+ cache = PlanCache(ttl_seconds=60)
646
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
647
+
648
+ await cache.set_all(plans)
649
+ assert await cache.get_all() is not None
650
+
651
+ await cache.invalidate_all()
652
+
653
+ assert await cache.get_all() is None
654
+
655
+ async def test_all_plans_cache_expires(self):
656
+ """All-plans cache respects TTL."""
657
+ cache = PlanCache(ttl_seconds=1)
658
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
659
+
660
+ await cache.set_all(plans)
661
+ assert await cache.get_all() is not None
662
+
663
+ # Wait for expiration
664
+ await asyncio.sleep(1.1)
665
+
666
+ assert await cache.get_all() is None
667
+
668
+ async def test_set_all_stale_write_rejected(self):
669
+ """set_all with stale fetch_ts is rejected after invalidate_all."""
670
+ cache = PlanCache(ttl_seconds=60)
671
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
672
+
673
+ # Get fetch timestamp before invalidation
674
+ fetch_ts = cache.get_fetch_timestamp()
675
+
676
+ await asyncio.sleep(0.001)
677
+ await cache.invalidate_all()
678
+
679
+ # Stale write should be rejected
680
+ result = await cache.set_all(plans, fetched_at=fetch_ts)
681
+ assert result is False
682
+ assert await cache.get_all() is None
683
+
684
+ async def test_disabled_cache_returns_none(self):
685
+ """Disabled cache (ttl=0) returns None for get_all."""
686
+ cache = PlanCache(ttl_seconds=0)
687
+ plans = [create_test_plan(f"plan_{i}") for i in range(3)]
688
+
689
+ result = await cache.set_all(plans)
690
+ assert result is False
691
+
692
+ assert await cache.get_all() is None
@@ -962,7 +962,7 @@ wheels = [
962
962
 
963
963
  [[package]]
964
964
  name = "identity-plan-kit"
965
- version = "0.3.0"
965
+ version = "0.3.1"
966
966
  source = { editable = "." }
967
967
  dependencies = [
968
968
  { name = "alembic" },