jsonstat-validator 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -177,6 +177,15 @@ class Category(JSONStatBaseModel):
177
177
  if not self.index and not self.label:
178
178
  raise ValueError("At least one of `index` or `label` is required.")
179
179
 
180
+ # Ensure index and label have the same keys if both are dictionaries
181
+ if self.index and self.label:
182
+ if isinstance(self.index, dict) and isinstance(self.label, dict):
183
+ if set(self.index.keys()) != set(self.label.keys()):
184
+ raise ValueError(
185
+ "If `index` and `label` are both dictionaries, "
186
+ "they must have the same keys."
187
+ )
188
+
180
189
  # Ensure coordinates are a dictionary where keys are category IDs
181
190
  # and values are an array of two numbers (longitude, latitude).
182
191
  if self.coordinates:
@@ -300,7 +309,7 @@ class Dimension(JSONStatBaseModel):
300
309
  default=None,
301
310
  description=(
302
311
  "It is used to provide a list of links related to a dataset or a dimension, "
303
- "sorted by relation (see relation ID)."
312
+ "sorted by relation (see https://json-stat.org/full/#relationid)."
304
313
  ),
305
314
  )
306
315
  note: Optional[List[str]] = Field(
@@ -344,6 +353,113 @@ class Dimension(JSONStatBaseModel):
344
353
  return v
345
354
 
346
355
 
356
+ class DatasetDimension(JSONStatBaseModel):
357
+ """Dataset dimension.
358
+
359
+ A dimension model for when the dimension is a child of a Dataset
360
+ as it has different validation rules than a root Dimension.
361
+ """
362
+
363
+ version: Optional[str] = Field(
364
+ default=None,
365
+ description=(
366
+ "It declares the JSON-stat version of the response. The goal "
367
+ "of this property is to help clients parsing that particular response."
368
+ ),
369
+ )
370
+ class_: Optional[str] = Field(
371
+ default="dataset_dimension",
372
+ alias="class",
373
+ description=(
374
+ "JSON-stat supports several classes of responses. "
375
+ "Possible values of class are: dataset, dimension and collection. "
376
+ "This is an addition to the standard JSON-stat classes to allow for "
377
+ "different validation rules for dataset dimensions."
378
+ ),
379
+ exclude=True,
380
+ init=False,
381
+ frozen=True,
382
+ )
383
+ label: Optional[str] = Field(
384
+ default=None,
385
+ description=(
386
+ "It is used to assign a very short (one line) descriptive text to IDs "
387
+ "at different levels of the response tree. It is language-dependent."
388
+ ),
389
+ )
390
+ category: Optional[Category] = Field(
391
+ default=None,
392
+ description=(
393
+ "It is used to describe the possible values of a dimension. "
394
+ "It is language-dependent."
395
+ ),
396
+ )
397
+ href: Optional[AnyUrl] = Field(
398
+ default=None,
399
+ description=(
400
+ "It specifies a URL. Providers can use this property to avoid "
401
+ "sending information that is shared between different requests "
402
+ "(for example, dimensions)."
403
+ ),
404
+ )
405
+ link: Optional[Dict[str, List[Union[Link, JSONStatSchema]]]] = Field(
406
+ default=None,
407
+ description=(
408
+ "It is used to provide a list of links related to a dataset or a dimension, "
409
+ "sorted by relation (see https://json-stat.org/full/#relationid)."
410
+ ),
411
+ )
412
+ note: Optional[List[str]] = Field(
413
+ default=None,
414
+ description=(
415
+ "Note allows to assign annotations to datasets (array), dimensions (array) "
416
+ "and categories (object)."
417
+ ),
418
+ )
419
+ updated: Optional[str] = Field(
420
+ default=None,
421
+ description=(
422
+ "It contains the update time of the dataset. It is a string representing "
423
+ "a date in an ISO 8601 format recognized by the Javascript Date.parse "
424
+ "method (see ECMA-262 Date Time String Format: "
425
+ "https://262.ecma-international.org/6.0/#sec-date-time-string-format)."
426
+ ),
427
+ )
428
+ source: Optional[str] = Field(
429
+ default=None,
430
+ description=(
431
+ "It contains a language-dependent short text describing the source "
432
+ "of the dataset."
433
+ ),
434
+ )
435
+ extension: Optional[Dict[str, Any]] = Field(
436
+ default=None,
437
+ description=(
438
+ "Extension allows JSON-stat to be extended for particular needs. "
439
+ "Providers are free to define where they include this property and "
440
+ "what children are allowed in each case."
441
+ ),
442
+ )
443
+
444
+ @field_validator("updated", mode="after")
445
+ @classmethod
446
+ def validate_updated_date(cls, v: Optional[str]):
447
+ """Validates the updated date is in ISO 8601 format."""
448
+ if v and not is_valid_iso_date(v):
449
+ raise ValueError(f"Updated date: '{v}' is an invalid ISO 8601 format.")
450
+ return v
451
+
452
+ @model_validator(mode="after")
453
+ def validate_dataset_dimension(self):
454
+ """Dataset dimension-wide validation checks."""
455
+ if not self.category and not self.href:
456
+ raise ValueError(
457
+ "A category is required if a reference (href) is not provided."
458
+ "For an example, see: https://json-stat.org/full/#href"
459
+ )
460
+ return self
461
+
462
+
347
463
  class DatasetRole(JSONStatBaseModel):
348
464
  """Role of a dataset."""
349
465
 
@@ -372,6 +488,13 @@ class DatasetRole(JSONStatBaseModel):
372
488
  ),
373
489
  )
374
490
 
491
+ @model_validator(mode="after")
492
+ def validate_dataset_role(self):
493
+ """Dataset role-wide validation checks."""
494
+ if not self.time and not self.geo and not self.metric:
495
+ raise ValueError("At least one role must be provided.")
496
+ return self
497
+
375
498
 
376
499
  class Dataset(JSONStatBaseModel):
377
500
  """JSON-stat dataset."""
@@ -430,12 +553,13 @@ class Dataset(JSONStatBaseModel):
430
553
  "and in the same order as in id."
431
554
  ),
432
555
  )
433
- role: Optional[DatasetRole] = Field(
556
+ role: DatasetRole = Field(
434
557
  default=None,
435
558
  description=(
436
559
  "It can be used to assign special roles to dimensions. "
437
560
  "At this moment, possible roles are: time, geo and metric. "
438
561
  "A role can be shared by several dimensions."
562
+ "We differ from the specification in that the role is required, not optional"
439
563
  ),
440
564
  )
441
565
  value: Union[
@@ -457,10 +581,11 @@ class Dataset(JSONStatBaseModel):
457
581
  ),
458
582
  )
459
583
 
460
- dimension: Dict[str, Dimension] = Field(
584
+ dimension: Dict[str, DatasetDimension] = Field(
461
585
  description=(
462
586
  "The dimension property contains information about the dimensions of "
463
- "the dataset. dimension must have properties (see dimension ID) with "
587
+ "the dataset. dimension must have properties "
588
+ "(see https://json-stat.org/full/#dimensionid) with "
464
589
  "the same names of each element in the id array."
465
590
  ),
466
591
  )
@@ -483,7 +608,7 @@ class Dataset(JSONStatBaseModel):
483
608
  default=None,
484
609
  description=(
485
610
  "It is used to provide a list of links related to a dataset or a dimension, "
486
- "sorted by relation (see relation ID)."
611
+ "sorted by relation (see https://json-stat.org/full/#relationid)."
487
612
  ),
488
613
  )
489
614
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jsonstat-validator
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: A Python validator for the JSON-stat 2.0 standard format, based on Pydantic.
5
5
  Author-email: Ahmed Hassan <ahmedhassan.ahmed@fao.org>
6
6
  License: # MIT License
@@ -76,6 +76,8 @@ Please note that this implementation is intentionally more strict than the offic
76
76
 
77
77
  This dataset would be considered valid by the official JSON-stat validator tool, but will fail validation in this package because it violates the rule in the `dataset.size` section of the specification stating that: `size has the same number of elements and in the same order as in id`.
78
78
 
79
+ Additionally, we enforce the `role` field as required when class=`dataset`.
80
+
79
81
  ## Table of Contents
80
82
 
81
83
  - [Installation](#installation)
@@ -0,0 +1,7 @@
1
+ jsonstat_validator/__init__.py,sha256=hvsaEgQFH8vY_bPRDyrhroxjJd1c5nFjmZQ_lxzH0AQ,806
2
+ jsonstat_validator/validator.py,sha256=_m3Q4VjnrCM11xg3LkC4A_rDle2rHnMsIk4SAv-dL4A,28121
3
+ jsonstat_validator-0.1.3.dist-info/LICENSE,sha256=cyCvx3tHW8u9ZWGkTMrCKaB2ft4RonlMBVOEHDa2tEk,1091
4
+ jsonstat_validator-0.1.3.dist-info/METADATA,sha256=tutn48KR1gZeDLpwagNii8TcYpUA5gQyVjzQXcDSmco,7336
5
+ jsonstat_validator-0.1.3.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
6
+ jsonstat_validator-0.1.3.dist-info/top_level.txt,sha256=l6xoMFlWGRRbXMxl4MG-q0FA3In7rYZLBzVF8W7IhdU,19
7
+ jsonstat_validator-0.1.3.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- jsonstat_validator/__init__.py,sha256=hvsaEgQFH8vY_bPRDyrhroxjJd1c5nFjmZQ_lxzH0AQ,806
2
- jsonstat_validator/validator.py,sha256=X9UmAp1R_h1xisAHdls_wh4l99z5Uqk23P008ZPrbM0,23284
3
- jsonstat_validator-0.1.1.dist-info/LICENSE,sha256=cyCvx3tHW8u9ZWGkTMrCKaB2ft4RonlMBVOEHDa2tEk,1091
4
- jsonstat_validator-0.1.1.dist-info/METADATA,sha256=8Zz-W22W3NRA2opmKUvlxViByUCSvfwIiCk5j7-5AWE,7259
5
- jsonstat_validator-0.1.1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
6
- jsonstat_validator-0.1.1.dist-info/top_level.txt,sha256=l6xoMFlWGRRbXMxl4MG-q0FA3In7rYZLBzVF8W7IhdU,19
7
- jsonstat_validator-0.1.1.dist-info/RECORD,,