Skip to content

Commit

Permalink
Added Table Schema (#1)
Browse files Browse the repository at this point in the history
* Bootstrapped table schema

* Bootstrapped reference generator

* Bootstrapped tests

* Improved schema model

* Added validate assignment
  • Loading branch information
roll authored Sep 7, 2023
1 parent 83e90f2 commit fc90e3e
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 6 deletions.
3 changes: 3 additions & 0 deletions docs/reference/schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Schema

::: dpspecs.Schema
3 changes: 3 additions & 0 deletions dpspecs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .models import *

__all__ = ["Schema"]
3 changes: 3 additions & 0 deletions dpspecs/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .schema import Schema

__all__ = ["Schema"]
25 changes: 25 additions & 0 deletions dpspecs/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import Any, Dict, List

from pydantic import BaseModel, ValidationError
from pydantic_core import ErrorDetails


class Model(BaseModel, validate_assignment=True):
def __str__(self):
return str(self.to_descriptor())

@classmethod
def validate_descriptor(cls, descriptor: Dict[str, Any]):
errors: List[ErrorDetails] = []
try:
cls.model_validate(descriptor)
except ValidationError as e:
errors = e.errors()
return errors

@classmethod
def from_descriptor(cls, descriptor: Dict[str, Any]):
return cls(**descriptor)

def to_descriptor(self):
return self.model_dump(exclude_unset=True, exclude_none=True)
137 changes: 137 additions & 0 deletions dpspecs/models/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from __future__ import annotations

from typing import Any, Dict, List, Literal, Optional, Union

import pydantic
from typing_extensions import Annotated

from .base import Model

# Schema


class Schema(Model):
"""Schema model"""

fields: List[Field]
"""List of fields"""

missingValues: Optional[List[str]] = None
primaryKey: Optional[List[str]] = None
foreignKeys: Optional[List[ForeignKey]] = None


# Fields


class BaseField(Model):
name: str
type: str
title: Optional[str] = None
description: Optional[str] = None
format: Optional[str] = None
missingValues: Optional[List[str]] = None


class AnyField(BaseField):
type: Literal["any"] = "any"


class ArrayField(BaseField):
type: Literal["array"] = "array"
# support json/csv format
arrayItem: Optional[Dict[str, Any]] = None


class BooleanField(BaseField):
type: Literal["boolean"] = "boolean"
trueValues: Optional[List[str]] = None
falseValues: Optional[List[str]] = None


class DateField(BaseField):
type: Literal["date"] = "date"


class DatetimeField(BaseField):
type: Literal["datetime"] = "datetime"


class DurationField(BaseField):
type: Literal["duration"] = "duration"


class GeojsonField(BaseField):
type: Literal["geojson"] = "geojson"


class GeopointField(BaseField):
type: Literal["geopoint"] = "geopoint"


class IntegerField(BaseField):
type: Literal["integer"] = "integer"
bareNumber: Optional[bool] = None
groupChar: Optional[str] = None


class NumberField(BaseField):
type: Literal["number"] = "number"
bareNumber: Optional[bool] = None
groupChar: Optional[str] = None
decimalChar: Optional[str] = None


class ObjectField(BaseField):
type: Literal["object"] = "object"


class StringField(BaseField):
type: Literal["string"] = "string"


class TimeField(BaseField):
type: Literal["time"] = "time"


class YearField(BaseField):
type: Literal["year"] = "year"


class YearmonthField(BaseField):
type: Literal["yearmonth"] = "yearmonth"


Field = Annotated[
Union[
AnyField,
ArrayField,
BooleanField,
DateField,
DatetimeField,
DurationField,
GeojsonField,
GeopointField,
IntegerField,
NumberField,
ObjectField,
StringField,
TimeField,
YearField,
YearmonthField,
],
pydantic.Field(discriminator="type"),
]


# Foreign keys


class ForeignKey(Model):
fields: List[str]
reference: Optional[ForeignKeyReference] = None


class ForeignKeyReference(Model):
fields: List[str]
resource: str
11 changes: 5 additions & 6 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ theme:
# - navigation.instant
# - navigation.prune
- navigation.sections
# - navigation.tabs
- navigation.tabs
# - navigation.tabs.sticky
- navigation.top
- navigation.tracking
Expand Down Expand Up @@ -58,11 +58,8 @@ theme:

# Plugins

# TODO: enable when it's released to general public
# https://squidfunk.github.io/mkdocs-material/insiders/#12000-piri-piri
# plugins:
# - blog:
# post_date_format: full
plugins:
- mkdocstrings

# Extras

Expand Down Expand Up @@ -111,5 +108,7 @@ nav:
- Documentation:
Installation: documentation/installation.md
Usage: documentation/usage.md
- Reference:
Schema: reference/schema.md
- Contributing:
Development: contributing/development.md
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dev = [
"pytest-dotenv",
"pytest-timeout",
"pytest-lazy-fixture",
"mkdocstrings[python]",
"mkdocs-material",
]

Expand Down
Empty file added tests/models/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions tests/models/test_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from dpspecs import Schema


def test_error():
schema = Schema(fields=[])
assert schema.fields == []

0 comments on commit fc90e3e

Please sign in to comment.