dyff-schema 0.7.2__py3-none-any.whl → 0.9.0__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.

Potentially problematic release.


This version of dyff-schema might be problematic. Click here for more details.

dyff/schema/errors.py ADDED
@@ -0,0 +1,51 @@
1
+ # SPDX-FileCopyrightText: 2024 UL Research Institutes
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+
5
+ from typing import NamedTuple, Optional
6
+
7
+
8
+ class HTTPResponseStatus(NamedTuple):
9
+ status_code: int
10
+ description: str
11
+
12
+
13
+ HTTP_400_BAD_REQUEST = HTTPResponseStatus(400, "Bad Request")
14
+ HTTP_500_INTERNAL_SERVER_ERROR = HTTPResponseStatus(500, "Internal Server Error")
15
+
16
+
17
+ class DyffError(Exception):
18
+ """Base class for Errors originating from the Dyff Platform."""
19
+
20
+ def __init__(self, message, *, http_status: Optional[HTTPResponseStatus] = None):
21
+ self.message = message
22
+ self.http_status = http_status
23
+
24
+
25
+ class ClientError(DyffError):
26
+ """The error was caused by the client using an API incorrectly."""
27
+
28
+ def __init__(
29
+ self, message, *, http_status: HTTPResponseStatus = HTTP_400_BAD_REQUEST
30
+ ):
31
+ if not (400 <= http_status.status_code <= 499):
32
+ raise AssertionError(
33
+ f"ClientError should map to a 4xx HTTP status; got {http_status}"
34
+ )
35
+ super().__init__(message, http_status=http_status)
36
+
37
+
38
+ class PlatformError(DyffError):
39
+ """The error was caused by an internal problem with the platform."""
40
+
41
+ def __init__(
42
+ self,
43
+ message,
44
+ *,
45
+ http_status: HTTPResponseStatus = HTTP_500_INTERNAL_SERVER_ERROR,
46
+ ):
47
+ if not (500 <= http_status.status_code <= 599):
48
+ raise AssertionError(
49
+ f"PlatformError should map to a 5xx HTTP status; got {http_status}"
50
+ )
51
+ super().__init__(message, http_status=http_status)
dyff/schema/v0/r1/base.py CHANGED
@@ -1,8 +1,11 @@
1
1
  # SPDX-FileCopyrightText: 2024 UL Research Institutes
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
- from datetime import datetime
5
- from typing import Any, Generic, NamedTuple, Optional, Type, TypeVar
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ from datetime import datetime, timezone
8
+ from typing import Any, Generic, Literal, NamedTuple, Optional, Type, TypeVar
6
9
 
7
10
  import pydantic
8
11
 
@@ -555,8 +558,12 @@ def list_(
555
558
  return pydantic.conlist(item_type, min_items=list_size, max_items=list_size)
556
559
 
557
560
 
558
- class DyffSchemaBaseModel(pydantic.BaseModel):
559
- """Base class for pydantic models that used for defining data schemas.
561
+ # mypy gets confused because 'dict' is the name of a method in DyffDefaultSerializers
562
+ _ModelAsDict = dict[str, Any]
563
+
564
+
565
+ class DyffDefaultSerializers(pydantic.BaseModel):
566
+ """This must be the base class for *all pydantic models* in the Dyff schema.
560
567
 
561
568
  Overrides serialization functions to serialize by alias, so that "round-trip"
562
569
  serialization is the default for fields with aliases. We prefer aliases because we
@@ -564,24 +571,66 @@ class DyffSchemaBaseModel(pydantic.BaseModel):
564
571
  Python reserved words like 'bytes' as field names.
565
572
  """
566
573
 
574
+ def dict(self, *, by_alias: bool = True, **kwargs) -> _ModelAsDict:
575
+ return super().dict(by_alias=by_alias, **kwargs)
576
+
577
+ def json(self, *, by_alias: bool = True, **kwargs) -> str:
578
+ return super().json(by_alias=by_alias, **kwargs)
579
+
580
+ def model_dump(
581
+ self,
582
+ *,
583
+ mode: Literal["python", "json"] = "python",
584
+ by_alias: bool = True,
585
+ **kwargs,
586
+ ) -> _ModelAsDict:
587
+ """Encode the object as a dict containing only JSON datatypes.
588
+
589
+ .. deprecated::
590
+
591
+ FIXME: This emulates a Pydantic 2 feature, but the mode="json"
592
+ option can only be implemented in an inefficient way. Remove when
593
+ we convert to Pydantic 2. See: DYFF-223
594
+ """
595
+ if mode == "python":
596
+ return self.dict(by_alias=by_alias, **kwargs)
597
+ else:
598
+ return json.loads(self.json(by_alias=by_alias, **kwargs))
599
+
600
+
601
+ # Note: I *really* wanted to require datetimes to have timezones, like in
602
+ # DyffRequestDefaultValidators, but some existing objects in the Auth database
603
+ # don't have timezones set currently for historical reasons. It's actually
604
+ # better if all datetimes in the system are UTC, so that their JSON
605
+ # representations (i.e., isoformat strings) are well-ordered.
606
+ class DyffSchemaBaseModel(DyffDefaultSerializers):
607
+ """This should be the base class for *almost all* non-request models in the Dyff
608
+ schema. Models that do not inherit from this class *must* still inherit from
609
+ DyffDefaultSerializers.
610
+
611
+ Adds a root validator to ensure that all datetime fields are represented in the UTC
612
+ timezone. This is necessary to avoid errors when comparing "naive" and "aware"
613
+ datetimes. Using the UTC timezone everywhere ensures that JSON representations of
614
+ datetimes are well-ordered.
615
+ """
616
+
567
617
  @pydantic.root_validator
568
- def _require_datetime_timezone_aware(cls, values):
618
+ def _ensure_datetime_timezone_utc(cls, values):
619
+ update = {}
569
620
  for k, v in values.items():
570
621
  if isinstance(v, datetime):
571
622
  if v.tzinfo is None:
572
- raise ValueError(f"{cls.__qualname__}.{k}: timezone not set")
623
+ update[k] = v.replace(tzinfo=timezone.utc)
624
+ elif v.tzinfo != timezone.utc:
625
+ update[k] = v.astimezone(timezone.utc)
626
+ values.update(update)
573
627
  return values
574
628
 
575
- def dict(self, *, by_alias: bool = True, **kwargs) -> dict[str, Any]:
576
- return super().dict(by_alias=by_alias, **kwargs)
577
-
578
- def json(self, *, by_alias: bool = True, **kwargs) -> str:
579
- return super().json(by_alias=by_alias, **kwargs)
580
-
581
629
 
582
630
  __all__ = [
583
631
  "DTYPE",
584
632
  "DType",
633
+ "DyffDefaultSerializers",
585
634
  "DyffSchemaBaseModel",
586
635
  "FixedWidthFloat",
587
636
  "FixedWidthInt",
@@ -18,7 +18,7 @@ We use the following naming convention:
18
18
  # mypy: disable-error-code="import-untyped"
19
19
  import abc
20
20
  import enum
21
- from datetime import datetime, timezone
21
+ from datetime import datetime
22
22
  from enum import Enum
23
23
  from typing import Any, Literal, NamedTuple, Optional, Type, Union
24
24
 
@@ -67,7 +67,7 @@ def _dns_domain_regex():
67
67
  Note that its maximum length is 253 characters, but we can't enforce this in the
68
68
  regex.
69
69
  """
70
- return f"{_dns_label_regex()}(\.{_dns_label_regex()})*"
70
+ return rf"{_dns_label_regex()}(\.{_dns_label_regex()})*"
71
71
 
72
72
 
73
73
  def _k8s_domain_maxlen():
@@ -96,7 +96,7 @@ def _k8s_label_key_regex():
96
96
  * my-multi_segment.key
97
97
  * dyff.io/reserved-key
98
98
  """
99
- return f"^({_dns_domain_regex()}/)?{_dns_label_regex()}$"
99
+ return rf"^({_dns_domain_regex()}/)?{_dns_label_regex()}$"
100
100
 
101
101
 
102
102
  def _k8s_label_key_maxlen():
@@ -126,7 +126,7 @@ def _k8s_label_value_regex():
126
126
 
127
127
  See: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
128
128
  """
129
- return f"^({_k8s_label_regex()})?$"
129
+ return rf"^({_k8s_label_regex()})?$"
130
130
 
131
131
 
132
132
  def _k8s_label_value_maxlen():
@@ -532,10 +532,7 @@ class AccessGrant(DyffSchemaBaseModel):
532
532
  )
533
533
 
534
534
 
535
- # FIXME: We can't derive from DyffSchemaBaseModel right now because existing
536
- # APIKeys don't have a timezone set for their datetime members.
537
- # See: DYFF-406
538
- class APIKey(pydantic.BaseModel):
535
+ class APIKey(DyffSchemaBaseModel):
539
536
  """A description of a set of permissions granted to a single subject (either an
540
537
  account or a workload).
541
538
 
@@ -564,21 +561,26 @@ class APIKey(pydantic.BaseModel):
564
561
  default_factory=list, description="AccessGrants associated with the APIKey"
565
562
  )
566
563
 
567
- @pydantic.root_validator
568
- def _ensure_datetime_timezone_aware(cls, values):
569
- update = {}
570
- for k, v in values.items():
571
- if isinstance(v, datetime):
572
- if v.tzinfo is None:
573
- update[k] = v.replace(tzinfo=timezone.utc)
574
- values.update(update)
575
- return values
576
564
 
577
- def dict(self, *, by_alias: bool = True, **kwargs) -> dict[str, Any]:
578
- return super().dict(by_alias=by_alias, **kwargs)
565
+ class Identity(DyffSchemaBaseModel):
566
+ """The identity of an Account according to one or more external identity
567
+ providers."""
579
568
 
580
- def json(self, *, by_alias: bool = True, **kwargs) -> str:
581
- return super().json(by_alias=by_alias, **kwargs)
569
+ google: Optional[str] = pydantic.Field(default=None)
570
+
571
+
572
+ class Account(DyffSchemaBaseModel):
573
+ """An Account in the system.
574
+
575
+ All entities are owned by an Account.
576
+ """
577
+
578
+ name: str
579
+ identity: Identity = pydantic.Field(default_factory=Identity)
580
+ apiKeys: list[APIKey] = pydantic.Field(default_factory=list)
581
+ # --- Added by system
582
+ id: Optional[str] = None
583
+ creationTime: Optional[datetime] = None
582
584
 
583
585
 
584
586
  # ----------------------------------------------------------------------------
@@ -1860,6 +1862,7 @@ __all__ = [
1860
1862
  "Accelerator",
1861
1863
  "AcceleratorGPU",
1862
1864
  "AccessGrant",
1865
+ "Account",
1863
1866
  "Analysis",
1864
1867
  "AnalysisArgument",
1865
1868
  "AnalysisBase",
@@ -1900,6 +1903,7 @@ __all__ = [
1900
1903
  "ForeignMethod",
1901
1904
  "ForeignModel",
1902
1905
  "Frameworks",
1906
+ "Identity",
1903
1907
  "InferenceInterface",
1904
1908
  "InferenceService",
1905
1909
  "InferenceServiceBase",
@@ -12,13 +12,14 @@ resource will include the full dependency object as a sub-resource. The
12
12
  in response.
13
13
  """
14
14
 
15
+ from __future__ import annotations
15
16
 
16
17
  from datetime import datetime
17
- from typing import Any, Optional, Union
18
+ from typing import Optional, Union
18
19
 
19
20
  import pydantic
20
21
 
21
- from .base import DyffSchemaBaseModel
22
+ from .base import DyffDefaultSerializers
22
23
  from .platform import (
23
24
  AnalysisBase,
24
25
  DatasetBase,
@@ -37,7 +38,28 @@ from .platform import (
37
38
  from .version import SchemaVersion
38
39
 
39
40
 
40
- class DyffEntityCreateRequest(SchemaVersion, DyffSchemaBaseModel):
41
+ class DyffRequestDefaultValidators(DyffDefaultSerializers):
42
+ """This must be the base class for *all* request models in the Dyff schema.
43
+
44
+ Adds a root validator to ensure that all user-provided datetime fields have a
45
+ timezone set. Timezones will be converted to UTC once the data enters the platform,
46
+ but we allow requests to have non-UTC timezones for user convenience.
47
+ """
48
+
49
+ @pydantic.root_validator
50
+ def _require_datetime_timezone_aware(cls, values):
51
+ for k, v in values.items():
52
+ if isinstance(v, datetime):
53
+ if v.tzinfo is None:
54
+ raise ValueError(f"{cls.__qualname__}.{k}: timezone not set")
55
+ return values
56
+
57
+
58
+ class DyffRequestBase(SchemaVersion, DyffRequestDefaultValidators):
59
+ pass
60
+
61
+
62
+ class DyffEntityCreateRequest(DyffRequestBase):
41
63
  account: str = pydantic.Field(description="Account that owns the entity")
42
64
 
43
65
 
@@ -52,7 +74,7 @@ class DatasetCreateRequest(DyffEntityCreateRequest, DatasetBase):
52
74
  pass
53
75
 
54
76
 
55
- class DocumentationEditRequest(SchemaVersion, DocumentationBase):
77
+ class DocumentationEditRequest(DyffRequestBase, DocumentationBase):
56
78
  pass
57
79
 
58
80
 
@@ -66,7 +88,7 @@ class InferenceSessionCreateRequest(DyffEntityCreateRequest, InferenceSessionBas
66
88
  inferenceService: str = pydantic.Field(description="InferenceService ID")
67
89
 
68
90
 
69
- class InferenceSessionTokenCreateRequest(SchemaVersion, DyffSchemaBaseModel):
91
+ class InferenceSessionTokenCreateRequest(DyffRequestBase):
70
92
  expires: Optional[datetime] = pydantic.Field(
71
93
  default=None,
72
94
  description="Expiration time of the token. Must be <= expiration time"
@@ -137,11 +159,11 @@ class ReportCreateRequest(DyffEntityCreateRequest, ReportBase):
137
159
  )
138
160
 
139
161
 
140
- class TagCreateRequest(SchemaVersion, TagBase):
162
+ class TagCreateRequest(DyffRequestBase, TagBase):
141
163
  pass
142
164
 
143
165
 
144
- class LabelUpdateRequest(SchemaVersion, Labeled):
166
+ class LabelUpdateRequest(DyffRequestBase, Labeled):
145
167
  pass
146
168
 
147
169
 
@@ -150,7 +172,15 @@ class LabelUpdateRequest(SchemaVersion, Labeled):
150
172
  # specify. I think it's not that important, because all of the query parameters
151
173
  # will always be optional. There could be a problem if the semantics of a
152
174
  # name change, but let's just not do that!
153
- class DyffEntityQueryRequest(DyffSchemaBaseModel):
175
+ class DyffEntityQueryRequest(DyffRequestDefaultValidators):
176
+ query: Optional[str] = pydantic.Field(
177
+ default=None,
178
+ description="A JSON structure describing a query, encoded as a string."
179
+ " Valid keys are the same as the valid query keys for the corresponding"
180
+ " endpoint. Values can be scalars or lists. Lists are treated as"
181
+ " disjunctive queries (i.e., 'value $in list').",
182
+ )
183
+
154
184
  id: Optional[str] = pydantic.Field(default=None)
155
185
  account: Optional[str] = pydantic.Field(default=None)
156
186
  status: Optional[str] = pydantic.Field(default=None)
@@ -159,12 +189,6 @@ class DyffEntityQueryRequest(DyffSchemaBaseModel):
159
189
  default=None, description="Labels dict represented as a JSON string."
160
190
  )
161
191
 
162
- def dict(self, exclude_unset=True, **kwargs) -> dict[str, Any]:
163
- return super().dict(exclude_unset=exclude_unset, **kwargs)
164
-
165
- def json(self, exclude_unset=True, **kwargs) -> Any:
166
- return super().json(exclude_unset=exclude_unset, **kwargs)
167
-
168
192
 
169
193
  class _AnalysisProductQueryRequest(DyffEntityQueryRequest):
170
194
  method: Optional[str] = pydantic.Field(default=None)
@@ -173,7 +197,7 @@ class _AnalysisProductQueryRequest(DyffEntityQueryRequest):
173
197
  evaluation: Optional[str] = pydantic.Field(default=None)
174
198
  inferenceService: Optional[str] = pydantic.Field(default=None)
175
199
  model: Optional[str] = pydantic.Field(default=None)
176
- inputsAnyOf: Optional[str] = pydantic.Field(default=None)
200
+ inputs: Optional[str] = pydantic.Field(default=None)
177
201
 
178
202
 
179
203
  class AuditQueryRequest(DyffEntityQueryRequest):
@@ -240,6 +264,8 @@ __all__ = [
240
264
  "AuditQueryRequest",
241
265
  "DyffEntityCreateRequest",
242
266
  "DyffEntityQueryRequest",
267
+ "DyffRequestBase",
268
+ "DyffRequestDefaultValidators",
243
269
  "DatasetCreateRequest",
244
270
  "DatasetQueryRequest",
245
271
  "DocumentationEditRequest",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dyff-schema
3
- Version: 0.7.2
3
+ Version: 0.9.0
4
4
  Summary: Data models for the Dyff AI auditing platform.
5
5
  Author-email: Digital Safety Research Institute <contact@dsri.org>
6
6
  License: Apache-2.0
@@ -2,6 +2,7 @@ dyff/schema/__init__.py,sha256=JcpxaRHNYgLjJWLjVayLlqacb2GX49Pazpwb8m-BctM,1031
2
2
  dyff/schema/adapters.py,sha256=YMTHv_2VlLGFp-Kqwa6H51hjffHmk8gXjZilHysIF5Q,123
3
3
  dyff/schema/base.py,sha256=jvaNtsSZyFfsdUZTcY_U-yfLY5_GyrMxSXhON2R9XR0,119
4
4
  dyff/schema/copydoc.py,sha256=B4ZRpQmbFxi-3l9LCHvaJiVKb9VxADgC5vey804Febc,1075
5
+ dyff/schema/errors.py,sha256=4FwatI-0RUYYqVR0hHoifXPgANk89QgSq9PTS9FzfwU,1568
5
6
  dyff/schema/ids.py,sha256=Z3JQzlAJQC2Pam7ehxb4TXA4MIuFQN5SyzL5Ql0RukA,1422
6
7
  dyff/schema/platform.py,sha256=peHzGGSd5dQ-EFXrWDjBqMUtoOL3iCHxcV3XzW6Rjag,123
7
8
  dyff/schema/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -20,9 +21,9 @@ dyff/schema/io/vllm.py,sha256=2q05M_-lTzq9oywKXHPPpCFCSDVCSsRQqtmERzWTtio,123
20
21
  dyff/schema/v0/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
21
22
  dyff/schema/v0/r1/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
22
23
  dyff/schema/v0/r1/adapters.py,sha256=2t2oxsnGfSEDKKDIEYw4qqLXMH7qlFIwPVuLyUmbsHs,23552
23
- dyff/schema/v0/r1/base.py,sha256=X4QVwOzNw5xPCF_f14w2JhARj1CHmGMjUmTcNzb-X0c,17471
24
- dyff/schema/v0/r1/platform.py,sha256=ocEofM51a4bC0wlHB5zJO6jZUwJVUkYwv7RadTawhVA,61574
25
- dyff/schema/v0/r1/requests.py,sha256=bTLtQUK80_fFeEWRG6QUJoLwy1_PyXf8ua9Ei4av0ug,8913
24
+ dyff/schema/v0/r1/base.py,sha256=QX1TfqX3jBafxpBnf2bUTcgP0sMyqZFFNJZQHhM48BI,19385
25
+ dyff/schema/v0/r1/platform.py,sha256=t7XKNBXByrkleLhcRZn1Et0Dtb36CtmghWeezThz_UI,61416
26
+ dyff/schema/v0/r1/requests.py,sha256=I2K3thOsga0X9-6oRsAO1Hqgkr8qV_8haw-7ALIf_yc,9857
26
27
  dyff/schema/v0/r1/test.py,sha256=X6dUyVd5svcPCI-PBMOAqEfK9jv3bRDvkQTJzwS96c0,10720
27
28
  dyff/schema/v0/r1/version.py,sha256=isKAGuGxsdru8vDaYmI4YiZdJOu_wNxXK7u6QzD6FE4,392
28
29
  dyff/schema/v0/r1/dataset/__init__.py,sha256=LbVlkO2asyGYBKk2z49xjJYTM-pu9y9e4eQDXgTDLnM,2553
@@ -33,9 +34,9 @@ dyff/schema/v0/r1/dataset/text.py,sha256=nLIn91Zlt0tNdXUklSgjJ-kEDxoPX32ISLkiv2D
33
34
  dyff/schema/v0/r1/dataset/vision.py,sha256=aIe0fbfM_g3DsrDTdg2K803YKLjZBpurM_VJcJFuZLc,369
34
35
  dyff/schema/v0/r1/io/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
35
36
  dyff/schema/v0/r1/io/vllm.py,sha256=CUE9y8KthtUI7sD49S875rDmPvKotSXVIRaBS79aBZs,5320
36
- dyff_schema-0.7.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
37
- dyff_schema-0.7.2.dist-info/METADATA,sha256=zEELaVi3ZL67mNV3031i02KZH7DeuXqEwSkXR_C6LhI,3459
38
- dyff_schema-0.7.2.dist-info/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
39
- dyff_schema-0.7.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
40
- dyff_schema-0.7.2.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
41
- dyff_schema-0.7.2.dist-info/RECORD,,
37
+ dyff_schema-0.9.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
38
+ dyff_schema-0.9.0.dist-info/METADATA,sha256=y8iwfPwD_j6hEaMZXg7wTYwt319cWdKjB2eDNRiyc14,3459
39
+ dyff_schema-0.9.0.dist-info/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
40
+ dyff_schema-0.9.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
41
+ dyff_schema-0.9.0.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
42
+ dyff_schema-0.9.0.dist-info/RECORD,,