Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix (hack?) so that the JSON schema/swagger spec does not contain oneOf null types for optional fields. #11

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']

steps:
- uses: actions/checkout@v3
Expand Down
15 changes: 15 additions & 0 deletions src/covjson_pydantic/base_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# from typing import Annotated
import typing
from typing import TypeVar
from typing import Union

from pydantic import BaseModel as PydanticBaseModel
from pydantic import ConfigDict
from pydantic import Field
from pydantic.json_schema import SkipJsonSchema


# Define an alternative Optional type that doesn't show the None/null value and the default in the schema
T = TypeVar("T")
if hasattr(typing, "Annotated"): # Check if Annotated exists (Python >=3.9)
OptionalS = typing.Annotated[Union[T, SkipJsonSchema[None]], Field(json_schema_extra=lambda x: x.pop("default"))]
else: # For Python 3.8 we don't support dropping the default value
OptionalS = Union[T, SkipJsonSchema[None]] # type: ignore


class CovJsonBaseModel(PydanticBaseModel):
Expand Down
16 changes: 8 additions & 8 deletions src/covjson_pydantic/coverage.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Union

from pydantic import AnyUrl

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .domain import Domain
from .domain import DomainType
from .ndarray import NdArray
Expand All @@ -17,18 +17,18 @@


class Coverage(CovJsonBaseModel, extra="allow"):
id: Optional[str] = None
id: OptionalS[str] = None
type: Literal["Coverage"] = "Coverage"
domain: Domain
parameters: Optional[Dict[str, Parameter]] = None
parameterGroups: Optional[List[ParameterGroup]] = None # noqa: N815
parameters: OptionalS[Dict[str, Parameter]] = None
parameterGroups: OptionalS[List[ParameterGroup]] = None # noqa: N815
ranges: Dict[str, Union[NdArray, TiledNdArray, AnyUrl]]


class CoverageCollection(CovJsonBaseModel, extra="allow"):
type: Literal["CoverageCollection"] = "CoverageCollection"
domainType: Optional[DomainType] = None # noqa: N815
domainType: OptionalS[DomainType] = None # noqa: N815
coverages: List[Coverage]
parameters: Optional[Dict[str, Parameter]] = None
parameterGroups: Optional[List[ParameterGroup]] = None # noqa: N815
referencing: Optional[List[ReferenceSystemConnectionObject]] = None
parameters: OptionalS[Dict[str, Parameter]] = None
parameterGroups: OptionalS[List[ParameterGroup]] = None # noqa: N815
referencing: OptionalS[List[ReferenceSystemConnectionObject]] = None
22 changes: 11 additions & 11 deletions src/covjson_pydantic/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from typing import Generic
from typing import List
from typing import Literal
from typing import Optional
from typing import Tuple
from typing import TypeVar
from typing import Union
Expand All @@ -13,6 +12,7 @@
from pydantic import PositiveInt

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .reference_system import ReferenceSystemConnectionObject


Expand All @@ -34,10 +34,10 @@ def single_value_case(self):
# Combination between Generics (ValuesT) and datetime and strict mode causes issues between JSON <-> Pydantic
# conversions. Strict mode has been disabled. Issue: https://github.com/KNMI/covjson-pydantic/issues/4
class ValuesAxis(CovJsonBaseModel, Generic[ValuesT], extra="allow", strict=False):
dataType: Optional[str] = None # noqa: N815
coordinates: Optional[List[str]] = None
dataType: OptionalS[str] = None # noqa: N815
coordinates: OptionalS[List[str]] = None
values: List[ValuesT]
bounds: Optional[List[ValuesT]] = None
bounds: OptionalS[List[ValuesT]] = None

@model_validator(mode="after")
def bounds_length(self):
Expand All @@ -56,11 +56,11 @@ class DomainType(str, Enum):


class Axes(CovJsonBaseModel):
x: Optional[Union[ValuesAxis[float], CompactAxis]] = None
y: Optional[Union[ValuesAxis[float], CompactAxis]] = None
z: Optional[Union[ValuesAxis[float], CompactAxis]] = None
t: Optional[ValuesAxis[AwareDatetime]] = None
composite: Optional[ValuesAxis[Tuple]] = None
x: OptionalS[Union[ValuesAxis[float], CompactAxis]] = None
y: OptionalS[Union[ValuesAxis[float], CompactAxis]] = None
z: OptionalS[Union[ValuesAxis[float], CompactAxis]] = None
t: OptionalS[ValuesAxis[AwareDatetime]] = None
composite: OptionalS[ValuesAxis[Tuple]] = None

@model_validator(mode="after")
def at_least_one_axes(self):
Expand All @@ -71,9 +71,9 @@ def at_least_one_axes(self):

class Domain(CovJsonBaseModel, extra="allow"):
type: Literal["Domain"] = "Domain"
domainType: Optional[DomainType] = None # noqa: N815
domainType: OptionalS[DomainType] = None # noqa: N815
axes: Axes
referencing: Optional[List[ReferenceSystemConnectionObject]] = None
referencing: OptionalS[List[ReferenceSystemConnectionObject]] = None

# TODO: This is a workaround to allow domainType to work in strict mode, in combination with FastAPI.
# See: https://github.com/tiangolo/fastapi/discussions/9868
Expand Down
5 changes: 3 additions & 2 deletions src/covjson_pydantic/ndarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pydantic import model_validator

from .base_models import CovJsonBaseModel
from .base_models import OptionalS


# TODO: Support for integers and strings
Expand All @@ -17,8 +18,8 @@ class DataType(str, Enum):
class NdArray(CovJsonBaseModel, extra="allow"):
type: Literal["NdArray"] = "NdArray"
dataType: DataType = DataType.float # noqa: N815
axisNames: Optional[List[str]] = None # noqa: N815
shape: Optional[List[int]] = None
axisNames: OptionalS[List[str]] = None # noqa: N815
shape: OptionalS[List[int]] = None
values: List[Optional[float]]

@model_validator(mode="after")
Expand Down
10 changes: 5 additions & 5 deletions src/covjson_pydantic/observed_property.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
from typing import List
from typing import Optional

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .i18n import i18n


class Category(CovJsonBaseModel):
id: str
label: i18n
description: Optional[i18n] = None
description: OptionalS[i18n] = None


class ObservedProperty(CovJsonBaseModel):
id: Optional[str] = None
id: OptionalS[str] = None
label: i18n
description: Optional[i18n] = None
categories: Optional[List[Category]] = None
description: OptionalS[i18n] = None
categories: OptionalS[List[Category]] = None
20 changes: 10 additions & 10 deletions src/covjson_pydantic/parameter.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Union

from pydantic import model_validator

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .i18n import i18n
from .observed_property import ObservedProperty
from .unit import Unit


class Parameter(CovJsonBaseModel, extra="allow"):
type: Literal["Parameter"] = "Parameter"
id: Optional[str] = None
label: Optional[i18n] = None
description: Optional[i18n] = None
id: OptionalS[str] = None
label: OptionalS[i18n] = None
description: OptionalS[i18n] = None
observedProperty: ObservedProperty # noqa: N815
categoryEncoding: Optional[Dict[str, Union[int, List[int]]]] = None # noqa: N815
unit: Optional[Unit] = None
categoryEncoding: OptionalS[Dict[str, Union[int, List[int]]]] = None # noqa: N815
unit: OptionalS[Unit] = None

@model_validator(mode="after")
def must_not_have_unit_if_observed_property_has_categories(self):
Expand All @@ -34,10 +34,10 @@ def must_not_have_unit_if_observed_property_has_categories(self):

class ParameterGroup(CovJsonBaseModel, extra="allow"):
type: Literal["ParameterGroup"] = "ParameterGroup"
id: Optional[str] = None
label: Optional[i18n] = None
description: Optional[i18n] = None
observedProperty: Optional[ObservedProperty] = None # noqa: N815
id: OptionalS[str] = None
label: OptionalS[i18n] = None
description: OptionalS[i18n] = None
observedProperty: OptionalS[ObservedProperty] = None # noqa: N815
members: List[str]

@model_validator(mode="after")
Expand Down
20 changes: 10 additions & 10 deletions src/covjson_pydantic/reference_system.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
from typing import Dict
from typing import List
from typing import Literal
from typing import Optional
from typing import Union

from pydantic import AnyUrl
from pydantic import model_validator

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .i18n import i18n


class TargetConcept(CovJsonBaseModel):
id: Optional[str] = None # Not in spec, but needed for example in spec for 'Identifier-based Reference Systems'
id: OptionalS[str] = None # Not in spec, but needed for example in spec for 'Identifier-based Reference Systems'
label: i18n
description: Optional[i18n] = None
description: OptionalS[i18n] = None


class ReferenceSystem(CovJsonBaseModel, extra="allow"):
type: Literal["GeographicCRS", "ProjectedCRS", "VerticalCRS", "TemporalRS", "IdentifierRS"]
id: Optional[str] = None
description: Optional[i18n] = None
id: OptionalS[str] = None
description: OptionalS[i18n] = None

# Only for TemporalRS
calendar: Optional[Union[Literal["Gregorian"], AnyUrl]] = None
timeScale: Optional[AnyUrl] = None # noqa: N815
calendar: OptionalS[Union[Literal["Gregorian"], AnyUrl]] = None
timeScale: OptionalS[AnyUrl] = None # noqa: N815

# Only for IdentifierRS
label: Optional[i18n] = None
targetConcept: Optional[TargetConcept] = None # noqa: N815
identifiers: Optional[Dict[str, TargetConcept]] = None
label: OptionalS[i18n] = None
targetConcept: OptionalS[TargetConcept] = None # noqa: N815
identifiers: OptionalS[Dict[str, TargetConcept]] = None

@model_validator(mode="after")
def check_type_specific_fields(self):
Expand Down
8 changes: 4 additions & 4 deletions src/covjson_pydantic/unit.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Optional
from typing import Union

from pydantic import model_validator

from .base_models import CovJsonBaseModel
from .base_models import OptionalS
from .i18n import i18n


Expand All @@ -13,9 +13,9 @@ class Symbol(CovJsonBaseModel):


class Unit(CovJsonBaseModel):
id: Optional[str] = None
label: Optional[i18n] = None
symbol: Optional[Union[str, Symbol]] = None
id: OptionalS[str] = None
label: OptionalS[i18n] = None
symbol: OptionalS[Union[str, Symbol]] = None

@model_validator(mode="after")
def check_either_label_or_symbol(self):
Expand Down