dyff-schema 0.35.4__py3-none-any.whl → 0.36.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.35.4"
2
- __version_tuple__ = version_tuple = (0, 35, 4)
1
+ __version__ = version = "0.36.0"
2
+ __version_tuple__ = version_tuple = (0, 36, 0)
@@ -7,6 +7,7 @@ These are used internally by the platform and users typically won't encounter th
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ from datetime import datetime, timedelta, timezone
10
11
  from typing import Literal, Optional, Union
11
12
 
12
13
  import pydantic
@@ -15,6 +16,9 @@ from typing_extensions import Annotated
15
16
 
16
17
  from .base import DyffSchemaBaseModel, JsonMergePatchSemantics
17
18
  from .platform import (
19
+ ChallengeTask,
20
+ ChallengeTaskExecutionEnvironment,
21
+ ChallengeTaskSchedule,
18
22
  DyffEntityType,
19
23
  EntityIdentifier,
20
24
  FamilyMember,
@@ -24,6 +28,7 @@ from .platform import (
24
28
  SchemaVersion,
25
29
  Status,
26
30
  TagNameType,
31
+ body_maxlen,
27
32
  summary_maxlen,
28
33
  title_maxlen,
29
34
  )
@@ -42,7 +47,10 @@ class Command(SchemaVersion, DyffSchemaBaseModel):
42
47
  """
43
48
 
44
49
  command: Literal[
50
+ "CreateChallengeTask",
45
51
  "CreateEntity",
52
+ "EditChallengeContent",
53
+ "EditChallengeTaskRules",
46
54
  "EditEntityDocumentation",
47
55
  "EditEntityLabels",
48
56
  "EditFamilyMembers",
@@ -68,11 +76,220 @@ class CreateEntity(Command):
68
76
  # ----------------------------------------------------------------------------
69
77
 
70
78
 
79
+ class CreateChallengeTaskAttributes(DyffSchemaBaseModel):
80
+ """Attributes for the CreateChallengeTask command."""
81
+
82
+ task: ChallengeTask = pydantic.Field(description="The task to create.")
83
+
84
+
85
+ class CreateChallengeTaskData(EntityIdentifier):
86
+ """Payload data for the CreateChallengeTask command."""
87
+
88
+ attributes: CreateChallengeTaskAttributes = pydantic.Field(
89
+ description="The command attributes"
90
+ )
91
+
92
+
93
+ class CreateChallengeTask(Command):
94
+ """Create a new challenge task within an existing challenge."""
95
+
96
+ command: Literal["CreateChallengeTask"] = "CreateChallengeTask"
97
+
98
+ data: CreateChallengeTaskData = pydantic.Field(description="The command data.")
99
+
100
+
101
+ # ----------------------------------------------------------------------------
102
+
103
+
104
+ class EditChallengeContentPagePatch(JsonMergePatchSemantics):
105
+ """Same properties as ChallengeContentPage.
106
+
107
+ Fields that are not assigned explicitly remain unchanged.
108
+ """
109
+
110
+ title: Annotated[str, StringConstraints(max_length=title_maxlen())] = ( # type: ignore
111
+ pydantic.Field(
112
+ default="",
113
+ description='A short plain string suitable as a title or "headline".',
114
+ )
115
+ )
116
+
117
+ summary: Annotated[str, StringConstraints(max_length=summary_maxlen())] = ( # type: ignore
118
+ pydantic.Field(
119
+ default="",
120
+ description="A brief summary, suitable for display in"
121
+ " small UI elements.",
122
+ )
123
+ )
124
+
125
+ body: Annotated[str, StringConstraints(max_length=body_maxlen())] = pydantic.Field( # type: ignore
126
+ default="",
127
+ description="Long-form documentation. Interpreted as"
128
+ " Markdown. There are no length constraints, but be reasonable.",
129
+ )
130
+
131
+
132
+ class EditChallengeContentPatch(DyffSchemaBaseModel):
133
+ page: EditChallengeContentPagePatch
134
+
135
+ @field_serializer("page")
136
+ def _serialize_page(self, page: EditChallengeContentPagePatch, _info):
137
+ return page.model_dump(mode=_info.mode)
138
+
139
+
140
+ class EditChallengeContentAttributes(DyffSchemaBaseModel):
141
+ """Attributes for the EditChallengePageContent command."""
142
+
143
+ challenge: Optional[EditChallengeContentPatch] = pydantic.Field(
144
+ default=None,
145
+ description="Edits to make to the content of the main challenge page.",
146
+ )
147
+
148
+ tasks: Optional[dict[str, EditChallengeContentPatch]] = pydantic.Field(
149
+ default=None, description="Edits to make to the content of the challenge tasks."
150
+ )
151
+
152
+
153
+ class EditChallengeContentData(EntityIdentifier):
154
+ """Payload data for the EditChallengePageContent command."""
155
+
156
+ attributes: EditChallengeContentAttributes = pydantic.Field(
157
+ description="The command attributes"
158
+ )
159
+
160
+
161
+ class EditChallengeContent(Command):
162
+ """Edit the page content associated with a challenge-related entity.
163
+
164
+ Setting a documentation field to null/None deletes the corresponding value. To
165
+ preserve the existing value, leave the field *unset*.
166
+ """
167
+
168
+ command: Literal["EditChallengeContent"] = "EditChallengeContent"
169
+
170
+ data: EditChallengeContentData = pydantic.Field(description="The edit data.")
171
+
172
+
173
+ # ----------------------------------------------------------------------------
174
+
175
+
176
+ class EditChallengeTaskRulesExecutionEnvironmentChoices(JsonMergePatchSemantics):
177
+ """Same properties as ChallengeTaskExecutionEnvironmentChocies, but assigning None
178
+ to a field is interpreted as a command to delete that field.
179
+
180
+ Fields that are not assigned explicitly remain unchanged.
181
+ """
182
+
183
+ choices: dict[str, Optional[ChallengeTaskExecutionEnvironment]] = pydantic.Field(
184
+ default_factory=dict, description="Execution environment choices."
185
+ )
186
+ default: Optional[str] = pydantic.Field(
187
+ default=None, description="The default execution environment."
188
+ )
189
+
190
+
191
+ class EditChallengeTaskRulesSchedule(JsonMergePatchSemantics):
192
+ """Same properties as ChallengeTaskSchedule, but assigning None to a field is
193
+ interpreted as a command to delete that field.
194
+
195
+ Fields that are not assigned explicitly remain unchanged.
196
+ """
197
+
198
+ openingTime: Optional[datetime] = pydantic.Field(
199
+ default=None, description="The announced opening time for task submissions."
200
+ )
201
+ closingTime: Optional[datetime] = pydantic.Field(
202
+ default=None, description="The announced closing time for task submissions."
203
+ )
204
+
205
+ submissionCycleDuration: timedelta = pydantic.Field(
206
+ default=timedelta(days=1),
207
+ description="The duration of a submission cycle."
208
+ " Teams are limited to a maximum number of submissions per cycle.",
209
+ )
210
+ submissionCycleEpoch: datetime = pydantic.Field(
211
+ default=datetime.fromtimestamp(0, timezone.utc),
212
+ description="The epoch of a submission cycle."
213
+ " For example, any given cycle lasts from"
214
+ " [epoch + N*duration, epoch + (N+1)*duration)."
215
+ " Teams are limited to a maximum number of submissions per cycle.",
216
+ )
217
+ submissionLimitPerCycle: int = pydantic.Field(
218
+ default=1,
219
+ ge=1,
220
+ description="Teams are limited to this many submissions per cycle.",
221
+ )
222
+
223
+
224
+ class EditChallengeTaskRulesPatch(DyffSchemaBaseModel):
225
+ """Same properties as ChallengeTaskRules, but assigning None to a field is
226
+ interpreted as a command to delete that field.
227
+
228
+ Fields that are not assigned explicitly remain unchanged.
229
+ """
230
+
231
+ executionEnvironment: Optional[
232
+ EditChallengeTaskRulesExecutionEnvironmentChoices
233
+ ] = pydantic.Field(
234
+ default=None, description="Patch for the .rules.executionEnvironments field."
235
+ )
236
+ schedule: Optional[EditChallengeTaskRulesSchedule] = pydantic.Field(
237
+ default=None, description="Patch for the .rules.schedule field."
238
+ )
239
+
240
+ @field_serializer("executionEnvironment")
241
+ def _serialize_executionEnvironment(
242
+ self,
243
+ executionEnvironment: EditChallengeTaskRulesExecutionEnvironmentChoices,
244
+ _info,
245
+ ):
246
+ return executionEnvironment.model_dump(mode=_info.mode)
247
+
248
+ @field_serializer("schedule")
249
+ def _serialize_schedule(
250
+ self,
251
+ schedule: EditChallengeTaskRulesSchedule,
252
+ _info,
253
+ ):
254
+ return schedule.model_dump(mode=_info.mode)
255
+
256
+
257
+ class EditChallengeTaskRulesAttributes(DyffSchemaBaseModel):
258
+ """Attributes for the EditChallengeTaskRules command."""
259
+
260
+ rules: EditChallengeTaskRulesPatch = pydantic.Field(
261
+ description="Edits to make to the task rules."
262
+ )
263
+
264
+
265
+ class EditChallengeTaskRulesData(EntityIdentifier):
266
+ """Payload data for the EditChallengeTaskRules command."""
267
+
268
+ attributes: EditChallengeTaskRulesAttributes = pydantic.Field(
269
+ description="The command attributes"
270
+ )
271
+
272
+
273
+ class EditChallengeTaskRules(Command):
274
+ """Edit the rules of a challenge task.
275
+
276
+ Setting a field to null/None deletes the corresponding value. To preserve the
277
+ existing value, leave the field *unset*.
278
+ """
279
+
280
+ command: Literal["EditChallengeTaskRules"] = "EditChallengeTaskRules"
281
+
282
+ data: EditChallengeTaskRulesData = pydantic.Field(description="The edit data.")
283
+
284
+
285
+ # ----------------------------------------------------------------------------
286
+
287
+
71
288
  class EditEntityDocumentationPatch(JsonMergePatchSemantics):
72
289
  """Same properties as DocumentationBase, but assigning None to a field is
73
290
  interpreted as a command to delete that field.
74
291
 
75
- Fields that are assigned explicitly remain unchanged.
292
+ Fields that are not assigned explicitly remain unchanged.
76
293
  """
77
294
 
78
295
  title: Optional[Annotated[str, StringConstraints(max_length=title_maxlen())]] = ( # type: ignore
@@ -292,7 +509,9 @@ class UpdateEntityStatus(Command):
292
509
 
293
510
 
294
511
  DyffCommandType = Union[
512
+ CreateChallengeTask,
295
513
  CreateEntity,
514
+ EditChallengeContent,
296
515
  EditEntityDocumentation,
297
516
  EditEntityLabels,
298
517
  EditFamilyMembers,
@@ -304,8 +523,16 @@ DyffCommandType = Union[
304
523
 
305
524
  __all__ = [
306
525
  "Command",
526
+ "CreateChallengeTask",
527
+ "CreateChallengeTaskAttributes",
528
+ "CreateChallengeTaskData",
307
529
  "CreateEntity",
308
530
  "DyffCommandType",
531
+ "EditChallengeContent",
532
+ "EditChallengeContentAttributes",
533
+ "EditChallengeContentData",
534
+ "EditChallengeContentPagePatch",
535
+ "EditChallengeContentPatch",
309
536
  "EditEntityDocumentation",
310
537
  "EditEntityDocumentationAttributes",
311
538
  "EditEntityDocumentationData",
dyff/schema/v0/r1/oci.py CHANGED
@@ -53,18 +53,16 @@ class _OCISchemaBaseModel(pydantic.BaseModel):
53
53
  )
54
54
 
55
55
  @pydantic.model_validator(mode="after")
56
- def _ensure_datetime_timezone_utc(cls, values):
57
- for field_name, field_value in values.__dict__.items():
56
+ def _ensure_datetime_timezone_utc(self):
57
+ for field_name, field_value in self.__dict__.items():
58
58
  if isinstance(field_value, datetime):
59
59
  if field_value.tzinfo is None:
60
60
  # Set UTC timezone for naive datetime
61
- setattr(
62
- values, field_name, field_value.replace(tzinfo=timezone.utc)
63
- )
61
+ setattr(self, field_name, field_value.replace(tzinfo=timezone.utc))
64
62
  elif field_value.tzinfo != timezone.utc:
65
63
  # Convert to UTC timezone
66
- setattr(values, field_name, field_value.astimezone(timezone.utc))
67
- return values
64
+ setattr(self, field_name, field_value.astimezone(timezone.utc))
65
+ return self
68
66
 
69
67
 
70
68
  class _DigestMixin(pydantic.BaseModel):
@@ -22,11 +22,12 @@ We use the following naming convention:
22
22
  import abc
23
23
  import enum
24
24
  import urllib.parse
25
- from datetime import datetime
25
+ from datetime import datetime, timedelta, timezone
26
26
  from enum import Enum
27
27
  from pathlib import Path
28
28
  from typing import Any, Literal, NamedTuple, Optional, Type, Union
29
29
 
30
+ import i18naddress
30
31
  import pyarrow
31
32
  import pydantic
32
33
  from pydantic import StringConstraints
@@ -182,6 +183,10 @@ def summary_maxlen() -> int:
182
183
  return 280
183
184
 
184
185
 
186
+ def body_maxlen() -> int:
187
+ return 1_000_000
188
+
189
+
185
190
  def entity_id_regex() -> str:
186
191
  """An entity ID is a 32-character HEX string.
187
192
 
@@ -199,6 +204,7 @@ class Entities(str, enum.Enum):
199
204
  Artifact = "OCIArtifact"
200
205
  Audit = "Audit"
201
206
  AuditProcedure = "AuditProcedure"
207
+ Challenge = "Challenge"
202
208
  Concern = "Concern"
203
209
  DataSource = "DataSource"
204
210
  Dataset = "Dataset"
@@ -217,6 +223,8 @@ class Entities(str, enum.Enum):
217
223
  Revision = "Revision"
218
224
  SafetyCase = "SafetyCase"
219
225
  Score = "Score"
226
+ Submission = "Submission"
227
+ Team = "Team"
220
228
  UseCase = "UseCase"
221
229
 
222
230
 
@@ -227,6 +235,7 @@ class Resources(str, enum.Enum):
227
235
  Artifact = "artifacts"
228
236
  Audit = "audits"
229
237
  AuditProcedure = "auditprocedures"
238
+ Challenge = "challenges"
230
239
  Concern = "concerns"
231
240
  Dataset = "datasets"
232
241
  DataSource = "datasources"
@@ -246,6 +255,8 @@ class Resources(str, enum.Enum):
246
255
  Revision = "revisions"
247
256
  SafetyCase = "safetycases"
248
257
  Score = "scores"
258
+ Submission = "submissions"
259
+ Team = "teams"
249
260
  UseCase = "usecases"
250
261
 
251
262
  Task = "tasks"
@@ -276,6 +287,7 @@ EntityKindLiteral = Literal[
276
287
  "Analysis",
277
288
  "Audit",
278
289
  "AuditProcedure",
290
+ "Challenge",
279
291
  "DataSource",
280
292
  "Dataset",
281
293
  "Evaluation",
@@ -293,6 +305,8 @@ EntityKindLiteral = Literal[
293
305
  "Report",
294
306
  "Revision",
295
307
  "SafetyCase",
308
+ "Submission",
309
+ "Team",
296
310
  "UseCase",
297
311
  ]
298
312
 
@@ -503,6 +517,7 @@ class DyffEntity(Status, Labeled, SchemaVersion, DyffModelWithID):
503
517
  "Analysis",
504
518
  "Audit",
505
519
  "AuditProcedure",
520
+ "Challenge",
506
521
  "DataSource",
507
522
  "Dataset",
508
523
  "Evaluation",
@@ -520,6 +535,8 @@ class DyffEntity(Status, Labeled, SchemaVersion, DyffModelWithID):
520
535
  "Report",
521
536
  "Revision",
522
537
  "SafetyCase",
538
+ "Submission",
539
+ "Team",
523
540
  "UseCase",
524
541
  ]
525
542
 
@@ -730,7 +747,9 @@ class FamilyMemberBase(DyffSchemaBaseModel):
730
747
  description="ID of the resource this member references.",
731
748
  )
732
749
 
733
- description: Optional[Annotated[str, StringConstraints(max_length=summary_maxlen())]] = pydantic.Field( # type: ignore
750
+ description: Optional[
751
+ Annotated[str, StringConstraints(max_length=summary_maxlen())]
752
+ ] = pydantic.Field( # type: ignore
734
753
  default=None,
735
754
  description="A short description of the member."
736
755
  " This should describe how this version of the resource"
@@ -2003,12 +2022,12 @@ class ScoreSpec(DyffSchemaBaseModel):
2003
2022
  return self.format_quantity(self.format, quantity, unit=self.unit)
2004
2023
 
2005
2024
  @pydantic.model_validator(mode="after")
2006
- def _validate_minimum_maximum(cls, values):
2007
- minimum = values.minimum
2008
- maximum = values.maximum
2025
+ def _validate_minimum_maximum(self):
2026
+ minimum = self.minimum
2027
+ maximum = self.maximum
2009
2028
  if minimum is not None and maximum is not None and minimum > maximum:
2010
2029
  raise ValueError(f"minimum {minimum} is greater than maximum {maximum}")
2011
- return values
2030
+ return self
2012
2031
 
2013
2032
  @pydantic.field_validator("format")
2014
2033
  def _validate_format(cls, v):
@@ -2292,6 +2311,372 @@ class OCIArtifact(DyffEntity):
2292
2311
  return None
2293
2312
 
2294
2313
 
2314
+ # ----------------------------------------------------------------------------
2315
+ # Challenges
2316
+
2317
+
2318
+ class ChallengeContentPage(DyffSchemaBaseModel):
2319
+ """The content for the Web page corresponding to a challenge or challenge-related
2320
+ object."""
2321
+
2322
+ title: str = pydantic.Field(
2323
+ default="Untitled Challenge",
2324
+ description='A short plain string suitable as a title or "headline".',
2325
+ max_length=title_maxlen(),
2326
+ )
2327
+
2328
+ summary: str = pydantic.Field(
2329
+ default="",
2330
+ description="A brief summary, suitable for display in small UI elements.",
2331
+ max_length=summary_maxlen(),
2332
+ )
2333
+
2334
+ body: str = pydantic.Field(
2335
+ default="",
2336
+ description="Long-form documentation. Interpreted as"
2337
+ " Markdown and rendered as a single page. There are no length"
2338
+ " constraints, but be reasonable.",
2339
+ max_length=body_maxlen(),
2340
+ )
2341
+
2342
+
2343
+ class ChallengeNewsItem(DyffSchemaBaseModel):
2344
+ """A news item to display in the challenge news feed."""
2345
+
2346
+ id: str = pydantic.Field(description="Unique ID of the news item.")
2347
+ title: str = pydantic.Field(
2348
+ description='A short plain string suitable as a title or "headline".',
2349
+ max_length=title_maxlen(),
2350
+ )
2351
+ summary: str = pydantic.Field(
2352
+ description="A brief summary, suitable for display in small UI elements.",
2353
+ max_length=summary_maxlen(),
2354
+ )
2355
+ postingTime: datetime = pydantic.Field(
2356
+ description="The timestamp when the news item was posted."
2357
+ )
2358
+
2359
+
2360
+ class TeamMember(DyffSchemaBaseModel):
2361
+ """A member of a team."""
2362
+
2363
+ name: str = pydantic.Field(
2364
+ description="The member's full name as it will be displayed in the UI."
2365
+ " Include all desired titles and honorifics.",
2366
+ max_length=title_maxlen(),
2367
+ )
2368
+ isCorrespondingMember: bool = pydantic.Field(
2369
+ description="Indicates that this member will receive and respond to"
2370
+ " correspondence pertaining to the team's participation in the challenge."
2371
+ " At least one member must be a corresponding member."
2372
+ )
2373
+ email: Optional[pydantic.EmailStr] = pydantic.Field(
2374
+ default=None, description="The member's email address."
2375
+ )
2376
+ orcid: Optional[str] = pydantic.Field(
2377
+ default=None, description="The member's ORCID."
2378
+ )
2379
+ url: Optional[pydantic.HttpUrl] = pydantic.Field(
2380
+ default=None,
2381
+ description="The URL of the member's personal Web page or similar.",
2382
+ )
2383
+ note: Optional[str] = pydantic.Field(
2384
+ default=None,
2385
+ description="A brief note giving additional information about the member.",
2386
+ max_length=summary_maxlen(),
2387
+ )
2388
+ affiliations: Optional[list[str]] = pydantic.Field(
2389
+ default=None,
2390
+ description="The member's affiliations, specified as keys in the"
2391
+ " .affiliations dict in the corresonding Team object.",
2392
+ )
2393
+
2394
+
2395
+ class GoogleI18nMailingAddress(DyffSchemaBaseModel):
2396
+ """An international mailing address, in the schema used by Google's
2397
+ i18n address metadata repository
2398
+ (https://chromium-i18n.appspot.com/ssl-address) and as interpreted by the
2399
+ Python ``google-i18n-address`` package.
2400
+
2401
+ The field names depart from our camelCase convention to match the
2402
+ existing google-i18n-address format. We also use the empty string rather
2403
+ than
2404
+ """
2405
+
2406
+ street_address: str = pydantic.Field(
2407
+ description="The (possibly multiline) street address.",
2408
+ )
2409
+ country_code: str = pydantic.Field(
2410
+ description="Two-letter ISO 3166-1 country code.",
2411
+ )
2412
+ city: Optional[str] = pydantic.Field(
2413
+ default=None,
2414
+ description="A city or town name.",
2415
+ )
2416
+ country_area: Optional[str] = pydantic.Field(
2417
+ default=None,
2418
+ description="A designation of a region, province, or state.",
2419
+ )
2420
+ postal_code: Optional[str] = pydantic.Field(
2421
+ default=None,
2422
+ description="A postal code or zip code.",
2423
+ )
2424
+ sorting_code: Optional[str] = pydantic.Field(
2425
+ default=None,
2426
+ description="A sorting code.",
2427
+ )
2428
+ name: Optional[str] = pydantic.Field(
2429
+ default=None,
2430
+ description="A person's name.",
2431
+ )
2432
+ company_name: Optional[str] = pydantic.Field(
2433
+ default=None,
2434
+ description="A name of a company or organization.",
2435
+ )
2436
+
2437
+ formatted: str = pydantic.Field(
2438
+ default="",
2439
+ description="The address formatted as a multi-line string (auto-generated).",
2440
+ )
2441
+
2442
+ @pydantic.model_validator(mode="after")
2443
+ def normalize_address(self):
2444
+ """Normalizes the address and populates .formatted."""
2445
+ unnormalized = self.model_dump()
2446
+ normalized = i18naddress.normalize_address(unnormalized)
2447
+ for k, v in normalized.items():
2448
+ if v != "":
2449
+ setattr(self, k, v)
2450
+ self.formatted = i18naddress.format_address(normalized, latin=True)
2451
+ return self
2452
+
2453
+
2454
+ class TeamAffiliation(DyffSchemaBaseModel):
2455
+ """An organization with which one or more team members are affiliated, such as a
2456
+ university or company."""
2457
+
2458
+ name: str = pydantic.Field(
2459
+ description="The name of the organization as it will be displayed in the UI.",
2460
+ max_length=title_maxlen(),
2461
+ )
2462
+ department: Optional[str] = pydantic.Field(
2463
+ default=None,
2464
+ description="A department within the organization.",
2465
+ max_length=title_maxlen(),
2466
+ )
2467
+ group: Optional[str] = pydantic.Field(
2468
+ default=None,
2469
+ description="A group within the organization or department.",
2470
+ max_length=title_maxlen(),
2471
+ )
2472
+
2473
+ address: Optional[GoogleI18nMailingAddress] = pydantic.Field(
2474
+ default=None,
2475
+ description="The mailing address of the organization.",
2476
+ )
2477
+
2478
+ url: Optional[pydantic.HttpUrl] = pydantic.Field(
2479
+ default=None, description="The organization's Web page or similar."
2480
+ )
2481
+
2482
+
2483
+ class Team(DyffEntity):
2484
+ """The members and affiliations of a team that has entered a challenge."""
2485
+
2486
+ kind: Literal["Team"] = Entities.Team.value
2487
+
2488
+ challenge: str = pydantic.Field(
2489
+ description="ID of the Challenge that this Team is participating in"
2490
+ )
2491
+ members: dict[str, TeamMember] = pydantic.Field(
2492
+ description="The members of this team"
2493
+ )
2494
+ affiliations: dict[str, TeamAffiliation] = pydantic.Field(
2495
+ description="The affiliations of the team. Team members state their"
2496
+ " affiliations by referencing these entries by their keys."
2497
+ )
2498
+
2499
+ def dependencies(self) -> list[str]:
2500
+ return []
2501
+
2502
+ def resource_allocation(self) -> Optional[ResourceAllocation]:
2503
+ return None
2504
+
2505
+
2506
+ class ChallengeTaskExecutionEnvironment(DyffSchemaBaseModel):
2507
+ """Description of an execution environment that is available to run challenge
2508
+ entries.
2509
+
2510
+ The specified computational resources are maximums; entries are free to request and
2511
+ use fewer resources.
2512
+ """
2513
+
2514
+ cpu: Quantity
2515
+ memory: Quantity
2516
+ accelerators: dict[str, int]
2517
+
2518
+
2519
+ class ChallengeTaskExecutionEnvironmentChoices(DyffSchemaBaseModel):
2520
+ """The execution environment(s) available to run challenge entries.
2521
+
2522
+ For an InferenceService to be a valid submission for a Challenge, there must be at
2523
+ least one execution environment defined for the challenge such that all of the
2524
+ resource requests of the service are less than or equal to the corresponding limits
2525
+ defined in the execution environment.
2526
+ """
2527
+
2528
+ choices: dict[str, ChallengeTaskExecutionEnvironment]
2529
+ default: str
2530
+
2531
+
2532
+ class ChallengeTaskSchedule(DyffSchemaBaseModel):
2533
+ """The schedule of a challenge task."""
2534
+
2535
+ openingTime: Optional[datetime] = pydantic.Field(
2536
+ default=None, description="The announced opening time for task submissions."
2537
+ )
2538
+ closingTime: Optional[datetime] = pydantic.Field(
2539
+ default=None, description="The announced closing time for task submissions."
2540
+ )
2541
+ submissionCycleDuration: timedelta = pydantic.Field(
2542
+ default=timedelta(days=1),
2543
+ description="The duration of a submission cycle."
2544
+ " Teams are limited to a maximum number of submissions per cycle.",
2545
+ )
2546
+ submissionCycleEpoch: datetime = pydantic.Field(
2547
+ default=datetime.fromtimestamp(0, timezone.utc),
2548
+ description="The epoch of a submission cycle."
2549
+ " For example, any given cycle lasts from"
2550
+ " [epoch + N*duration, epoch + (N+1)*duration)."
2551
+ " Teams are limited to a maximum number of submissions per cycle.",
2552
+ )
2553
+ submissionLimitPerCycle: int = pydantic.Field(
2554
+ default=1,
2555
+ ge=1,
2556
+ description="Teams are limited to this many submissions per cycle.",
2557
+ )
2558
+
2559
+
2560
+ class ChallengeTaskRules(DyffSchemaBaseModel):
2561
+ """The rules of the challenge."""
2562
+
2563
+ executionEnvironment: ChallengeTaskExecutionEnvironmentChoices = pydantic.Field(
2564
+ description="The available choices for the execution environment."
2565
+ )
2566
+ schedule: ChallengeTaskSchedule = pydantic.Field(
2567
+ description="The challenge schedule."
2568
+ )
2569
+
2570
+
2571
+ class ChallengeTaskContent(DyffSchemaBaseModel):
2572
+ """The content of a ChallengeTask UI view."."""
2573
+
2574
+ page: ChallengeContentPage = pydantic.Field(
2575
+ description="The content of the challenge task Web page."
2576
+ )
2577
+
2578
+
2579
+ class ChallengeTaskBase(DyffSchemaBaseModel):
2580
+ """The part of the ChallengeTask spec that is not system-populated."""
2581
+
2582
+ challenge: str = pydantic.Field(
2583
+ description="The ID of the Challenge of which this task is a member."
2584
+ )
2585
+ name: str = pydantic.Field(
2586
+ description="Unique name for the task in the context of the challenge."
2587
+ " This may appear in URLs and must follow naming restrictions.",
2588
+ max_length=title_maxlen(),
2589
+ pattern=r"[a-z0-9-]*",
2590
+ )
2591
+ assessment: str = pydantic.Field(
2592
+ description="ID of the Assessment that the task runs."
2593
+ )
2594
+ rules: ChallengeTaskRules = pydantic.Field(description="The rules for submissions.")
2595
+ content: ChallengeTaskContent = pydantic.Field(
2596
+ description="Content of the task view in the Dyff App."
2597
+ )
2598
+
2599
+
2600
+ class ChallengeTask(Status, ChallengeTaskBase, DyffModelWithID):
2601
+ """A task that is part of a challenge.
2602
+
2603
+ Teams make submissions to individual tasks, rather than the overall challenge. A
2604
+ task combines an assessment pipeline that actually implements the computations along
2605
+ with rules for submissions and descriptive content for the Web app.
2606
+ """
2607
+
2608
+ creationTime: datetime = pydantic.Field(
2609
+ description="Resource creation time (assigned by system)"
2610
+ )
2611
+
2612
+ lastTransitionTime: Optional[datetime] = pydantic.Field(
2613
+ default=None, description="Time of last (status, reason) change."
2614
+ )
2615
+
2616
+
2617
+ class ChallengeContent(DyffSchemaBaseModel):
2618
+ """The content of a Challenge UI view."."""
2619
+
2620
+ page: ChallengeContentPage = pydantic.Field(
2621
+ default_factory=ChallengeContentPage,
2622
+ description="The content of the challenge Web page.",
2623
+ )
2624
+ news: dict[str, ChallengeNewsItem] = pydantic.Field(
2625
+ default_factory=dict,
2626
+ description="News items to display in the challenge news feed.",
2627
+ )
2628
+
2629
+
2630
+ class Challenge(DyffEntity):
2631
+ """A Challenge is a collection of assessments on which participating teams compete
2632
+ to achieve the best performance."""
2633
+
2634
+ kind: Literal["Challenge"] = Entities.Challenge.value
2635
+
2636
+ content: ChallengeContent = pydantic.Field(
2637
+ description="Content of the challenge view in the Dyff App."
2638
+ )
2639
+
2640
+ tasks: dict[str, ChallengeTask] = pydantic.Field(
2641
+ default_factory=dict, description="The assessments that comprise the challenge."
2642
+ )
2643
+
2644
+ def dependencies(self) -> list[str]:
2645
+ return []
2646
+
2647
+ def resource_allocation(self) -> Optional[ResourceAllocation]:
2648
+ return None
2649
+
2650
+
2651
+ class ChallengeSubmission(DyffEntity):
2652
+ """A submission of an inference system to a challenge by a team.
2653
+
2654
+ All of the constituent resources must already exist. Creating a Submission simply
2655
+ indicates that the participating team wishes to submit the inference service as an
2656
+ official entry. Most challenges will limit the number of submissions that can be
2657
+ made in a given time interval.
2658
+ """
2659
+
2660
+ kind: Literal["Submission"] = Entities.Submission.value
2661
+
2662
+ challenge: str = pydantic.Field(
2663
+ description="The ID of the challenge being submitted to."
2664
+ )
2665
+ task: str = pydantic.Field(
2666
+ description="The key of the task within the challenge being submitted to."
2667
+ )
2668
+ team: str = pydantic.Field(description="The ID of the team making the submission.")
2669
+ inferenceService: str = pydantic.Field(
2670
+ description="The ID of the inference service being submitted."
2671
+ )
2672
+
2673
+ def dependencies(self) -> list[str]:
2674
+ return []
2675
+
2676
+ def resource_allocation(self) -> Optional[ResourceAllocation]:
2677
+ return None
2678
+
2679
+
2295
2680
  # ---------------------------------------------------------------------------
2296
2681
  # Status enumerations
2297
2682
 
@@ -2537,9 +2922,12 @@ _ENTITY_CLASS = {
2537
2922
  Entities.Artifact: OCIArtifact,
2538
2923
  Entities.Audit: Audit,
2539
2924
  Entities.AuditProcedure: AuditProcedure,
2925
+ Entities.Challenge: Challenge,
2540
2926
  Entities.Dataset: Dataset,
2541
2927
  Entities.DataSource: DataSource,
2542
2928
  Entities.Evaluation: Evaluation,
2929
+ Entities.Family: Family,
2930
+ Entities.Hazard: Hazard,
2543
2931
  Entities.InferenceService: InferenceService,
2544
2932
  Entities.InferenceSession: InferenceSession,
2545
2933
  Entities.Measurement: Measurement,
@@ -2548,6 +2936,8 @@ _ENTITY_CLASS = {
2548
2936
  Entities.Module: Module,
2549
2937
  Entities.Report: Report,
2550
2938
  Entities.SafetyCase: SafetyCase,
2939
+ Entities.Team: Team,
2940
+ Entities.UseCase: UseCase,
2551
2941
  }
2552
2942
 
2553
2943
 
@@ -2558,6 +2948,7 @@ def entity_class(kind: Entities):
2558
2948
  _DyffEntityTypeRevisable = Union[
2559
2949
  Audit,
2560
2950
  AuditProcedure,
2951
+ Challenge,
2561
2952
  DataSource,
2562
2953
  Dataset,
2563
2954
  Evaluation,
@@ -2572,6 +2963,7 @@ _DyffEntityTypeRevisable = Union[
2572
2963
  OCIArtifact,
2573
2964
  Report,
2574
2965
  SafetyCase,
2966
+ Team,
2575
2967
  UseCase,
2576
2968
  ]
2577
2969
 
@@ -2649,6 +3041,12 @@ __all__ = [
2649
3041
  "Audit",
2650
3042
  "AuditProcedure",
2651
3043
  "AuditRequirement",
3044
+ "Challenge",
3045
+ "ChallengeContent",
3046
+ "ChallengeContentPage",
3047
+ "ChallengeSubmission",
3048
+ "ChallengeTask",
3049
+ "ChallengeTaskBase",
2652
3050
  "Concern",
2653
3051
  "ConcernBase",
2654
3052
  "Container",
@@ -2769,6 +3167,9 @@ __all__ = [
2769
3167
  "TagName",
2770
3168
  "TagNameType",
2771
3169
  "TaskSchema",
3170
+ "Team",
3171
+ "TeamMember",
3172
+ "TeamAffiliation",
2772
3173
  "UseCase",
2773
3174
  "Volume",
2774
3175
  "VolumeMount",
@@ -2791,6 +3192,7 @@ __all__ = [
2791
3192
  "ModelStatusReason",
2792
3193
  "ReportStatus",
2793
3194
  "ReportStatusReason",
3195
+ "body_maxlen",
2794
3196
  "identifier_regex",
2795
3197
  "identifier_maxlen",
2796
3198
  "is_status_terminal",
@@ -26,6 +26,8 @@ from .base import DyffBaseModel, JsonMergePatchSemantics
26
26
  from .platform import (
27
27
  AnalysisBase,
28
28
  AnalysisScope,
29
+ ChallengeContent,
30
+ ChallengeTaskBase,
29
31
  ConcernBase,
30
32
  DatasetBase,
31
33
  DataView,
@@ -60,12 +62,14 @@ class DyffRequestDefaultValidators(DyffBaseModel):
60
62
  """
61
63
 
62
64
  @pydantic.model_validator(mode="after")
63
- def _require_datetime_timezone_aware(cls, values):
64
- for k, v in values.__dict__.items():
65
+ def _require_datetime_timezone_aware(self):
66
+ for k, v in self.__dict__.items():
65
67
  if isinstance(v, datetime):
66
68
  if v.tzinfo is None:
67
- raise ValueError(f"{cls.__qualname__}.{k}: timezone not set")
68
- return values
69
+ raise ValueError(
70
+ f"{self.__class__.__qualname__}.{k}: timezone not set"
71
+ )
72
+ return self
69
73
 
70
74
 
71
75
  class DyffRequestBase(SchemaVersion, DyffRequestDefaultValidators):
@@ -136,6 +140,17 @@ class ArtifactCreateRequest(DyffEntityCreateRequest):
136
140
  pass
137
141
 
138
142
 
143
+ class ChallengeCreateRequest(DyffEntityCreateRequest):
144
+ content: ChallengeContent = pydantic.Field(
145
+ default_factory=ChallengeContent,
146
+ description="Content of the challenge view in the Dyff App.",
147
+ )
148
+
149
+
150
+ class ChallengeTaskCreateRequest(DyffEntityCreateRequest, ChallengeTaskBase):
151
+ pass
152
+
153
+
139
154
  class ConcernCreateRequest(DyffEntityCreateRequest, ConcernBase):
140
155
  @pydantic.field_validator("documentation", check_fields=False)
141
156
  def _validate_documentation(
@@ -171,14 +186,14 @@ class InferenceServiceCreateRequest(DyffEntityCreateRequest, InferenceServiceBas
171
186
  )
172
187
 
173
188
  @pydantic.model_validator(mode="after")
174
- def check_runner_and_image_specified(cls, values):
175
- if values.runner is None:
189
+ def check_runner_and_image_specified(self):
190
+ if self.runner is None:
176
191
  raise ValueError("must specify .runner in new inference services")
177
- image = values.runner.image is not None
178
- imageRef = values.runner.imageRef is not None
192
+ image = self.runner.image is not None
193
+ imageRef = self.runner.imageRef is not None
179
194
  if sum([image, imageRef]) != 1:
180
195
  raise ValueError("must specify exactly one of .runner.{image, imageRef}")
181
- return values
196
+ return self
182
197
 
183
198
 
184
199
  class InferenceSessionCreateRequest(DyffEntityCreateRequest, InferenceSessionBase):
@@ -213,14 +228,14 @@ class EvaluationCreateRequest(DyffEntityCreateRequest, EvaluationBase):
213
228
  )
214
229
 
215
230
  @pydantic.model_validator(mode="after")
216
- def check_session_exactly_one(cls, values):
217
- session = values.inferenceSession is not None
218
- session_ref = values.inferenceSessionReference is not None
231
+ def check_session_exactly_one(self):
232
+ session = self.inferenceSession is not None
233
+ session_ref = self.inferenceSessionReference is not None
219
234
  if not (session ^ session_ref):
220
235
  raise ValueError(
221
236
  "must specify exactly one of {inferenceSession, inferenceSessionReference}"
222
237
  )
223
- return values
238
+ return self
224
239
 
225
240
  @staticmethod
226
241
  def repeat_of(evaluation: Evaluation) -> EvaluationCreateRequest:
@@ -286,6 +301,10 @@ class ReportCreateRequest(DyffEntityCreateRequest, ReportBase):
286
301
  # ----------------------------------------------------------------------------
287
302
 
288
303
 
304
+ class ChallengeContentEditRequest(DyffRequestBase, commands.EditChallengeContentPatch):
305
+ pass
306
+
307
+
289
308
  class DocumentationEditRequest(
290
309
  DyffRequestBase, commands.EditEntityDocumentationAttributes
291
310
  ):
@@ -471,6 +490,9 @@ __all__ = [
471
490
  "ArtifactCreateRequest",
472
491
  "ArtifactQueryRequest",
473
492
  "AuditQueryRequest",
493
+ "ChallengeContentEditRequest",
494
+ "ChallengeCreateRequest",
495
+ "ChallengeTaskCreateRequest",
474
496
  "ConcernCreateRequest",
475
497
  "DyffEntityCreateRequest",
476
498
  "DyffEntityQueryRequest",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dyff-schema
3
- Version: 0.35.4
3
+ Version: 0.36.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
@@ -21,6 +21,8 @@ Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  License-File: NOTICE
23
23
  Requires-Dist: canonicaljson==2.0.0
24
+ Requires-Dist: email-validator
25
+ Requires-Dist: google-i18n-address
24
26
  Requires-Dist: hypothesis
25
27
  Requires-Dist: hypothesis-jsonschema
26
28
  Requires-Dist: jsonpath-ng
@@ -1,5 +1,5 @@
1
1
  dyff/schema/__init__.py,sha256=w7OWDFuyGKd6xt_yllNtKzHahPgywrfU4Ue02psYaMA,2244
2
- dyff/schema/_version.py,sha256=ex3qIUAM413mFfnfEYAcT0gprZTh1AbIC1kQnwFNARc,80
2
+ dyff/schema/_version.py,sha256=WugzP-ohXoqseZsih5K8n5URp30AcEarRZlsq9obhyE,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
@@ -27,10 +27,10 @@ dyff/schema/v0/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
27
27
  dyff/schema/v0/r1/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
28
28
  dyff/schema/v0/r1/adapters.py,sha256=hpwCSW8lkMkUKCLe0zaMUDu-VS_caSxJvPsECEi_XRA,33069
29
29
  dyff/schema/v0/r1/base.py,sha256=1VJXVLKldOq3aH2HdVgxXXk4DJTZEIiaTa4GzMKzziU,20228
30
- dyff/schema/v0/r1/commands.py,sha256=VoThKeIVz4ZQAqBXhb6TVOG6aG4m_Oa0_6Sc4oxyFhs,9801
31
- dyff/schema/v0/r1/oci.py,sha256=Jnnu2EBeYQKC-EbKLG60a5z3WwpOuZMogeJ7vQA74bM,6761
32
- dyff/schema/v0/r1/platform.py,sha256=MpgQ3_ookO8R2eJ6yXPXcZ7cFl82_YqV2LCrdlBoMAY,88563
33
- dyff/schema/v0/r1/requests.py,sha256=y1S0UDxXHUpM1fpFjVEUVXfiq6pnpIKBRypYNJFJsA0,18011
30
+ dyff/schema/v0/r1/commands.py,sha256=mFKuPtRrk3Zr7Ce2snmqIG_vGIPCv-RTiXDo6BLILTU,17485
31
+ dyff/schema/v0/r1/oci.py,sha256=YjHDVBJ2IIxqijll70OK6pM-qT6pq8tvU7D3YB9vGM0,6700
32
+ dyff/schema/v0/r1/platform.py,sha256=UVDc4loKNQIw0J6PD_aDSGhxQLdorwz8lXwikhf-ORo,102071
33
+ dyff/schema/v0/r1/requests.py,sha256=v9t8wg6ZYGCPp05HFtOnw093nCD9ED_lLOK__4Z2d4s,18584
34
34
  dyff/schema/v0/r1/responses.py,sha256=nxy7FPtfw2B_bljz5UGGuSE79HTkDQxKH56AJVmd4Qo,1287
35
35
  dyff/schema/v0/r1/test.py,sha256=X6dUyVd5svcPCI-PBMOAqEfK9jv3bRDvkQTJzwS96c0,10720
36
36
  dyff/schema/v0/r1/version.py,sha256=NONebgcv5Thsw_ymud6PacZdGjV6ndBrmLnap-obcpo,428
@@ -43,9 +43,9 @@ dyff/schema/v0/r1/dataset/text.py,sha256=MYG5seGODDryRSCy-g0Unh5dD0HCytmZ3FeElC-
43
43
  dyff/schema/v0/r1/dataset/vision.py,sha256=tJFF4dkhHX0UXTj1sPW-G22xTSI40gbYO465FuvmvAU,443
44
44
  dyff/schema/v0/r1/io/__init__.py,sha256=L5y8UhRnojerPYHumsxQJRcHCNz8Hj9NM8b47mewMNs,92
45
45
  dyff/schema/v0/r1/io/vllm.py,sha256=vWyLg-susbg0JDfv6VExBpgFdU2GHP2a14ChOdbckvs,5321
46
- dyff_schema-0.35.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
- dyff_schema-0.35.4.dist-info/licenses/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
48
- dyff_schema-0.35.4.dist-info/METADATA,sha256=XzVBMfd4vbMGUC85Sim9XojnK8B17kQQfvvljXcrwQE,3668
49
- dyff_schema-0.35.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
- dyff_schema-0.35.4.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
51
- dyff_schema-0.35.4.dist-info/RECORD,,
46
+ dyff_schema-0.36.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ dyff_schema-0.36.0.dist-info/licenses/NOTICE,sha256=YONACu0s_Ui6jNi-wtEsVQbTU1JIkh8wvLH6d1-Ni_w,43
48
+ dyff_schema-0.36.0.dist-info/METADATA,sha256=kinDhGaP-46BDtS4QXZ6xFLgsCLhLjy66JnqDa3zdfk,3734
49
+ dyff_schema-0.36.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
50
+ dyff_schema-0.36.0.dist-info/top_level.txt,sha256=9e3VVdeX73t_sUJOPQPCcGtYO1JhoErhHIi3WoWGcFI,5
51
+ dyff_schema-0.36.0.dist-info/RECORD,,