codeapi-client 0.4.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.
codeapi/types/_zips.py ADDED
@@ -0,0 +1,466 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import base64
5
+ import zipfile
6
+ from io import BytesIO, IOBase
7
+ from pathlib import Path
8
+ from typing import BinaryIO, Type, TypeVar
9
+
10
+ from pydantic import BaseModel, field_serializer, field_validator
11
+ from typing_extensions import Self
12
+
13
+ from codeapi.types import JsonData
14
+
15
+ from ._enums import ExitType
16
+
17
+ EMPTY_ZIP_BYTES = b"\x50\x4b\x05\x06" + b"\x00" * 18
18
+
19
+ DOTENV_FILE = ".env"
20
+ PEXENV_FILE = "venv.pex"
21
+ PEXENV_CMD = f"./{PEXENV_FILE}"
22
+ REQUIREMENTS_TXT = "requirements.txt"
23
+
24
+
25
+ TZip = TypeVar("TZip", bound="ZipBytes")
26
+
27
+
28
+ class ZipBytes(BaseModel):
29
+ zip_bytes: bytes = EMPTY_ZIP_BYTES
30
+
31
+ @field_serializer("zip_bytes")
32
+ def serialize_bytes(self, value: bytes) -> str:
33
+ return base64.b64encode(value).decode()
34
+
35
+ @field_validator("zip_bytes", mode="before")
36
+ @classmethod
37
+ def deserialize_bytes(cls, value: bytes | str) -> bytes:
38
+ if isinstance(value, bytes):
39
+ return value
40
+ return base64.b64decode(value)
41
+
42
+ @property
43
+ def files(self) -> list[str]:
44
+ return self.list_files()
45
+
46
+ def list_files(self) -> list[str]:
47
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
48
+ return zf.namelist()
49
+
50
+ def has_file(self, file: str) -> bool:
51
+ return file in self.list_files()
52
+
53
+ def print_files(self) -> None:
54
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
55
+ return zf.printdir()
56
+
57
+ def read_file(self, file: str) -> str:
58
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
59
+ return zf.read(name=file).decode()
60
+
61
+ def read_file_bytes(self, file: str) -> bytes:
62
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
63
+ return zf.read(name=file)
64
+
65
+ def read_files(self, files: list[str]) -> list[str]:
66
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
67
+ return [zf.read(name=file).decode() for file in files]
68
+
69
+ def read_files_bytes(self, files: list[str]) -> list[bytes]:
70
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
71
+ return [zf.read(name=file) for file in files]
72
+
73
+ def add_empty_file(self, name: str, arcdir: str = ".") -> Self:
74
+ return self.add_file(arcdir=arcdir, name=name, file=b"")
75
+
76
+ def add_file(
77
+ self, file: Path | str | bytes, name: str | None = None, arcdir: str = "."
78
+ ) -> Self:
79
+ names = [name] if name is not None else None
80
+ return self.add_files(files=[file], names=names, arcdir=arcdir)
81
+
82
+ def add_files(
83
+ self,
84
+ files: list[Path | str | bytes],
85
+ names: list[str] | None = None,
86
+ arcdir: str = ".",
87
+ ) -> Self:
88
+ if names is not None and len(files) != len(names):
89
+ raise ValueError("files and names must have the same length")
90
+
91
+ arcnames = set()
92
+ mem_zip = BytesIO()
93
+ with zipfile.ZipFile(BytesIO(self.zip_bytes), "r") as zf_old:
94
+ with zipfile.ZipFile(mem_zip, "w") as zf_new:
95
+ for i, file in enumerate(files):
96
+ if isinstance(file, bytes):
97
+ name = names[i] if names is not None else None
98
+ if not name:
99
+ raise ValueError("name required when file is of type bytes")
100
+ arcname = (arcdir.rstrip("/") + "/" + name).removeprefix("./")
101
+ arcnames.add(arcname)
102
+ zf_new.writestr(arcname, file)
103
+ else:
104
+ name = names[i] if names is not None else Path(file).name
105
+ if not name:
106
+ name = Path(file).name
107
+ arcname = (arcdir.rstrip("/") + "/" + name).removeprefix("./")
108
+ arcnames.add(arcname)
109
+ zf_new.write(str(file), arcname)
110
+ for f in set(zf_old.namelist()) - arcnames:
111
+ zf_new.writestr(f, zf_old.read(f))
112
+
113
+ self.zip_bytes = mem_zip.getvalue()
114
+ return self
115
+
116
+ def delete_file(self, file: str) -> Self:
117
+ return self.delete_files(files=[file])
118
+
119
+ def delete_files(self, files: list[str]) -> Self:
120
+ mem_zip = BytesIO()
121
+ with zipfile.ZipFile(BytesIO(self.zip_bytes), "r") as zf_old:
122
+ with zipfile.ZipFile(mem_zip, "w") as zf_new:
123
+ for f in zf_old.namelist():
124
+ if f not in files:
125
+ zf_new.writestr(f, zf_old.read(f))
126
+ self.zip_bytes = mem_zip.getvalue()
127
+ return self
128
+
129
+ def extract_file(
130
+ self, file: str, path: Path | str = ".", flatten: bool = False
131
+ ) -> Self:
132
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
133
+ if flatten:
134
+ target_path = Path(path) / Path(file).name
135
+ target_path.parent.mkdir(parents=True, exist_ok=True)
136
+ target_path.write_bytes(zf.read(name=file))
137
+ elif Path(file).name in str(path):
138
+ target_path = Path(path)
139
+ target_path.parent.mkdir(parents=True, exist_ok=True)
140
+ target_path.write_bytes(zf.read(name=file))
141
+ else:
142
+ zf.extract(member=file, path=str(path))
143
+ return self
144
+
145
+ def extract(self, path: Path | str, files: list[str] | None = None) -> Self:
146
+ with zipfile.ZipFile(BytesIO(self.zip_bytes)) as zf:
147
+ for file_info in zf.infolist():
148
+ if ".." in file_info.filename or file_info.filename.startswith("/"):
149
+ raise ValueError("invalid file paths in archive")
150
+ zf.extractall(path=str(path), members=files)
151
+ return self
152
+
153
+ def to_zipfile(self, path: Path | str) -> None:
154
+ Path(path).write_bytes(data=self.zip_bytes)
155
+
156
+ def to_bytesio(self) -> BytesIO:
157
+ return BytesIO(self.zip_bytes)
158
+
159
+ def to_codezip(self) -> CodeZip:
160
+ return CodeZip(zip_bytes=self.zip_bytes)
161
+
162
+ def to_datazip(self) -> DataZip:
163
+ return DataZip(zip_bytes=self.zip_bytes)
164
+
165
+ @classmethod
166
+ def from_zipfile(cls: Type[TZip], path: Path | str) -> TZip:
167
+ path = Path(path)
168
+ if not path.is_file():
169
+ raise ValueError(f"{path} is not a file")
170
+
171
+ zip_bytes = path.read_bytes()
172
+ if not cls.is_valid_zip(zip_bytes=zip_bytes):
173
+ raise ValueError(f"{path} is not a valid zip file")
174
+ return cls(zip_bytes=zip_bytes)
175
+
176
+ @classmethod
177
+ def from_dir(
178
+ cls: Type[TZip],
179
+ dir: Path | str,
180
+ include_dir: bool = False,
181
+ exclude: list[str] = ["__pycache__", ".venv"],
182
+ ) -> TZip:
183
+ dir = Path(dir)
184
+ if not dir.is_dir():
185
+ raise ValueError(f"{dir} is not a directory")
186
+
187
+ zip_dir = dir.parent if include_dir else dir
188
+ mem_zip = BytesIO()
189
+ with zipfile.ZipFile(mem_zip, "w") as zf:
190
+ for file in dir.rglob("*"):
191
+ if all(excl not in str(file) for excl in exclude):
192
+ zf.write(file, file.relative_to(zip_dir))
193
+
194
+ return cls(zip_bytes=mem_zip.getvalue())
195
+
196
+ @classmethod
197
+ def from_bytes(cls: Type[TZip], zip_bytes: bytes | BinaryIO) -> TZip:
198
+ if isinstance(zip_bytes, (BinaryIO, IOBase)):
199
+ zip_bytes.seek(0)
200
+ zip_bytes = zip_bytes.read()
201
+ if not cls.is_valid_zip(zip_bytes=zip_bytes):
202
+ raise ValueError("given bytes are not a valid zip-archive")
203
+ return cls(zip_bytes=zip_bytes)
204
+
205
+ @classmethod
206
+ def from_file(cls: Type[TZip], file: Path | str, arcdir: str = ".") -> TZip:
207
+ return cls().add_file(file=file, arcdir=arcdir)
208
+
209
+ @classmethod
210
+ def from_files(
211
+ cls: Type[TZip],
212
+ files: list[Path | str | bytes],
213
+ names: list[str] | None = None,
214
+ arcdir: str = ".",
215
+ ) -> TZip:
216
+ return cls().add_files(files=files, names=names, arcdir=arcdir)
217
+
218
+ @staticmethod
219
+ def is_valid_zip(zip_bytes: bytes) -> bool:
220
+ try:
221
+ with zipfile.ZipFile(BytesIO(zip_bytes)) as zf:
222
+ return zf.testzip() is None
223
+ except zipfile.BadZipFile:
224
+ return False
225
+
226
+ async def list_files_async(self) -> list[str]:
227
+ return await asyncio.to_thread(self.list_files)
228
+
229
+ async def print_files_async(self) -> None:
230
+ return await asyncio.to_thread(self.print_files)
231
+
232
+ async def read_file_async(self, file: str) -> str:
233
+ return await asyncio.to_thread(self.read_file, file)
234
+
235
+ async def read_file_bytes_async(self, file: str) -> bytes:
236
+ return await asyncio.to_thread(self.read_file_bytes, file)
237
+
238
+ async def read_files_async(self, files: list[str]) -> list[str]:
239
+ return await asyncio.to_thread(self.read_files, files)
240
+
241
+ async def read_files_bytes_async(self, files: list[str]) -> list[bytes]:
242
+ return await asyncio.to_thread(self.read_files_bytes, files)
243
+
244
+ async def add_file_async(self, file: Path | str, arcdir: str = ".") -> Self:
245
+ return await asyncio.to_thread(self.add_file, file, arcdir)
246
+
247
+ async def add_empty_file_async(self, name: str, arcdir: str = ".") -> Self:
248
+ return await asyncio.to_thread(self.add_empty_file, name, arcdir)
249
+
250
+ async def add_files_async(
251
+ self,
252
+ files: list[Path | str | bytes],
253
+ names: list[str] | None = None,
254
+ arcdir: str = ".",
255
+ ) -> Self:
256
+ return await asyncio.to_thread(
257
+ self.add_files, files=files, names=names, arcdir=arcdir
258
+ )
259
+
260
+ async def delete_file_async(self, file: str) -> Self:
261
+ return await asyncio.to_thread(self.delete_file, file)
262
+
263
+ async def delete_files_async(self, files: list[str]) -> Self:
264
+ return await asyncio.to_thread(self.delete_files, files)
265
+
266
+ async def extract_file_async(
267
+ self, file: str, path: Path | str, flatten: bool = False
268
+ ) -> Self:
269
+ return await asyncio.to_thread(self.extract_file, file, path, flatten)
270
+
271
+ async def extract_async(
272
+ self, path: Path | str, files: list[str] | None = None
273
+ ) -> Self:
274
+ return await asyncio.to_thread(self.extract, path, files)
275
+
276
+ async def to_zipfile_async(self, path: Path | str) -> None:
277
+ return await asyncio.to_thread(self.to_zipfile, path)
278
+
279
+ async def to_bytesio_async(self) -> BytesIO:
280
+ return await asyncio.to_thread(self.to_bytesio)
281
+
282
+ async def to_codezip_async(self) -> CodeZip:
283
+ return await asyncio.to_thread(self.to_codezip)
284
+
285
+ async def to_datazip_async(self) -> DataZip:
286
+ return await asyncio.to_thread(self.to_datazip)
287
+
288
+ @classmethod
289
+ async def from_zipfile_async(cls: Type[TZip], path: Path | str) -> TZip:
290
+ return await asyncio.to_thread(cls.from_zipfile, path)
291
+
292
+ @classmethod
293
+ async def from_dir_async(
294
+ cls: Type[TZip],
295
+ directory: Path | str,
296
+ include_dir: bool = False,
297
+ exclude: list[str] = ["__pycache__", ".venv"],
298
+ ) -> TZip:
299
+ return await asyncio.to_thread(cls.from_dir, directory, include_dir, exclude)
300
+
301
+ @classmethod
302
+ async def from_bytes_async(cls: Type[TZip], zip_bytes: bytes | BinaryIO) -> TZip:
303
+ return await asyncio.to_thread(cls.from_bytes, zip_bytes)
304
+
305
+ @classmethod
306
+ async def from_file_async(
307
+ cls: Type[TZip], file: Path | str, arcdir: str = "."
308
+ ) -> TZip:
309
+ return await asyncio.to_thread(cls.from_file, file, arcdir)
310
+
311
+ @classmethod
312
+ async def from_files_async(
313
+ cls: Type[TZip],
314
+ files: list[Path | str | bytes],
315
+ names: list[str] | None = None,
316
+ arcdir: str = ".",
317
+ ) -> TZip:
318
+ return await asyncio.to_thread(
319
+ cls.from_files, files=files, names=names, arcdir=arcdir
320
+ )
321
+
322
+ @staticmethod
323
+ async def is_valid_zip_async(zip_bytes: bytes) -> bool:
324
+ return await asyncio.to_thread(ZipBytes.is_valid_zip, zip_bytes)
325
+
326
+ @property
327
+ def n_bytes(self) -> int:
328
+ return len(self.zip_bytes)
329
+
330
+ @property
331
+ def n_files(self) -> int:
332
+ return len(self.files)
333
+
334
+ def __str__(self):
335
+ return f"{self.__class__.__name__}(n_bytes={self.n_bytes})"
336
+
337
+
338
+ class CodeZip(ZipBytes):
339
+ @property
340
+ def code_id(self) -> str | None:
341
+ if ".code_id" not in self.list_files():
342
+ return None
343
+ return self.read_file(".code_id").strip()
344
+
345
+ @code_id.setter
346
+ def code_id(self, value):
347
+ self.add_file(file=f"{value}".encode(), name=".code_id")
348
+
349
+ @property
350
+ def has_dotenv(self) -> bool:
351
+ return DOTENV_FILE in self.list_files()
352
+
353
+ @property
354
+ def has_requirements(self) -> bool:
355
+ return REQUIREMENTS_TXT in self.list_files()
356
+
357
+ @property
358
+ def requirements(self) -> list[str] | None:
359
+ if not self.has_requirements:
360
+ return None
361
+ return self.read_file(REQUIREMENTS_TXT).splitlines()
362
+
363
+ @property
364
+ def has_pexenv(self) -> bool:
365
+ return PEXENV_FILE in self.list_files()
366
+
367
+ @property
368
+ def pexenv_bytes(self) -> bytes | None:
369
+ if not self.has_requirements:
370
+ return None
371
+ return self.read_file_bytes(PEXENV_FILE)
372
+
373
+
374
+ class DataZip(ZipBytes):
375
+ @property
376
+ def data_id(self) -> str | None:
377
+ if ".data_id" not in self.list_files():
378
+ return None
379
+ return self.read_file(".data_id").strip()
380
+
381
+ @data_id.setter
382
+ def data_id(self, value):
383
+ self.add_file(file=f"{value}".encode(), name=".data_id")
384
+
385
+
386
+ class JobResult(ZipBytes):
387
+ @property
388
+ def job_id(self) -> str | None:
389
+ if ".job_id" not in self.list_files():
390
+ return None
391
+ return self.read_file(".job_id").rstrip("\n") or None
392
+
393
+ @property
394
+ def code_id(self) -> str | None:
395
+ if ".code_id" not in self.list_files():
396
+ return None
397
+ return self.read_file(".code_id").rstrip("\n") or None
398
+
399
+ @property
400
+ def stdout(self) -> str:
401
+ if ".stdout" not in self.list_files():
402
+ return ""
403
+ return self.read_file(".stdout").rstrip("\n")
404
+
405
+ @property
406
+ def stderr(self) -> str:
407
+ if ".stderr" not in self.list_files():
408
+ return ""
409
+ return self.read_file(".stderr").rstrip("\n")
410
+
411
+ @property
412
+ def output(self) -> str:
413
+ if ".output" not in self.list_files():
414
+ return ""
415
+ return self.read_file(".output").rstrip("\n")
416
+
417
+ @property
418
+ def exit_code(self) -> int:
419
+ if ".exit_code" not in self.list_files():
420
+ return 0
421
+ return int(self.read_file(".exit_code"))
422
+
423
+ @property
424
+ def cmd(self) -> str:
425
+ if ".cmd" not in self.list_files():
426
+ return ""
427
+ return self.read_file(".cmd")
428
+
429
+ @property
430
+ def cwd(self) -> str:
431
+ if ".cwd" not in self.list_files():
432
+ return ""
433
+ return self.read_file(".cwd")
434
+
435
+ @property
436
+ def exit_type(self) -> ExitType:
437
+ if ".exit_type" not in self.list_files():
438
+ return ExitType.NORMAL
439
+ return ExitType(self.read_file(".exit_type"))
440
+
441
+ @property
442
+ def result_json(self) -> JsonData:
443
+ if "code_execution.json" in self.list_files():
444
+ return JsonData(data=self.read_file_bytes("code_execution.json"))
445
+ if "code_ingestion.json" in self.list_files():
446
+ return JsonData(data=self.read_file_bytes("code_ingestion.json"))
447
+ return JsonData()
448
+
449
+ @property
450
+ def n_artifacts(self) -> int:
451
+ return len(self.list_artifacts())
452
+
453
+ @property
454
+ def artifacts(self) -> list[str]:
455
+ return self.list_artifacts()
456
+
457
+ def list_artifacts(self) -> list[str]:
458
+ return [x for x in self.list_files() if x.startswith("artifacts/")]
459
+
460
+ def extract_artifact(self, artifact: str, directory: str = "."):
461
+ path = f"{directory.rstrip('/')}/{artifact.lstrip('artifacts/')}"
462
+ self.extract_file(file=artifact, path=path)
463
+
464
+ def extract_artifacts(self, directory: str = "."):
465
+ for artifact in self.list_artifacts():
466
+ self.extract_artifact(artifact=artifact, directory=directory)
codeapi/types/py.typed ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeapi-client
3
+ Version: 0.4.1
4
+ Summary: CodeAPI Client
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: aiofiles>=25.1.0
7
+ Requires-Dist: fastapi==0.118.3
8
+ Requires-Dist: fastmcp>=2.12.4
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: psutil>=7.1.0
11
+ Requires-Dist: pydantic>=2.12.2
12
+ Requires-Dist: python-dotenv>=1.1.1
13
+ Requires-Dist: strenum>=0.4.15
14
+ Requires-Dist: typing-extensions>=4.15.0
@@ -0,0 +1,36 @@
1
+ codeapi/__init__.py,sha256=sJX-IniQsZJ6KoZd3VT86baPfT2c5kfFjocYfAsAgvM,254
2
+ codeapi/client/__init__.py,sha256=f0Fi_ZVGax_oevqr6jYeSUlb3M4jFNN0LpEAOnHEBz8,711
3
+ codeapi/client/_base.py,sha256=WIRgt667SsZHfzSR2NYMNnY2PEhgEUV4gNKbr_4SEGc,4406
4
+ codeapi/client/_utils.py,sha256=-0QCNoIQ2TrEFAWG7jdPgwjFG6aB2PTj7uskyp5V9F8,1429
5
+ codeapi/client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ codeapi/client/_async/__init__.py,sha256=10KUVLd8PE23TLnTlTC_fFbofp3TMtNM5akItEWqAyM,10382
7
+ codeapi/client/_async/_app.py,sha256=ak5py4o2tVtW4omw_Baxhz-F-honA8dRexReSZ0Y6SY,10766
8
+ codeapi/client/_async/_cli.py,sha256=G2av964evWrCLppyzgH6RsHrXkRSsMybGReR4OutIJ4,9782
9
+ codeapi/client/_async/_code.py,sha256=zeA3pm08mE3tEVNlEE4MLw8_GlyRg3trQ8pVN247rWQ,7128
10
+ codeapi/client/_async/_jobs.py,sha256=o6jYRcFIKlN_GscHmHX7SFLCH1OUKtNu5STpcUQ4ogg,17571
11
+ codeapi/client/_async/_mcp.py,sha256=8xnYXndts5USB3GtJA1vT15WsEUrKPxjnGmkM4m1xxo,13648
12
+ codeapi/client/_async/_web.py,sha256=_q84Jhh7chnBe4RASI70jdV6nNJqVdksXfkoTELzsi0,9711
13
+ codeapi/client/_sync/__init__.py,sha256=hoXwlCf2jUWHrnUAbg29AMI-48bQdqmOkXw-wbaffgE,9689
14
+ codeapi/client/_sync/_app.py,sha256=dzUJO-Xy4gLqNxoFNyzxxf1Kg-dMDi_Z5xBoijlW5Ow,10266
15
+ codeapi/client/_sync/_cli.py,sha256=FgreWU1-AxR1HYMU0zyBOp2lhbgtrGjwjxS__M4lkPI,9298
16
+ codeapi/client/_sync/_code.py,sha256=NoYGVlyYbqRwnXO357_kGCwmGDIYTHGJwTMOdVWIM-U,6439
17
+ codeapi/client/_sync/_jobs.py,sha256=wbjazYAWL2X62lR1BgK_GC6JpXOk-AcQ12JNjt9ddFw,16363
18
+ codeapi/client/_sync/_mcp.py,sha256=1jQPLUWqbSmItenTVmkADhJlXTic6uiHj8Als5I1vMs,13026
19
+ codeapi/client/_sync/_web.py,sha256=vGjZOEaaNoT5swIjmZOPKAYtCe6t_shDQBpCa9JQ_Pw,9223
20
+ codeapi/types/__init__.py,sha256=YtSeCdCAdjLI5GKlOMKYqipYZU9E9XBwZ01w27mV2oI,1511
21
+ codeapi/types/_api.py,sha256=D9uccciZSG7wnRDOhuxDibPMKJgalezBheMYm6rdfYQ,554
22
+ codeapi/types/_base.py,sha256=ZN2q-1PC1OT2ud1Yl6ASlAQPfu0gJX8BtnVKI17aH7k,638
23
+ codeapi/types/_code.py,sha256=04ke5_twnhTKzXDLl-0P3V6XbuZnB41VwToQt4n8TXo,928
24
+ codeapi/types/_enums.py,sha256=zJmux3wC4QFUki9t4guJUi-kFpJhsVgRnuLTj1QVO6s,3689
25
+ codeapi/types/_env.py,sha256=Jf7FkBm1vHzsYVp9HQUZqhrnHqKcy7srV8gzuYt2PkM,2065
26
+ codeapi/types/_exc.py,sha256=wb8qyM9i633mfhZ0kUgKg5VfgCgZD07Drokskl1wCJ8,1218
27
+ codeapi/types/_job.py,sha256=5TZB55FXrAhhanZy3DeLWYJScJQMdkk4HoLuqL2QgOc,1834
28
+ codeapi/types/_json.py,sha256=U70zwFL00FnNTZwIJ94IdJnSu3xmO3qJvwgUkV1P4qA,2026
29
+ codeapi/types/_stream.py,sha256=72jKc7jmdpFg8KfUJhTyxgoFi2jqBuQ2hhjuxr8UMv4,965
30
+ codeapi/types/_swarm.py,sha256=4Dnn5hoO3zrbqeUO3vAaP4tPHRPacw5_09GmfMk20dU,2703
31
+ codeapi/types/_time.py,sha256=1Tca-eYr2qOjNV-jgI4WT3fQf4uIOOD-SlcH3x-XzA8,916
32
+ codeapi/types/_zips.py,sha256=IysInLwMNOC424jna8nbA3NukDGUoQcIuZvnLAEW30Q,15850
33
+ codeapi/types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ codeapi_client-0.4.1.dist-info/METADATA,sha256=JQJMexXDKBLajd9x3hK7TUv4KjuAy1D6A2uNAWRHE8M,399
35
+ codeapi_client-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ codeapi_client-0.4.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any