dyff-schema 0.32.0__py3-none-any.whl → 0.33.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/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = version = "0.32.0"
2
- __version_tuple__ = version_tuple = (0, 32, 0)
1
+ __version__ = version = "0.33.0"
2
+ __version_tuple__ = version_tuple = (0, 33, 0)
@@ -0,0 +1,183 @@
1
+ # SPDX-FileCopyrightText: 2024 UL Research Institutes
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+
6
+ import enum
7
+ import hashlib
8
+ from datetime import datetime, timezone
9
+ from typing import Any, Literal, Optional
10
+
11
+ import canonicaljson
12
+ import pydantic
13
+
14
+ from .base import int64
15
+
16
+
17
+ class KnownMediaTypes(str, enum.Enum):
18
+ oci_image_manifest = "application/vnd.oci.image.manifest.v1+json"
19
+ oci_image_config = "application/vnd.oci.image.config.v1+json"
20
+ oci_image_layer_tar = "application/vnd.oci.image.layer.v1.tar"
21
+ oci_image_layer_tar_gzip = "application/vnd.oci.image.layer.v1.tar+gzip"
22
+
23
+
24
+ # mypy gets confused because 'dict' is the name of a method in DyffBaseModel
25
+ _ModelAsDict = dict[str, Any]
26
+
27
+
28
+ class _OCISchemaBaseModel(pydantic.BaseModel):
29
+ def model_dump( # type: ignore [override]
30
+ self, *, by_alias: bool = True, exclude_none: bool = True, **kwargs
31
+ ) -> _ModelAsDict:
32
+ return super().model_dump(
33
+ by_alias=by_alias, exclude_none=exclude_none, **kwargs
34
+ )
35
+
36
+ def model_dump_json( # type: ignore [override]
37
+ self, *, by_alias: bool = True, exclude_none: bool = True, **kwargs
38
+ ) -> str:
39
+ return super().model_dump_json(
40
+ by_alias=by_alias, exclude_none=exclude_none, **kwargs
41
+ )
42
+
43
+ def dict(
44
+ self, *, by_alias: bool = True, exclude_none: bool = True, **kwargs
45
+ ) -> _ModelAsDict:
46
+ return self.model_dump(by_alias=by_alias, exclude_none=exclude_none, **kwargs)
47
+
48
+ def json(
49
+ self, *, by_alias: bool = True, exclude_none: bool = True, **kwargs
50
+ ) -> str:
51
+ return self.model_dump_json(
52
+ by_alias=by_alias, exclude_none=exclude_none, **kwargs
53
+ )
54
+
55
+ @pydantic.model_validator(mode="after")
56
+ def _ensure_datetime_timezone_utc(cls, values):
57
+ for field_name, field_value in values.__dict__.items():
58
+ if isinstance(field_value, datetime):
59
+ if field_value.tzinfo is None:
60
+ # Set UTC timezone for naive datetime
61
+ setattr(
62
+ values, field_name, field_value.replace(tzinfo=timezone.utc)
63
+ )
64
+ elif field_value.tzinfo != timezone.utc:
65
+ # Convert to UTC timezone
66
+ setattr(values, field_name, field_value.astimezone(timezone.utc))
67
+ return values
68
+
69
+
70
+ class _DigestMixin(pydantic.BaseModel):
71
+ def digest(self) -> str:
72
+ obj = self.model_dump(mode="json")
73
+ h = hashlib.sha256(usedforsecurity=False)
74
+ for chunk in canonicaljson.iterencode_canonical_json(obj):
75
+ h.update(chunk)
76
+ return "sha256:{}".format(h.hexdigest())
77
+
78
+
79
+ class Platform(_OCISchemaBaseModel):
80
+ architecture: str
81
+ os: str
82
+ os_version: Optional[str] = pydantic.Field(alias="os.version", default=None)
83
+ os_features: Optional[list[str]] = pydantic.Field(alias="os.features", default=None)
84
+ variant: Optional[str] = pydantic.Field(default=None)
85
+
86
+
87
+ class Descriptor(_OCISchemaBaseModel):
88
+ """
89
+ https://github.com/opencontainers/image-spec/blob/v1.1.1/descriptor.md
90
+ """
91
+
92
+ mediaType: str = pydantic.Field()
93
+ digest: str = pydantic.Field()
94
+ size: int64() = pydantic.Field( # type: ignore
95
+ description="This REQUIRED property specifies the size, in bytes,"
96
+ " of the raw content. This property exists so that a client"
97
+ " will have an expected size for the content before processing."
98
+ " If the length of the retrieved content does not match"
99
+ " the specified length, the content SHOULD NOT be trusted."
100
+ )
101
+ urls: Optional[list[str]] = pydantic.Field(default=None)
102
+ annotations: Optional[dict[str, str]] = pydantic.Field(default=None)
103
+ data: Optional[str] = pydantic.Field(default=None)
104
+ artifactType: Optional[str] = pydantic.Field(default=None)
105
+
106
+ @staticmethod
107
+ def empty() -> Descriptor:
108
+ """
109
+ https://github.com/opencontainers/image-spec/blob/v1.1.1/manifest.md#guidance-for-an-empty-descriptor
110
+ """
111
+ return Descriptor.model_validate(
112
+ {
113
+ "mediaType": "application/vnd.oci.empty.v1+json",
114
+ "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
115
+ "size": 2,
116
+ "data": "e30=",
117
+ }
118
+ )
119
+
120
+
121
+ class ImageDescriptor(Descriptor):
122
+ platform: Platform
123
+
124
+
125
+ class ExecutionParameters(_OCISchemaBaseModel):
126
+ # TODO
127
+ pass
128
+
129
+
130
+ class RootFS(_OCISchemaBaseModel):
131
+ type_: Literal["layers"] = pydantic.Field(alias="type", default="layers")
132
+ diff_ids: list[str] = pydantic.Field()
133
+
134
+
135
+ class HistoryEntry(_OCISchemaBaseModel):
136
+ created: Optional[str] = pydantic.Field(default=None)
137
+ author: Optional[str] = pydantic.Field(default=None)
138
+ created_by: Optional[str] = pydantic.Field(default=None)
139
+ comment: Optional[str] = pydantic.Field(default=None)
140
+ empty_layer: Optional[bool] = pydantic.Field(default=None)
141
+
142
+
143
+ class ImageConfig(Platform, _DigestMixin):
144
+ """
145
+ https://github.com/opencontainers/image-spec/blob/v1.1.1/config.md
146
+ """
147
+
148
+ created: Optional[str] = pydantic.Field(default=None)
149
+ author: Optional[str] = pydantic.Field(default=None)
150
+ config: Optional[ExecutionParameters] = pydantic.Field(default=None)
151
+ rootfs: RootFS
152
+ history: Optional[list[HistoryEntry]] = pydantic.Field(default=None)
153
+
154
+
155
+ class ImageManifest(_OCISchemaBaseModel):
156
+ schemaVersion: Literal["2"] = "2"
157
+ mediaType: Literal["application/vnd.oci.image.manifest.v1+json"] = (
158
+ "application/vnd.oci.image.manifest.v1+json"
159
+ )
160
+ artifactType: Optional[str] = pydantic.Field(default=None)
161
+ config: Descriptor = pydantic.Field(default_factory=Descriptor.empty)
162
+ layers: list[Descriptor] = pydantic.Field(
163
+ default_factory=lambda: [Descriptor.empty()]
164
+ )
165
+ subject: Optional[Descriptor] = pydantic.Field(default=None)
166
+ annotations: Optional[dict[str, str]] = pydantic.Field(default=None)
167
+
168
+ def is_image_manifest(self) -> bool:
169
+ return self.artifactType is None
170
+
171
+ def is_artifact_manifest(self) -> bool:
172
+ return self.artifactType is not None
173
+
174
+ @pydantic.model_validator(mode="after")
175
+ def validate_types(self):
176
+ if self.artifactType is None:
177
+ if self.config.mediaType != KnownMediaTypes.oci_image_config.value:
178
+ raise ValueError(
179
+ ".config.mediaType must be"
180
+ f"{KnownMediaTypes.oci_image_config.value}"
181
+ " if .artifactType is unspecified"
182
+ )
183
+ return self
@@ -33,6 +33,7 @@ from typing_extensions import Annotated, TypeAlias
33
33
 
34
34
  from ... import named_data_schema, product_schema
35
35
  from ...version import SomeSchemaVersion
36
+ from . import oci
36
37
  from .base import DyffSchemaBaseModel
37
38
  from .dataset import arrow, make_item_type, make_response_type
38
39
  from .version import SCHEMA_VERSION, SchemaVersion
@@ -193,6 +194,7 @@ class Entities(str, enum.Enum):
193
194
 
194
195
  Account = "Account"
195
196
  Analysis = "Analysis"
197
+ Artifact = "Artifact"
196
198
  Audit = "Audit"
197
199
  AuditProcedure = "AuditProcedure"
198
200
  Concern = "Concern"
@@ -220,6 +222,7 @@ class Resources(str, enum.Enum):
220
222
  """The resource names corresponding to entities that have API endpoints."""
221
223
 
222
224
  Analysis = "analyses"
225
+ Artifact = "artifacts"
223
226
  Audit = "audits"
224
227
  AuditProcedure = "auditprocedures"
225
228
  Concern = "concerns"
@@ -266,6 +269,7 @@ class Resources(str, enum.Enum):
266
269
 
267
270
  EntityKindLiteral = Literal[
268
271
  "Analysis",
272
+ "Artifact",
269
273
  "Audit",
270
274
  "AuditProcedure",
271
275
  "DataSource",
@@ -488,6 +492,7 @@ class DyffEntityMetadata(DyffSchemaBaseModel):
488
492
  class DyffEntity(Status, Labeled, SchemaVersion, DyffModelWithID):
489
493
  kind: Literal[
490
494
  "Analysis",
495
+ "Artifact",
491
496
  "Audit",
492
497
  "AuditProcedure",
493
498
  "DataSource",
@@ -829,8 +834,10 @@ class Digest(DyffSchemaBaseModel):
829
834
  )
830
835
 
831
836
 
837
+ # TODO: (schema-v1) Rename this to "File" or something -- reserve Artifact
838
+ # for OCI artifacts.
832
839
  class Artifact(DyffSchemaBaseModel):
833
- # TODO: In v1, rename this to 'contentType' or something and commit to making it the MIME type
840
+ # TODO: (schema-v1) Rename this to 'contentType' or something and commit to making it the MIME type
834
841
  kind: Optional[str] = pydantic.Field(
835
842
  default=None, description="The kind of artifact"
836
843
  )
@@ -1122,6 +1129,10 @@ class ModelSource(DyffSchemaBaseModel):
1122
1129
  )
1123
1130
 
1124
1131
 
1132
+ class ContainerImage(DyffSchemaBaseModel):
1133
+ pass
1134
+
1135
+
1125
1136
  class AcceleratorGPU(DyffSchemaBaseModel):
1126
1137
  hardwareTypes: list[str] = pydantic.Field(
1127
1138
  min_length=1,
@@ -2130,6 +2141,25 @@ class Score(ScoreData):
2130
2141
  id: str = pydantic.Field(description="Unique identifier of the entity")
2131
2142
 
2132
2143
 
2144
+ # ---------------------------------------------------------------------------
2145
+ # OCI artifacts
2146
+
2147
+
2148
+ # TODO: (schema-v1) Rename this to Artifact
2149
+ class OCIArtifact(Documented, DyffEntity):
2150
+ kind: Literal["Artifact"] = Entities.Artifact.value
2151
+
2152
+ manifest: oci.ImageManifest = pydantic.Field(
2153
+ description="The OCI image manifest of the artifact"
2154
+ )
2155
+
2156
+ def dependencies(self) -> list[str]:
2157
+ return []
2158
+
2159
+ def resource_allocation(self) -> Optional[ResourceAllocation]:
2160
+ return None
2161
+
2162
+
2133
2163
  # ---------------------------------------------------------------------------
2134
2164
  # Status enumerations
2135
2165
 
@@ -2372,6 +2402,7 @@ def is_status_success(status: str) -> bool:
2372
2402
 
2373
2403
  _ENTITY_CLASS = {
2374
2404
  Entities.Analysis: Analysis,
2405
+ Entities.Artifact: OCIArtifact,
2375
2406
  Entities.Audit: Audit,
2376
2407
  Entities.AuditProcedure: AuditProcedure,
2377
2408
  Entities.Dataset: Dataset,
@@ -2406,6 +2437,7 @@ _DyffEntityTypeRevisable = Union[
2406
2437
  Method,
2407
2438
  Model,
2408
2439
  Module,
2440
+ OCIArtifact,
2409
2441
  Report,
2410
2442
  SafetyCase,
2411
2443
  UseCase,
@@ -2579,6 +2611,7 @@ __all__ = [
2579
2611
  "ModelStorage",
2580
2612
  "Module",
2581
2613
  "ModuleBase",
2614
+ "OCIArtifact",
2582
2615
  "QueryableDyffEntity",
2583
2616
  "Report",
2584
2617
  "ReportBase",
@@ -21,7 +21,7 @@ from typing import Any, Literal, Optional, Union
21
21
  import pydantic
22
22
 
23
23
  from ... import upcast
24
- from . import commands
24
+ from . import commands, oci
25
25
  from .base import DyffBaseModel, JsonMergePatchSemantics
26
26
  from .platform import (
27
27
  AnalysisBase,
@@ -72,6 +72,20 @@ class DyffRequestBase(SchemaVersion, DyffRequestDefaultValidators):
72
72
  # TODO: (DYFF-223) I think that exclude_unset=True should be the default
73
73
  # for all schema objects, but I'm unsure of the consequences of making
74
74
  # this change and we'll defer it until v1.
75
+ def model_dump( # type: ignore [override]
76
+ self, *, by_alias: bool = True, exclude_unset=True, **kwargs
77
+ ) -> _ModelAsDict:
78
+ return super().model_dump(
79
+ by_alias=by_alias, exclude_unset=exclude_unset, **kwargs
80
+ )
81
+
82
+ def model_dump_json( # type: ignore [override]
83
+ self, *, by_alias: bool = True, exclude_unset=True, **kwargs
84
+ ) -> str:
85
+ return super().model_dump_json(
86
+ by_alias=by_alias, exclude_unset=exclude_unset, **kwargs
87
+ )
88
+
75
89
  def dict(
76
90
  self, *, by_alias: bool = True, exclude_unset=True, **kwargs
77
91
  ) -> _ModelAsDict:
@@ -118,6 +132,10 @@ class AnalysisCreateRequest(DyffEntityCreateRequest, AnalysisBase):
118
132
  return scope
119
133
 
120
134
 
135
+ class ArtifactCreateRequest(DyffEntityCreateRequest):
136
+ manifest: oci.ImageManifest
137
+
138
+
121
139
  class ConcernCreateRequest(DyffEntityCreateRequest, ConcernBase):
122
140
  @pydantic.field_validator("documentation", check_fields=False)
123
141
  def _validate_documentation(
@@ -357,6 +375,10 @@ class _AnalysisProductQueryRequest(DyffEntityQueryRequest):
357
375
  inputs: Optional[str] = pydantic.Field(default=None)
358
376
 
359
377
 
378
+ class ArtifactQueryRequest(DyffEntityQueryRequest):
379
+ name: Optional[str] = pydantic.Field(default=None)
380
+
381
+
360
382
  class AuditQueryRequest(DyffEntityQueryRequest):
361
383
  name: Optional[str] = pydantic.Field(default=None)
362
384
 
@@ -446,6 +468,8 @@ class UseCaseQueryRequest(DyffEntityQueryRequest):
446
468
 
447
469
  __all__ = [
448
470
  "AnalysisCreateRequest",
471
+ "ArtifactCreateRequest",
472
+ "ArtifactQueryRequest",
449
473
  "AuditQueryRequest",
450
474
  "ConcernCreateRequest",
451
475
  "DyffEntityCreateRequest",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dyff-schema
3
- Version: 0.32.0
3
+ Version: 0.33.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
@@ -20,6 +20,7 @@ Requires-Python: >=3.10
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  License-File: NOTICE
23
+ Requires-Dist: canonicaljson==2.0.0
23
24
  Requires-Dist: hypothesis
24
25
  Requires-Dist: hypothesis-jsonschema
25
26
  Requires-Dist: jsonpath-ng
@@ -1,5 +1,5 @@
1
1
  dyff/schema/__init__.py,sha256=w7OWDFuyGKd6xt_yllNtKzHahPgywrfU4Ue02psYaMA,2244
2
- dyff/schema/_version.py,sha256=cSUDf-klGYAh3VKPYjjxhNT8qv04wlp3-jjPmvgZjS0,80
2
+ dyff/schema/_version.py,sha256=uTbdsxpcOU7mMXqX3G50NVmPGutL-k0_3StqoCGV-b0,80
3
3
  dyff/schema/adapters.py,sha256=YMTHv_2VlLGFp-Kqwa6H51hjffHmk8gXjZilHysIF5Q,123
4
4
  dyff/schema/annotations.py,sha256=nE6Jk1PLqlShj8uqjE_EzZC9zYnTDW5AVtQcjysiK8M,10018
5
5
  dyff/schema/base.py,sha256=jvaNtsSZyFfsdUZTcY_U-yfLY5_GyrMxSXhON2R9XR0,119
@@ -28,8 +28,9 @@ dyff/schema/v0/r1/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs
28
28
  dyff/schema/v0/r1/adapters.py,sha256=hpwCSW8lkMkUKCLe0zaMUDu-VS_caSxJvPsECEi_XRA,33069
29
29
  dyff/schema/v0/r1/base.py,sha256=fdhAa4hpbSn7m3U0qha4rG7gJiYUvPR8SaM-mwszoy0,20289
30
30
  dyff/schema/v0/r1/commands.py,sha256=VoThKeIVz4ZQAqBXhb6TVOG6aG4m_Oa0_6Sc4oxyFhs,9801
31
- dyff/schema/v0/r1/platform.py,sha256=7ErqSqPLQD2bIEqnJByktBb7RaQYRkZbqQsofb0dFIM,82736
32
- dyff/schema/v0/r1/requests.py,sha256=C-LX-NAIDgh88xCl3NW7wqLIpurK8SXqmhRnGvy4BM0,17197
31
+ dyff/schema/v0/r1/oci.py,sha256=mvh1BaiRahZbjIbMh7RqZxZjOKZT9GsS1yBpSdPw01Y,6587
32
+ dyff/schema/v0/r1/platform.py,sha256=s1O_mAVfi9owekqYxcjwPI10s3F7RNo8B8WvMunMi4U,83577
33
+ dyff/schema/v0/r1/requests.py,sha256=3XcFcKChubBrxoHCUHkV5GrNl6Ijdbxn4D4UZAAo09g,17965
33
34
  dyff/schema/v0/r1/responses.py,sha256=nxy7FPtfw2B_bljz5UGGuSE79HTkDQxKH56AJVmd4Qo,1287
34
35
  dyff/schema/v0/r1/test.py,sha256=X6dUyVd5svcPCI-PBMOAqEfK9jv3bRDvkQTJzwS96c0,10720
35
36
  dyff/schema/v0/r1/version.py,sha256=NONebgcv5Thsw_ymud6PacZdGjV6ndBrmLnap-obcpo,428
@@ -42,9 +43,9 @@ dyff/schema/v0/r1/dataset/text.py,sha256=MYG5seGODDryRSCy-g0Unh5dD0HCytmZ3FeElC-
42
43
  dyff/schema/v0/r1/dataset/vision.py,sha256=aIe0fbfM_g3DsrDTdg2K803YKLjZBpurM_VJcJFuZLc,369
43
44
  dyff/schema/v0/r1/io/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
44
45
  dyff/schema/v0/r1/io/vllm.py,sha256=vWyLg-susbg0JDfv6VExBpgFdU2GHP2a14ChOdbckvs,5321
45
- dyff_schema-0.32.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
46
- dyff_schema-0.32.0.dist-info/licenses/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
47
- dyff_schema-0.32.0.dist-info/METADATA,sha256=_DgORzGNTURV5pqYZS014MpFJOFufqQ--rpSCfIupKA,3632
48
- dyff_schema-0.32.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
- dyff_schema-0.32.0.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
50
- dyff_schema-0.32.0.dist-info/RECORD,,
46
+ dyff_schema-0.33.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ dyff_schema-0.33.0.dist-info/licenses/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
48
+ dyff_schema-0.33.0.dist-info/METADATA,sha256=XUEtF3Dgk_LSI4YKUPZp5RJzKvzx7nv7_Jxp30ic3W0,3668
49
+ dyff_schema-0.33.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
+ dyff_schema-0.33.0.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
51
+ dyff_schema-0.33.0.dist-info/RECORD,,