Skip to content

Commit

Permalink
Rename to onedm
Browse files Browse the repository at this point in the history
  • Loading branch information
christiansandberg committed Aug 2, 2024
1 parent 39b4f4f commit ad753e8
Show file tree
Hide file tree
Showing 17 changed files with 123 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/sdf-utils
url: https://pypi.org/p/onedm
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

Expand Down
73 changes: 65 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,67 @@
# sdf-utils
# OneDM Python library

[Semantic Definition Format (SDF) for Data and Interactions of Things](https://ietf-wg-asdf.github.io/SDF/sdf.html)
This Python package is an early work-in-progress to simplify working with
[One Data Model](https://onedm.org/) in Python.

The Semantic Definition Format (SDF) is a format for domain experts to use in
the creation and maintenance of data and interaction models that describe Things,
i.e., physical objects that are available for interaction over a network.
An SDF specification describes definitions of SDF Objects/SDF Things and their
associated interactions (Events, Actions, Properties), as well as the Data types
for the information exchanged in those interactions.
Since OneDM is in early stages, this library intends to follow the progress
as best as it can and should hence be considered unstable.


## SDF

Currently it supports limited loading and generation of
[SDF](https://ietf-wg-asdf.github.io/SDF/sdf.html) documents.

> The Semantic Definition Format (SDF) is a format for domain experts to use in
> the creation and maintenance of data and interaction models that describe Things,
> i.e., physical objects that are available for interaction over a network.
> An SDF specification describes definitions of SDF Objects/SDF Things and their
> associated interactions (Events, Actions, Properties), as well as the Data types
> for the information exchanged in those interactions.
This library uses [Pydantic](https://docs.pydantic.dev/) to parse, validate,
and dump model descriptions. The Pydantic models enforce a stricter validation
than the current SDF JSON schema where each data type has its own schema.


## Examples

Loading an existing SDF document:

```
>>> from onedm import sdf
>>> loader = sdf.SDFLoader()
>>> loader.load_file("tests/sdf/test.sdf.json")
>>> doc = loader.to_sdf()
>>> doc.info.title
'Example document for SDF (Semantic Definition Format)'
>>> doc.properties["IntegerProperty"]
IntegerProperty(observable=True, readable=True, writable=True, label='Example integer', description=None, ref=None, required=[], type=<DataType.INTEGER: 'integer'>, sdf_type=None, nullable=True, const=2, unit=None, minimum=-2, maximum=2, exclusive_minimum=None, exclusive_maximum=None, multiple_of=2, format=None, choices=None, default=None)
```

Creating a new document:

```
>>> from onedm import sdf
>>> doc = sdf.SDF()
>>> doc.things["switch"] = sdf.Thing(label="Generic switch")
>>> doc.things["switch"].actions["on"] = sdf.Action(label="Turn on")
>>> print(doc.to_json())
{
"sdfThing": {
"switch": {
"label": "Generic switch",
"sdfAction": {
"on": {
"label": "Turn on"
}
}
}
}
}
```
Empty file added onedm/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
56 changes: 49 additions & 7 deletions sdf/data.py → onedm/sdf/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from enum import Enum
from typing import Annotated, Any, Literal, Union

from pydantic import Field, NonNegativeInt
from pydantic import Field, NonNegativeInt, model_validator
from pydantic_core import SchemaValidator, core_schema

from .common import CommonQualities
Expand Down Expand Up @@ -47,7 +47,7 @@ def validate(self, input: Any) -> Any:


class NumberData(DataQualities):
type: Literal[DataType.NUMBER] = DataType.NUMBER
type: Literal[DataType.NUMBER]
unit: str | None = None
minimum: float | None = None
maximum: float | None = None
Expand All @@ -58,6 +58,13 @@ class NumberData(DataQualities):
const: float | None = None
default: float | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.NUMBER)
return data

def get_pydantic_schema(self) -> core_schema.FloatSchema:
return core_schema.float_schema(
ge=self.minimum,
Expand All @@ -72,7 +79,7 @@ def validate(self, input: Any) -> int:


class IntegerData(DataQualities):
type: Literal[DataType.INTEGER] = DataType.INTEGER
type: Literal[DataType.INTEGER]
unit: str | None = None
minimum: int | None = None
maximum: int | None = None
Expand All @@ -84,6 +91,13 @@ class IntegerData(DataQualities):
const: int | None = None
default: int | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.INTEGER)
return data

def get_pydantic_schema(self) -> core_schema.IntSchema:
return core_schema.int_schema(
ge=self.minimum,
Expand All @@ -98,10 +112,17 @@ def validate(self, input: Any) -> int:


class BooleanData(DataQualities):
type: Literal[DataType.BOOLEAN] = DataType.BOOLEAN
type: Literal[DataType.BOOLEAN]
const: bool | None = None
default: bool | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.BOOLEAN)
return data

def get_pydantic_schema(self) -> core_schema.BoolSchema:
return core_schema.bool_schema()

Expand All @@ -110,7 +131,7 @@ def validate(self, input: Any) -> bool:


class StringData(DataQualities):
type: Literal[DataType.STRING] = DataType.STRING
type: Literal[DataType.STRING]
enum: list[str] | None = None
min_length: NonNegativeInt = 0
max_length: NonNegativeInt | None = None
Expand All @@ -121,6 +142,13 @@ class StringData(DataQualities):
const: str | None = None
default: str | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.STRING)
return data

def get_pydantic_schema(self) -> core_schema.StringSchema | core_schema.BytesSchema:
if self.sdf_type == "byte-string":
return core_schema.bytes_schema(
Expand All @@ -137,14 +165,21 @@ def validate(self, input: Any) -> str | bytes:


class ArrayData(DataQualities):
type: Literal[DataType.ARRAY] = DataType.ARRAY
type: Literal[DataType.ARRAY]
min_items: NonNegativeInt = 0
max_items: NonNegativeInt | None = None
unique_items: bool = False
items: Data | None = None
const: list | None = None
default: list | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.ARRAY)
return data

def get_pydantic_schema(self) -> core_schema.ListSchema | core_schema.SetSchema:
if self.unique_items:
return core_schema.set_schema(
Expand All @@ -163,11 +198,18 @@ def validate(self, input: Any) -> list | set:


class ObjectData(DataQualities):
type: Literal[DataType.OBJECT] = DataType.OBJECT
type: Literal[DataType.OBJECT]
required: list[str] | None = None
properties: dict[str, Data] | None = None
const: dict[str, Any] | None = None

@model_validator(mode="before")
@classmethod
def set_default_type(cls, data: Any):
if isinstance(data, dict):
data.setdefault("type", DataType.OBJECT)
return data

def get_pydantic_schema(self) -> core_schema.TypedDictSchema:
required = self.required or []
fields = {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion sdf/document.py → onedm/sdf/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ class SDF(BaseModel):
data: dict[str, Data] = Field(default_factory=dict, alias="sdfData")

def to_json(self) -> str:
return self.model_dump_json(indent=2, exclude_unset=True, by_alias=True)
return self.model_dump_json(indent=2, exclude_defaults=True, by_alias=True)
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sdf-utils"
description = "Semantic Definition Format (SDF) for Data and Interactions of Things"
name = "onedm"
description = "Common data model for IoT and IoT devices"
keywords = ["iot", "data model"]
authors = [
{name = "Christian Sandberg", email = "christiansandberg@me.com"},
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py → tests/sdf/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path
import pytest

import sdf
from onedm import sdf


@pytest.fixture(scope="session")
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion tests/test_datatypes.py → tests/sdf/test_datatypes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sdf
from onedm import sdf


def test_integer_data(test_model: sdf.SDF):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import tempfile

import pytest
import sdf
from onedm import sdf


@pytest.fixture(scope="session")
Expand Down
2 changes: 1 addition & 1 deletion tests/test_properties.py → tests/sdf/test_properties.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sdf
from onedm import sdf


def test_integer_property(test_model: sdf.SDF):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_references.py → tests/sdf/test_references.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import sdf
from onedm import sdf


def test_reference(test_model: sdf.SDF):
Expand Down

0 comments on commit ad753e8

Please sign in to comment.