Python API

Construct or deserialize records with OGCRecord explicitly. Common metadata accessors are also available through record.record_metadata.

pystac.extensions.ogc_record

OGC API Records adapter for PySTAC 1.x.x; see README for boundaries.

OGCRecord

Bases: RecordMetadataMixin, Item

Item-compatible Record, with OGC serialization as the default.

Initializes STACObject directly to avoid Item's mandatory temporal extent. This compatibility seam must be reviewed for future PySTAC releases. time and STAC datetime fields are independent: no implicit conversion.

Source code in pystac/extensions/ogc_record.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
class OGCRecord(RecordMetadataMixin, pystac.Item):
    """Item-compatible Record, with OGC serialization as the default.

    Initializes STACObject directly to avoid Item's mandatory temporal extent.
    This compatibility seam must be reviewed for future PySTAC releases.
    `time` and STAC datetime fields are independent: no implicit conversion.
    """

    SCHEMA_URI = (
        "https://schemas.opengis.net/ogcapi/records/part1/1.0/openapi/schemas/recordGeoJSON.yaml"
    )
    _RESERVED = frozenset(
        {
            "id",
            "type",
            "geometry",
            "properties",
            "links",
            "assets",
            "bbox",
            "collection",
            "stac_extensions",
        }
    )

    # Preserve the positional PySTAC Item constructor API and OGC keyword options.
    def __init__(  # noqa: PLR0917
        self,
        id: str | int,
        geometry: dict[str, Any] | None = None,
        bbox: list[float] | None = None,
        datetime: Datetime | None = None,
        properties: dict[str, Any] | None = None,
        start_datetime: Datetime | None = None,
        end_datetime: Datetime | None = None,
        stac_extensions: list[str] | None = None,
        href: str | None = None,
        collection: str | pystac.Collection | None = None,
        extra_fields: dict[str, Any] | None = None,
        assets: dict[str, pystac.Asset] | None = None,
        *,
        time: dict[str, Any] | None = None,
        conforms_to: list[str] | None = None,
        link_templates: list[dict[str, Any]] | None = None,
    ) -> None:
        """Initialize a record, copying metadata and assigning asset ownership.

        OGC ``time`` is independent of the STAC datetime arguments. Optional
        OGC fields override matching entries in ``extra_fields`` when supplied.
        STAC timestamps from arguments or properties are normalized to UTC whole
        seconds; naive datetimes are treated as UTC and fractions are truncated.

        Raises:
            TypeError: If the identifier or a STAC timestamp has an invalid type.
            ValueError: If extra fields override managed record fields or a STAC
                timestamp string is invalid.
        """
        if isinstance(id, bool) or not isinstance(id, (str, int)):
            raise TypeError("Record id must be a string or integer")
        pystac.STACObject.__init__(self, list(stac_extensions or []))
        self._record_id = id
        self.id = str(id)  # PySTAC path/layout utilities require a string.
        self.geometry = deepcopy(geometry)
        self.bbox = deepcopy(bbox)
        self._null_properties = properties is None
        self.properties = deepcopy(properties) if properties is not None else {}
        self.extra_fields = deepcopy(extra_fields) if extra_fields else {}
        if self._RESERVED.intersection(self.extra_fields):
            raise ValueError("extra_fields must not override managed fields")
        self.assets = {}
        self.collection_id = None
        self._stac_io = None
        self._set_temporal_properties(datetime, start_datetime, end_datetime)
        for field_name, field_value in (
            ("time", time),
            ("conformsTo", conforms_to),
            ("linkTemplates", link_templates),
        ):
            if field_value is not None:
                self.extra_fields[field_name] = deepcopy(field_value)
        self._attach_stac_objects(collection, assets, href)

    def _attach_stac_objects(
        self,
        collection: str | pystac.Collection | None,
        assets: dict[str, pystac.Asset] | None,
        href: str | None,
    ) -> None:
        """Set collection links, asset ownership, and the record's location."""
        if isinstance(collection, pystac.Collection):
            self.set_collection(collection)
        elif collection is not None:
            self.collection_id = collection
        for key, asset in (assets or {}).items():
            self.add_asset(key, asset)
        if href is not None:
            self.set_self_href(href)

    def _set_temporal_properties(
        self,
        instant: Datetime | None,
        start: Datetime | None,
        end: Datetime | None,
    ) -> None:
        """Apply explicit STAC dates and normalize property fallbacks to UTC seconds.

        Non-null constructor arguments override the corresponding properties.
        """
        for field_name, value in (
            ("datetime", instant),
            ("start_datetime", start),
            ("end_datetime", end),
        ):
            if value is not None:
                self.properties[field_name] = value
        _normalize_temporal_properties(self.properties)
        stored_instant = self.properties.get("datetime")
        self.datetime = None if stored_instant is None else _metadata_datetime(stored_instant)

    @property
    def record_id(self) -> str | int:
        return self._record_id if str(self._record_id) == self.id else self.id

    @property
    def record_metadata(self) -> RecordCommonProperties:
        return RecordCommonProperties(self)

    @property
    def time(self) -> dict[str, Any] | None:
        return self.extra_fields.get("time")

    @time.setter
    def time(self, value: dict[str, Any] | None) -> None:
        self.extra_fields["time"] = value

    @property
    def conforms_to(self) -> list[str]:
        """The live list of conformance URIs.

        Raises:
            TypeError: If the stored value is not a list of strings.
        """
        conformance = self.extra_fields.setdefault("conformsTo", [])
        if not isinstance(conformance, list) or not all(
            isinstance(uri, str) for uri in conformance
        ):
            raise TypeError("conformsTo must be a list of strings")
        return conformance

    @conforms_to.setter
    def conforms_to(self, value: list[str]) -> None:
        self.extra_fields["conformsTo"] = value

    @property
    def link_templates(self) -> list[dict[str, Any]]:
        """The live list of link template dictionaries.

        Raises:
            TypeError: If the stored value is not a list of dictionaries.
        """
        templates = self.extra_fields.setdefault("linkTemplates", [])
        if not isinstance(templates, list) or not all(
            isinstance(template, dict) for template in templates
        ):
            raise TypeError("linkTemplates must be a list of dictionaries")
        return templates

    @link_templates.setter
    def link_templates(self, value: list[dict[str, Any]]) -> None:
        self.extra_fields["linkTemplates"] = value

    def to_dict(
        self, include_self_link: bool = True, transform_hrefs: bool = True
    ) -> dict[str, Any]:
        """Serialize a record with metadata and STAC timestamps in UTC whole seconds.

        Normalization includes nested metadata and does not mutate the record.

        Raises:
            ValueError: If a metadata or STAC timestamp string is invalid.
            TypeError: If a non-null timestamp is neither a datetime nor a string.
        """
        props = deepcopy(self.properties)
        if self.datetime is not None:
            props["datetime"] = self.datetime
        _normalize_temporal_properties(props)
        doc = deepcopy(self.extra_fields)
        doc.update(
            type="Feature",
            id=self.record_id,
            geometry=deepcopy(self.geometry),
            properties=None if self._null_properties and not props else props,
            links=[
                deepcopy(link.to_dict(transform_href=transform_hrefs))
                for link in self.links
                if include_self_link or link.rel != pystac.RelType.SELF
            ],
        )
        if self.bbox is not None:
            doc["bbox"] = list(self.bbox)
        # Preserve extension declarations/assets as GeoJSON foreign members.
        # Never substitute conformsTo for stac_extensions.
        if self.stac_extensions:
            doc["stac_extensions"] = list(self.stac_extensions)
        if self.assets:
            doc["assets"] = {key: deepcopy(asset.to_dict()) for key, asset in self.assets.items()}
        if self.collection_id is not None:
            doc["collection"] = self.collection_id
        _normalize_metadata_timestamps(doc)
        return doc

    def to_record_dict(
        self, include_self_link: bool = True, transform_hrefs: bool = True
    ) -> dict[str, Any]:
        return self.to_dict(include_self_link, transform_hrefs)

    @classmethod
    def matches_object_type(cls, d: dict[str, Any]) -> bool:
        # Structural recognition only; GeoJSON/STAC overlap prevents unique dispatch.
        return (
            d.get("type") == "Feature"
            and all(key in d for key in ("id", "geometry", "properties"))
            and isinstance(d["id"], (str, int))
            and not isinstance(d["id"], bool)
            and (d["properties"] is None or isinstance(d["properties"], dict))
        )

    @classmethod
    def from_dict(
        cls,
        d: dict[str, Any],
        href: str | None = None,
        root: pystac.Catalog | None = None,
        migrate: bool = True,
        preserve_dict: bool = True,
    ) -> OGCRecord:
        """Read Records; migrate is accepted for compatibility but never applied.

        Always copies document metadata, even when preserve_dict=False.
        """
        if not cls.matches_object_type(d):
            raise ValueError("Expected a Record GeoJSON Feature with id, geometry, properties")
        obj = cls(
            id=d["id"],
            geometry=d["geometry"],
            properties=d["properties"],
            bbox=d.get("bbox"),
            collection=d.get("collection"),
            stac_extensions=d.get("stac_extensions"),
            extra_fields={k: v for k, v in d.items() if k not in cls._RESERVED},
            assets={k: pystac.Asset.from_dict(deepcopy(v)) for k, v in d.get("assets", {}).items()},
        )
        for link in d.get("links", []):
            if href is None or link.get("rel") != "self":
                obj.add_link(pystac.Link.from_dict(deepcopy(link)))
        if href is not None:
            obj.set_self_href(href)
        if root is not None:
            obj.set_root(root)
        return obj

    def clone(self) -> OGCRecord:
        doc = self.to_dict(transform_hrefs=False)
        doc["links"] = []
        result = type(self).from_dict(doc)
        for link in self.links:
            cloned_link = link.clone()
            cloned_link.extra_fields = deepcopy(link.extra_fields)
            result.add_link(cloned_link)
        result._stac_io = self._stac_io
        return result

    def to_stac_item(self) -> pystac.Item:
        """Explicit export; requires a genuine STAC temporal extent.

        Metadata and STAC timestamps are normalized to UTC whole seconds.
        Constructing an Item does not replace full STAC schema validation.
        """
        if self.datetime is None and not all(
            self.properties.get(k) is not None for k in ("start_datetime", "end_datetime")
        ):
            raise ValueError("STAC export requires datetime or start_datetime/end_datetime")
        extra = deepcopy(self.extra_fields)
        extra.pop("stac_version", None)
        item = pystac.Item(
            id=self.id,
            geometry=deepcopy(self.geometry),
            bbox=deepcopy(self.bbox),
            datetime=None if self.datetime is None else _metadata_datetime(self.datetime),
            properties=deepcopy(self.properties),
            stac_extensions=list(self.stac_extensions),
            collection=self.collection_id,
            extra_fields=extra,
            assets={k: v.clone() for k, v in self.assets.items()},
        )
        for link in self.links:
            cloned_link = link.clone()
            cloned_link.extra_fields = deepcopy(link.extra_fields)
            item.add_link(cloned_link)
        _normalize_temporal_properties(item.properties)
        _normalize_metadata_timestamps(item.properties)
        _normalize_metadata_timestamps(item.extra_fields)
        for asset in item.assets.values():
            _normalize_metadata_timestamps(asset.extra_fields)
        for link in item.links:
            _normalize_metadata_timestamps(link.extra_fields)
        return item

    def validate(self, validator: Any = None) -> list[Any]:
        """Validate the OGC document with an explicitly supplied validator.

        Supply an object exposing validate(document), configured for the OGC
        OpenAPI 3.0 schema and its references. No implicit STAC validation.
        """
        if validator is None:
            raise ValueError(
                "Supply an OGC schema validator; for STAC use record.to_stac_item().validate()"
            )
        validator.validate(self.to_dict(transform_hrefs=False))
        return [self.SCHEMA_URI]

    def __repr__(self) -> str:
        return f"<OGCRecord id={self.record_id!r}>"

conforms_to: list[str] property writable

The live list of conformance URIs.

Raises:
  • TypeError –

    If the stored value is not a list of strings.

The live list of link template dictionaries.

Raises:
  • TypeError –

    If the stored value is not a list of dictionaries.

__init__(id: str | int, geometry: dict[str, Any] | None = None, bbox: list[float] | None = None, datetime: Datetime | None = None, properties: dict[str, Any] | None = None, start_datetime: Datetime | None = None, end_datetime: Datetime | None = None, stac_extensions: list[str] | None = None, href: str | None = None, collection: str | pystac.Collection | None = None, extra_fields: dict[str, Any] | None = None, assets: dict[str, pystac.Asset] | None = None, *, time: dict[str, Any] | None = None, conforms_to: list[str] | None = None, link_templates: list[dict[str, Any]] | None = None) -> None

Initialize a record, copying metadata and assigning asset ownership.

OGC time is independent of the STAC datetime arguments. Optional OGC fields override matching entries in extra_fields when supplied. STAC timestamps from arguments or properties are normalized to UTC whole seconds; naive datetimes are treated as UTC and fractions are truncated.

Raises:
  • TypeError –

    If the identifier or a STAC timestamp has an invalid type.

  • ValueError –

    If extra fields override managed record fields or a STAC timestamp string is invalid.

Source code in pystac/extensions/ogc_record.py
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def __init__(  # noqa: PLR0917
    self,
    id: str | int,
    geometry: dict[str, Any] | None = None,
    bbox: list[float] | None = None,
    datetime: Datetime | None = None,
    properties: dict[str, Any] | None = None,
    start_datetime: Datetime | None = None,
    end_datetime: Datetime | None = None,
    stac_extensions: list[str] | None = None,
    href: str | None = None,
    collection: str | pystac.Collection | None = None,
    extra_fields: dict[str, Any] | None = None,
    assets: dict[str, pystac.Asset] | None = None,
    *,
    time: dict[str, Any] | None = None,
    conforms_to: list[str] | None = None,
    link_templates: list[dict[str, Any]] | None = None,
) -> None:
    """Initialize a record, copying metadata and assigning asset ownership.

    OGC ``time`` is independent of the STAC datetime arguments. Optional
    OGC fields override matching entries in ``extra_fields`` when supplied.
    STAC timestamps from arguments or properties are normalized to UTC whole
    seconds; naive datetimes are treated as UTC and fractions are truncated.

    Raises:
        TypeError: If the identifier or a STAC timestamp has an invalid type.
        ValueError: If extra fields override managed record fields or a STAC
            timestamp string is invalid.
    """
    if isinstance(id, bool) or not isinstance(id, (str, int)):
        raise TypeError("Record id must be a string or integer")
    pystac.STACObject.__init__(self, list(stac_extensions or []))
    self._record_id = id
    self.id = str(id)  # PySTAC path/layout utilities require a string.
    self.geometry = deepcopy(geometry)
    self.bbox = deepcopy(bbox)
    self._null_properties = properties is None
    self.properties = deepcopy(properties) if properties is not None else {}
    self.extra_fields = deepcopy(extra_fields) if extra_fields else {}
    if self._RESERVED.intersection(self.extra_fields):
        raise ValueError("extra_fields must not override managed fields")
    self.assets = {}
    self.collection_id = None
    self._stac_io = None
    self._set_temporal_properties(datetime, start_datetime, end_datetime)
    for field_name, field_value in (
        ("time", time),
        ("conformsTo", conforms_to),
        ("linkTemplates", link_templates),
    ):
        if field_value is not None:
            self.extra_fields[field_name] = deepcopy(field_value)
    self._attach_stac_objects(collection, assets, href)

from_dict(d: dict[str, Any], href: str | None = None, root: pystac.Catalog | None = None, migrate: bool = True, preserve_dict: bool = True) -> OGCRecord classmethod

Read Records; migrate is accepted for compatibility but never applied.

Always copies document metadata, even when preserve_dict=False.

Source code in pystac/extensions/ogc_record.py
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
@classmethod
def from_dict(
    cls,
    d: dict[str, Any],
    href: str | None = None,
    root: pystac.Catalog | None = None,
    migrate: bool = True,
    preserve_dict: bool = True,
) -> OGCRecord:
    """Read Records; migrate is accepted for compatibility but never applied.

    Always copies document metadata, even when preserve_dict=False.
    """
    if not cls.matches_object_type(d):
        raise ValueError("Expected a Record GeoJSON Feature with id, geometry, properties")
    obj = cls(
        id=d["id"],
        geometry=d["geometry"],
        properties=d["properties"],
        bbox=d.get("bbox"),
        collection=d.get("collection"),
        stac_extensions=d.get("stac_extensions"),
        extra_fields={k: v for k, v in d.items() if k not in cls._RESERVED},
        assets={k: pystac.Asset.from_dict(deepcopy(v)) for k, v in d.get("assets", {}).items()},
    )
    for link in d.get("links", []):
        if href is None or link.get("rel") != "self":
            obj.add_link(pystac.Link.from_dict(deepcopy(link)))
    if href is not None:
        obj.set_self_href(href)
    if root is not None:
        obj.set_root(root)
    return obj

to_dict(include_self_link: bool = True, transform_hrefs: bool = True) -> dict[str, Any]

Serialize a record with metadata and STAC timestamps in UTC whole seconds.

Normalization includes nested metadata and does not mutate the record.

Raises:
  • ValueError –

    If a metadata or STAC timestamp string is invalid.

  • TypeError –

    If a non-null timestamp is neither a datetime nor a string.

Source code in pystac/extensions/ogc_record.py
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
def to_dict(
    self, include_self_link: bool = True, transform_hrefs: bool = True
) -> dict[str, Any]:
    """Serialize a record with metadata and STAC timestamps in UTC whole seconds.

    Normalization includes nested metadata and does not mutate the record.

    Raises:
        ValueError: If a metadata or STAC timestamp string is invalid.
        TypeError: If a non-null timestamp is neither a datetime nor a string.
    """
    props = deepcopy(self.properties)
    if self.datetime is not None:
        props["datetime"] = self.datetime
    _normalize_temporal_properties(props)
    doc = deepcopy(self.extra_fields)
    doc.update(
        type="Feature",
        id=self.record_id,
        geometry=deepcopy(self.geometry),
        properties=None if self._null_properties and not props else props,
        links=[
            deepcopy(link.to_dict(transform_href=transform_hrefs))
            for link in self.links
            if include_self_link or link.rel != pystac.RelType.SELF
        ],
    )
    if self.bbox is not None:
        doc["bbox"] = list(self.bbox)
    # Preserve extension declarations/assets as GeoJSON foreign members.
    # Never substitute conformsTo for stac_extensions.
    if self.stac_extensions:
        doc["stac_extensions"] = list(self.stac_extensions)
    if self.assets:
        doc["assets"] = {key: deepcopy(asset.to_dict()) for key, asset in self.assets.items()}
    if self.collection_id is not None:
        doc["collection"] = self.collection_id
    _normalize_metadata_timestamps(doc)
    return doc

to_stac_item() -> pystac.Item

Explicit export; requires a genuine STAC temporal extent.

Metadata and STAC timestamps are normalized to UTC whole seconds. Constructing an Item does not replace full STAC schema validation.

Source code in pystac/extensions/ogc_record.py
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def to_stac_item(self) -> pystac.Item:
    """Explicit export; requires a genuine STAC temporal extent.

    Metadata and STAC timestamps are normalized to UTC whole seconds.
    Constructing an Item does not replace full STAC schema validation.
    """
    if self.datetime is None and not all(
        self.properties.get(k) is not None for k in ("start_datetime", "end_datetime")
    ):
        raise ValueError("STAC export requires datetime or start_datetime/end_datetime")
    extra = deepcopy(self.extra_fields)
    extra.pop("stac_version", None)
    item = pystac.Item(
        id=self.id,
        geometry=deepcopy(self.geometry),
        bbox=deepcopy(self.bbox),
        datetime=None if self.datetime is None else _metadata_datetime(self.datetime),
        properties=deepcopy(self.properties),
        stac_extensions=list(self.stac_extensions),
        collection=self.collection_id,
        extra_fields=extra,
        assets={k: v.clone() for k, v in self.assets.items()},
    )
    for link in self.links:
        cloned_link = link.clone()
        cloned_link.extra_fields = deepcopy(link.extra_fields)
        item.add_link(cloned_link)
    _normalize_temporal_properties(item.properties)
    _normalize_metadata_timestamps(item.properties)
    _normalize_metadata_timestamps(item.extra_fields)
    for asset in item.assets.values():
        _normalize_metadata_timestamps(asset.extra_fields)
    for link in item.links:
        _normalize_metadata_timestamps(link.extra_fields)
    return item

validate(validator: Any = None) -> list[Any]

Validate the OGC document with an explicitly supplied validator.

Supply an object exposing validate(document), configured for the OGC OpenAPI 3.0 schema and its references. No implicit STAC validation.

Source code in pystac/extensions/ogc_record.py
689
690
691
692
693
694
695
696
697
698
699
700
def validate(self, validator: Any = None) -> list[Any]:
    """Validate the OGC document with an explicitly supplied validator.

    Supply an object exposing validate(document), configured for the OGC
    OpenAPI 3.0 schema and its references. No implicit STAC validation.
    """
    if validator is None:
        raise ValueError(
            "Supply an OGC schema validator; for STAC use record.to_stac_item().validate()"
        )
    validator.validate(self.to_dict(transform_hrefs=False))
    return [self.SCHEMA_URI]

RecordCommonProperties

Bases: RecordMetadataMixin

Backward-compatible metadata view sharing the record properties.

Source code in pystac/extensions/ogc_record.py
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
class RecordCommonProperties(RecordMetadataMixin):
    """Backward-compatible metadata view sharing the record properties."""

    def __init__(self, record: OGCRecord) -> None:
        self.record = record

    @property
    def properties(self) -> dict[str, Any]:
        """The live metadata dictionary shared with the record."""
        return self.record.properties

    @properties.setter
    def properties(self, value: dict[str, Any]) -> None:
        self.record.properties = value

    def to_dict(self) -> dict[str, Any]:
        """Copy properties with metadata and STAC timestamps normalized for JSON."""
        properties = deepcopy(self.properties)
        _normalize_temporal_properties(properties)
        _normalize_metadata_timestamps(properties)
        return properties

properties: dict[str, Any] property writable

The live metadata dictionary shared with the record.

to_dict() -> dict[str, Any]

Copy properties with metadata and STAC timestamps normalized for JSON.

Source code in pystac/extensions/ogc_record.py
373
374
375
376
377
378
def to_dict(self) -> dict[str, Any]:
    """Copy properties with metadata and STAC timestamps normalized for JSON."""
    properties = deepcopy(self.properties)
    _normalize_temporal_properties(properties)
    _normalize_metadata_timestamps(properties)
    return properties

RecordMetadataMixin

Record metadata accessors with datetime values for creation and update times.

Nested structures remain open dictionaries, allowing future extensions. These accessors do not perform JSON Schema validation.

Source code in pystac/extensions/ogc_record.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
class RecordMetadataMixin:
    """Record metadata accessors with datetime values for creation and update times.

    Nested structures remain open dictionaries, allowing future extensions.
    These accessors do not perform JSON Schema validation.
    """

    # PySTAC properties are an open JSON metadata boundary.
    properties: dict[str, Any]

    @property
    def created(self) -> Datetime | None:
        """The created timestamp in UTC whole seconds, or None if absent.

        Assign a datetime to store its ISO 8601 string, or None to remove it.
        Naive datetimes are treated as UTC; fractional seconds are truncated.

        Raises:
            ValueError: If the stored timestamp string is invalid.
            TypeError: If the stored value is neither a datetime nor a string.
        """
        value = self.properties.get("created")
        return None if value is None else _metadata_datetime(value)

    @created.setter
    def created(self, value: Datetime | None) -> None:
        if value is None:
            self.properties.pop("created", None)
        else:
            self.properties["created"] = datetime_to_str(
                _metadata_datetime(value), timespec="seconds"
            )

    @property
    def updated(self) -> Datetime | None:
        """The updated timestamp in UTC whole seconds, or None if absent.

        Assign a datetime to store its ISO 8601 string, or None to remove it.
        Naive datetimes are treated as UTC; fractional seconds are truncated.

        Raises:
            ValueError: If the stored timestamp string is invalid.
            TypeError: If the stored value is neither a datetime nor a string.
        """
        value = self.properties.get("updated")
        return None if value is None else _metadata_datetime(value)

    @updated.setter
    def updated(self, value: Datetime | None) -> None:
        if value is None:
            self.properties.pop("updated", None)
        else:
            self.properties["updated"] = datetime_to_str(
                _metadata_datetime(value), timespec="seconds"
            )

    type = MetadataField[str]("type")
    title = MetadataField[str]("title")
    description = MetadataField[str]("description")
    keywords = MetadataField[list[str]]("keywords")
    themes = MetadataField[list[Theme]]("themes")
    language = MetadataField[Language]("language")
    languages = MetadataField[list[Language]]("languages")
    resource_languages = MetadataField[list[Language]]("resourceLanguages")
    external_ids = MetadataField[list[ExternalId]]("externalIds")
    formats = MetadataField[list[Format]]("formats")
    contacts = MetadataField[list[Contact]]("contacts")
    license = MetadataField[str]("license")
    rights = MetadataField[str]("rights")

created: Datetime | None property writable

The created timestamp in UTC whole seconds, or None if absent.

Assign a datetime to store its ISO 8601 string, or None to remove it. Naive datetimes are treated as UTC; fractional seconds are truncated.

Raises:
  • ValueError –

    If the stored timestamp string is invalid.

  • TypeError –

    If the stored value is neither a datetime nor a string.

updated: Datetime | None property writable

The updated timestamp in UTC whole seconds, or None if absent.

Assign a datetime to store its ISO 8601 string, or None to remove it. Naive datetimes are treated as UTC; fractional seconds are truncated.

Raises:
  • ValueError –

    If the stored timestamp string is invalid.

  • TypeError –

    If the stored value is neither a datetime nor a string.