pyopenapi-gen 2.7.2__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.
- pyopenapi_gen/__init__.py +224 -0
- pyopenapi_gen/__main__.py +6 -0
- pyopenapi_gen/cli.py +62 -0
- pyopenapi_gen/context/CLAUDE.md +284 -0
- pyopenapi_gen/context/file_manager.py +52 -0
- pyopenapi_gen/context/import_collector.py +382 -0
- pyopenapi_gen/context/render_context.py +726 -0
- pyopenapi_gen/core/CLAUDE.md +224 -0
- pyopenapi_gen/core/__init__.py +0 -0
- pyopenapi_gen/core/auth/base.py +22 -0
- pyopenapi_gen/core/auth/plugins.py +89 -0
- pyopenapi_gen/core/cattrs_converter.py +810 -0
- pyopenapi_gen/core/exceptions.py +20 -0
- pyopenapi_gen/core/http_status_codes.py +218 -0
- pyopenapi_gen/core/http_transport.py +222 -0
- pyopenapi_gen/core/loader/__init__.py +12 -0
- pyopenapi_gen/core/loader/loader.py +174 -0
- pyopenapi_gen/core/loader/operations/__init__.py +12 -0
- pyopenapi_gen/core/loader/operations/parser.py +161 -0
- pyopenapi_gen/core/loader/operations/post_processor.py +62 -0
- pyopenapi_gen/core/loader/operations/request_body.py +90 -0
- pyopenapi_gen/core/loader/parameters/__init__.py +10 -0
- pyopenapi_gen/core/loader/parameters/parser.py +186 -0
- pyopenapi_gen/core/loader/responses/__init__.py +10 -0
- pyopenapi_gen/core/loader/responses/parser.py +111 -0
- pyopenapi_gen/core/loader/schemas/__init__.py +11 -0
- pyopenapi_gen/core/loader/schemas/extractor.py +275 -0
- pyopenapi_gen/core/pagination.py +64 -0
- pyopenapi_gen/core/parsing/__init__.py +13 -0
- pyopenapi_gen/core/parsing/common/__init__.py +1 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/__init__.py +9 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/cyclic_properties.py +66 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/direct_cycle.py +33 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/existing_schema.py +22 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/list_response.py +54 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/missing_ref.py +52 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/new_schema.py +50 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/helpers/stripped_suffix.py +51 -0
- pyopenapi_gen/core/parsing/common/ref_resolution/resolve_schema_ref.py +86 -0
- pyopenapi_gen/core/parsing/common/type_parser.py +73 -0
- pyopenapi_gen/core/parsing/context.py +187 -0
- pyopenapi_gen/core/parsing/cycle_helpers.py +126 -0
- pyopenapi_gen/core/parsing/keywords/__init__.py +1 -0
- pyopenapi_gen/core/parsing/keywords/all_of_parser.py +81 -0
- pyopenapi_gen/core/parsing/keywords/any_of_parser.py +84 -0
- pyopenapi_gen/core/parsing/keywords/array_items_parser.py +72 -0
- pyopenapi_gen/core/parsing/keywords/one_of_parser.py +77 -0
- pyopenapi_gen/core/parsing/keywords/properties_parser.py +98 -0
- pyopenapi_gen/core/parsing/schema_finalizer.py +169 -0
- pyopenapi_gen/core/parsing/schema_parser.py +804 -0
- pyopenapi_gen/core/parsing/transformers/__init__.py +0 -0
- pyopenapi_gen/core/parsing/transformers/inline_enum_extractor.py +285 -0
- pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py +120 -0
- pyopenapi_gen/core/parsing/unified_cycle_detection.py +293 -0
- pyopenapi_gen/core/postprocess_manager.py +260 -0
- pyopenapi_gen/core/spec_fetcher.py +148 -0
- pyopenapi_gen/core/streaming_helpers.py +84 -0
- pyopenapi_gen/core/telemetry.py +69 -0
- pyopenapi_gen/core/utils.py +456 -0
- pyopenapi_gen/core/warning_collector.py +83 -0
- pyopenapi_gen/core/writers/code_writer.py +135 -0
- pyopenapi_gen/core/writers/documentation_writer.py +222 -0
- pyopenapi_gen/core/writers/line_writer.py +217 -0
- pyopenapi_gen/core/writers/python_construct_renderer.py +321 -0
- pyopenapi_gen/core_package_template/README.md +21 -0
- pyopenapi_gen/emit/models_emitter.py +143 -0
- pyopenapi_gen/emitters/CLAUDE.md +286 -0
- pyopenapi_gen/emitters/client_emitter.py +51 -0
- pyopenapi_gen/emitters/core_emitter.py +181 -0
- pyopenapi_gen/emitters/docs_emitter.py +44 -0
- pyopenapi_gen/emitters/endpoints_emitter.py +247 -0
- pyopenapi_gen/emitters/exceptions_emitter.py +187 -0
- pyopenapi_gen/emitters/mocks_emitter.py +185 -0
- pyopenapi_gen/emitters/models_emitter.py +426 -0
- pyopenapi_gen/generator/CLAUDE.md +352 -0
- pyopenapi_gen/generator/client_generator.py +567 -0
- pyopenapi_gen/generator/exceptions.py +7 -0
- pyopenapi_gen/helpers/CLAUDE.md +325 -0
- pyopenapi_gen/helpers/__init__.py +1 -0
- pyopenapi_gen/helpers/endpoint_utils.py +532 -0
- pyopenapi_gen/helpers/type_cleaner.py +334 -0
- pyopenapi_gen/helpers/type_helper.py +112 -0
- pyopenapi_gen/helpers/type_resolution/__init__.py +1 -0
- pyopenapi_gen/helpers/type_resolution/array_resolver.py +57 -0
- pyopenapi_gen/helpers/type_resolution/composition_resolver.py +79 -0
- pyopenapi_gen/helpers/type_resolution/finalizer.py +105 -0
- pyopenapi_gen/helpers/type_resolution/named_resolver.py +172 -0
- pyopenapi_gen/helpers/type_resolution/object_resolver.py +216 -0
- pyopenapi_gen/helpers/type_resolution/primitive_resolver.py +109 -0
- pyopenapi_gen/helpers/type_resolution/resolver.py +47 -0
- pyopenapi_gen/helpers/url_utils.py +14 -0
- pyopenapi_gen/http_types.py +20 -0
- pyopenapi_gen/ir.py +165 -0
- pyopenapi_gen/py.typed +1 -0
- pyopenapi_gen/types/CLAUDE.md +140 -0
- pyopenapi_gen/types/__init__.py +11 -0
- pyopenapi_gen/types/contracts/__init__.py +13 -0
- pyopenapi_gen/types/contracts/protocols.py +106 -0
- pyopenapi_gen/types/contracts/types.py +28 -0
- pyopenapi_gen/types/resolvers/__init__.py +7 -0
- pyopenapi_gen/types/resolvers/reference_resolver.py +71 -0
- pyopenapi_gen/types/resolvers/response_resolver.py +177 -0
- pyopenapi_gen/types/resolvers/schema_resolver.py +498 -0
- pyopenapi_gen/types/services/__init__.py +5 -0
- pyopenapi_gen/types/services/type_service.py +165 -0
- pyopenapi_gen/types/strategies/__init__.py +5 -0
- pyopenapi_gen/types/strategies/response_strategy.py +310 -0
- pyopenapi_gen/visit/CLAUDE.md +272 -0
- pyopenapi_gen/visit/client_visitor.py +477 -0
- pyopenapi_gen/visit/docs_visitor.py +38 -0
- pyopenapi_gen/visit/endpoint/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/endpoint_visitor.py +292 -0
- pyopenapi_gen/visit/endpoint/generators/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/generators/docstring_generator.py +123 -0
- pyopenapi_gen/visit/endpoint/generators/endpoint_method_generator.py +222 -0
- pyopenapi_gen/visit/endpoint/generators/mock_generator.py +140 -0
- pyopenapi_gen/visit/endpoint/generators/overload_generator.py +252 -0
- pyopenapi_gen/visit/endpoint/generators/request_generator.py +103 -0
- pyopenapi_gen/visit/endpoint/generators/response_handler_generator.py +705 -0
- pyopenapi_gen/visit/endpoint/generators/signature_generator.py +83 -0
- pyopenapi_gen/visit/endpoint/generators/url_args_generator.py +207 -0
- pyopenapi_gen/visit/endpoint/processors/__init__.py +1 -0
- pyopenapi_gen/visit/endpoint/processors/import_analyzer.py +78 -0
- pyopenapi_gen/visit/endpoint/processors/parameter_processor.py +171 -0
- pyopenapi_gen/visit/exception_visitor.py +90 -0
- pyopenapi_gen/visit/model/__init__.py +0 -0
- pyopenapi_gen/visit/model/alias_generator.py +93 -0
- pyopenapi_gen/visit/model/dataclass_generator.py +553 -0
- pyopenapi_gen/visit/model/enum_generator.py +212 -0
- pyopenapi_gen/visit/model/model_visitor.py +198 -0
- pyopenapi_gen/visit/visitor.py +97 -0
- pyopenapi_gen-2.7.2.dist-info/METADATA +1169 -0
- pyopenapi_gen-2.7.2.dist-info/RECORD +137 -0
- pyopenapi_gen-2.7.2.dist-info/WHEEL +4 -0
- pyopenapi_gen-2.7.2.dist-info/entry_points.txt +2 -0
- pyopenapi_gen-2.7.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# core/ - OpenAPI Parsing and Runtime Components
|
|
2
|
+
|
|
3
|
+
## Why This Folder?
|
|
4
|
+
Raw OpenAPI spec → Intermediate Representation (IR) transformation. Contains parsing logic, cycle detection, and runtime components that get copied to generated clients.
|
|
5
|
+
|
|
6
|
+
## Key Dependencies
|
|
7
|
+
- **Input**: Raw OpenAPI spec dicts from YAML/JSON
|
|
8
|
+
- **Output**: `IRSpec`, `IRSchema`, `IRResponse`, `IROperation` objects
|
|
9
|
+
- **Runtime**: Components copied to generated clients (`auth/`, `exceptions.py`, `http_transport.py`)
|
|
10
|
+
|
|
11
|
+
## Essential Architecture
|
|
12
|
+
|
|
13
|
+
### 1. Parsing Pipeline
|
|
14
|
+
```mermaid
|
|
15
|
+
graph LR
|
|
16
|
+
A[Raw OpenAPI] --> B[loader/] --> C[parsing/] --> D[IRSpec]
|
|
17
|
+
B --> E[schemas/extractor.py]
|
|
18
|
+
C --> F[unified_cycle_detection.py]
|
|
19
|
+
C --> G[transformers/]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Cycle Detection State Machine
|
|
23
|
+
```python
|
|
24
|
+
# parsing/unified_cycle_detection.py
|
|
25
|
+
@dataclass
|
|
26
|
+
class CycleInfo:
|
|
27
|
+
type: CycleType # STRUCTURAL, SELF_REF, DEPTH_LIMIT
|
|
28
|
+
action: CycleAction # PLACEHOLDER, FORWARD_REF, DEPTH_CUTOFF
|
|
29
|
+
depth: int
|
|
30
|
+
|
|
31
|
+
# Usage in parsing
|
|
32
|
+
if context.detect_cycle(schema_name, current_depth):
|
|
33
|
+
return create_cycle_placeholder(schema_name, cycle_info)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Critical Components
|
|
37
|
+
|
|
38
|
+
### parsing/schema_parser.py
|
|
39
|
+
**Purpose**: Main schema parsing with cycle detection
|
|
40
|
+
```python
|
|
41
|
+
def parse_schema(schema_data: Dict[str, Any], context: ParsingContext) -> IRSchema:
|
|
42
|
+
# 1. Cycle detection
|
|
43
|
+
# 2. Keyword parsing (allOf, oneOf, anyOf)
|
|
44
|
+
# 3. Transformer application
|
|
45
|
+
# 4. IR object creation
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### parsing/transformers/
|
|
49
|
+
**Purpose**: Modify parsed schemas before IR creation
|
|
50
|
+
- **inline_enum_extractor.py**: Extract inline enums to global schemas
|
|
51
|
+
- **inline_object_promoter.py**: Promote inline objects to named schemas
|
|
52
|
+
|
|
53
|
+
### loader/
|
|
54
|
+
**Purpose**: Load and validate OpenAPI specs
|
|
55
|
+
```python
|
|
56
|
+
# loader/loader.py
|
|
57
|
+
def load_spec(spec_path: str) -> IRSpec:
|
|
58
|
+
# 1. Load YAML/JSON
|
|
59
|
+
# 2. Validate OpenAPI format
|
|
60
|
+
# 3. Parse operations, schemas, responses
|
|
61
|
+
# 4. Build IRSpec
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Runtime Components (Copied to Clients)
|
|
65
|
+
|
|
66
|
+
#### auth/
|
|
67
|
+
```python
|
|
68
|
+
# auth/base.py - Base authentication classes
|
|
69
|
+
class AuthBase(ABC):
|
|
70
|
+
async def apply_auth(self, request: httpx.Request) -> httpx.Request:
|
|
71
|
+
# Modify request with auth info
|
|
72
|
+
|
|
73
|
+
# auth/plugins.py - Concrete implementations
|
|
74
|
+
class BearerAuth(AuthBase):
|
|
75
|
+
def __init__(self, token: str): ...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### exceptions.py
|
|
79
|
+
```python
|
|
80
|
+
# Exception hierarchy for generated clients
|
|
81
|
+
class ClientError(Exception): ...
|
|
82
|
+
class ServerError(ClientError): ...
|
|
83
|
+
class ValidationError(ClientError): ...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### http_transport.py
|
|
87
|
+
```python
|
|
88
|
+
# HTTP client abstraction
|
|
89
|
+
class HTTPTransport:
|
|
90
|
+
def __init__(self, base_url: str, auth: Optional[AuthBase] = None):
|
|
91
|
+
self.client = httpx.AsyncClient(base_url=base_url)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Environment Variables
|
|
95
|
+
```python
|
|
96
|
+
# parsing/unified_cycle_detection.py
|
|
97
|
+
PYOPENAPI_MAX_DEPTH = int(os.getenv("PYOPENAPI_MAX_DEPTH", "150"))
|
|
98
|
+
PYOPENAPI_MAX_CYCLES = int(os.getenv("PYOPENAPI_MAX_CYCLES", "0"))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Dependencies on Other Systems
|
|
102
|
+
|
|
103
|
+
### From types/
|
|
104
|
+
- Schema → Python type conversion after parsing
|
|
105
|
+
- Type resolution for complex compositions
|
|
106
|
+
|
|
107
|
+
### From context/
|
|
108
|
+
- `ParsingContext` for cycle detection state
|
|
109
|
+
- Import management during parsing
|
|
110
|
+
|
|
111
|
+
### To visit/
|
|
112
|
+
- Provides `IRSpec` for visitor pattern traversal
|
|
113
|
+
- IR objects consumed by code generators
|
|
114
|
+
|
|
115
|
+
## Common Parsing Patterns
|
|
116
|
+
|
|
117
|
+
### 1. Keyword Composition
|
|
118
|
+
```python
|
|
119
|
+
# parsing/keywords/all_of_parser.py
|
|
120
|
+
def parse_all_of(all_of_items: List[Dict], context: ParsingContext) -> IRSchema:
|
|
121
|
+
# Merge all schemas into single IR object
|
|
122
|
+
merged_schema = IRSchema(type="object")
|
|
123
|
+
for item in all_of_items:
|
|
124
|
+
parsed_item = parse_schema(item, context)
|
|
125
|
+
merged_schema = merge_schemas(merged_schema, parsed_item)
|
|
126
|
+
return merged_schema
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 2. Reference Resolution
|
|
130
|
+
```python
|
|
131
|
+
# parsing/common/ref_resolution/resolve_schema_ref.py
|
|
132
|
+
def resolve_schema_ref(ref: str, context: ParsingContext) -> IRSchema:
|
|
133
|
+
if ref.startswith("#/components/schemas/"):
|
|
134
|
+
schema_name = ref.split("/")[-1]
|
|
135
|
+
return context.get_schema(schema_name)
|
|
136
|
+
raise ValueError(f"Unsupported ref: {ref}")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 3. Cycle Detection
|
|
140
|
+
```python
|
|
141
|
+
# parsing/unified_cycle_detection.py
|
|
142
|
+
def detect_cycle(schema_name: str, current_depth: int, context: ParsingContext) -> Optional[CycleInfo]:
|
|
143
|
+
if current_depth > PYOPENAPI_MAX_DEPTH:
|
|
144
|
+
return CycleInfo(CycleType.DEPTH_LIMIT, CycleAction.DEPTH_CUTOFF, current_depth)
|
|
145
|
+
|
|
146
|
+
if schema_name in context.parsing_stack:
|
|
147
|
+
return CycleInfo(CycleType.STRUCTURAL, CycleAction.PLACEHOLDER, current_depth)
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Testing Requirements
|
|
153
|
+
|
|
154
|
+
### Parser Tests
|
|
155
|
+
```python
|
|
156
|
+
def test_parse_schema__all_of_composition__merges_correctly():
|
|
157
|
+
# Test keyword parsers with real OpenAPI data
|
|
158
|
+
schema_data = {
|
|
159
|
+
"allOf": [
|
|
160
|
+
{"type": "object", "properties": {"name": {"type": "string"}}},
|
|
161
|
+
{"type": "object", "properties": {"age": {"type": "integer"}}}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
# Test parsing result
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Cycle Detection Tests
|
|
168
|
+
```python
|
|
169
|
+
def test_unified_cycle_detection__structural_cycle__creates_placeholder():
|
|
170
|
+
# Test cycle detection with circular references
|
|
171
|
+
context = ParsingContext()
|
|
172
|
+
# Create circular reference scenario
|
|
173
|
+
# Verify placeholder creation
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Extension Points
|
|
177
|
+
|
|
178
|
+
### Adding New Keywords
|
|
179
|
+
```python
|
|
180
|
+
# parsing/keywords/new_keyword_parser.py
|
|
181
|
+
def parse_new_keyword(keyword_data: Any, context: ParsingContext) -> IRSchema:
|
|
182
|
+
# Custom keyword parsing logic
|
|
183
|
+
pass
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Adding New Transformers
|
|
187
|
+
```python
|
|
188
|
+
# parsing/transformers/new_transformer.py
|
|
189
|
+
def transform_schema(schema: IRSchema, context: ParsingContext) -> IRSchema:
|
|
190
|
+
# Custom transformation logic
|
|
191
|
+
return modified_schema
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Critical Implementation Details
|
|
195
|
+
|
|
196
|
+
### IR Object Creation
|
|
197
|
+
```python
|
|
198
|
+
# Always use IRSchema constructor with all required fields
|
|
199
|
+
schema = IRSchema(
|
|
200
|
+
name=schema_name,
|
|
201
|
+
type=schema_type,
|
|
202
|
+
properties=properties,
|
|
203
|
+
required=required_fields,
|
|
204
|
+
description=description,
|
|
205
|
+
enum=enum_values,
|
|
206
|
+
is_nullable=is_nullable
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Context State Management
|
|
211
|
+
```python
|
|
212
|
+
# parsing/context.py
|
|
213
|
+
class ParsingContext:
|
|
214
|
+
def __init__(self):
|
|
215
|
+
self.parsed_schemas: Dict[str, IRSchema] = {}
|
|
216
|
+
self.parsing_stack: List[str] = [] # For cycle detection
|
|
217
|
+
self.forward_refs: Set[str] = set()
|
|
218
|
+
|
|
219
|
+
def enter_schema(self, schema_name: str):
|
|
220
|
+
self.parsing_stack.append(schema_name)
|
|
221
|
+
|
|
222
|
+
def exit_schema(self, schema_name: str):
|
|
223
|
+
self.parsing_stack.remove(schema_name)
|
|
224
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Any, Protocol # noqa: F401
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseAuth(Protocol):
|
|
5
|
+
"""Protocol for authentication plugins."""
|
|
6
|
+
|
|
7
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
8
|
+
"""Modify or augment the request arguments for authentication."""
|
|
9
|
+
# Default stub returns the input unchanged
|
|
10
|
+
return request_args
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CompositeAuth(BaseAuth):
|
|
14
|
+
"""Compose multiple BaseAuth plugins, applying each in sequence to the request."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, *plugins: BaseAuth):
|
|
17
|
+
self.plugins = plugins
|
|
18
|
+
|
|
19
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
20
|
+
for plugin in self.plugins:
|
|
21
|
+
request_args = await plugin.authenticate_request(request_args)
|
|
22
|
+
return request_args
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
|
2
|
+
|
|
3
|
+
from .base import BaseAuth
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BearerAuth(BaseAuth):
|
|
7
|
+
"""Authentication plugin for Bearer tokens."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, token: str) -> None:
|
|
10
|
+
self.token = token
|
|
11
|
+
|
|
12
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
13
|
+
# Ensure headers dict exists
|
|
14
|
+
headers = dict(request_args.get("headers", {}))
|
|
15
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
16
|
+
request_args["headers"] = headers
|
|
17
|
+
return request_args
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class HeadersAuth(BaseAuth):
|
|
21
|
+
"""Authentication plugin for arbitrary headers."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, headers: dict[str, str]) -> None:
|
|
24
|
+
self.headers = headers
|
|
25
|
+
|
|
26
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
27
|
+
# Merge custom headers
|
|
28
|
+
hdrs = dict(request_args.get("headers", {}))
|
|
29
|
+
hdrs.update(self.headers)
|
|
30
|
+
request_args["headers"] = hdrs
|
|
31
|
+
return request_args
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ApiKeyAuth(BaseAuth):
|
|
35
|
+
"""Authentication plugin for API keys (header, query, or cookie)."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, key: str, location: str = "header", name: str = "X-API-Key") -> None:
|
|
38
|
+
"""
|
|
39
|
+
Args:
|
|
40
|
+
key: The API key value.
|
|
41
|
+
location: Where to add the key ("header", "query", or "cookie").
|
|
42
|
+
name: The name of the header/query/cookie parameter.
|
|
43
|
+
"""
|
|
44
|
+
self.key = key
|
|
45
|
+
self.location = location
|
|
46
|
+
self.name = name
|
|
47
|
+
|
|
48
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
49
|
+
if self.location == "header":
|
|
50
|
+
headers = dict(request_args.get("headers", {}))
|
|
51
|
+
headers[self.name] = self.key
|
|
52
|
+
request_args["headers"] = headers
|
|
53
|
+
elif self.location == "query":
|
|
54
|
+
params = dict(request_args.get("params", {}))
|
|
55
|
+
params[self.name] = self.key
|
|
56
|
+
request_args["params"] = params
|
|
57
|
+
elif self.location == "cookie":
|
|
58
|
+
cookies = dict(request_args.get("cookies", {}))
|
|
59
|
+
cookies[self.name] = self.key
|
|
60
|
+
request_args["cookies"] = cookies
|
|
61
|
+
else:
|
|
62
|
+
raise ValueError(f"Invalid API key location: {self.location}")
|
|
63
|
+
return request_args
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class OAuth2Auth(BaseAuth):
|
|
67
|
+
"""Authentication plugin for OAuth2 Bearer tokens, with optional auto-refresh."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, access_token: str, refresh_callback: Callable[[str], Awaitable[str]] | None = None) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Args:
|
|
72
|
+
access_token: The OAuth2 access token.
|
|
73
|
+
refresh_callback: Optional async function to refresh the token. If provided, will be called
|
|
74
|
+
if token is expired.
|
|
75
|
+
"""
|
|
76
|
+
self.access_token = access_token
|
|
77
|
+
self.refresh_callback = refresh_callback
|
|
78
|
+
|
|
79
|
+
async def authenticate_request(self, request_args: dict[str, Any]) -> dict[str, Any]:
|
|
80
|
+
# In a real implementation, check expiry and refresh if needed
|
|
81
|
+
if self.refresh_callback is not None:
|
|
82
|
+
# Optionally refresh token (user must implement expiry logic)
|
|
83
|
+
new_token = await self.refresh_callback(self.access_token)
|
|
84
|
+
if new_token and new_token != self.access_token:
|
|
85
|
+
self.access_token = new_token
|
|
86
|
+
headers = dict(request_args.get("headers", {}))
|
|
87
|
+
headers["Authorization"] = f"Bearer {self.access_token}"
|
|
88
|
+
request_args["headers"] = headers
|
|
89
|
+
return request_args
|