kotharcomputing 0.97.0__tar.gz → 0.99.0__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.
- {kotharcomputing-0.97.0/src/kotharcomputing.egg-info → kotharcomputing-0.99.0}/PKG-INFO +1 -1
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/pyproject.toml +1 -1
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/__init__.py +2 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/files.py +68 -3
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/jobs.py +18 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0/src/kotharcomputing.egg-info}/PKG-INFO +1 -1
- kotharcomputing-0.99.0/tests/test_fetch_url_building.py +215 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/tests/test_public_api.py +1 -0
- kotharcomputing-0.97.0/tests/test_fetch_url_building.py +0 -73
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/LICENSE +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/README.md +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/setup.cfg +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/agents.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/ai.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/aleph_language_server.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/api_tokens.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/client.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/common.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/credits.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/errors.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/executions.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/fetch.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/py.typed +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/runtimes.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/service_prices.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/subscribe_user.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/subscribe_workspace.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/subscriptions.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/users.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/workspaces.py +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/SOURCES.txt +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/dependency_links.txt +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/requires.txt +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/top_level.txt +0 -0
- {kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/tests/test_fetch_auth_headers.py +0 -0
|
@@ -113,6 +113,7 @@ from .jobs import (
|
|
|
113
113
|
JobLaunchInstructionMethod,
|
|
114
114
|
JobLaunchInstructions,
|
|
115
115
|
JobStatus,
|
|
116
|
+
JobUploadedFile,
|
|
116
117
|
UpdateJobArgs,
|
|
117
118
|
)
|
|
118
119
|
from .service_prices import ServicePrice
|
|
@@ -253,6 +254,7 @@ __all__ = [
|
|
|
253
254
|
"JobLaunchInstructionMethod",
|
|
254
255
|
"JobLaunchInstructions",
|
|
255
256
|
"JobStatus",
|
|
257
|
+
"JobUploadedFile",
|
|
256
258
|
"UpdateJobArgs",
|
|
257
259
|
# Service Prices
|
|
258
260
|
"ServicePrice",
|
|
@@ -51,6 +51,7 @@ class FileContent:
|
|
|
51
51
|
data: bytes
|
|
52
52
|
file_name: str
|
|
53
53
|
etag: str | None = None
|
|
54
|
+
version_id: str | None = None
|
|
54
55
|
mime_type: str | None = None
|
|
55
56
|
|
|
56
57
|
def text(self, encoding: str = "utf-8") -> str:
|
|
@@ -63,6 +64,11 @@ class FileByPathClient:
|
|
|
63
64
|
self._workspace_id = workspace_id
|
|
64
65
|
self._path = path
|
|
65
66
|
|
|
67
|
+
def version(self, version_id: str) -> "FileByPathVersionClient":
|
|
68
|
+
return FileByPathVersionClient(
|
|
69
|
+
self._transport, self._workspace_id, self._path, version_id
|
|
70
|
+
)
|
|
71
|
+
|
|
66
72
|
def get(self, *, etag: str | None = None) -> FileContent | None:
|
|
67
73
|
response = self._transport.raw(
|
|
68
74
|
"get",
|
|
@@ -80,6 +86,7 @@ class FileByPathClient:
|
|
|
80
86
|
data=response.body,
|
|
81
87
|
file_name=file_name,
|
|
82
88
|
etag=response.headers.get("etag"),
|
|
89
|
+
version_id=response.headers.get("x-kothar-file-version-id"),
|
|
83
90
|
mime_type=response.headers.get("content-type"),
|
|
84
91
|
)
|
|
85
92
|
|
|
@@ -93,11 +100,14 @@ class FileByPathClient:
|
|
|
93
100
|
search_params={"asFolder": as_folder},
|
|
94
101
|
body=content,
|
|
95
102
|
)
|
|
96
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
"etag": response.headers.get("etag"),
|
|
105
|
+
"version_id": response.headers.get("x-kothar-file-version-id"),
|
|
106
|
+
}
|
|
97
107
|
|
|
98
108
|
def update(
|
|
99
109
|
self, *, content: bytes | str, etag: str | None = None
|
|
100
|
-
) -> dict[str, str]:
|
|
110
|
+
) -> dict[str, str | None]:
|
|
101
111
|
response = self._transport.raw(
|
|
102
112
|
"put",
|
|
103
113
|
"v1/workspaces/:workspaceId/files/:filePath",
|
|
@@ -114,7 +124,10 @@ class FileByPathClient:
|
|
|
114
124
|
"status": response.status,
|
|
115
125
|
},
|
|
116
126
|
)
|
|
117
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
"etag": response_etag,
|
|
129
|
+
"version_id": response.headers.get("x-kothar-file-version-id"),
|
|
130
|
+
}
|
|
118
131
|
|
|
119
132
|
def download_url(self) -> str:
|
|
120
133
|
payload = cast(
|
|
@@ -127,6 +140,15 @@ class FileByPathClient:
|
|
|
127
140
|
)
|
|
128
141
|
return payload["url"]
|
|
129
142
|
|
|
143
|
+
def restore(self, version_id: str) -> dict[str, str | None]:
|
|
144
|
+
response = self._transport.raw(
|
|
145
|
+
"post",
|
|
146
|
+
"v1/workspaces/:workspaceId/files/$restore",
|
|
147
|
+
path_params={"workspaceId": self._workspace_id},
|
|
148
|
+
json_data={"path": self._path, "versionId": version_id},
|
|
149
|
+
)
|
|
150
|
+
return {"version_id": response.headers.get("x-kothar-file-version-id")}
|
|
151
|
+
|
|
130
152
|
def move(self, target_path: str) -> None:
|
|
131
153
|
self._transport.raw(
|
|
132
154
|
"post",
|
|
@@ -155,6 +177,49 @@ class FileByPathClient:
|
|
|
155
177
|
)
|
|
156
178
|
|
|
157
179
|
|
|
180
|
+
class FileByPathVersionClient:
|
|
181
|
+
def __init__(
|
|
182
|
+
self, transport: ApiTransport, workspace_id: str, path: str, version_id: str
|
|
183
|
+
) -> None:
|
|
184
|
+
self._transport = transport
|
|
185
|
+
self._workspace_id = workspace_id
|
|
186
|
+
self._path = path
|
|
187
|
+
self._version_id = version_id
|
|
188
|
+
|
|
189
|
+
def get(self, *, etag: str | None = None) -> FileContent | None:
|
|
190
|
+
response = self._transport.raw(
|
|
191
|
+
"get",
|
|
192
|
+
"v1/workspaces/:workspaceId/files/:filePath",
|
|
193
|
+
path_params={"workspaceId": self._workspace_id, "filePath": self._path},
|
|
194
|
+
search_params={"versionId": self._version_id},
|
|
195
|
+
headers={"if-none-match": etag} if etag else None,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if response.status == 304:
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
content_disposition = response.headers.get("content-disposition", "")
|
|
202
|
+
file_name = extract_filename(content_disposition) or self._path.split("/")[-1]
|
|
203
|
+
return FileContent(
|
|
204
|
+
data=response.body,
|
|
205
|
+
file_name=file_name,
|
|
206
|
+
etag=response.headers.get("etag"),
|
|
207
|
+
version_id=response.headers.get("x-kothar-file-version-id"),
|
|
208
|
+
mime_type=response.headers.get("content-type"),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def download_url(self) -> str:
|
|
212
|
+
payload = cast(
|
|
213
|
+
dict[str, str],
|
|
214
|
+
self._transport.get_json(
|
|
215
|
+
"v1/workspaces/:workspaceId/files/$downloadUrl",
|
|
216
|
+
path_params={"workspaceId": self._workspace_id},
|
|
217
|
+
search_params={"path": self._path, "versionId": self._version_id},
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
return payload["url"]
|
|
221
|
+
|
|
222
|
+
|
|
158
223
|
class FilesClient:
|
|
159
224
|
def __init__(self, transport: ApiTransport, workspace_id: str) -> None:
|
|
160
225
|
self._transport = transport
|
|
@@ -15,10 +15,25 @@ JobStatus: TypeAlias = Literal[
|
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class JobDependency(TypedDict):
|
|
19
|
+
path: str
|
|
20
|
+
versionId: NotRequired[str]
|
|
21
|
+
source: Literal["explicit", "detected"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class JobUploadedFile(TypedDict):
|
|
25
|
+
path: str
|
|
26
|
+
kind: Literal["file", "archive"]
|
|
27
|
+
action: Literal["created", "modified"]
|
|
28
|
+
hash: str
|
|
29
|
+
versionId: NotRequired[str]
|
|
30
|
+
|
|
31
|
+
|
|
18
32
|
class Job(WithUser, WithAgent):
|
|
19
33
|
id: str
|
|
20
34
|
workspaceId: str
|
|
21
35
|
path: str
|
|
36
|
+
versionId: NotRequired[str]
|
|
22
37
|
status: JobStatus
|
|
23
38
|
createdAt: str
|
|
24
39
|
isTerminated: bool
|
|
@@ -35,6 +50,8 @@ class Job(WithUser, WithAgent):
|
|
|
35
50
|
launchInstructions: NotRequired[JobLaunchInstructions]
|
|
36
51
|
consumedCredits: NotRequired[float]
|
|
37
52
|
data: NotRequired[JSONValue]
|
|
53
|
+
uploadedFiles: NotRequired[list[JobUploadedFile]]
|
|
54
|
+
dependencies: NotRequired[list[JobDependency]]
|
|
38
55
|
|
|
39
56
|
|
|
40
57
|
class JobLaunchInstructions(TypedDict):
|
|
@@ -57,6 +74,7 @@ class JobLaunchInstructionCommand(TypedDict):
|
|
|
57
74
|
class CreateJobArgs(TypedDict):
|
|
58
75
|
agentId: str
|
|
59
76
|
path: str
|
|
77
|
+
versionId: NotRequired[str]
|
|
60
78
|
runtimeId: NotRequired[str]
|
|
61
79
|
name: NotRequired[str]
|
|
62
80
|
notes: NotRequired[str]
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
from urllib import parse as urllib_parse
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
SRC_DIR = Path(__file__).resolve().parents[1] / "src"
|
|
10
|
+
if str(SRC_DIR) not in sys.path:
|
|
11
|
+
sys.path.insert(0, str(SRC_DIR))
|
|
12
|
+
|
|
13
|
+
from kotharcomputing.fetch import ApiTransport, RawResponse
|
|
14
|
+
from kotharcomputing.files import FileByPathClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_build_url_encodes_path_params_and_bool_query_values() -> None:
|
|
18
|
+
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
19
|
+
|
|
20
|
+
url = transport.build_url(
|
|
21
|
+
"v1/workspaces/:workspaceId/files/:filePath",
|
|
22
|
+
path_params={"workspaceId": "personal", "filePath": "folder/hello world.txt"},
|
|
23
|
+
search_params={"withUsernames": True, "limit": 25},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
assert (
|
|
27
|
+
url
|
|
28
|
+
== "https://api.example.com/v1/workspaces/personal/files/folder%2Fhello%20world.txt?withUsernames=true&limit=25"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_build_url_expands_list_query_params_and_skips_none_values() -> None:
|
|
33
|
+
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
34
|
+
|
|
35
|
+
url = transport.build_url(
|
|
36
|
+
"v1/users/me/credits/transactions",
|
|
37
|
+
search_params={
|
|
38
|
+
"references": ["job-1", "job-2"],
|
|
39
|
+
"active": False,
|
|
40
|
+
"next": None,
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
query_pairs = urllib_parse.parse_qsl(
|
|
45
|
+
urllib_parse.urlsplit(url).query, keep_blank_values=True
|
|
46
|
+
)
|
|
47
|
+
assert query_pairs == [
|
|
48
|
+
("references", "job-1"),
|
|
49
|
+
("references", "job-2"),
|
|
50
|
+
("active", "false"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_build_url_preserves_existing_query_and_appends_new_params() -> None:
|
|
55
|
+
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
56
|
+
|
|
57
|
+
url = transport.build_url(
|
|
58
|
+
"v1/jobs?next=cursor-1",
|
|
59
|
+
search_params={"withAgents": True},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
assert (
|
|
63
|
+
url == "https://api.example.com/v1/jobs?next=cursor-1&withAgents=true"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_build_url_rejects_absolute_url_outside_base_url() -> None:
|
|
68
|
+
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
69
|
+
|
|
70
|
+
with pytest.raises(ValueError, match="does not start with the base URL"):
|
|
71
|
+
transport.build_url(
|
|
72
|
+
"https://malicious.example.com/v1/jobs",
|
|
73
|
+
absolute_url=True,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_files_client_sends_version_id_for_versioned_download_url() -> None:
|
|
78
|
+
class Transport:
|
|
79
|
+
def __init__(self) -> None:
|
|
80
|
+
self.calls: list[object] = []
|
|
81
|
+
|
|
82
|
+
def get_json(
|
|
83
|
+
self,
|
|
84
|
+
templated_url: str,
|
|
85
|
+
*,
|
|
86
|
+
path_params: object | None = None,
|
|
87
|
+
search_params: object | None = None,
|
|
88
|
+
headers: object | None = None,
|
|
89
|
+
) -> object:
|
|
90
|
+
self.calls.append(
|
|
91
|
+
{
|
|
92
|
+
"templated_url": templated_url,
|
|
93
|
+
"path_params": path_params,
|
|
94
|
+
"search_params": search_params,
|
|
95
|
+
"headers": headers,
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
return {"url": "https://api.example.com/download"}
|
|
99
|
+
|
|
100
|
+
transport = Transport()
|
|
101
|
+
client = FileByPathClient(transport, "workspace", "folder/file.txt") # type: ignore[arg-type]
|
|
102
|
+
|
|
103
|
+
url = client.version("version+1").download_url()
|
|
104
|
+
|
|
105
|
+
assert url == "https://api.example.com/download"
|
|
106
|
+
assert transport.calls == [
|
|
107
|
+
{
|
|
108
|
+
"templated_url": "v1/workspaces/:workspaceId/files/$downloadUrl",
|
|
109
|
+
"path_params": {"workspaceId": "workspace"},
|
|
110
|
+
"search_params": {
|
|
111
|
+
"path": "folder/file.txt",
|
|
112
|
+
"versionId": "version+1",
|
|
113
|
+
},
|
|
114
|
+
"headers": None,
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_files_client_exposes_resolved_version_id_from_get_header() -> None:
|
|
120
|
+
class Transport:
|
|
121
|
+
def raw(
|
|
122
|
+
self,
|
|
123
|
+
method: str,
|
|
124
|
+
templated_url: str,
|
|
125
|
+
*,
|
|
126
|
+
path_params: object | None = None,
|
|
127
|
+
headers: object | None = None,
|
|
128
|
+
) -> RawResponse:
|
|
129
|
+
return RawResponse(
|
|
130
|
+
status=200,
|
|
131
|
+
headers={
|
|
132
|
+
"content-disposition": "inline; filename=file.txt",
|
|
133
|
+
"x-kothar-file-version-id": "version-1",
|
|
134
|
+
},
|
|
135
|
+
body=b"content",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
client = FileByPathClient(Transport(), "workspace", "file.txt") # type: ignore[arg-type]
|
|
139
|
+
|
|
140
|
+
file = client.get()
|
|
141
|
+
|
|
142
|
+
assert file is not None
|
|
143
|
+
assert file.version_id == "version-1"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_files_client_restores_versions_from_base_path_client() -> None:
|
|
147
|
+
class Transport:
|
|
148
|
+
def __init__(self) -> None:
|
|
149
|
+
self.call: object | None = None
|
|
150
|
+
|
|
151
|
+
def raw(
|
|
152
|
+
self,
|
|
153
|
+
method: str,
|
|
154
|
+
templated_url: str,
|
|
155
|
+
*,
|
|
156
|
+
path_params: object | None = None,
|
|
157
|
+
json_data: object | None = None,
|
|
158
|
+
) -> RawResponse:
|
|
159
|
+
self.call = {
|
|
160
|
+
"method": method,
|
|
161
|
+
"templated_url": templated_url,
|
|
162
|
+
"path_params": path_params,
|
|
163
|
+
"json_data": json_data,
|
|
164
|
+
}
|
|
165
|
+
return RawResponse(
|
|
166
|
+
status=204,
|
|
167
|
+
headers={"x-kothar-file-version-id": "restored-version"},
|
|
168
|
+
body=b"",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
transport = Transport()
|
|
172
|
+
client = FileByPathClient(transport, "workspace", "file.txt") # type: ignore[arg-type]
|
|
173
|
+
|
|
174
|
+
result = client.restore("version-1")
|
|
175
|
+
|
|
176
|
+
assert transport.call == {
|
|
177
|
+
"method": "post",
|
|
178
|
+
"templated_url": "v1/workspaces/:workspaceId/files/$restore",
|
|
179
|
+
"path_params": {"workspaceId": "workspace"},
|
|
180
|
+
"json_data": {"path": "file.txt", "versionId": "version-1"},
|
|
181
|
+
}
|
|
182
|
+
assert result == {"version_id": "restored-version"}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_files_client_exposes_write_version_headers() -> None:
|
|
186
|
+
class Transport:
|
|
187
|
+
def raw(
|
|
188
|
+
self,
|
|
189
|
+
method: str,
|
|
190
|
+
templated_url: str,
|
|
191
|
+
*,
|
|
192
|
+
path_params: object | None = None,
|
|
193
|
+
search_params: object | None = None,
|
|
194
|
+
headers: object | None = None,
|
|
195
|
+
body: object | None = None,
|
|
196
|
+
) -> RawResponse:
|
|
197
|
+
return RawResponse(
|
|
198
|
+
status=204,
|
|
199
|
+
headers={
|
|
200
|
+
"etag": "etag-1",
|
|
201
|
+
"x-kothar-file-version-id": f"{method}-version",
|
|
202
|
+
},
|
|
203
|
+
body=b"",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
client = FileByPathClient(Transport(), "workspace", "file.txt") # type: ignore[arg-type]
|
|
207
|
+
|
|
208
|
+
assert client.create(content=b"content") == {
|
|
209
|
+
"etag": "etag-1",
|
|
210
|
+
"version_id": "post-version",
|
|
211
|
+
}
|
|
212
|
+
assert client.update(content=b"content") == {
|
|
213
|
+
"etag": "etag-1",
|
|
214
|
+
"version_id": "put-version",
|
|
215
|
+
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
import sys
|
|
5
|
-
from urllib import parse as urllib_parse
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
|
|
9
|
-
SRC_DIR = Path(__file__).resolve().parents[1] / "src"
|
|
10
|
-
if str(SRC_DIR) not in sys.path:
|
|
11
|
-
sys.path.insert(0, str(SRC_DIR))
|
|
12
|
-
|
|
13
|
-
from kotharcomputing.fetch import ApiTransport
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def test_build_url_encodes_path_params_and_bool_query_values() -> None:
|
|
17
|
-
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
18
|
-
|
|
19
|
-
url = transport.build_url(
|
|
20
|
-
"v1/workspaces/:workspaceId/files/:filePath",
|
|
21
|
-
path_params={"workspaceId": "personal", "filePath": "folder/hello world.txt"},
|
|
22
|
-
search_params={"withUsernames": True, "limit": 25},
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
assert (
|
|
26
|
-
url
|
|
27
|
-
== "https://api.example.com/v1/workspaces/personal/files/folder%2Fhello%20world.txt?withUsernames=true&limit=25"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_build_url_expands_list_query_params_and_skips_none_values() -> None:
|
|
32
|
-
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
33
|
-
|
|
34
|
-
url = transport.build_url(
|
|
35
|
-
"v1/users/me/credits/transactions",
|
|
36
|
-
search_params={
|
|
37
|
-
"references": ["job-1", "job-2"],
|
|
38
|
-
"active": False,
|
|
39
|
-
"next": None,
|
|
40
|
-
},
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
query_pairs = urllib_parse.parse_qsl(
|
|
44
|
-
urllib_parse.urlsplit(url).query, keep_blank_values=True
|
|
45
|
-
)
|
|
46
|
-
assert query_pairs == [
|
|
47
|
-
("references", "job-1"),
|
|
48
|
-
("references", "job-2"),
|
|
49
|
-
("active", "false"),
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_build_url_preserves_existing_query_and_appends_new_params() -> None:
|
|
54
|
-
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
55
|
-
|
|
56
|
-
url = transport.build_url(
|
|
57
|
-
"v1/jobs?next=cursor-1",
|
|
58
|
-
search_params={"withAgents": True},
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
assert (
|
|
62
|
-
url == "https://api.example.com/v1/jobs?next=cursor-1&withAgents=true"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def test_build_url_rejects_absolute_url_outside_base_url() -> None:
|
|
67
|
-
transport = ApiTransport("https://api.example.com", access_token=None, timeout=30.0)
|
|
68
|
-
|
|
69
|
-
with pytest.raises(ValueError, match="does not start with the base URL"):
|
|
70
|
-
transport.build_url(
|
|
71
|
-
"https://malicious.example.com/v1/jobs",
|
|
72
|
-
absolute_url=True,
|
|
73
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/aleph_language_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing/subscribe_workspace.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{kotharcomputing-0.97.0 → kotharcomputing-0.99.0}/src/kotharcomputing.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|