proxilion 0.0.1__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.
Files changed (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,67 @@
1
+ """
2
+ Policy system for Proxilion.
3
+
4
+ This module provides the Pundit-inspired policy pattern for defining
5
+ authorization rules. Policies are classes that define what actions
6
+ a user can perform on a resource.
7
+
8
+ Quick Start:
9
+ >>> from proxilion.policies import Policy, PolicyRegistry
10
+ >>>
11
+ >>> registry = PolicyRegistry()
12
+ >>>
13
+ >>> @registry.policy("document")
14
+ >>> class DocumentPolicy(Policy):
15
+ ... def can_read(self, context: dict) -> bool:
16
+ ... return True # All users can read
17
+ ...
18
+ ... def can_write(self, context: dict) -> bool:
19
+ ... return "editor" in self.user.roles
20
+ ...
21
+ ... def can_delete(self, context: dict) -> bool:
22
+ ... return "admin" in self.user.roles
23
+ >>>
24
+ >>> # Check permissions
25
+ >>> policy = registry.get_policy_instance("document", user, document)
26
+ >>> if policy.can("write"):
27
+ ... document.save()
28
+ """
29
+
30
+ from proxilion.policies.base import (
31
+ ActionContext,
32
+ Policy,
33
+ PolicyWithScope,
34
+ Scope,
35
+ )
36
+ from proxilion.policies.builtin import (
37
+ AllowAllPolicy,
38
+ AttributeBasedPolicy,
39
+ CompositePolicy,
40
+ DenyAllPolicy,
41
+ OwnershipPolicy,
42
+ RoleBasedPolicy,
43
+ )
44
+ from proxilion.policies.registry import (
45
+ PolicyRegistry,
46
+ get_global_registry,
47
+ reset_global_registry,
48
+ )
49
+
50
+ __all__ = [
51
+ # Base classes
52
+ "Policy",
53
+ "PolicyWithScope",
54
+ "Scope",
55
+ "ActionContext",
56
+ # Registry
57
+ "PolicyRegistry",
58
+ "get_global_registry",
59
+ "reset_global_registry",
60
+ # Built-in policies
61
+ "DenyAllPolicy",
62
+ "AllowAllPolicy",
63
+ "RoleBasedPolicy",
64
+ "AttributeBasedPolicy",
65
+ "OwnershipPolicy",
66
+ "CompositePolicy",
67
+ ]
@@ -0,0 +1,304 @@
1
+ """
2
+ Policy base classes for Proxilion.
3
+
4
+ This module implements a Pundit-inspired policy pattern for Python,
5
+ allowing developers to define authorization rules in a clean,
6
+ object-oriented manner.
7
+
8
+ The Policy pattern separates authorization logic from business logic,
9
+ making it easier to understand, test, and maintain security rules.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from abc import ABC
16
+ from typing import TYPE_CHECKING, Any, Generic, TypeVar
17
+
18
+ if TYPE_CHECKING:
19
+ from proxilion.types import UserContext
20
+
21
+ # Type variable for the resource being authorized
22
+ T = TypeVar("T")
23
+
24
+
25
+ class Policy(ABC, Generic[T]):
26
+ """
27
+ Abstract base class for all Proxilion policies.
28
+
29
+ Policies define what actions a user can perform on a resource.
30
+ Following the Pundit pattern, each policy class corresponds to
31
+ a resource type, and methods named `can_<action>` define
32
+ permissions for specific actions.
33
+
34
+ Attributes:
35
+ user: The user context for authorization decisions.
36
+ resource: The resource being accessed (can be an instance,
37
+ class, or any object representing the resource).
38
+
39
+ Example:
40
+ >>> class DocumentPolicy(Policy):
41
+ ... def can_read(self, context: dict) -> bool:
42
+ ... # All authenticated users can read
43
+ ... return True
44
+ ...
45
+ ... def can_write(self, context: dict) -> bool:
46
+ ... # Only owners can write
47
+ ... return self.resource.owner_id == self.user.user_id
48
+ ...
49
+ ... def can_delete(self, context: dict) -> bool:
50
+ ... # Only admins can delete
51
+ ... return "admin" in self.user.roles
52
+
53
+ The `context` parameter allows passing additional runtime
54
+ information for authorization decisions (e.g., IP address,
55
+ time of day, request metadata).
56
+ """
57
+
58
+ # Class-level attribute to store the resource name this policy handles
59
+ # Set automatically by the @registry.policy decorator
60
+ _resource_name: str | None = None
61
+
62
+ def __init__(self, user: UserContext, resource: T | None = None) -> None:
63
+ """
64
+ Initialize a policy instance.
65
+
66
+ Args:
67
+ user: The authenticated user making the request.
68
+ resource: The resource being accessed. Can be None for
69
+ resource-type level checks (e.g., "can user create documents?").
70
+ """
71
+ self.user = user
72
+ self.resource = resource
73
+
74
+ def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
75
+ """
76
+ Check if the user is authorized to perform an action.
77
+
78
+ This method looks up the corresponding `can_<action>` method
79
+ and calls it with the provided context.
80
+
81
+ Args:
82
+ action: The action to check (e.g., "read", "write", "execute").
83
+ context: Additional context for the authorization decision.
84
+
85
+ Returns:
86
+ True if authorized, False otherwise.
87
+
88
+ Raises:
89
+ AttributeError: If no `can_<action>` method is defined.
90
+
91
+ Example:
92
+ >>> policy = DocumentPolicy(user, document)
93
+ >>> if policy.authorize("write", {"ip": "192.168.1.1"}):
94
+ ... document.save()
95
+ """
96
+ method_name = f"can_{action}"
97
+ method = getattr(self, method_name, None)
98
+
99
+ if method is None:
100
+ # Default deny if action method doesn't exist
101
+ return False
102
+
103
+ return method(context or {})
104
+
105
+ def can(self, action: str, context: dict[str, Any] | None = None) -> bool:
106
+ """
107
+ Alias for authorize() for a more fluent API.
108
+
109
+ Example:
110
+ >>> if policy.can("delete"):
111
+ ... resource.delete()
112
+ """
113
+ return self.authorize(action, context)
114
+
115
+ @classmethod
116
+ def get_resource_name(cls) -> str:
117
+ """
118
+ Get the resource name this policy handles.
119
+
120
+ By default, derives from the class name following the convention
121
+ `ResourcePolicy` -> `resource`. Can be overridden by setting
122
+ `_resource_name` or using the @registry.policy decorator.
123
+
124
+ Returns:
125
+ The resource name as a lowercase string.
126
+
127
+ Example:
128
+ >>> class DatabaseQueryPolicy(Policy):
129
+ ... pass
130
+ >>> DatabaseQueryPolicy.get_resource_name()
131
+ 'database_query'
132
+ """
133
+ if cls._resource_name:
134
+ return cls._resource_name
135
+
136
+ # Convert CamelCase to snake_case and remove 'Policy' suffix
137
+ name = cls.__name__
138
+ if name.endswith("Policy"):
139
+ name = name[:-6] # Remove 'Policy' suffix
140
+
141
+ # Convert CamelCase to snake_case
142
+ # Insert underscore before uppercase letters (except first)
143
+ snake_case = re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
144
+ return snake_case
145
+
146
+ @classmethod
147
+ def get_available_actions(cls) -> list[str]:
148
+ """
149
+ Get all actions defined by this policy.
150
+
151
+ Scans the class for methods matching the `can_<action>` pattern.
152
+
153
+ Returns:
154
+ List of action names (without the `can_` prefix).
155
+
156
+ Example:
157
+ >>> class MyPolicy(Policy):
158
+ ... def can_read(self, ctx): return True
159
+ ... def can_write(self, ctx): return False
160
+ >>> MyPolicy.get_available_actions()
161
+ ['read', 'write']
162
+ """
163
+ actions = []
164
+ for name in dir(cls):
165
+ if name.startswith("can_") and callable(getattr(cls, name)):
166
+ action = name[4:] # Remove 'can_' prefix
167
+ actions.append(action)
168
+ return sorted(actions)
169
+
170
+
171
+ class Scope(Generic[T]):
172
+ """
173
+ Base class for filtering collections based on user permissions.
174
+
175
+ The Scope pattern allows filtering a collection of resources
176
+ to only those the user is authorized to access. This is useful
177
+ for listing operations.
178
+
179
+ Example:
180
+ >>> class DocumentScope(Scope[Document]):
181
+ ... def resolve(self) -> list[Document]:
182
+ ... if "admin" in self.user.roles:
183
+ ... return self.scope # Admins see all
184
+ ... return [doc for doc in self.scope
185
+ ... if doc.owner_id == self.user.user_id]
186
+ >>>
187
+ >>> # Usage
188
+ >>> visible_docs = DocumentScope(user, all_documents).resolve()
189
+ """
190
+
191
+ def __init__(self, user: UserContext, scope: list[T]) -> None:
192
+ """
193
+ Initialize a scope instance.
194
+
195
+ Args:
196
+ user: The authenticated user.
197
+ scope: The initial collection to filter.
198
+ """
199
+ self.user = user
200
+ self.scope = scope
201
+
202
+ def resolve(self) -> list[T]:
203
+ """
204
+ Filter the scope to authorized items.
205
+
206
+ Subclasses should override this method to implement
207
+ their filtering logic.
208
+
209
+ Returns:
210
+ Filtered list of items the user can access.
211
+ """
212
+ # Default implementation returns empty list (safe default)
213
+ return []
214
+
215
+
216
+ class PolicyWithScope(Policy[T]):
217
+ """
218
+ Policy class that includes a Scope inner class.
219
+
220
+ This combines the Policy and Scope patterns, following
221
+ the Pundit convention where each Policy can have an
222
+ associated Scope class.
223
+
224
+ Example:
225
+ >>> class DocumentPolicy(PolicyWithScope[Document]):
226
+ ... def can_read(self, context: dict) -> bool:
227
+ ... return True
228
+ ...
229
+ ... class Scope(Scope[Document]):
230
+ ... def resolve(self) -> list[Document]:
231
+ ... if "admin" in self.user.roles:
232
+ ... return self.scope
233
+ ... return [d for d in self.scope
234
+ ... if d.owner_id == self.user.user_id]
235
+ >>>
236
+ >>> # Check permission for single resource
237
+ >>> policy = DocumentPolicy(user, document)
238
+ >>> can_read = policy.can("read")
239
+ >>>
240
+ >>> # Filter collection
241
+ >>> visible = DocumentPolicy.Scope(user, all_docs).resolve()
242
+ """
243
+
244
+ # Nested Scope class - subclasses should override
245
+ class Scope(Scope[T]):
246
+ """Default scope that returns empty list."""
247
+ pass
248
+
249
+
250
+ class ActionContext:
251
+ """
252
+ Context object passed to policy methods.
253
+
254
+ Provides a structured way to pass runtime context to
255
+ authorization decisions. Can be extended with additional
256
+ attributes as needed.
257
+
258
+ Attributes:
259
+ request_id: Unique identifier for the request.
260
+ ip_address: Client IP address.
261
+ timestamp: Request timestamp.
262
+ metadata: Additional key-value metadata.
263
+
264
+ Example:
265
+ >>> context = ActionContext(
266
+ ... ip_address="192.168.1.1",
267
+ ... metadata={"user_agent": "Claude/1.0"}
268
+ ... )
269
+ >>> policy.authorize("read", context.to_dict())
270
+ """
271
+
272
+ def __init__(
273
+ self,
274
+ request_id: str | None = None,
275
+ ip_address: str | None = None,
276
+ timestamp: str | None = None,
277
+ metadata: dict[str, Any] | None = None,
278
+ ) -> None:
279
+ self.request_id = request_id
280
+ self.ip_address = ip_address
281
+ self.timestamp = timestamp
282
+ self.metadata = metadata or {}
283
+
284
+ def to_dict(self) -> dict[str, Any]:
285
+ """Convert to dictionary for passing to policy methods."""
286
+ result: dict[str, Any] = {}
287
+ if self.request_id:
288
+ result["request_id"] = self.request_id
289
+ if self.ip_address:
290
+ result["ip_address"] = self.ip_address
291
+ if self.timestamp:
292
+ result["timestamp"] = self.timestamp
293
+ result.update(self.metadata)
294
+ return result
295
+
296
+ def with_metadata(self, **kwargs: Any) -> ActionContext:
297
+ """Create a new context with additional metadata."""
298
+ new_metadata = {**self.metadata, **kwargs}
299
+ return ActionContext(
300
+ request_id=self.request_id,
301
+ ip_address=self.ip_address,
302
+ timestamp=self.timestamp,
303
+ metadata=new_metadata,
304
+ )