revenium-python-sdk 0.9.0__tar.gz → 0.9.1__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 (150) hide show
  1. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/PKG-INFO +2 -2
  2. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/README.md +1 -1
  3. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/pyproject.toml +1 -1
  4. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/enforcement.py +57 -38
  5. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/PKG-INFO +2 -2
  6. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/LICENSE +0 -0
  7. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/__init__.py +0 -0
  8. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/__init__.py +0 -0
  9. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/cache_tokens.py +0 -0
  10. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/config.py +0 -0
  11. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/context.py +0 -0
  12. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/decorators.py +0 -0
  13. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/exceptions.py +0 -0
  14. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/fields.py +0 -0
  15. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/load_diagnostics.py +0 -0
  16. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/log_sanitize.py +0 -0
  17. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering.py +0 -0
  18. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_buffer.py +0 -0
  19. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_status.py +0 -0
  20. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/metering_submission.py +0 -0
  21. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/outcomes.py +0 -0
  22. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/patch_registry.py +0 -0
  23. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/prompt_extraction.py +0 -0
  24. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/subscriber.py +0 -0
  25. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_core/trace_fields.py +0 -0
  26. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/LICENSE +0 -0
  27. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/__init__.py +0 -0
  28. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_base_client.py +0 -0
  29. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_client.py +0 -0
  30. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_compat.py +0 -0
  31. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_constants.py +0 -0
  32. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_exceptions.py +0 -0
  33. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_files.py +0 -0
  34. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_models.py +0 -0
  35. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_qs.py +0 -0
  36. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_resource.py +0 -0
  37. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_response.py +0 -0
  38. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_streaming.py +0 -0
  39. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_types.py +0 -0
  40. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/__init__.py +0 -0
  41. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_logs.py +0 -0
  42. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_proxy.py +0 -0
  43. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_reflection.py +0 -0
  44. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_resources_proxy.py +0 -0
  45. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_streams.py +0 -0
  46. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_sync.py +0 -0
  47. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_transform.py +0 -0
  48. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_typing.py +0 -0
  49. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_utils/_utils.py +0 -0
  50. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/_version.py +0 -0
  51. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/context.py +0 -0
  52. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/decorator.py +0 -0
  53. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/py.typed +0 -0
  54. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/__init__.py +0 -0
  55. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/ai.py +0 -0
  56. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/apis.py +0 -0
  57. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/resources/events.py +0 -0
  58. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/__init__.py +0 -0
  59. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_audio_params.py +0 -0
  60. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_completion_params.py +0 -0
  61. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_image_params.py +0 -0
  62. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/ai_create_video_params.py +0 -0
  63. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/api_meter_request_params.py +0 -0
  64. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/api_meter_response_params.py +0 -0
  65. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/event_create_params.py +0 -0
  66. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/_metering/types/metering_response_resource.py +0 -0
  67. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/agentic_outcomes.py +0 -0
  68. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/__init__.py +0 -0
  69. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/bedrock_adapter.py +0 -0
  70. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/bedrock_transport.py +0 -0
  71. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/config.py +0 -0
  72. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/middleware.py +0 -0
  73. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/prompt_extractor.py +0 -0
  74. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/provider.py +0 -0
  75. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/stream_create.py +0 -0
  76. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/anthropic/trace_fields.py +0 -0
  77. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/__init__.py +0 -0
  78. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/_metering.py +0 -0
  79. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/config.py +0 -0
  80. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/middleware.py +0 -0
  81. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/fal/trace_fields.py +0 -0
  82. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/__init__.py +0 -0
  83. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/__init__.py +0 -0
  84. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/exceptions.py +0 -0
  85. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/protocols.py +0 -0
  86. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/trace_fields.py +0 -0
  87. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/types.py +0 -0
  88. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/common/utils.py +0 -0
  89. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/config.py +0 -0
  90. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/__init__.py +0 -0
  91. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/middleware.py +0 -0
  92. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/google_ai/provider.py +0 -0
  93. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/prompt_extractor.py +0 -0
  94. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/__init__.py +0 -0
  95. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/middleware.py +0 -0
  96. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/google/vertex_ai/provider.py +0 -0
  97. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/__init__.py +0 -0
  98. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/_metadata.py +0 -0
  99. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/anthropic_driver.py +0 -0
  100. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/litellm_driver.py +0 -0
  101. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/ollama_driver.py +0 -0
  102. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/openai_driver.py +0 -0
  103. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/openai_embedding_driver.py +0 -0
  104. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/griptape/universal_driver.py +0 -0
  105. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_context.py +0 -0
  106. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_history.py +0 -0
  107. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/job_type_economics.py +0 -0
  108. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/__init__.py +0 -0
  109. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/__init__.py +0 -0
  110. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/config.py +0 -0
  111. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/context.py +0 -0
  112. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/decorators.py +0 -0
  113. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/hooks.py +0 -0
  114. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/integrations/__init__.py +0 -0
  115. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/integrations/crewai.py +0 -0
  116. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/middleware.py +0 -0
  117. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/trace_fields.py +0 -0
  118. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/client/validation.py +0 -0
  119. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/proxy/__init__.py +0 -0
  120. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/proxy/_metering_owner.py +0 -0
  121. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/proxy/guardrail.py +0 -0
  122. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/litellm/proxy/middleware.py +0 -0
  123. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/__init__.py +0 -0
  124. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/middleware.py +0 -0
  125. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/ollama/trace_fields.py +0 -0
  126. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/__init__.py +0 -0
  127. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/azure_config.py +0 -0
  128. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/azure_model_resolver.py +0 -0
  129. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/config.py +0 -0
  130. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/exceptions.py +0 -0
  131. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/__init__.py +0 -0
  132. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/_utils.py +0 -0
  133. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/langchain/unified_handler.py +0 -0
  134. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/middleware.py +0 -0
  135. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/prompt_extractor.py +0 -0
  136. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/provider.py +0 -0
  137. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/openai/trace_fields.py +0 -0
  138. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/__init__.py +0 -0
  139. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/middleware.py +0 -0
  140. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/perplexity_sdk.py +0 -0
  141. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/provider.py +0 -0
  142. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/perplexity/trace_fields.py +0 -0
  143. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/webhooks/__init__.py +0 -0
  144. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_middleware/webhooks/_verify.py +0 -0
  145. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/SOURCES.txt +0 -0
  146. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/dependency_links.txt +0 -0
  147. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/requires.txt +0 -0
  148. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/revenium_python_sdk.egg-info/top_level.txt +0 -0
  149. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/setup.cfg +0 -0
  150. {revenium_python_sdk-0.9.0 → revenium_python_sdk-0.9.1}/tests/test_metering.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revenium-python-sdk
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai.
5
5
  Author-email: Revenium <support@revenium.io>
6
6
  License: MIT
@@ -978,7 +978,7 @@ guardrail to that one event type. `pre_call` alone enforces without metering;
978
978
 
979
979
  **Budget enforcement** reuses the SDK's own circuit breaker, so a proxy enforces
980
980
  exactly what every other Revenium integration enforces — including department
981
- (org-unit) budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
981
+ budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
982
982
  [Cost Controls](#cost-controls). A blocked call never reaches the provider and the
983
983
  caller receives HTTP 429:
984
984
 
@@ -881,7 +881,7 @@ guardrail to that one event type. `pre_call` alone enforces without metering;
881
881
 
882
882
  **Budget enforcement** reuses the SDK's own circuit breaker, so a proxy enforces
883
883
  exactly what every other Revenium integration enforces — including department
884
- (org-unit) budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
884
+ budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
885
885
  [Cost Controls](#cost-controls). A blocked call never reaches the provider and the
886
886
  caller receives HTTP 429:
887
887
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "revenium-python-sdk"
7
- version = "0.9.0"
7
+ version = "0.9.1"
8
8
  description = "The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -6,15 +6,17 @@ them in memory. ``check_enforcement(...)`` is a pre-call hook that raises
6
6
  ``BudgetExceededError`` when a tripped rule matches the current
7
7
  request, blocking the outbound provider call before any spend occurs.
8
8
 
9
- Department (org-unit) budgets are decided by the server: the rules response
10
- carries a top-level ``orgUnitBudgetBlocks`` map of subscriber email ->
11
- blocking rule id, pre-computed from org-unit membership, an
12
- ``orgUnitBudgetBlockBalances`` map giving the balance each of those people was
13
- actually judged against, and an ``orgUnitBudgetWarnings`` map of the people who
14
- have crossed a warn tier without being blocked. All three are keyed by the
15
- *normalized* address (trimmed, lower-cased), so the SDK re-keys them by that
16
- form as they come in and normalizes the caller's address the same way before
17
- looking it up; it never resolves org-unit identity itself.
9
+ Department budgets are decided by the server: the rules response carries a
10
+ top-level ``departmentBudgetBlocks`` map of subscriber email -> blocking rule
11
+ id, pre-computed from department membership, a
12
+ ``departmentBudgetBlockBalances`` map giving the balance each of those people
13
+ was actually judged against, and a ``departmentBudgetWarnings`` map of the
14
+ people who have crossed a warn tier without being blocked (a platform predating
15
+ BACK-3448 publishes them as ``orgUnitBudget*``, see ``_wire_value``). All three
16
+ are keyed by the *normalized* address (trimmed, lower-cased), so the SDK
17
+ re-keys them by that form as they come in and normalizes the caller's address
18
+ the same way before looking it up; it never resolves department identity
19
+ itself.
18
20
 
19
21
  Decision (BACK-3077): the warn tier surfaces as a log line and nothing else.
20
22
  A warning carries no directive a pre-call breaker can enforce -- raising on it
@@ -112,24 +114,26 @@ _RULES_CACHE_FILENAME = "revenium_enforcement_rules.json"
112
114
  # new data without being load-bearing for a downgrade.
113
115
  _ORG_UNIT_BLOCKS_CACHE_FILENAME = "revenium_enforcement_org_unit_blocks.json"
114
116
 
115
- # Top-level key of the server-computed department-budget map, on both the API
116
- # response and the disk snapshot.
117
- _ORG_UNIT_BLOCKS_KEY = "orgUnitBudgetBlocks"
117
+ # Top-level keys of the server-computed department-budget map on the API
118
+ # response, current spelling first (see ``_wire_value``).
119
+ _ORG_UNIT_BLOCKS_KEYS = ("departmentBudgetBlocks", "orgUnitBudgetBlocks")
118
120
  # Top-level key of the per-person balance map published beside the block map:
119
121
  # normalized email -> the balance that person's threshold was compared against,
120
122
  # in dollars. Absent on a server predating it, which is why every read of it
121
123
  # falls back to the rule's own ``currentValue``.
122
- _ORG_UNIT_BLOCK_BALANCES_KEY = "orgUnitBudgetBlockBalances"
124
+ _ORG_UNIT_BLOCK_BALANCES_KEYS = ("departmentBudgetBlockBalances", "orgUnitBudgetBlockBalances")
123
125
  # Top-level key of the warn-tier map published beside them: normalized email ->
124
126
  # the rule whose warn tier that person crossed, for a per-person cap scoped to
125
- # one department. Disjoint from _ORG_UNIT_BLOCKS_KEY (a person already blocked
127
+ # one department. Disjoint from _ORG_UNIT_BLOCKS_KEYS (a person already blocked
126
128
  # is not warned) and never a block verdict -- see the module docstring's
127
129
  # BACK-3077 decision. Absent on a server predating it, which warns nobody.
128
- _ORG_UNIT_WARNINGS_KEY = "orgUnitBudgetWarnings"
129
- # ``groupBy`` value of an org-unit rule. Such rules are skipped by the
130
- # per-rule loop: their verdict comes only from _ORG_UNIT_BLOCKS_KEY, matching
130
+ _ORG_UNIT_WARNINGS_KEYS = ("departmentBudgetWarnings", "orgUnitBudgetWarnings")
131
+ # ``groupBy`` values of a department rule. Such rules are skipped by the
132
+ # per-rule loop: their verdict comes only from _ORG_UNIT_BLOCKS_KEYS, matching
131
133
  # the server's own ``applicableRules`` filter.
132
- _ORG_UNIT_GROUP_BY = "ORG_UNIT"
134
+ _ORG_UNIT_GROUP_BY_VALUES = frozenset({"DEPARTMENT", "ORG_UNIT"})
135
+ # Rule field naming the department an ancestor-cap rule is scoped to.
136
+ _ORG_UNIT_ID_KEYS = ("departmentId", "orgUnitId")
133
137
  # Reported when the email map names a rule id that is absent from the cached
134
138
  # rules (a stale or racing payload). The server deliberately fails toward
135
139
  # enforcing here, so the block still happens — just without a rule name.
@@ -623,6 +627,20 @@ def _plan_retry(response: "httpx.Response", backoff: float, budget_remaining: fl
623
627
  return _RetryPlan(retry_after, budget_remaining - retry_after)
624
628
 
625
629
 
630
+ def _wire_value(payload: dict, keys: Tuple[str, ...]):
631
+ """The value under the first of ``keys`` that ``payload`` carries, else ``None``.
632
+
633
+ Each department field is read under its current name and then under its
634
+ org-unit spelling, which the platform served until BACK-3448 renamed it with
635
+ no alias; the fallback stays for one release so the SDK works on both sides.
636
+ """
637
+ for key in keys:
638
+ value = payload.get(key)
639
+ if value is not None:
640
+ return value
641
+ return None
642
+
643
+
626
644
  class _FetchedRules(NamedTuple):
627
645
  """One enforcement payload: the rules plus the department-budget maps."""
628
646
 
@@ -651,9 +669,9 @@ def _fetched_from_payload(data) -> Optional[_FetchedRules]:
651
669
  if isinstance(data, dict):
652
670
  return _FetchedRules(
653
671
  data.get("rules", []),
654
- _normalize_map_keys(data.get(_ORG_UNIT_BLOCKS_KEY)),
655
- _coerce_balances(data.get(_ORG_UNIT_BLOCK_BALANCES_KEY)),
656
- _normalize_map_keys(data.get(_ORG_UNIT_WARNINGS_KEY)),
672
+ _normalize_map_keys(_wire_value(data, _ORG_UNIT_BLOCKS_KEYS)),
673
+ _coerce_balances(_wire_value(data, _ORG_UNIT_BLOCK_BALANCES_KEYS)),
674
+ _normalize_map_keys(_wire_value(data, _ORG_UNIT_WARNINGS_KEYS)),
657
675
  )
658
676
  logger.warning("Unexpected enforcement response shape: %r", type(data).__name__)
659
677
  return None
@@ -788,8 +806,8 @@ def _fetch_rules(rule_id: Optional[str] = None) -> Optional[_FetchedRules]:
788
806
  return _FetchedRules([], {}, {}, {})
789
807
  response.raise_for_status()
790
808
  # Server currently returns ``{"rules": [...], "compiledAt": ...,
791
- # "orgUnitBudgetBlocks": {...}, "orgUnitBudgetBlockBalances":
792
- # {...}, "orgUnitBudgetWarnings": {...}}``; a bare list is accepted
809
+ # "departmentBudgetBlocks": {...}, "departmentBudgetBlockBalances":
810
+ # {...}, "departmentBudgetWarnings": {...}}``; a bare list is accepted
793
811
  # too. See ``_fetched_from_payload``.
794
812
  return _fetched_from_payload(response.json())
795
813
  except Exception:
@@ -1095,22 +1113,23 @@ def _rule_blocks(rule: dict, usage_metadata: Optional[dict]) -> Tuple[bool, Opti
1095
1113
 
1096
1114
 
1097
1115
  def _is_org_unit_rule(rule: dict) -> bool:
1098
- """True for any org-unit-scoped or org-unit-grouped rule.
1116
+ """True for any department-scoped or department-grouped rule.
1099
1117
 
1100
1118
  Mirrors the server's own ``applicableRules`` exclusion exactly:
1101
- ``orgUnitId == null && groupBy != "ORG_UNIT"`` — a rule is org-unit when
1102
- EITHER field says so. Ancestor-cap department rules carry
1103
- ``orgUnitId != null`` with a non-ORG_UNIT (typically null) ``groupBy``,
1119
+ ``departmentId == null && groupBy != "DEPARTMENT"`` — a rule is a
1120
+ department rule when EITHER field says so (``orgUnitId`` / ``ORG_UNIT`` on a
1121
+ platform predating BACK-3448). Ancestor-cap department rules carry
1122
+ ``departmentId != null`` with a non-DEPARTMENT (typically null) ``groupBy``,
1104
1123
  and their rule-level ``breached`` means "this department is over budget",
1105
1124
  not "this caller is over budget" — evaluated in the per-rule loop they
1106
1125
  would block every unrelated employee company-wide. Their verdict, like
1107
- the grouped shape's, lives solely in the ``orgUnitBudgetBlocks`` map,
1108
- because only the server knows which org unit the caller belongs to.
1126
+ the grouped shape's, lives solely in the ``departmentBudgetBlocks`` map,
1127
+ because only the server knows which department the caller belongs to.
1109
1128
  """
1110
- if rule.get("orgUnitId") is not None:
1129
+ if _wire_value(rule, _ORG_UNIT_ID_KEYS) is not None:
1111
1130
  return True
1112
1131
  group_by = rule.get("groupBy")
1113
- return isinstance(group_by, str) and group_by.strip().upper() == _ORG_UNIT_GROUP_BY
1132
+ return isinstance(group_by, str) and group_by.strip().upper() in _ORG_UNIT_GROUP_BY_VALUES
1114
1133
 
1115
1134
 
1116
1135
  def _normalize_email(email: str) -> str:
@@ -1134,7 +1153,7 @@ def _normalize_email(email: str) -> str:
1134
1153
  def _caller_emails(usage_metadata: Optional[dict]) -> List[str]:
1135
1154
  """The caller's normalized subscriber email(s), nested-first then flat.
1136
1155
 
1137
- Deliberately email-only: ``orgUnitBudgetBlocks`` is keyed by subscriber
1156
+ Deliberately email-only: ``departmentBudgetBlocks`` is keyed by subscriber
1138
1157
  email because that is the identity the server can resolve to an org-unit
1139
1158
  membership. A caller who supplies no email simply has no key, and — as
1140
1159
  with ``_matching_group_entry`` — no sentinel is invented for them: a
@@ -1203,7 +1222,7 @@ def _caller_block_balance(balances: dict, usage_metadata: Optional[dict]) -> Opt
1203
1222
  """The balance the server judged *this* caller against, when it published one.
1204
1223
 
1205
1224
  Mirrors the server's own violation consumer, which reads
1206
- ``orgUnitBudgetBlockBalances[email] ?: rule.currentValue``. For a per-person
1225
+ ``departmentBudgetBlockBalances[email] ?: rule.currentValue``. For a per-person
1207
1226
  cap scoped to one department the rule's own ``currentValue`` is the highest
1208
1227
  single balance in that department, so reporting it hands the blocked
1209
1228
  developer somebody else's spend.
@@ -1252,7 +1271,7 @@ def _raise_org_unit_block(rules: list, departments: _DepartmentBudgets,
1252
1271
  usage_metadata: Optional[dict]) -> None:
1253
1272
  """Raise ``BudgetExceededError`` when the department map blocks the caller.
1254
1273
 
1255
- Mirrors the server's own consumer of ``orgUnitBudgetBlocks``: the map is
1274
+ Mirrors the server's own consumer of ``departmentBudgetBlocks``: the map is
1256
1275
  the verdict, and the rule is looked up only to describe it. A map hit
1257
1276
  naming a rule that is not in the cached list — a stale or racing payload
1258
1277
  — still blocks, under a generic name, because the server publishes the
@@ -1425,11 +1444,11 @@ def check_enforcement(usage_metadata: Optional[dict] = None) -> None:
1425
1444
  from ``groupBreakdown`` rather than the rule-level aggregate; see
1426
1445
  ``_rule_blocks``.
1427
1446
 
1428
- Department (org-unit) budgets are decided before and independently of the
1429
- per-rule loop, from the server's ``orgUnitBudgetBlocks`` email map, looked
1447
+ Department budgets are decided before and independently of the
1448
+ per-rule loop, from the server's ``departmentBudgetBlocks`` email map, looked
1430
1449
  up under the normalized form of the caller's address; see
1431
1450
  ``_raise_org_unit_block``. A caller the server has only *warned* about
1432
- (``orgUnitBudgetWarnings``) is never blocked and never raised on: they get
1451
+ (``departmentBudgetWarnings``) is never blocked and never raised on: they get
1433
1452
  one log line per refreshed verdict, so the block is not the first thing
1434
1453
  they hear about their department budget. See ``_warn_org_unit_threshold``.
1435
1454
 
@@ -1440,7 +1459,7 @@ def check_enforcement(usage_metadata: Optional[dict] = None) -> None:
1440
1459
  server provides them. For a grouped rule ``current_value`` is the
1441
1460
  caller's group balance while ``threshold`` stays rule-level; for a
1442
1461
  department budget it is the caller's own balance from
1443
- ``orgUnitBudgetBlockBalances`` when the server publishes one.
1462
+ ``departmentBudgetBlockBalances`` when the server publishes one.
1444
1463
  """
1445
1464
  if is_bypass_enabled():
1446
1465
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: revenium-python-sdk
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: The official Revenium Python SDK — unified AI metering middleware for OpenAI, Anthropic, Google, Ollama, LiteLLM, Perplexity, and fal.ai.
5
5
  Author-email: Revenium <support@revenium.io>
6
6
  License: MIT
@@ -978,7 +978,7 @@ guardrail to that one event type. `pre_call` alone enforces without metering;
978
978
 
979
979
  **Budget enforcement** reuses the SDK's own circuit breaker, so a proxy enforces
980
980
  exactly what every other Revenium integration enforces — including department
981
- (org-unit) budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
981
+ budgets. It is opt-in via `REVENIUM_CIRCUIT_BREAKER_ENABLED=true`; see
982
982
  [Cost Controls](#cost-controls). A blocked call never reaches the provider and the
983
983
  caller receives HTTP 429:
984
984