python-open-router 0.3.3__tar.gz → 0.4.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.
- {python_open_router-0.3.3 → python_open_router-0.4.1}/LICENSE.md +1 -1
- {python_open_router-0.3.3 → python_open_router-0.4.1}/PKG-INFO +3 -3
- {python_open_router-0.3.3 → python_open_router-0.4.1}/README.md +2 -2
- {python_open_router-0.3.3 → python_open_router-0.4.1}/pyproject.toml +18 -14
- {python_open_router-0.3.3 → python_open_router-0.4.1}/src/python_open_router/__init__.py +18 -4
- python_open_router-0.4.1/src/python_open_router/models.py +272 -0
- python_open_router-0.4.1/src/python_open_router/open_router.py +290 -0
- python_open_router-0.3.3/src/python_open_router/models.py +0 -149
- python_open_router-0.3.3/src/python_open_router/open_router.py +0 -142
- {python_open_router-0.3.3 → python_open_router-0.4.1}/src/python_open_router/exceptions.py +0 -0
- {python_open_router-0.3.3 → python_open_router-0.4.1}/src/python_open_router/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_open_router
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Asynchronous Python client for Open Router.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE.md
|
|
@@ -121,7 +121,7 @@ check [the contributor's page][contributors].
|
|
|
121
121
|
|
|
122
122
|
MIT License
|
|
123
123
|
|
|
124
|
-
Copyright (c) 2025 Joost Lekkerkerker
|
|
124
|
+
Copyright (c) 2025-2026 Joost Lekkerkerker
|
|
125
125
|
|
|
126
126
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
127
127
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -151,7 +151,7 @@ SOFTWARE.
|
|
|
151
151
|
[joostlek]: https://github.com/joostlek
|
|
152
152
|
[keepchangelog]: http://keepachangelog.com/en/1.0.0/
|
|
153
153
|
[license-shield]: https://img.shields.io/github/license/joostlek/python-open-router.svg
|
|
154
|
-
[maintenance-shield]: https://img.shields.io/maintenance/yes/
|
|
154
|
+
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg
|
|
155
155
|
[poetry-install]: https://python-poetry.org/docs/#installation
|
|
156
156
|
[poetry]: https://python-poetry.org
|
|
157
157
|
[pre-commit]: https://pre-commit.com/
|
|
@@ -88,7 +88,7 @@ check [the contributor's page][contributors].
|
|
|
88
88
|
|
|
89
89
|
MIT License
|
|
90
90
|
|
|
91
|
-
Copyright (c) 2025 Joost Lekkerkerker
|
|
91
|
+
Copyright (c) 2025-2026 Joost Lekkerkerker
|
|
92
92
|
|
|
93
93
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
94
94
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -118,7 +118,7 @@ SOFTWARE.
|
|
|
118
118
|
[joostlek]: https://github.com/joostlek
|
|
119
119
|
[keepchangelog]: http://keepachangelog.com/en/1.0.0/
|
|
120
120
|
[license-shield]: https://img.shields.io/github/license/joostlek/python-open-router.svg
|
|
121
|
-
[maintenance-shield]: https://img.shields.io/maintenance/yes/
|
|
121
|
+
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg
|
|
122
122
|
[poetry-install]: https://python-poetry.org/docs/#installation
|
|
123
123
|
[poetry]: https://python-poetry.org
|
|
124
124
|
[pre-commit]: https://pre-commit.com/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "python_open_router"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.1"
|
|
4
4
|
description = "Asynchronous Python client for Open Router."
|
|
5
5
|
authors = ["Joost Lekkerkerker <joostlek@outlook.com>"]
|
|
6
6
|
maintainers = ["Joost Lekkerkerker <joostlek@outlook.com>"]
|
|
@@ -32,21 +32,21 @@ mashumaro = "^3.11"
|
|
|
32
32
|
orjson = ">=3.9.0"
|
|
33
33
|
|
|
34
34
|
[tool.poetry.group.dev.dependencies]
|
|
35
|
-
codespell = "2.4.
|
|
35
|
+
codespell = "2.4.3"
|
|
36
36
|
covdefaults = "2.3.0"
|
|
37
|
-
coverage = {version = "7.
|
|
38
|
-
mypy = "
|
|
39
|
-
pre-commit = "4.
|
|
37
|
+
coverage = {version = "7.16.1", extras = ["toml"]}
|
|
38
|
+
mypy = "2.3.1"
|
|
39
|
+
pre-commit = "4.6.2"
|
|
40
40
|
pre-commit-hooks = "6.0.0"
|
|
41
|
-
pylint = "4.0.
|
|
42
|
-
pytest = "
|
|
43
|
-
pytest-asyncio = "1.
|
|
44
|
-
pytest-cov = "7.
|
|
45
|
-
ruff = "0.
|
|
46
|
-
safety = "3.
|
|
47
|
-
yamllint = "1.
|
|
48
|
-
syrupy = "
|
|
49
|
-
|
|
41
|
+
pylint = "4.0.8"
|
|
42
|
+
pytest = "9.1.1"
|
|
43
|
+
pytest-asyncio = "1.4.0"
|
|
44
|
+
pytest-cov = "7.1.0"
|
|
45
|
+
ruff = "0.16.8"
|
|
46
|
+
safety = "3.8.1"
|
|
47
|
+
yamllint = "1.38.0"
|
|
48
|
+
syrupy = "6.1.1"
|
|
49
|
+
aiointercept = "0.1.10"
|
|
50
50
|
|
|
51
51
|
[tool.poetry.urls]
|
|
52
52
|
"Bug Tracker" = "https://github.com/joostlek/python-open-router/issues"
|
|
@@ -137,10 +137,14 @@ asyncio_mode = "auto"
|
|
|
137
137
|
ignore = [
|
|
138
138
|
"ANN401", # Opinioated warning on disallowing dynamically typed expressions
|
|
139
139
|
"COM812", # Conflicts with other rules
|
|
140
|
+
"CPY001", # No copyright notices used in this project
|
|
140
141
|
"D203", # Conflicts with other rules
|
|
141
142
|
"D213", # Conflicts with other rules
|
|
142
143
|
"D417", # False positives in some occasions
|
|
143
144
|
"ISC001", # Conflicts with other rules
|
|
145
|
+
"PLR0912", # Too many branches - legitimate for API client methods
|
|
146
|
+
"PLR0913", # Too many arguments - legitimate for API client methods
|
|
147
|
+
"PLR0917", # Too many positional arguments - legitimate for API client methods
|
|
144
148
|
"PLR2004", # Just annoying, not really useful
|
|
145
149
|
]
|
|
146
150
|
select = ["ALL"]
|
|
@@ -6,34 +6,48 @@ from python_open_router.exceptions import (
|
|
|
6
6
|
OpenRouterError,
|
|
7
7
|
)
|
|
8
8
|
from python_open_router.models import (
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
DefaultParameters,
|
|
10
|
+
DeletedKeyResponse,
|
|
11
11
|
Key,
|
|
12
|
+
KeyCreateResponse,
|
|
12
13
|
KeyData,
|
|
13
14
|
KeyDataWrapper,
|
|
15
|
+
KeyResponseWrapper,
|
|
14
16
|
KeysDataWrapper,
|
|
15
17
|
Modality,
|
|
16
18
|
Model,
|
|
17
19
|
ModelArchitecture,
|
|
20
|
+
ModelLinks,
|
|
21
|
+
ModelResponseWrapper,
|
|
18
22
|
ModelsDataWrapper,
|
|
23
|
+
PerRequestLimits,
|
|
24
|
+
PublicPricing,
|
|
19
25
|
SupportedParameter,
|
|
26
|
+
TopProviderInfo,
|
|
20
27
|
)
|
|
21
28
|
from python_open_router.open_router import OpenRouterClient
|
|
22
29
|
|
|
23
30
|
__all__ = [
|
|
24
|
-
"
|
|
25
|
-
"
|
|
31
|
+
"DefaultParameters",
|
|
32
|
+
"DeletedKeyResponse",
|
|
26
33
|
"Key",
|
|
34
|
+
"KeyCreateResponse",
|
|
27
35
|
"KeyData",
|
|
28
36
|
"KeyDataWrapper",
|
|
37
|
+
"KeyResponseWrapper",
|
|
29
38
|
"KeysDataWrapper",
|
|
30
39
|
"Modality",
|
|
31
40
|
"Model",
|
|
32
41
|
"ModelArchitecture",
|
|
42
|
+
"ModelLinks",
|
|
43
|
+
"ModelResponseWrapper",
|
|
33
44
|
"ModelsDataWrapper",
|
|
34
45
|
"OpenRouterAuthenticationError",
|
|
35
46
|
"OpenRouterClient",
|
|
36
47
|
"OpenRouterConnectionError",
|
|
37
48
|
"OpenRouterError",
|
|
49
|
+
"PerRequestLimits",
|
|
50
|
+
"PublicPricing",
|
|
38
51
|
"SupportedParameter",
|
|
52
|
+
"TopProviderInfo",
|
|
39
53
|
]
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Models for OpenRouter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import StrEnum
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
11
|
+
|
|
12
|
+
_LOGGER = logging.getLogger(__package__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class KeyDataWrapper(DataClassORJSONMixin):
|
|
17
|
+
"""Wrapper for OpenRouter key data."""
|
|
18
|
+
|
|
19
|
+
data: KeyData
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class KeyData(DataClassORJSONMixin):
|
|
24
|
+
"""The OpenRouter key data."""
|
|
25
|
+
|
|
26
|
+
label: str
|
|
27
|
+
usage: float
|
|
28
|
+
is_provisioning_key: bool
|
|
29
|
+
limit_remaining: float | None
|
|
30
|
+
is_free_tier: bool
|
|
31
|
+
limit: float | None = None
|
|
32
|
+
limit_reset: str | None = None
|
|
33
|
+
usage_daily: float = 0.0
|
|
34
|
+
usage_weekly: float = 0.0
|
|
35
|
+
usage_monthly: float = 0.0
|
|
36
|
+
byok_usage: float = 0.0
|
|
37
|
+
byok_usage_daily: float = 0.0
|
|
38
|
+
byok_usage_weekly: float = 0.0
|
|
39
|
+
byok_usage_monthly: float = 0.0
|
|
40
|
+
include_byok_in_limit: bool = False
|
|
41
|
+
is_management_key: bool = False
|
|
42
|
+
creator_user_id: str | None = None
|
|
43
|
+
expires_at: str | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class KeysDataWrapper(DataClassORJSONMixin):
|
|
48
|
+
"""Wrapper for OpenRouter key data."""
|
|
49
|
+
|
|
50
|
+
data: list[Key]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class KeyCreateResponse(DataClassORJSONMixin):
|
|
55
|
+
"""Response from creating a key."""
|
|
56
|
+
|
|
57
|
+
data: Key
|
|
58
|
+
key: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Key(DataClassORJSONMixin):
|
|
63
|
+
"""The OpenRouter key data."""
|
|
64
|
+
|
|
65
|
+
hash: str
|
|
66
|
+
name: str
|
|
67
|
+
label: str
|
|
68
|
+
disabled: bool
|
|
69
|
+
limit: float | None = None
|
|
70
|
+
usage: float = 0.0
|
|
71
|
+
created_at: str = ""
|
|
72
|
+
updated_at: str | None = None
|
|
73
|
+
limit_remaining: float | None = None
|
|
74
|
+
limit_reset: str | None = None
|
|
75
|
+
workspace_id: str = ""
|
|
76
|
+
expires_at: str | None = None
|
|
77
|
+
usage_daily: float = 0.0
|
|
78
|
+
usage_weekly: float = 0.0
|
|
79
|
+
usage_monthly: float = 0.0
|
|
80
|
+
byok_usage: float = 0.0
|
|
81
|
+
byok_usage_daily: float = 0.0
|
|
82
|
+
byok_usage_weekly: float = 0.0
|
|
83
|
+
byok_usage_monthly: float = 0.0
|
|
84
|
+
include_byok_in_limit: bool = False
|
|
85
|
+
creator_user_id: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class KeyResponseWrapper(DataClassORJSONMixin):
|
|
90
|
+
"""Wrapper for single key response."""
|
|
91
|
+
|
|
92
|
+
data: Key
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class DeletedKeyResponse(DataClassORJSONMixin):
|
|
97
|
+
"""Response after deleting a key."""
|
|
98
|
+
|
|
99
|
+
deleted: bool
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ModelResponseWrapper(DataClassORJSONMixin):
|
|
104
|
+
"""Wrapper for single OpenRouter model response."""
|
|
105
|
+
|
|
106
|
+
data: Model
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@dataclass
|
|
110
|
+
class ModelsDataWrapper(DataClassORJSONMixin):
|
|
111
|
+
"""Wrapper for OpenRouter model data."""
|
|
112
|
+
|
|
113
|
+
data: list[Model]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class SupportedParameter(StrEnum):
|
|
117
|
+
"""Supported parameters for models."""
|
|
118
|
+
|
|
119
|
+
TEMPERATURE = "temperature"
|
|
120
|
+
TOP_P = "top_p"
|
|
121
|
+
TOP_K = "top_k"
|
|
122
|
+
MAX_TOKENS = "max_tokens"
|
|
123
|
+
MAX_COMPLETION_TOKENS = "max_completion_tokens"
|
|
124
|
+
REASONING = "reasoning"
|
|
125
|
+
REASONING_EFFORT = "reasoning_effort"
|
|
126
|
+
INCLUDE_REASONING = "include_reasoning"
|
|
127
|
+
STOP = "stop"
|
|
128
|
+
SEED = "seed"
|
|
129
|
+
RESPONSE_FORMAT = "response_format"
|
|
130
|
+
STRUCTURED_OUTPUTS = "structured_outputs"
|
|
131
|
+
PREDICTION = "prediction"
|
|
132
|
+
TOOLS = "tools"
|
|
133
|
+
TOOL_CHOICE = "tool_choice"
|
|
134
|
+
PARALLEL_TOOL_CALLS = "parallel_tool_calls"
|
|
135
|
+
FREQUENCY_PENALTY = "frequency_penalty"
|
|
136
|
+
PRESENCE_PENALTY = "presence_penalty"
|
|
137
|
+
REPETITION_PENALTY = "repetition_penalty"
|
|
138
|
+
MIN_P = "min_p"
|
|
139
|
+
TOP_A = "top_a"
|
|
140
|
+
LOGPROBS = "logprobs"
|
|
141
|
+
LOGIT_BIAS = "logit_bias"
|
|
142
|
+
TOP_LOGPROBS = "top_logprobs"
|
|
143
|
+
WEB_SEARCH_OPTIONS = "web_search_options"
|
|
144
|
+
VERBOSITY = "verbosity"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class Modality(StrEnum):
|
|
148
|
+
"""Supported modalities for models."""
|
|
149
|
+
|
|
150
|
+
AUDIO = "audio"
|
|
151
|
+
TEXT = "text"
|
|
152
|
+
IMAGE = "image"
|
|
153
|
+
FILE = "file"
|
|
154
|
+
EMBEDDINGS = "embeddings"
|
|
155
|
+
VIDEO = "video"
|
|
156
|
+
RERANK = "rerank"
|
|
157
|
+
SPEECH = "speech"
|
|
158
|
+
TRANSCRIPTION = "transcription"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(kw_only=True)
|
|
162
|
+
class ModelArchitecture(DataClassORJSONMixin):
|
|
163
|
+
"""Model architecture data."""
|
|
164
|
+
|
|
165
|
+
input_modalities: list[Modality]
|
|
166
|
+
output_modalities: list[Modality]
|
|
167
|
+
modality: str | None = None
|
|
168
|
+
instruct_type: str | None = None
|
|
169
|
+
tokenizer: str | None = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(kw_only=True)
|
|
173
|
+
class PublicPricing(DataClassORJSONMixin):
|
|
174
|
+
"""Model pricing data."""
|
|
175
|
+
|
|
176
|
+
completion: str
|
|
177
|
+
prompt: str
|
|
178
|
+
audio: str | None = None
|
|
179
|
+
audio_output: str | None = None
|
|
180
|
+
image: str | None = None
|
|
181
|
+
image_output: str | None = None
|
|
182
|
+
image_token: str | None = None
|
|
183
|
+
input_audio_cache: str | None = None
|
|
184
|
+
input_cache_read: str | None = None
|
|
185
|
+
input_cache_write: str | None = None
|
|
186
|
+
input_cache_write_1h: str | None = None
|
|
187
|
+
internal_reasoning: str | None = None
|
|
188
|
+
request: str | None = None
|
|
189
|
+
web_search: str | None = None
|
|
190
|
+
discount: float | None = None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass(kw_only=True)
|
|
194
|
+
class TopProviderInfo(DataClassORJSONMixin):
|
|
195
|
+
"""Top provider information."""
|
|
196
|
+
|
|
197
|
+
is_moderated: bool
|
|
198
|
+
context_length: int | None = None
|
|
199
|
+
max_completion_tokens: int | None = None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass(kw_only=True)
|
|
203
|
+
class DefaultParameters(DataClassORJSONMixin):
|
|
204
|
+
"""Default model parameters."""
|
|
205
|
+
|
|
206
|
+
temperature: float | None = None
|
|
207
|
+
top_p: float | None = None
|
|
208
|
+
top_k: int | None = None
|
|
209
|
+
frequency_penalty: float | None = None
|
|
210
|
+
presence_penalty: float | None = None
|
|
211
|
+
repetition_penalty: float | None = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(kw_only=True)
|
|
215
|
+
class PerRequestLimits(DataClassORJSONMixin):
|
|
216
|
+
"""Per-request limits."""
|
|
217
|
+
|
|
218
|
+
completion_tokens: float
|
|
219
|
+
prompt_tokens: float
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@dataclass(kw_only=True)
|
|
223
|
+
class ModelLinks(DataClassORJSONMixin):
|
|
224
|
+
"""Model links."""
|
|
225
|
+
|
|
226
|
+
details: str
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(kw_only=True)
|
|
230
|
+
class Model(DataClassORJSONMixin):
|
|
231
|
+
"""Model data."""
|
|
232
|
+
|
|
233
|
+
id: str
|
|
234
|
+
canonical_slug: str
|
|
235
|
+
name: str
|
|
236
|
+
context_length: int | None
|
|
237
|
+
architecture: ModelArchitecture
|
|
238
|
+
supported_parameters: list[SupportedParameter]
|
|
239
|
+
pricing: PublicPricing
|
|
240
|
+
top_provider: TopProviderInfo
|
|
241
|
+
hugging_face_id: str | None = None
|
|
242
|
+
description: str | None = None
|
|
243
|
+
created: int | None = None
|
|
244
|
+
default_parameters: DefaultParameters | None = None
|
|
245
|
+
links: ModelLinks | None = None
|
|
246
|
+
per_request_limits: PerRequestLimits | None = None
|
|
247
|
+
supported_voices: list[str] | None = None
|
|
248
|
+
expiration_date: str | None = None
|
|
249
|
+
knowledge_cutoff: str | None = None
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
|
|
253
|
+
"""Pre deserialize hook."""
|
|
254
|
+
parameters = d.get("supported_parameters", [])
|
|
255
|
+
supported = set(SupportedParameter)
|
|
256
|
+
d["supported_parameters"] = [p for p in parameters if p in supported]
|
|
257
|
+
for parameter in parameters:
|
|
258
|
+
if parameter not in supported:
|
|
259
|
+
_LOGGER.warning(
|
|
260
|
+
"Unsupported parameter: %s. Please report at https://github.com/joostlek/python-open-router/issues.",
|
|
261
|
+
parameter,
|
|
262
|
+
)
|
|
263
|
+
return d
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def __post_deserialize__(cls, obj: Model) -> Model:
|
|
267
|
+
"""Post deserialize hook."""
|
|
268
|
+
if obj.hugging_face_id == "":
|
|
269
|
+
obj.hugging_face_id = None
|
|
270
|
+
if obj.description == "":
|
|
271
|
+
obj.description = None
|
|
272
|
+
return obj
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Asynchronous Python client for Open Router."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from importlib import metadata
|
|
8
|
+
import socket
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from aiohttp import ClientError, ClientResponseError, ClientSession
|
|
12
|
+
from aiohttp.hdrs import METH_DELETE, METH_GET, METH_PATCH, METH_POST
|
|
13
|
+
import orjson
|
|
14
|
+
from yarl import URL
|
|
15
|
+
|
|
16
|
+
from python_open_router.exceptions import (
|
|
17
|
+
OpenRouterAuthenticationError,
|
|
18
|
+
OpenRouterConnectionError,
|
|
19
|
+
)
|
|
20
|
+
from python_open_router.models import (
|
|
21
|
+
DeletedKeyResponse,
|
|
22
|
+
Key,
|
|
23
|
+
KeyCreateResponse,
|
|
24
|
+
KeyData,
|
|
25
|
+
KeyDataWrapper,
|
|
26
|
+
KeyResponseWrapper,
|
|
27
|
+
KeysDataWrapper,
|
|
28
|
+
Model,
|
|
29
|
+
ModelResponseWrapper,
|
|
30
|
+
ModelsDataWrapper,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from typing_extensions import Self
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
VERSION = metadata.version(__package__)
|
|
38
|
+
HOST = "openrouter.ai"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class OpenRouterClient:
|
|
43
|
+
"""Main class for handling connections with OpenRouter."""
|
|
44
|
+
|
|
45
|
+
api_key: str
|
|
46
|
+
session: ClientSession | None = None
|
|
47
|
+
request_timeout: int = 10
|
|
48
|
+
_close_session: bool = False
|
|
49
|
+
|
|
50
|
+
async def _request(
|
|
51
|
+
self,
|
|
52
|
+
method: str,
|
|
53
|
+
uri: str,
|
|
54
|
+
*,
|
|
55
|
+
params: dict[str, Any] | None = None,
|
|
56
|
+
data: dict[str, Any] | None = None,
|
|
57
|
+
) -> str:
|
|
58
|
+
"""Handle a request to OpenRouter."""
|
|
59
|
+
url = URL.build(host=HOST, scheme="https").joinpath(f"api/v1/{uri}")
|
|
60
|
+
|
|
61
|
+
headers = {
|
|
62
|
+
"User-Agent": f"PythonOpenRouter/{VERSION}",
|
|
63
|
+
"Accept": "application/json",
|
|
64
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if self.session is None:
|
|
68
|
+
self.session = ClientSession()
|
|
69
|
+
self._close_session = True
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
async with asyncio.timeout(self.request_timeout):
|
|
73
|
+
response = await self.session.request(
|
|
74
|
+
method,
|
|
75
|
+
url,
|
|
76
|
+
params=params,
|
|
77
|
+
headers=headers,
|
|
78
|
+
json=data,
|
|
79
|
+
)
|
|
80
|
+
except asyncio.TimeoutError as exception:
|
|
81
|
+
msg = "Timeout occurred while connecting to the service"
|
|
82
|
+
raise OpenRouterConnectionError(msg) from exception
|
|
83
|
+
except (
|
|
84
|
+
ClientError,
|
|
85
|
+
ClientResponseError,
|
|
86
|
+
socket.gaierror,
|
|
87
|
+
) as exception:
|
|
88
|
+
msg = "Error occurred while communicating with the service"
|
|
89
|
+
raise OpenRouterConnectionError(msg) from exception
|
|
90
|
+
|
|
91
|
+
if response.status == 401:
|
|
92
|
+
msg = "Authentication failed"
|
|
93
|
+
raise OpenRouterAuthenticationError(msg)
|
|
94
|
+
|
|
95
|
+
if response.status >= 400:
|
|
96
|
+
content_type = response.headers.get("Content-Type", "")
|
|
97
|
+
text = await response.text()
|
|
98
|
+
msg = "Unexpected response from OpenRouter"
|
|
99
|
+
raise OpenRouterConnectionError(
|
|
100
|
+
msg,
|
|
101
|
+
{"Content-Type": content_type, "response": text},
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return await response.text()
|
|
105
|
+
|
|
106
|
+
async def get_key_data(self) -> KeyData:
|
|
107
|
+
"""Get key data for API key."""
|
|
108
|
+
response = await self._request(METH_GET, "key")
|
|
109
|
+
return KeyDataWrapper.from_json(response).data
|
|
110
|
+
|
|
111
|
+
async def get_keys(
|
|
112
|
+
self,
|
|
113
|
+
*,
|
|
114
|
+
include_disabled: bool | None = None,
|
|
115
|
+
offset: int | None = None,
|
|
116
|
+
workspace_id: str | None = None,
|
|
117
|
+
) -> list[Key]:
|
|
118
|
+
"""Get all keys."""
|
|
119
|
+
params: dict[str, Any] = {}
|
|
120
|
+
if include_disabled is not None:
|
|
121
|
+
params["include_disabled"] = str(include_disabled).lower()
|
|
122
|
+
if offset is not None:
|
|
123
|
+
params["offset"] = str(offset)
|
|
124
|
+
if workspace_id is not None:
|
|
125
|
+
params["workspace_id"] = workspace_id
|
|
126
|
+
response = await self._request(METH_GET, "keys", params=params or None)
|
|
127
|
+
return KeysDataWrapper.from_json(response).data
|
|
128
|
+
|
|
129
|
+
async def create_key(
|
|
130
|
+
self,
|
|
131
|
+
name: str,
|
|
132
|
+
limit: float | None = None,
|
|
133
|
+
*,
|
|
134
|
+
limit_reset: str | None = None,
|
|
135
|
+
workspace_id: str | None = None,
|
|
136
|
+
expires_at: str | None = None,
|
|
137
|
+
include_byok_in_limit: bool | None = None,
|
|
138
|
+
) -> Key:
|
|
139
|
+
"""Create a new key."""
|
|
140
|
+
data: dict[str, Any] = {"name": name}
|
|
141
|
+
if limit is not None:
|
|
142
|
+
data["limit"] = limit
|
|
143
|
+
if limit_reset is not None:
|
|
144
|
+
data["limit_reset"] = limit_reset
|
|
145
|
+
if workspace_id is not None:
|
|
146
|
+
data["workspace_id"] = workspace_id
|
|
147
|
+
if expires_at is not None:
|
|
148
|
+
data["expires_at"] = expires_at
|
|
149
|
+
if include_byok_in_limit is not None:
|
|
150
|
+
data["include_byok_in_limit"] = include_byok_in_limit
|
|
151
|
+
|
|
152
|
+
response = await self._request(METH_POST, "keys", data=data)
|
|
153
|
+
return KeyCreateResponse.from_json(response).data
|
|
154
|
+
|
|
155
|
+
async def get_key(self, key_hash: str) -> Key:
|
|
156
|
+
"""Get a single key by hash."""
|
|
157
|
+
response = await self._request(METH_GET, f"keys/{key_hash}")
|
|
158
|
+
return KeyResponseWrapper.from_json(response).data
|
|
159
|
+
|
|
160
|
+
async def update_key(
|
|
161
|
+
self,
|
|
162
|
+
key_hash: str,
|
|
163
|
+
*,
|
|
164
|
+
name: str | None = None,
|
|
165
|
+
disabled: bool | None = None,
|
|
166
|
+
limit: float | None = None,
|
|
167
|
+
limit_reset: str | None = None,
|
|
168
|
+
include_byok_in_limit: bool | None = None,
|
|
169
|
+
) -> Key:
|
|
170
|
+
"""Update an existing key."""
|
|
171
|
+
data: dict[str, Any] = {}
|
|
172
|
+
if name is not None:
|
|
173
|
+
data["name"] = name
|
|
174
|
+
if disabled is not None:
|
|
175
|
+
data["disabled"] = disabled
|
|
176
|
+
if limit is not None:
|
|
177
|
+
data["limit"] = limit
|
|
178
|
+
if limit_reset is not None:
|
|
179
|
+
data["limit_reset"] = limit_reset
|
|
180
|
+
if include_byok_in_limit is not None:
|
|
181
|
+
data["include_byok_in_limit"] = include_byok_in_limit
|
|
182
|
+
|
|
183
|
+
response = await self._request(METH_PATCH, f"keys/{key_hash}", data=data)
|
|
184
|
+
return KeyResponseWrapper.from_json(response).data
|
|
185
|
+
|
|
186
|
+
async def delete_key(self, key_hash: str) -> bool:
|
|
187
|
+
"""Delete a key by hash."""
|
|
188
|
+
response = await self._request(METH_DELETE, f"keys/{key_hash}")
|
|
189
|
+
return DeletedKeyResponse.from_json(response).deleted
|
|
190
|
+
|
|
191
|
+
async def get_model(self, author: str, slug: str) -> Model:
|
|
192
|
+
"""Get a single model by author and slug."""
|
|
193
|
+
response = await self._request(METH_GET, f"model/{author}/{slug}")
|
|
194
|
+
return ModelResponseWrapper.from_json(response).data
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def _filter_model_params(
|
|
198
|
+
kwargs: dict[str, Any],
|
|
199
|
+
*,
|
|
200
|
+
numeric_keys: frozenset[str] = frozenset(),
|
|
201
|
+
) -> dict[str, Any]:
|
|
202
|
+
"""Filter None values and convert numeric params to strings."""
|
|
203
|
+
params = {k: v for k, v in kwargs.items() if v is not None}
|
|
204
|
+
for key in numeric_keys:
|
|
205
|
+
if key in params:
|
|
206
|
+
params[key] = str(params[key])
|
|
207
|
+
return params
|
|
208
|
+
|
|
209
|
+
async def get_models( # pylint: disable=too-many-locals
|
|
210
|
+
self,
|
|
211
|
+
*,
|
|
212
|
+
category: str | None = None,
|
|
213
|
+
supported_parameters: str | None = None,
|
|
214
|
+
output_modalities: str | None = None,
|
|
215
|
+
sort: str | None = None,
|
|
216
|
+
q: str | None = None,
|
|
217
|
+
input_modalities: str | None = None,
|
|
218
|
+
context: int | None = None,
|
|
219
|
+
min_price: float | None = None,
|
|
220
|
+
max_price: float | None = None,
|
|
221
|
+
arch: str | None = None,
|
|
222
|
+
model_authors: str | None = None,
|
|
223
|
+
providers: str | None = None,
|
|
224
|
+
distillable: str | None = None,
|
|
225
|
+
zdr: str | None = None,
|
|
226
|
+
region: str | None = None,
|
|
227
|
+
) -> list[Model]:
|
|
228
|
+
"""Get all available models."""
|
|
229
|
+
numeric = frozenset({"context", "min_price", "max_price"})
|
|
230
|
+
params = self._filter_model_params(
|
|
231
|
+
{
|
|
232
|
+
"category": category,
|
|
233
|
+
"supported_parameters": supported_parameters,
|
|
234
|
+
"output_modalities": output_modalities,
|
|
235
|
+
"sort": sort,
|
|
236
|
+
"q": q,
|
|
237
|
+
"input_modalities": input_modalities,
|
|
238
|
+
"context": context,
|
|
239
|
+
"min_price": min_price,
|
|
240
|
+
"max_price": max_price,
|
|
241
|
+
"arch": arch,
|
|
242
|
+
"model_authors": model_authors,
|
|
243
|
+
"providers": providers,
|
|
244
|
+
"distillable": distillable,
|
|
245
|
+
"zdr": zdr,
|
|
246
|
+
"region": region,
|
|
247
|
+
},
|
|
248
|
+
numeric_keys=numeric,
|
|
249
|
+
)
|
|
250
|
+
response = await self._request(METH_GET, "models", params=params or None)
|
|
251
|
+
return ModelsDataWrapper.from_json(response).data
|
|
252
|
+
|
|
253
|
+
async def count_models(
|
|
254
|
+
self,
|
|
255
|
+
*,
|
|
256
|
+
output_modalities: str | None = None,
|
|
257
|
+
) -> int:
|
|
258
|
+
"""Get total count of available models."""
|
|
259
|
+
params: dict[str, Any] = {}
|
|
260
|
+
if output_modalities is not None:
|
|
261
|
+
params["output_modalities"] = output_modalities
|
|
262
|
+
response = await self._request(METH_GET, "models/count", params=params or None)
|
|
263
|
+
result: dict[str, Any] = orjson.loads(response) # pylint: disable=no-member
|
|
264
|
+
count: int = result.get("data", {}).get("count", 0)
|
|
265
|
+
return count
|
|
266
|
+
|
|
267
|
+
async def close(self) -> None:
|
|
268
|
+
"""Close open client session."""
|
|
269
|
+
if self.session and self._close_session:
|
|
270
|
+
await self.session.close()
|
|
271
|
+
|
|
272
|
+
async def __aenter__(self) -> Self:
|
|
273
|
+
"""Async enter.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
The OpenRouterClient object.
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
async def __aexit__(self, *_exc_info: object) -> None:
|
|
283
|
+
"""Async exit.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
----
|
|
287
|
+
_exc_info: Exec type.
|
|
288
|
+
|
|
289
|
+
"""
|
|
290
|
+
await self.close()
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
"""Models for OpenRouter."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import StrEnum
|
|
7
|
-
import logging
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from mashumaro.mixins.orjson import DataClassORJSONMixin
|
|
11
|
-
|
|
12
|
-
_LOGGER = logging.getLogger(__package__)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class KeyDataWrapper(DataClassORJSONMixin):
|
|
17
|
-
"""Wrapper for OpenRouter key data."""
|
|
18
|
-
|
|
19
|
-
data: KeyData
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@dataclass
|
|
23
|
-
class KeyData(DataClassORJSONMixin):
|
|
24
|
-
"""The OpenRouter key data."""
|
|
25
|
-
|
|
26
|
-
label: str
|
|
27
|
-
usage: int
|
|
28
|
-
is_provisioning_key: bool
|
|
29
|
-
limit_remaining: int | None
|
|
30
|
-
is_free_tier: bool
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class KeysDataWrapper(DataClassORJSONMixin):
|
|
35
|
-
"""Wrapper for OpenRouter key data."""
|
|
36
|
-
|
|
37
|
-
data: list[Key]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass
|
|
41
|
-
class CreateKeyDataWrapper(DataClassORJSONMixin):
|
|
42
|
-
"""Wrapper for OpenRouter key data."""
|
|
43
|
-
|
|
44
|
-
data: CreatedKey
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@dataclass
|
|
48
|
-
class Key(DataClassORJSONMixin):
|
|
49
|
-
"""The OpenRouter key data."""
|
|
50
|
-
|
|
51
|
-
hash: str
|
|
52
|
-
name: str
|
|
53
|
-
label: str
|
|
54
|
-
disabled: bool
|
|
55
|
-
limit: float
|
|
56
|
-
usage: float = 0.0
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@dataclass(kw_only=True)
|
|
60
|
-
class CreatedKey(Key):
|
|
61
|
-
"""Created key."""
|
|
62
|
-
|
|
63
|
-
key: str
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
@dataclass
|
|
67
|
-
class ModelsDataWrapper(DataClassORJSONMixin):
|
|
68
|
-
"""Wrapper for OpenRouter model data."""
|
|
69
|
-
|
|
70
|
-
data: list[Model]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class SupportedParameter(StrEnum):
|
|
74
|
-
"""Supported parameters for models."""
|
|
75
|
-
|
|
76
|
-
TEMPERATURE = "temperature"
|
|
77
|
-
TOP_P = "top_p"
|
|
78
|
-
MAX_TOKENS = "max_tokens"
|
|
79
|
-
REASONING = "reasoning"
|
|
80
|
-
INCLUDE_REASONING = "include_reasoning"
|
|
81
|
-
STOP = "stop"
|
|
82
|
-
TOK_K = "top_k"
|
|
83
|
-
SEED = "seed"
|
|
84
|
-
RESPONSE_FORMAT = "response_format"
|
|
85
|
-
TOOLS = "tools"
|
|
86
|
-
TOOL_CHOICE = "tool_choice"
|
|
87
|
-
FREQUENCY_PENALTY = "frequency_penalty"
|
|
88
|
-
PRESENCE_PENALTY = "presence_penalty"
|
|
89
|
-
MIN_P = "min_p"
|
|
90
|
-
REPETITION_PENALTY = "repetition_penalty"
|
|
91
|
-
LOGPROBS = "logprobs"
|
|
92
|
-
LOGIT_BIAS = "logit_bias"
|
|
93
|
-
TOP_LOGPROBS = "top_logprobs"
|
|
94
|
-
STRUCTURED_OUTPUTS = "structured_outputs"
|
|
95
|
-
WEB_SEARCH_OPTIONS = "web_search_options"
|
|
96
|
-
TOP_A = "top_a"
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class Modality(StrEnum):
|
|
100
|
-
"""Supported modalities for models."""
|
|
101
|
-
|
|
102
|
-
AUDIO = "audio"
|
|
103
|
-
TEXT = "text"
|
|
104
|
-
IMAGE = "image"
|
|
105
|
-
FILE = "file"
|
|
106
|
-
EMBEDDINGS = "embeddings"
|
|
107
|
-
VIDEO = "video"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@dataclass(kw_only=True)
|
|
111
|
-
class ModelArchitecture(DataClassORJSONMixin):
|
|
112
|
-
"""Model data."""
|
|
113
|
-
|
|
114
|
-
input_modalities: list[Modality]
|
|
115
|
-
output_modalities: list[Modality]
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@dataclass(kw_only=True)
|
|
119
|
-
class Model(DataClassORJSONMixin):
|
|
120
|
-
"""Model data."""
|
|
121
|
-
|
|
122
|
-
id: str
|
|
123
|
-
canonical_slug: str
|
|
124
|
-
hugging_face_id: str | None
|
|
125
|
-
name: str
|
|
126
|
-
description: str
|
|
127
|
-
context_length: int
|
|
128
|
-
architecture: ModelArchitecture
|
|
129
|
-
supported_parameters: list[SupportedParameter]
|
|
130
|
-
|
|
131
|
-
@classmethod
|
|
132
|
-
def __pre_deserialize__(cls, d: dict[str, Any]) -> dict[str, Any]:
|
|
133
|
-
"""Pre deserialize hook."""
|
|
134
|
-
parameters = d.get("supported_parameters", [])
|
|
135
|
-
for parameter in parameters:
|
|
136
|
-
if parameter not in SupportedParameter:
|
|
137
|
-
_LOGGER.warning(
|
|
138
|
-
"Unsupported parameter: %s. Please report at https://github.com/joostlek/python-open-router/issues.",
|
|
139
|
-
parameter,
|
|
140
|
-
)
|
|
141
|
-
parameters.remove(parameter)
|
|
142
|
-
return d
|
|
143
|
-
|
|
144
|
-
@classmethod
|
|
145
|
-
def __post_deserialize__(cls, obj: Model) -> Model:
|
|
146
|
-
"""Post deserialize hook."""
|
|
147
|
-
if obj.hugging_face_id == "":
|
|
148
|
-
obj.hugging_face_id = None
|
|
149
|
-
return obj
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"""Asynchronous Python client for Open Router."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
from dataclasses import dataclass
|
|
7
|
-
from importlib import metadata
|
|
8
|
-
import socket
|
|
9
|
-
from typing import TYPE_CHECKING, Any
|
|
10
|
-
|
|
11
|
-
from aiohttp import ClientError, ClientResponseError, ClientSession
|
|
12
|
-
from aiohttp.hdrs import METH_GET, METH_POST
|
|
13
|
-
from yarl import URL
|
|
14
|
-
|
|
15
|
-
from python_open_router.exceptions import OpenRouterConnectionError
|
|
16
|
-
from python_open_router.models import (
|
|
17
|
-
CreateKeyDataWrapper,
|
|
18
|
-
Key,
|
|
19
|
-
KeyData,
|
|
20
|
-
KeyDataWrapper,
|
|
21
|
-
KeysDataWrapper,
|
|
22
|
-
Model,
|
|
23
|
-
ModelsDataWrapper,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
from typing_extensions import Self
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
VERSION = metadata.version(__package__)
|
|
31
|
-
HOST = "openrouter.ai"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@dataclass
|
|
35
|
-
class OpenRouterClient:
|
|
36
|
-
"""Main class for handling connections with OpenRouter."""
|
|
37
|
-
|
|
38
|
-
api_key: str
|
|
39
|
-
session: ClientSession | None = None
|
|
40
|
-
request_timeout: int = 10
|
|
41
|
-
_close_session: bool = False
|
|
42
|
-
|
|
43
|
-
async def _request(
|
|
44
|
-
self,
|
|
45
|
-
method: str,
|
|
46
|
-
uri: str,
|
|
47
|
-
*,
|
|
48
|
-
params: dict[str, Any] | None = None,
|
|
49
|
-
data: dict[str, Any] | None = None,
|
|
50
|
-
) -> str:
|
|
51
|
-
"""Handle a request to OpenRouter."""
|
|
52
|
-
url = URL.build(host=HOST, scheme="https").joinpath(f"api/v1/{uri}")
|
|
53
|
-
|
|
54
|
-
headers = {
|
|
55
|
-
"User-Agent": f"PythonOpenRouter/{VERSION}",
|
|
56
|
-
"Accept": "application/json",
|
|
57
|
-
"Authorization": f"Bearer {self.api_key}",
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if self.session is None:
|
|
61
|
-
self.session = ClientSession()
|
|
62
|
-
self._close_session = True
|
|
63
|
-
|
|
64
|
-
try:
|
|
65
|
-
async with asyncio.timeout(self.request_timeout):
|
|
66
|
-
response = await self.session.request(
|
|
67
|
-
method,
|
|
68
|
-
url,
|
|
69
|
-
params=params,
|
|
70
|
-
headers=headers,
|
|
71
|
-
json=data,
|
|
72
|
-
)
|
|
73
|
-
except asyncio.TimeoutError as exception:
|
|
74
|
-
msg = "Timeout occurred while connecting to the service"
|
|
75
|
-
raise OpenRouterConnectionError(msg) from exception
|
|
76
|
-
except (
|
|
77
|
-
ClientError,
|
|
78
|
-
ClientResponseError,
|
|
79
|
-
socket.gaierror,
|
|
80
|
-
) as exception:
|
|
81
|
-
msg = "Error occurred while communicating with the service"
|
|
82
|
-
raise OpenRouterConnectionError(msg) from exception
|
|
83
|
-
|
|
84
|
-
if response.status >= 400:
|
|
85
|
-
content_type = response.headers.get("Content-Type", "")
|
|
86
|
-
text = await response.text()
|
|
87
|
-
msg = "Unexpected response from OpenRouter"
|
|
88
|
-
raise OpenRouterConnectionError(
|
|
89
|
-
msg,
|
|
90
|
-
{"Content-Type": content_type, "response": text},
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
return await response.text()
|
|
94
|
-
|
|
95
|
-
async def get_key_data(self) -> KeyData:
|
|
96
|
-
"""Get key data for API key."""
|
|
97
|
-
response = await self._request(METH_GET, "key")
|
|
98
|
-
return KeyDataWrapper.from_json(response).data
|
|
99
|
-
|
|
100
|
-
async def get_keys(self) -> list[Key]:
|
|
101
|
-
"""Get all keys."""
|
|
102
|
-
response = await self._request(METH_GET, "keys")
|
|
103
|
-
return KeysDataWrapper.from_json(response).data
|
|
104
|
-
|
|
105
|
-
async def create_key(self, name: str, limit: float | None = None) -> Key:
|
|
106
|
-
"""Create a new key."""
|
|
107
|
-
data: dict[str, Any] = {"name": name}
|
|
108
|
-
if limit is not None:
|
|
109
|
-
data["limit"] = limit
|
|
110
|
-
|
|
111
|
-
response = await self._request(METH_POST, "keys", data=data)
|
|
112
|
-
return CreateKeyDataWrapper.from_json(response).data
|
|
113
|
-
|
|
114
|
-
async def get_models(self) -> list[Model]:
|
|
115
|
-
"""Get all available models."""
|
|
116
|
-
response = await self._request(METH_GET, "models")
|
|
117
|
-
return ModelsDataWrapper.from_json(response).data
|
|
118
|
-
|
|
119
|
-
async def close(self) -> None:
|
|
120
|
-
"""Close open client session."""
|
|
121
|
-
if self.session and self._close_session:
|
|
122
|
-
await self.session.close()
|
|
123
|
-
|
|
124
|
-
async def __aenter__(self) -> Self:
|
|
125
|
-
"""Async enter.
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
The OpenRouterClient object.
|
|
130
|
-
|
|
131
|
-
"""
|
|
132
|
-
return self
|
|
133
|
-
|
|
134
|
-
async def __aexit__(self, *_exc_info: object) -> None:
|
|
135
|
-
"""Async exit.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
----
|
|
139
|
-
_exc_info: Exec type.
|
|
140
|
-
|
|
141
|
-
"""
|
|
142
|
-
await self.close()
|
|
File without changes
|
|
File without changes
|