datashare-python 0.9.9__py3-none-any.whl → 0.10.0.dev0__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.
- datashare_python/cli/worker.py +1 -1
- datashare_python/config.py +17 -7
- datashare_python/conftest.py +176 -44
- datashare_python/constants.py +0 -2
- datashare_python/dependencies.py +0 -16
- datashare_python/mimetypes_.py +1105 -0
- datashare_python/objects.py +95 -85
- datashare_python/utils.py +255 -40
- datashare_python/worker-template.tar.gz +0 -0
- datashare_python/worker.py +14 -3
- {datashare_python-0.9.9.dist-info → datashare_python-0.10.0.dev0.dist-info}/METADATA +3 -2
- datashare_python-0.10.0.dev0.dist-info/RECORD +28 -0
- datashare_python-0.9.9.dist-info/RECORD +0 -27
- {datashare_python-0.9.9.dist-info → datashare_python-0.10.0.dev0.dist-info}/WHEEL +0 -0
- {datashare_python-0.9.9.dist-info → datashare_python-0.10.0.dev0.dist-info}/entry_points.txt +0 -0
datashare_python/objects.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
from abc import ABC
|
|
4
|
-
from asyncio import Lock
|
|
5
4
|
from collections.abc import Awaitable, Callable
|
|
6
|
-
from dataclasses import
|
|
5
|
+
from dataclasses import dataclass
|
|
7
6
|
from datetime import UTC, datetime
|
|
8
7
|
from enum import StrEnum, unique
|
|
9
8
|
from io import BytesIO
|
|
@@ -12,15 +11,12 @@ from typing import Annotated, Any, ClassVar, Generic, Literal, Self, TypeVar, ca
|
|
|
12
11
|
|
|
13
12
|
import langcodes
|
|
14
13
|
from icij_common.registrable import Registrable
|
|
15
|
-
from lru import LRU
|
|
16
14
|
from pydantic_core import PydanticCustomError, ValidationError, core_schema
|
|
17
|
-
from pydantic_core.core_schema import
|
|
18
|
-
PlainValidatorFunctionSchema,
|
|
19
|
-
)
|
|
15
|
+
from pydantic_core.core_schema import PlainValidatorFunctionSchema
|
|
20
16
|
from pydantic_extra_types.language_code import LanguageName
|
|
21
17
|
from temporalio import workflow
|
|
22
18
|
|
|
23
|
-
from .constants import
|
|
19
|
+
from .constants import TIKA_METADATA_RESOURCENAME
|
|
24
20
|
|
|
25
21
|
with workflow.unsafe.imports_passed_through():
|
|
26
22
|
from icij_common.es import (
|
|
@@ -113,9 +109,15 @@ class DatashareLanguage(str):
|
|
|
113
109
|
return self.as_language_name.alpha3
|
|
114
110
|
|
|
115
111
|
|
|
112
|
+
class WorkerPaths(BaseModel):
|
|
113
|
+
filesystem: Path
|
|
114
|
+
artifacts: Path
|
|
115
|
+
workdir: Path
|
|
116
|
+
|
|
117
|
+
|
|
116
118
|
@unique
|
|
117
119
|
class DocumentLocation(StrEnum):
|
|
118
|
-
|
|
120
|
+
FILESYSTEM = "filesystem"
|
|
119
121
|
ARTIFACTS = "artifacts"
|
|
120
122
|
WORKDIR = "workdir"
|
|
121
123
|
|
|
@@ -128,30 +130,49 @@ def _is_relative(value: Path) -> Path:
|
|
|
128
130
|
return value
|
|
129
131
|
|
|
130
132
|
|
|
131
|
-
class
|
|
133
|
+
class ProcessedFile(BaseModel):
|
|
132
134
|
id: str
|
|
133
135
|
path: Annotated[Path, AfterValidator(_is_relative)]
|
|
134
|
-
|
|
136
|
+
project: str
|
|
135
137
|
location: DocumentLocation
|
|
136
138
|
resource_name: str
|
|
139
|
+
n_pages: int
|
|
140
|
+
parent: "ProcessedFile | None" = None
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_doc(cls, doc: "Document") -> Self:
|
|
144
|
+
return doc.to_processed_file()
|
|
145
|
+
|
|
146
|
+
def child(self, path: Path, paths: WorkerPaths) -> Self:
|
|
147
|
+
return ProcessedFile(
|
|
148
|
+
id=self.id,
|
|
149
|
+
path=path.relative_to(paths.workdir),
|
|
150
|
+
project=self.project,
|
|
151
|
+
location=DocumentLocation.WORKDIR,
|
|
152
|
+
resource_name=path.name,
|
|
153
|
+
n_pages=self.n_pages,
|
|
154
|
+
parent=self,
|
|
155
|
+
)
|
|
137
156
|
|
|
138
|
-
def locate(
|
|
139
|
-
self, original_root: Path, *, artifacts_root: Path, workdir: Path
|
|
140
|
-
) -> Path:
|
|
157
|
+
def locate(self, paths: WorkerPaths) -> Path:
|
|
141
158
|
from datashare_python.utils import artifacts_dir # noqa: PLC0415
|
|
142
159
|
|
|
143
160
|
match self.location:
|
|
144
|
-
case DocumentLocation.
|
|
145
|
-
return
|
|
161
|
+
case DocumentLocation.FILESYSTEM:
|
|
162
|
+
return paths.filesystem / self.path
|
|
146
163
|
case DocumentLocation.ARTIFACTS:
|
|
147
|
-
project = self.
|
|
148
|
-
return
|
|
164
|
+
project = self.project
|
|
165
|
+
return paths.artifacts / artifacts_dir(self.id, project=project) / "raw"
|
|
149
166
|
case DocumentLocation.WORKDIR:
|
|
150
|
-
return workdir / self.path
|
|
167
|
+
return paths.workdir / self.path
|
|
151
168
|
case _:
|
|
152
169
|
raise ValueError(f"invalid location: {self.path}")
|
|
153
170
|
|
|
154
171
|
|
|
172
|
+
class ProcessedPage(ProcessedFile):
|
|
173
|
+
page_number: int
|
|
174
|
+
|
|
175
|
+
|
|
155
176
|
class IETFLanguage(str):
|
|
156
177
|
@classmethod
|
|
157
178
|
def __get_pydantic_core_schema__(
|
|
@@ -183,6 +204,11 @@ class Translation(BaseModel): # No camelcase here we don't know why
|
|
|
183
204
|
content: Annotated[str, BeforeValidator(_from_sentences)]
|
|
184
205
|
|
|
185
206
|
|
|
207
|
+
Routing = str
|
|
208
|
+
DocID = str
|
|
209
|
+
DocRoute = tuple[DocID, Routing]
|
|
210
|
+
|
|
211
|
+
|
|
186
212
|
class Document(DatashareModel):
|
|
187
213
|
id: str
|
|
188
214
|
language: DatashareLanguage
|
|
@@ -226,17 +252,17 @@ class Document(DatashareModel):
|
|
|
226
252
|
metadata=sources.get(DOC_METADATA),
|
|
227
253
|
)
|
|
228
254
|
|
|
229
|
-
def
|
|
255
|
+
def to_processed_file(self) -> ProcessedFile:
|
|
230
256
|
from .utils import artifacts_dir # noqa: PLC0415
|
|
231
257
|
|
|
232
258
|
if self.metadata is None:
|
|
233
259
|
raise ValueError(
|
|
234
|
-
"can't compute
|
|
260
|
+
"can't compute filesystem path for document withtout metadata"
|
|
235
261
|
)
|
|
236
262
|
resource_name = cast(str, self.metadata[TIKA_METADATA_RESOURCENAME])
|
|
237
263
|
if self.root_document is None:
|
|
238
264
|
path = self.path
|
|
239
|
-
location = DocumentLocation.
|
|
265
|
+
location = DocumentLocation.FILESYSTEM
|
|
240
266
|
else:
|
|
241
267
|
if self.index is None:
|
|
242
268
|
msg = (
|
|
@@ -250,14 +276,23 @@ class Document(DatashareModel):
|
|
|
250
276
|
# we store a relative path otherwise joining with the location will fail
|
|
251
277
|
if path.parts and path.parts[0] == os.path.sep:
|
|
252
278
|
path = Path(*path.parts[1:])
|
|
253
|
-
|
|
279
|
+
n_pages = 1
|
|
280
|
+
if self.metadata:
|
|
281
|
+
n_pages = self.metadata.get("tika_metadata_xmptpg_npages", n_pages)
|
|
282
|
+
return ProcessedFile(
|
|
254
283
|
id=self.id,
|
|
255
284
|
path=path,
|
|
256
|
-
|
|
285
|
+
project=self.index,
|
|
257
286
|
location=location,
|
|
258
287
|
resource_name=resource_name,
|
|
288
|
+
n_pages=n_pages,
|
|
259
289
|
)
|
|
260
290
|
|
|
291
|
+
def to_route(self) -> DocRoute:
|
|
292
|
+
if self.root_document is not None:
|
|
293
|
+
return self.root_document, self.id
|
|
294
|
+
return self.id, self.id
|
|
295
|
+
|
|
261
296
|
|
|
262
297
|
def _is_absolute_path(v: bytes | BytesIO | Path) -> Any:
|
|
263
298
|
if isinstance(v, Path) and not v.is_absolute():
|
|
@@ -268,17 +303,20 @@ def _is_absolute_path(v: bytes | BytesIO | Path) -> Any:
|
|
|
268
303
|
class ArtifactType(StrEnum):
|
|
269
304
|
STRUCTURE = "structure"
|
|
270
305
|
ASR_TRANSCRIPTION = "transcription"
|
|
306
|
+
PASSPORTS = "passports"
|
|
271
307
|
|
|
272
308
|
|
|
273
309
|
class ManifestEntryStatus(StrEnum):
|
|
274
310
|
COMPLETE = "complete"
|
|
311
|
+
PARTIAL = "partial"
|
|
275
312
|
|
|
276
313
|
|
|
277
314
|
class TaskArgs(DatashareModel, ABC):
|
|
278
315
|
def as_manifest_task_input(self) -> dict[str, Any]:
|
|
279
316
|
# This is a base implementation, if the input is too large to be dumped,
|
|
280
317
|
# override this and pop large keys
|
|
281
|
-
|
|
318
|
+
# Dump in json mode to make testing easier
|
|
319
|
+
as_manifest = self.model_dump(by_alias=True, mode="json")
|
|
282
320
|
return as_manifest
|
|
283
321
|
|
|
284
322
|
|
|
@@ -305,19 +343,21 @@ class ManifestEntry(DatashareModel, Generic[A], ABC):
|
|
|
305
343
|
**kwargs,
|
|
306
344
|
)
|
|
307
345
|
|
|
346
|
+
@classmethod
|
|
347
|
+
def partial(cls, args: A, label: str | None = None, **kwargs) -> Self:
|
|
348
|
+
return cls(
|
|
349
|
+
input=args.as_manifest_task_input(),
|
|
350
|
+
label=label,
|
|
351
|
+
status=ManifestEntryStatus.PARTIAL,
|
|
352
|
+
**kwargs,
|
|
353
|
+
)
|
|
354
|
+
|
|
308
355
|
|
|
309
356
|
class PaginationType(StrEnum):
|
|
310
357
|
FILESYSTEM = "filesystem"
|
|
311
358
|
BYTE_RANGES = "byteRanges"
|
|
312
359
|
|
|
313
360
|
|
|
314
|
-
class BasePagination(DatashareModel, Registrable, ABC):
|
|
315
|
-
registry_key: ClassVar[str] = Field(frozen=True, default="type")
|
|
316
|
-
|
|
317
|
-
total: int
|
|
318
|
-
type: ClassVar[PaginationType] = Field(frozen=True)
|
|
319
|
-
|
|
320
|
-
|
|
321
361
|
def _validate_pages_range(v: Any) -> None:
|
|
322
362
|
if not isinstance(v, list):
|
|
323
363
|
msg = f"expected a list, got {type(v)}"
|
|
@@ -339,6 +379,11 @@ def _validate_pages_range(v: Any) -> None:
|
|
|
339
379
|
PagesRange = Annotated[list[tuple[int, int]], AfterValidator(_validate_pages_range)]
|
|
340
380
|
|
|
341
381
|
|
|
382
|
+
class BasePagination(Registrable, DatashareModel):
|
|
383
|
+
registry_key: ClassVar[str] = Field(frozen=True, default="type")
|
|
384
|
+
type: ClassVar[PaginationType] = Field(frozen=True)
|
|
385
|
+
|
|
386
|
+
|
|
342
387
|
@BasePagination.register(PaginationType.FILESYSTEM)
|
|
343
388
|
class FilesystemPagination(BasePagination):
|
|
344
389
|
type: ClassVar[PaginationType] = Field(
|
|
@@ -353,25 +398,34 @@ class ByteRangesPagination(BasePagination):
|
|
|
353
398
|
)
|
|
354
399
|
byte_ranges: PagesRange
|
|
355
400
|
|
|
401
|
+
|
|
402
|
+
pagination_discriminator = make_enum_discriminator("type", PaginationType)
|
|
403
|
+
Pagination = Annotated[
|
|
404
|
+
tagged_union(BasePagination.__subclasses__(), lambda x: x.type.default),
|
|
405
|
+
Discriminator(pagination_discriminator),
|
|
406
|
+
]
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class Pages(DatashareModel):
|
|
410
|
+
total: int
|
|
411
|
+
pagination: Pagination
|
|
412
|
+
|
|
356
413
|
@model_validator(mode="after")
|
|
357
414
|
def byte_ranges_length_should_match_total(self) -> Self:
|
|
358
|
-
if
|
|
415
|
+
if (
|
|
416
|
+
isinstance(self.pagination, ByteRangesPagination)
|
|
417
|
+
and len(self.pagination.byte_ranges) != self.total
|
|
418
|
+
):
|
|
419
|
+
n_pages = len(self.pagination.byte_ranges)
|
|
359
420
|
msg = (
|
|
360
|
-
f"byte_ranges must match total. Found {
|
|
421
|
+
f"byte_ranges must match total. Found {n_pages} for"
|
|
361
422
|
f" byte_ranges and {self.total} for total."
|
|
362
423
|
)
|
|
363
424
|
raise ValueError(msg)
|
|
364
425
|
return self
|
|
365
426
|
|
|
366
427
|
|
|
367
|
-
|
|
368
|
-
Pagination = Annotated[
|
|
369
|
-
tagged_union(BasePagination.__subclasses__(), lambda x: x.type.default),
|
|
370
|
-
Discriminator(pagination_discriminator),
|
|
371
|
-
]
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
class DocArtifact(BaseModel, ABC):
|
|
428
|
+
class DocArtifact(DatashareModel, ABC):
|
|
375
429
|
# This object is not used for serde, just as a container, it's OK to allow
|
|
376
430
|
# arbitrary types (to allow storing BytesIO)
|
|
377
431
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
@@ -488,47 +542,3 @@ class TaskGroup:
|
|
|
488
542
|
@classmethod
|
|
489
543
|
def python(cls) -> Self:
|
|
490
544
|
return cls(name="PYTHON")
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
@dataclass(frozen=True)
|
|
494
|
-
class Shared:
|
|
495
|
-
cache_size: InitVar[int] = DEFAULT_SHARED_RESOURCES_SIZE
|
|
496
|
-
eviction_callback: InitVar[Callable] = None
|
|
497
|
-
_resources: LRU = field(init=False, repr=False)
|
|
498
|
-
_lock: Lock = field(init=False, repr=False)
|
|
499
|
-
|
|
500
|
-
def __post_init__(self, cache_size: int, eviction_callback: Callable) -> None:
|
|
501
|
-
object.__setattr__(
|
|
502
|
-
self, "_resources", LRU(cache_size, callback=eviction_callback)
|
|
503
|
-
)
|
|
504
|
-
object.__setattr__(self, "_lock", Lock())
|
|
505
|
-
|
|
506
|
-
def get_resource(
|
|
507
|
-
self, key: str, default: Any = None, *, set_if_unavailable: bool = True
|
|
508
|
-
) -> Any:
|
|
509
|
-
if key not in self._resources and set_if_unavailable:
|
|
510
|
-
self.set_resource(key, default)
|
|
511
|
-
|
|
512
|
-
return self._resources.get(key, default)
|
|
513
|
-
|
|
514
|
-
def set_resource(self, key: str, value: Any) -> None:
|
|
515
|
-
self._resources[key] = value
|
|
516
|
-
|
|
517
|
-
def pop_resource(self, key: str, default: Any = None) -> Any:
|
|
518
|
-
return self._resources.pop(key, default)
|
|
519
|
-
|
|
520
|
-
async def async_get_resource(
|
|
521
|
-
self, key: str, default: Any = None, *, set_if_unavailable: bool = True
|
|
522
|
-
) -> Any:
|
|
523
|
-
if key not in self._resources and set_if_unavailable:
|
|
524
|
-
await self.async_set_resource(key, default)
|
|
525
|
-
|
|
526
|
-
return self._resources.get(key, default)
|
|
527
|
-
|
|
528
|
-
async def async_set_resource(self, key: str, value: Any) -> None:
|
|
529
|
-
async with self._lock:
|
|
530
|
-
self._resources[key] = value
|
|
531
|
-
|
|
532
|
-
async def async_pop_resource(self, key: str, default: Any = None) -> Any:
|
|
533
|
-
async with self._lock:
|
|
534
|
-
return self._resources.pop(key, default)
|