-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #134 from PDOK/pdok-16629-validate-constraints-and…
…-indices [PDOK-16629] validate constraints and indices
- Loading branch information
Showing
14 changed files
with
681 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,95 @@ | ||
import copy | ||
from typing import List, Optional | ||
from typing import Optional, Tuple | ||
|
||
from pydantic import BaseModel | ||
from pydantic import BaseModel, Field, field_validator, ConfigDict | ||
from semver import Version | ||
|
||
|
||
class Named(BaseModel): | ||
name: str | ||
|
||
|
||
class ColumnDefinition(Named): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
type: str | ||
|
||
|
||
class IndexDefinition(BaseModel): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
columns: Tuple[str, ...] = Field(min_length=1) | ||
unique: bool = False | ||
|
||
|
||
class ColumnMapping(BaseModel): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
src: str | ||
dst: str | ||
|
||
|
||
class ForeignKeyDefinition(BaseModel): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
@field_validator("columns") | ||
@classmethod | ||
def unique_src_columns( | ||
cls, v: Tuple[ColumnMapping, ...] | ||
) -> Tuple[ColumnMapping, ...]: | ||
src_columns = set() | ||
for c in v: | ||
if c.src in src_columns: | ||
raise ValueError(f"Duplicate src column detected: {c.src}") | ||
src_columns.add(c.src) | ||
return v | ||
|
||
table: str = Field(min_length=1) | ||
columns: Tuple[ColumnMapping, ...] = Field(min_length=1) | ||
|
||
|
||
class TableDefinition(Named): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
geometry_column: str = "geom" | ||
columns: List[ColumnDefinition] = [] | ||
columns: Tuple[ColumnDefinition, ...] = tuple() | ||
"""Ordered as in the table (left to right), but with FID and geometry columns always first. | ||
(This order is not validated.)""" | ||
indexes: Optional[Tuple[IndexDefinition, ...]] = None | ||
"""None means: don't validate. Empty list means: there should be no indexes.""" | ||
foreign_keys: Optional[Tuple[ForeignKeyDefinition, ...]] = None | ||
"""None means: don't validate. Empty list means: there should be no foreign keys.""" | ||
|
||
|
||
class TablesDefinition(BaseModel): | ||
model_config = ConfigDict(frozen=True) | ||
|
||
geopackage_validator_version: str = "0" | ||
projection: Optional[int] | ||
tables: List[TableDefinition] | ||
tables: Tuple[TableDefinition, ...] | ||
"""Ordered by table name""" | ||
|
||
def with_indexes_and_fks(self) -> bool: | ||
for table in self.tables: | ||
if table.indexes is not None or table.foreign_keys is not None: | ||
return True | ||
return False | ||
|
||
|
||
def migrate_tables_definition(old: dict) -> dict: | ||
def migrate_tables_definition(original: dict) -> dict: | ||
"""Migrate a possibly old tables definition to new schema/model""" | ||
version = old.get("geopackage_validator_version", "0") | ||
# older versions where not versioned (?), so assuming "0" if there is no version | ||
version_tuple = tuple(int(v) for v in version.split(".")) | ||
if version_tuple == (0, 0, 0, "-dev") or version_tuple > ( | ||
0, | ||
5, | ||
8, | ||
): # no changes after 0.5.8 | ||
return old | ||
new = copy.deepcopy(old) | ||
if version_tuple <= ( | ||
0, | ||
5, | ||
8, | ||
): # until 0.5.8, column's "type" property was named "data_type" | ||
for t in new.get("tables", []): | ||
# older versions were not versioned (?), so assuming "0.0.0" if there is no version | ||
version = Version.parse(original.get("geopackage_validator_version", "0.0.0")) | ||
if version == Version(0, 0, 0, "dev"): | ||
return original | ||
# nothing changed after v0.5.8 | ||
if version > Version(0, 5, 8): | ||
return original | ||
migrated = copy.deepcopy(original) | ||
# until and including 0.5.8, column's "type" property was named "data_type" | ||
if version <= Version(0, 5, 8): | ||
for t in migrated.get("tables", []): | ||
for c in t.get("columns", []): | ||
c["type"] = c["data_type"] | ||
del c["data_type"] | ||
return new | ||
return migrated |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.