diff --git a/setup.cfg b/setup.cfg
index fa544bc..cdb5aaf 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -48,6 +48,8 @@ package_dir =
# new major versions. This works if the required packages follow Semantic Versioning.
# For more information, check out https://semver.org/.
install_requires =
+ oold
+ opensemantic
pydantic>=1.10.17
datamodel-code-generator>=0.25
mwclient>=0.11.0
@@ -68,7 +70,6 @@ install_requires =
asyncio
tqdm
pybars3-wheel
- backports.strenum; python_version<"3.11"
[options.packages.find]
where = src
diff --git a/src/osw/auth.py b/src/osw/auth.py
index 93e57c0..1df2f40 100644
--- a/src/osw/auth.py
+++ b/src/osw/auth.py
@@ -7,10 +7,10 @@
from warnings import warn
import yaml
+from opensemantic import OswBaseModel
from pydantic.v1 import PrivateAttr
from osw.defaults import paths as default_paths
-from osw.model.static import OswBaseModel
if TYPE_CHECKING:
PossibleFilePath = Path
diff --git a/src/osw/controller/database.py b/src/osw/controller/database.py
index 8bc65ec..16b7a0d 100644
--- a/src/osw/controller/database.py
+++ b/src/osw/controller/database.py
@@ -1,5 +1,6 @@
from typing import Optional, Union
+from opensemantic import OswBaseModel
from sqlalchemy import URL, create_engine
from sqlalchemy import text as sql_text
from sqlalchemy.engine import Engine
@@ -7,7 +8,6 @@
import osw.model.entity as model
from osw.auth import CredentialManager
from osw.core import OSW
-from osw.model.static import OswBaseModel
class DatabaseController(model.Database):
diff --git a/src/osw/controller/page_package.py b/src/osw/controller/page_package.py
index 26bc70a..4c74b42 100644
--- a/src/osw/controller/page_package.py
+++ b/src/osw/controller/page_package.py
@@ -6,6 +6,7 @@
from typing import Optional, Union
from warnings import warn
+from opensemantic import OswBaseModel
from pydantic.v1 import FilePath
from typing_extensions import Dict, List
@@ -13,7 +14,6 @@
from osw.auth import CredentialManager
from osw.model import page_package as package
from osw.model.page_package import NAMESPACE_CONST_TO_NAMESPACE_MAPPING
-from osw.model.static import OswBaseModel
from osw.utils.regex import RegExPatternExtended
from osw.wtsite import WtPage, WtSite
diff --git a/src/osw/core.py b/src/osw/core.py
index 2e35618..23f073c 100644
--- a/src/osw/core.py
+++ b/src/osw/core.py
@@ -18,13 +18,22 @@
import rdflib
from jsonpath_ng.ext import parse
from mwclient.client import Site
+from oold.generator import Generator
+from oold.model.v1 import (
+ ResolveParam,
+ Resolver,
+ ResolveResult,
+ SetResolverParam,
+ set_resolver,
+)
+from oold.utils.codegen import OOLDJsonSchemaParser
+from opensemantic import OswBaseModel
from pydantic import PydanticDeprecatedSince20
from pydantic.v1 import BaseModel, Field, PrivateAttr, create_model, validator
from pyld import jsonld
import osw.model.entity as model
from osw.defaults import params as default_params
-from osw.model.static import OswBaseModel
from osw.utils.oold import (
AggregateGeneratedSchemasParam,
AggregateGeneratedSchemasParamMode,
@@ -99,6 +108,30 @@ class Config:
site: WtSite
+ def __init__(self, **data: Any):
+ super().__init__(**data)
+
+ # implement resolver backend with osw.load_entity
+ class MyResolver(Resolver):
+
+ osw_obj: OSW
+
+ def resolve(self, request: ResolveParam):
+ print("RESOLVE", request)
+ entities = self.osw_obj.load_entity(
+ OSW.LoadEntityParam(titles=request.iris)
+ ).entities
+ # create a dict with request.iris as keys and the loaded entities as values
+ # by iterating over both lists
+ nodes = {}
+ for iri, entity in zip(request.iris, entities):
+ nodes[iri] = entity
+ return ResolveResult(nodes=nodes)
+
+ r = MyResolver(osw_obj=self)
+ set_resolver(SetResolverParam(iri="Item", resolver=r))
+ set_resolver(SetResolverParam(iri="Category", resolver=r))
+
@property
def mw_site(self) -> Site:
"""Returns the mwclient Site object of the OSW instance."""
@@ -348,6 +381,17 @@ class FetchSchemaParam(BaseModel):
)
legacy_generator: Optional[bool] = False
"""uses legacy command line for code generation if true"""
+ generate_annotations: Optional[bool] = True
+ """generate custom schema keywords in Fields and Classes.
+ Required to update the schema in OSW without information loss"""
+ offline_pages: Optional[Dict[str, WtPage]] = None
+ """pages to be used offline instead of fetching them from the OSW instance"""
+ result_model_path: Optional[Union[str, pathlib.Path]] = None
+ """path to the generated model file, if None,
+ the default path ./model/entity.py is used"""
+
+ class Config:
+ arbitrary_types_allowed = True
def fetch_schema(self, fetchSchemaParam: FetchSchemaParam = None) -> None:
"""Loads the given schemas from the OSW instance and auto-generates python
@@ -370,6 +414,9 @@ def fetch_schema(self, fetchSchemaParam: FetchSchemaParam = None) -> None:
schema_title=schema_title,
mode=mode,
legacy_generator=fetchSchemaParam.legacy_generator,
+ generate_annotations=fetchSchemaParam.generate_annotations,
+ offline_pages=fetchSchemaParam.offline_pages,
+ result_model_path=fetchSchemaParam.result_model_path,
)
)
first = False
@@ -396,6 +443,19 @@ class _FetchSchemaParam(BaseModel):
)
legacy_generator: Optional[bool] = False
"""uses legacy command line for code generation if true"""
+ generate_annotations: Optional[bool] = False
+ """generate custom schema keywords in Fields and Classes.
+ Required to update the schema in OSW without information loss"""
+ offline_pages: Optional[Dict[str, WtPage]] = None
+ """pages to be used offline instead of fetching them from the OSW instance"""
+ result_model_path: Optional[Union[str, pathlib.Path]] = None
+ """path to the generated model file, if None,
+ the default path ./model/entity.py is used"""
+ fetched_schema_titles: Optional[List[str]] = []
+ """keep track of fetched schema titles to prevent recursion"""
+
+ class Config:
+ arbitrary_types_allowed = True
def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
"""Loads the given schema from the OSW instance and autogenerates python
@@ -411,12 +471,23 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
if fetchSchemaParam is None:
fetchSchemaParam = OSW._FetchSchemaParam()
schema_title = fetchSchemaParam.schema_title
+ fetchSchemaParam.fetched_schema_titles.append(schema_title)
root = fetchSchemaParam.root
schema_name = schema_title.split(":")[-1]
- page = self.site.get_page(WtSite.GetPageParam(titles=[schema_title])).pages[0]
- if not page.exists:
- print(f"Error: Page {schema_title} does not exist")
- return
+ if (
+ fetchSchemaParam.offline_pages is not None
+ and schema_title in fetchSchemaParam.offline_pages
+ ):
+ print(f"Fetch {schema_title} from offline pages")
+ page = fetchSchemaParam.offline_pages[schema_title]
+ else:
+ print(f"Fetch {schema_title} from online pages")
+ page = self.site.get_page(WtSite.GetPageParam(titles=[schema_title])).pages[
+ 0
+ ]
+ if not page.exists:
+ print(f"Error: Page {schema_title} does not exist")
+ return
# not only in the JsonSchema namespace the schema is located in the main sot
# in all other namespaces, the json_schema slot is used
if schema_title.startswith("JsonSchema:"):
@@ -433,6 +504,12 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
if (schema_str is None) or (schema_str == ""):
print(f"Error: Schema {schema_title} does not exist")
schema_str = "{}" # empty schema to make reference work
+
+ generator = Generator()
+ schemas_for_preprocessing = [json.loads(schema_str)]
+ generator.preprocess(schemas_for_preprocessing)
+ schema_str = json.dumps(schemas_for_preprocessing[0])
+
schema = json.loads(
schema_str.replace("$ref", "dollarref").replace(
# '$' is a special char for root object in jsonpath
@@ -441,7 +518,6 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
)
# fix https://github.com/koxudaxi/datamodel-code-generator/issues/1910
)
- print(f"Fetch {schema_title}")
jsonpath_expr = parse("$..dollarref")
for match in jsonpath_expr.find(schema):
@@ -461,10 +537,12 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
# print(f"replace {match.value} with {value}")
if (
ref_schema_title != schema_title
+ and ref_schema_title not in fetchSchemaParam.fetched_schema_titles
): # prevent recursion in case of self references
- self._fetch_schema(
- OSW._FetchSchemaParam(schema_title=ref_schema_title, root=False)
- ) # resolve references recursive
+ _param = fetchSchemaParam.copy()
+ _param.root = False
+ _param.schema_title = ref_schema_title
+ self._fetch_schema(_param) # resolve references recursive
model_dir_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "model"
@@ -480,6 +558,10 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
# result_model_path = schema_path.replace(".json", ".py")
result_model_path = os.path.join(model_dir_path, "entity.py")
+ if fetchSchemaParam.result_model_path:
+ result_model_path = fetchSchemaParam.result_model_path
+ if not isinstance(result_model_path, str):
+ result_model_path = str(result_model_path)
temp_model_path = os.path.join(model_dir_path, "temp.py")
if root:
if fetchSchemaParam.legacy_generator:
@@ -505,7 +587,7 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
--input {schema_path} \
--input-file-type jsonschema \
--output {temp_model_path} \
- --base-class osw.model.static.OswBaseModel \
+ --base-class opensemantic.OswBaseModel \
--use-default \
--use-unique-items-as-set \
--enum-field-as-literal all \
@@ -522,15 +604,25 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
# suppress deprecation warnings from pydantic
# see https://github.com/koxudaxi/datamodel-code-generator/issues/2213
warnings.filterwarnings("ignore", category=PydanticDeprecatedSince20)
+
+ if fetchSchemaParam.generate_annotations:
+ # monkey patch class
+ datamodel_code_generator.parser.jsonschema.JsonSchemaParser = (
+ OOLDJsonSchemaParser
+ )
datamodel_code_generator.generate(
input_=pathlib.Path(schema_path),
input_file_type="jsonschema",
output=pathlib.Path(temp_model_path),
- base_class="osw.model.static.OswBaseModel",
+ base_class="opensemantic.OswBaseModel",
# use_default=True,
apply_default_values_for_required_fields=True,
use_unique_items_as_set=True,
- enum_field_as_literal=datamodel_code_generator.LiteralType.All,
+ # enum_field_as_literal=datamodel_code_generator.LiteralType.All,
+ enum_field_as_literal="all",
+ # will create MyEnum(str, Enum) instead of MyEnum(Enum)
+ use_subclass_enum=True,
+ set_default_enum_member=True,
use_title_as_name=True,
use_schema_description=True,
use_field_description=True,
@@ -538,9 +630,47 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
use_double_quotes=True,
collapse_root_models=True,
reuse_model=True,
+ field_include_all_keys=True,
)
warnings.filterwarnings("default", category=PydanticDeprecatedSince20)
+ # note: we could use OOLDJsonSchemaParser directly (see below),
+ # but datamodel_code_generator.generate
+ # does some pre- and postprocessing we do not want to duplicate
+
+ # data_model_type = datamodel_code_generator.DataModelType.PydanticBaseModel
+ # #data_model_type = DataModelType.PydanticV2BaseModel
+ # target_python_version = datamodel_code_generator.PythonVersion.PY_38
+ # data_model_types = datamodel_code_generator.model.get_data_model_types(
+ # data_model_type, target_python_version
+ # )
+ # parser = OOLDJsonSchemaParserFixedRefs(
+ # source=pathlib.Path(schema_path),
+
+ # base_class="opensemantic.OswBaseModel",
+ # data_model_type=data_model_types.data_model,
+ # data_model_root_type=data_model_types.root_model,
+ # data_model_field_type=data_model_types.field_model,
+ # data_type_manager_type=data_model_types.data_type_manager,
+ # target_python_version=target_python_version,
+
+ # #use_default=True,
+ # apply_default_values_for_required_fields=True,
+ # use_unique_items_as_set=True,
+ # enum_field_as_literal=datamodel_code_generator.LiteralType.All,
+ # use_title_as_name=True,
+ # use_schema_description=True,
+ # use_field_description=True,
+ # encoding="utf-8",
+ # use_double_quotes=True,
+ # collapse_root_models=True,
+ # reuse_model=True,
+ # #field_include_all_keys=True
+ # )
+ # result = parser.parse()
+ # with open(temp_model_path, "w", encoding="utf-8") as f:
+ # f.write(result)
+
# see https://koxudaxi.github.io/datamodel-code-generator/
# --base-class OswBaseModel: use a custom base class
# --custom-template-dir src/model/template_data/
@@ -590,8 +720,8 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
header = (
"from uuid import uuid4\n"
"from typing import Type, TypeVar\n"
- "from osw.model.static import OswBaseModel, Ontology\n"
- # "from osw.model.static import *\n"
+ "from opensemantic import OswBaseModel\n"
+ # "from opensemantic import *\n"
"\n"
)
@@ -614,7 +744,6 @@ def _fetch_schema(self, fetchSchemaParam: _FetchSchemaParam = None) -> None:
r"class\s*([\S]*)\s*\(\s*[\S\s]*?\s*\)\s*:.*\n"
) # match class definition [\s\S]*(?:[^\S\n]*\n){2,}
for cls in re.findall(pattern, org_content):
- print(cls)
content = re.sub(
r"(class\s*"
+ cls
diff --git a/src/osw/data/mining.py b/src/osw/data/mining.py
index 2ac86a7..579528c 100644
--- a/src/osw/data/mining.py
+++ b/src/osw/data/mining.py
@@ -6,5 +6,5 @@
#
# from pydantic import validator
#
-# from osw.model.static import OswBaseModel
+# from opensemantic import OswBaseModel
# from osw.utils.strings import *
diff --git a/src/osw/defaults.py b/src/osw/defaults.py
index ee6b45b..56352bd 100644
--- a/src/osw/defaults.py
+++ b/src/osw/defaults.py
@@ -4,9 +4,7 @@
from pathlib import Path
from typing import List, Union
-from pydantic.v1 import PrivateAttr, validator
-
-from osw.model.static import OswBaseModel
+from pydantic.v1 import BaseModel, PrivateAttr, validator
PACKAGE_ROOT_PATH = Path(__file__).parents[2]
SRC_PATH = PACKAGE_ROOT_PATH / "src"
@@ -18,7 +16,7 @@
WIKI_DOMAIN_DEFAULT = "wiki-dev.open-semantic-lab.org"
-class FilePathDefault(OswBaseModel):
+class FilePathDefault(BaseModel):
"""A class to store the default file path. This is a helper class to make the
default file path, defined within this module, accessible from a calling script."""
@@ -53,7 +51,7 @@ def get(self):
return self._default
-class Defaults(OswBaseModel):
+class Defaults(BaseModel):
"""Helper class to create an inheriting classes for storing default values."""
_changed: List[str] = PrivateAttr(default_factory=list)
diff --git a/src/osw/express.py b/src/osw/express.py
index 7cb52a7..0cf1e47 100644
--- a/src/osw/express.py
+++ b/src/osw/express.py
@@ -12,6 +12,7 @@
from warnings import warn
import requests
+from opensemantic import OswBaseModel
from pydantic.v1 import validator
from typing_extensions import (
IO,
@@ -30,7 +31,6 @@
from osw.core import OSW, OVERWRITE_CLASS_OPTIONS, OverwriteOptions
from osw.defaults import params as default_params
from osw.defaults import paths as default_paths
-from osw.model.static import OswBaseModel
from osw.utils.wiki import namespace_from_full_title, title_from_full_title
from osw.wtsite import WtSite
diff --git a/src/osw/model/entity.py b/src/osw/model/entity.py
index d2310b5..d2b2666 100644
--- a/src/osw/model/entity.py
+++ b/src/osw/model/entity.py
@@ -1,34 +1,118 @@
# generated by datamodel-codegen:
# filename: Item.json
-# timestamp: 2024-09-12T12:35:11+00:00
+# timestamp: 2025-05-18T15:16:02+00:00
from __future__ import annotations
-from typing import Any, List, Literal, Optional, Set, Union
-from uuid import UUID, uuid4
+from enum import Enum
+from typing import Any, List, Optional, Set, Union
+from uuid import UUID
+from opensemantic import OswBaseModel
from pydantic.v1 import Field, constr
-from osw.model.static import OswBaseModel
+
+class Level(str, Enum):
+ public = "public"
+ internal = "internal"
+ restricted = "restricted"
+
+
+from typing import Type, TypeVar
+from uuid import uuid4
+
+from opensemantic import OswBaseModel
class ReadAccess(OswBaseModel):
- level: Optional[Literal["public", "internal", "restricted"]] = Field(
- None, title="Level"
+ class Config:
+ schema_extra = {"title": "Read access", "title*": {"de": "Lesezugriff"}}
+
+ level: Optional[Level] = Field(
+ None,
+ options={
+ "enum_titles": ["Public", "For all users", "For some users"],
+ "enum_titles*": ["Öffentlich", "Für alle Nutzer", "Für bestimmte Nutzer"],
+ },
+ title="Level",
+ title_={"de": "Level"},
)
class AccessRestrictions(OswBaseModel):
- read: Optional[ReadAccess] = Field(None, title="Read access")
+ class Config:
+ schema_extra = {
+ "title": "Access restrictions",
+ "title*": {"de": "Zugriffsbeschränkungen"},
+ "eval_template": {
+ "$comment": "See https://www.mediawiki.org/wiki/Extension:Semantic_ACL",
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{entry_access.read.level}} {{=<% %>=}} {{#set: |Visible to= {{#switch: <%={{ }}=%> {{{entry_access.read.level}}} {{=<% %>=}} |public=public |internal=users |restricted=whitelist |#default=}} }} <%={{ }}=%>",
+ },
+ }
+
+ read: Optional[ReadAccess] = Field(
+ None, title="Read access", title_={"de": "Lesezugriff"}
+ )
+
+
+class LangCode(str, Enum):
+ en = "en"
+ de = "de"
class Label(OswBaseModel):
- text: constr(min_length=1) = Field(..., title="Text")
- lang: Optional[Literal["en", "de"]] = Field("en", title="Lang code")
+ class Config:
+ schema_extra = {
+ "@context": {
+ "rdf": "http://www.w3.org/2000/01/rdf-schema#",
+ "text": {"@id": "@value"},
+ "lang": {"@id": "@language"},
+ },
+ "title": "Label",
+ }
+ text: constr(min_length=1) = Field(
+ ...,
+ options={
+ "input_width": "800px",
+ "inputAttributes": {"placeholder": "Title of the entry"},
+ "inputAttributes*": {"de": {"placeholder": "Titel dieses Eintrags"}},
+ },
+ title="Text",
+ title_={"de": "Text"},
+ )
+ lang: Optional[LangCode] = Field(
+ LangCode.en,
+ default_={"en": "en", "de": "de"},
+ options={"input_width": "100px"},
+ title="Lang code",
+ title_={"de": "Sprache"},
+ )
-class Description(Label):
- pass
+
+class Description(OswBaseModel):
+ class Config:
+ schema_extra = {
+ "@context": {
+ "rdf": "http://www.w3.org/2000/01/rdf-schema#",
+ "text": {"@id": "@value"},
+ "lang": {"@id": "@language"},
+ },
+ "title": "Description",
+ }
+
+ text: constr(min_length=1) = Field(
+ ..., options={"input_width": "800px"}, title="Text", title_={"de": "Text"}
+ )
+ lang: Optional[LangCode] = Field(
+ LangCode.en,
+ default_={"en": "en", "de": "de"},
+ options={"input_width": "100px"},
+ title="Lang code",
+ title_={"de": "Sprache"},
+ )
class WikiPage(OswBaseModel):
@@ -36,6 +120,12 @@ class WikiPage(OswBaseModel):
The wiki page containing this entity
"""
+ class Config:
+ schema_extra = {
+ "title": "Wiki page",
+ "description": "The wiki page containing this entity",
+ }
+
title: Optional[str] = Field(None, title="Title")
"""
The page title
@@ -47,7 +137,15 @@ class WikiPage(OswBaseModel):
class Meta(OswBaseModel):
- uuid: UUID = Field(default_factory=uuid4, title="UUID")
+ class Config:
+ schema_extra = {
+ "@context": {
+ "change_id": {"@id": "Property:HasChangeId", "@type": "xsd:string"}
+ },
+ "title": "Meta",
+ }
+
+ uuid: UUID = Field(default_factory=uuid4, options={"hidden": True}, title="UUID")
wiki_page: Optional[WikiPage] = Field(None, title="Wiki page")
"""
The wiki page containing this entity
@@ -59,108 +157,533 @@ class Meta(OswBaseModel):
class Entity(OswBaseModel):
- rdf_type: Optional[Set[str]] = Field(None, title="Additional RDF type(s)")
+ class Config:
+ schema_extra = {
+ "@context": {
+ "schema": "https://schema.org/",
+ "skos": "https://www.w3.org/TR/skos-reference/",
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
+ "wiki": "https://wiki-dev.open-semantic-lab.org/id/",
+ "Category": {"@id": "wiki:Category-3A", "@prefix": True},
+ "File": {
+ "@id": "https://wiki-dev.open-semantic-lab.org/wiki/Special:Redirect/file/",
+ "@prefix": True,
+ },
+ "Property": {"@id": "wiki:Property-3A", "@prefix": True},
+ "Item": {"@id": "wiki:Item-3A", "@prefix": True},
+ "attachments*": {"@id": "Property:HasFileAttachment", "@type": "@id"},
+ "based_on": {"@id": "skos:isBasedOn", "@type": "@id"},
+ "based_on*": {"@id": "Property:IsBasedOn", "@type": "@id"},
+ "description": {"@id": "skos:definition"},
+ "description*": {"@id": "Property:HasDescription"},
+ "image": {"@id": "schema:image", "@type": "@id"},
+ "image*": {"@id": "Property:HasImage", "@type": "@id"},
+ "label": {"@id": "skos:prefLabel"},
+ "label*": {"@id": "Property:HasLabel"},
+ "lang": {"@id": "@language"},
+ "text": {"@id": "@value"},
+ "uuid*": {"@id": "Property:HasUuid"},
+ "meta": {
+ "@id": "Property:HasMeta",
+ "@type": "@id",
+ "@context": {
+ "change_id": {
+ "@id": "Property:HasChangeId",
+ "@type": "xsd:string",
+ }
+ },
+ },
+ "name*": {"@id": "Property:HasName"},
+ "ordering_categories": {"@id": "Property:Category", "@type": "@id"},
+ "ordering_categories*": {
+ "@id": "Property:HasClassificationCategory",
+ "@type": "@id",
+ },
+ "query_label": {"@id": "Property:HasLabel", "@type": "@id"},
+ "rdf_type": {"@id": "@type", "@type": "@id"},
+ "rdf_type*": {"@id": "schema:additionalType", "@type": "@id"},
+ "rdf_type**": {"@id": "owl:sameAs", "@type": "@id"},
+ "rdf_type***": {"@id": "Property:Equivalent_URI", "@type": "@id"},
+ "short_name": {"@id": "Property:HasShortName"},
+ "keywords": {"@id": "schema:keywords", "@type": "@id"},
+ "keywords*": {"@id": "Property:HasKeyword", "@type": "@id"},
+ "statements": {"@id": "Property:HasStatement", "@type": "@id"},
+ },
+ "uuid": "ce353767-c628-45bd-9d88-d6eb3009aec0",
+ "title": "Entity",
+ "defaultProperties": ["description"],
+ }
+
+ rdf_type: Optional[Set[str]] = Field(
+ None,
+ description_={
+ "de": "Gibt zusätzliche Typens für diese Entität an, z. B. um anzugeben, dass diese Entität dieselbe Bedeutung hat wie ein Begriff in einem kontrollierten Vokabular oder einer Ontologie. Diese Eigenschaft ist ein Synonym für schema:additionalType und owl:sameAs. Die Standardsyntax ist ontology:TermName. Das Ontologie-Präfix muss im @cotext der Entität, der Kategorie oder einer der beerbten Kategorien definiert werden. Der Termname muss ein gültiger Bezeichner in der Ontologie sein."
+ },
+ title="Additional RDF type(s)",
+ title_={"de": "Zusätzliche(r) RDF-Typ(en)"},
+ )
"""
Declares additional type(s) for this entity, e.g., to state that this entity has the same meaning as a term in a controlled vocabulary or ontology. This property is synonymous to the schema:additionalType and owl:sameAs. The default syntax is ontology:TermName. The ontology prefix has to be defined in the @context of the Entity, the category or any of the parent categories. The term name has to be a valid identifier in the ontology.
"""
- uuid: UUID = Field(default_factory=uuid4, title="UUID")
- iri: Optional[str] = Field(None, title="IRI")
+ uuid: UUID = Field(default_factory=uuid4, options={"hidden": True}, title="UUID")
+ iri: Optional[str] = Field(
+ None,
+ description_={
+ "de": "Der internationalisierte Ressourcenbezeichner (IRI) dieser Entität"
+ },
+ options={"hidden": True},
+ title="IRI",
+ )
"""
The Internationalized Resource Identifier (IRI) of this entity
"""
- name: Optional[str] = Field(None, title="Technical name")
+ name: Optional[str] = Field(
+ None,
+ description_={"de": "Technischer / Maschinenkompatibler Name"},
+ options={"hidden": True},
+ title="Technical name",
+ title_={"de": "Technischer Name"},
+ )
"""
Technical / Machine compatible name
"""
- label: List[Label] = Field(..., min_items=1, title="Label(s)")
+ label: List[Label] = Field(
+ ...,
+ description_={"de": "Mindestens eine Bezeichnung ist erforderlich."},
+ eval_template=[
+ {
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "$comment": "Displays value according to user language with eng as fallback option. Note: {{=<% %>=}} changes mustache expression from {{..}} to <% %> for mixing with wikitext templates",
+ "value": "{{=<% %>=}} {{#switch:{{USERLANGUAGECODE}} <%#label%> | {{#ifeq: <%lang%>|en|#default|<%lang%>}} = <%text%> <%/label%> }}",
+ }
+ ],
+ min_items=1,
+ title="Label(s)",
+ title_={"de": "Bezeichnung(en)"},
+ )
"""
At least one label is required.
"""
- short_name: Optional[List[Label]] = Field(None, title="Short name(s)")
+ short_name: Optional[List[Label]] = Field(
+ None,
+ description_={"de": "Abkürzung, Akronym, etc."},
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "$comment": "Displays value according to user language with eng as fallback option. Note: {{=<% %>=}} changes mustache expression from {{..}} to <% %> for mixing with wikitext templates",
+ "value": "{{=<% %>=}} {{#switch:{{USERLANGUAGECODE}} <%#short_name%> | {{#ifeq: <%lang%>|en|#default|<%lang%>}} = <%text%> <%/short_name%> }}",
+ },
+ title="Short name(s)",
+ title_={"de": "Kurzname(n)"},
+ )
"""
Abbreviation, Acronym, etc.
"""
- query_label: Optional[str] = Field(None, title="Query label")
- description: Optional[List[Description]] = Field(None, title="Description")
- image: Optional[str] = Field(None, title="Image")
- ordering_categories: Optional[List[str]] = Field(None, title="Ordering categories")
- """
- Ordering categories are used to categorize instances, e.g., according to their use but not their properties. When querying for instances of a here listed ordering category, this instance will be returned. Note: Ordering categories define no properties, while 'regular' categories define properties, which an instance assigns values to.
+ query_label: Optional[str] = Field(
+ None,
+ options={"hidden": True, "conditional_visible": {"modes": ["query"]}},
+ title="Query label",
+ title_={"de": "Abfrage-Bezeichnung"},
+ )
+ description: Optional[List[Description]] = Field(
+ None,
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "$comment": "Displays value according to user language with eng as fallback option. Note: {{=<% %>=}} changes mustache expression from {{..}} to <% %> for mixing with wikitext templates",
+ "value": "{{=<% %>=}} {{#switch:{{USERLANGUAGECODE}} <%#description%> | {{#ifeq: <%lang%>|en|#default|<%lang%>}} = <%text%> <%/description%> }}",
+ },
+ title="Description",
+ title_={"de": "Beschreibung"},
+ )
+ image: Optional[str] = Field(
+ None,
+ links=[
+ {
+ "href": "{{#if self}}/w/index.php?title=Special:Redirect/file/{{self}}&width=200&height=200{{/if}}",
+ "mediaType": "image",
+ },
+ {
+ "href": "{{#if self}}/w/index.php?title=Special:Redirect/file/{{self}}{{/if}}",
+ "rel": "{{#if self}}download{{/if}}",
+ "download": True,
+ },
+ ],
+ options={"upload": {}},
+ propertyOrder=1020,
+ title="Image",
+ title_={"de": "Bild"},
+ )
+ ordering_categories: Optional[List[str]] = Field(
+ None,
+ description_={
+ "de": "Ordnungskategorien werden verwendet, um Instanzen zu kategorisieren, z. B. nach ihrer Verwendung, nicht aber nach ihren Eigenschaften. Bei der Abfrage nach Instanzen einer hier aufgeführten Ordnungskategorie wird diese Instanz zurückgegeben. Hinweis: Ordnungskategorien definieren keine Eigenschaften, während 'normale' Kategorien Eigenschaften definieren, denen eine Instanz Werte zuweist."
+ },
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{=<% %>=}}<%#ordering_categories%>[[:<%.%>]]
<%/ordering_categories%>",
+ },
+ title="Classification categories",
+ title_={"de": "Ordnungskategorien"},
+ )
"""
- keywords: Optional[List[str]] = Field(None, title="Keywords / Tags")
+ Classification categories are used to categorize instances, e.g., according to their use but not their properties. When querying for instances of a here listed classification category, this instance will be returned. Note: Classification categories define no properties, while 'regular' categories define properties, which an instance assigns values to.
+ """
+ keywords: Optional[List[Keyword]] = Field(
+ None,
+ description_={"de": "Dient der nutzerdefinierten Kategorisierung des Elements"},
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{#keywords}}[[{{{.}}}]]
{{/keywords}}",
+ },
+ range="Category:OSW09f6cdd54bc54de786eafced5f675cbe",
+ title="Keywords / Tags",
+ title_={"de": "Schlagworte / Tags"},
+ )
"""
Designated to the user defined categorization of this element
"""
- based_on: Optional[List[str]] = Field(None, title="Based on")
+ based_on: Optional[List[str]] = Field(
+ None,
+ description_={
+ "de": "Andere Entitäten auf die diese basiert, z. B. wenn sie durch Kopieren entstanden ist"
+ },
+ options={"hidden": "true"},
+ title="Based on",
+ title_={"de": "Basierend auf"},
+ )
"""
Other entities on which this one is based, e.g. when it is created by copying
"""
statements: Optional[
List[Union[ObjectStatement, DataStatement, QuantityStatement]]
- ] = Field(None, title="Statements")
- attachments: Optional[List[str]] = Field(None, title="File attachments")
- meta: Optional[Meta] = None
+ ] = Field(
+ None,
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{#statements}}.. {{#predicate}}[[{{predicate}}]]{{/predicate}}{{#property}}[[{{property}}]]{{/property}}{{#quantity}}[[{{quantity}}]]{{/quantity}} {{#object}}[[{{object}}]]{{/object}}{{#value}}{{value}}{{/value}}
{{/statements}}",
+ },
+ propertyOrder=1010,
+ title="Statements",
+ title_={"de": "Aussagen"},
+ )
+ attachments: Optional[List[str]] = Field(
+ None,
+ eval_template={
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{=<% %>=}} <%={{ }}=%> {{#attachments}}{{{.}}};{{/attachments}} {{=<% %>=}} <%={{ }}=%>",
+ },
+ options={"collapsed": True},
+ propertyOrder=1030,
+ title="File attachments",
+ title_={"de": "Dateianhänge"},
+ )
+ meta: Optional[Meta] = Field(None, options={"hidden": True})
class ObjectStatement(OswBaseModel):
- rdf_type: Optional[Any] = "rdf:Statement"
- uuid: UUID = Field(default_factory=uuid4, title="UUID")
- label: Optional[List[Label]] = Field(None, title="Label")
+ class Config:
+ schema_extra = {"title": "Object Statement"}
+
+ rdf_type: Optional[Any] = Field("rdf:Statement", options={"hidden": True})
+ uuid: UUID = Field(default_factory=uuid4, options={"hidden": True}, title="UUID")
+ label: Optional[List[Label]] = Field(
+ None, options={"collapsed": True, "grid_columns": 12}, title="Label"
+ )
"""
Human readable name
"""
- subject: Optional[str] = Field(None, title="Subject")
+ subject: Optional[str] = Field(
+ None,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"query": "[[Category:Entity]]OR[[:Category:%2B]]"},
+ },
+ title="Subject",
+ title_={"de": "Subjekt"},
+ )
substatements: Optional[
List[Union[ObjectStatement, DataStatement, QuantityStatement]]
- ] = Field(None, title="Substatements")
- predicate: str = Field(..., title="Predicate")
- object: str = Field(..., title="Object")
+ ] = Field(
+ None, options={"grid_columns": 12}, propertyOrder=3010, title="Substatements"
+ )
+ predicate: str = Field(
+ ...,
+ options={"grid_columns": 4, "autocomplete": {"category": "Category:Property"}},
+ title="Predicate",
+ title_={"de": "Prädikat"},
+ )
+ object: str = Field(
+ ...,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"query": "[[Category:Entity]]OR[[:Category:%2B]]"},
+ },
+ title="Object",
+ title_={"de": "Objekt"},
+ )
class DataStatement(OswBaseModel):
- rdf_type: Optional[Any] = "rdf:Statement"
- uuid: UUID = Field(default_factory=uuid4, title="UUID")
- label: Optional[List[Label]] = Field(None, title="Label")
+ class Config:
+ schema_extra = {"title": "Data Statement"}
+
+ rdf_type: Optional[Any] = Field("rdf:Statement", options={"hidden": True})
+ uuid: UUID = Field(default_factory=uuid4, options={"hidden": True}, title="UUID")
+ label: Optional[List[Label]] = Field(
+ None, options={"collapsed": True, "grid_columns": 12}, title="Label"
+ )
"""
Human readable name
"""
- subject: Optional[str] = Field(None, title="Subject")
+ subject: Optional[str] = Field(
+ None,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"query": "[[Category:Entity]]OR[[:Category:%2B]]"},
+ },
+ title="Subject",
+ title_={"de": "Subjekt"},
+ )
substatements: Optional[
List[Union[ObjectStatement, DataStatement, QuantityStatement]]
- ] = Field(None, title="Substatements")
- property: str = Field(..., title="Property")
- value: str = Field(..., title="Value")
+ ] = Field(
+ None, options={"grid_columns": 12}, propertyOrder=3010, title="Substatements"
+ )
+ property: str = Field(
+ ...,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"category": "Category:DataProperty"},
+ },
+ title="Property",
+ title_={"de": "Attribut"},
+ )
+ value: str = Field(
+ ..., options={"grid_columns": 4}, title="Value", title_={"de": "Wert"}
+ )
class QuantityStatement(OswBaseModel):
- rdf_type: Optional[Any] = "rdf:Statement"
- uuid: UUID = Field(default_factory=uuid4, title="UUID")
- label: Optional[List[Label]] = Field(None, title="Label")
+ class Config:
+ schema_extra = {"title": "Quantity Statement"}
+
+ rdf_type: Optional[Any] = Field("rdf:Statement", options={"hidden": True})
+ uuid: UUID = Field(default_factory=uuid4, options={"hidden": True}, title="UUID")
+ label: Optional[List[Label]] = Field(
+ None, options={"collapsed": True, "grid_columns": 12}, title="Label"
+ )
"""
Human readable name
"""
- subject: Optional[str] = Field(None, title="Subject")
+ subject: Optional[str] = Field(
+ None,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"query": "[[Category:Entity]]OR[[:Category:%2B]]"},
+ },
+ title="Subject",
+ title_={"de": "Subjekt"},
+ )
substatements: Optional[
List[Union[ObjectStatement, DataStatement, QuantityStatement]]
- ] = Field(None, title="Substatements")
- quantity: str = Field(..., title="Property")
- numerical_value: str = Field(..., title="Value")
- unit: str = Field(..., title="Unit")
- unit_symbol: str
- value: str = Field(..., title="Value")
+ ] = Field(
+ None, options={"grid_columns": 12}, propertyOrder=3010, title="Substatements"
+ )
+ quantity: str = Field(
+ ...,
+ options={
+ "grid_columns": 4,
+ "autocomplete": {"category": "Category:QuantityProperty"},
+ },
+ title="Property",
+ title_={"de": "Attribut"},
+ )
+ numerical_value: str = Field(
+ ..., options={"grid_columns": 2}, title="Value", title_={"de": "Wert"}
+ )
+ unit: str = Field(
+ ...,
+ options={
+ "grid_columns": 2,
+ "autocomplete": {
+ "query": "[[-HasInputUnit::{{$(quantity)}}]][[Display_title_of::like:*{{_user_input}}*]]|?Display_title_of=label",
+ "render_template": {
+ "type": ["handlebars"],
+ "value": "{{result.printouts.label.[0]}}",
+ },
+ "field_maps": [
+ {
+ "source_path": "$",
+ "template": "{{{result.printouts.label.[0]}}}",
+ "target_path": "$(unit_symbol)",
+ }
+ ],
+ },
+ },
+ propertyOrder=1010,
+ title="Unit",
+ title_={"de": "Einheit"},
+ watch={
+ "quantity": "statement.quantity",
+ "unit_symbol": "statement.unit_symbol",
+ },
+ )
+ unit_symbol: str = Field(..., options={"hidden": True})
+ value: str = Field(
+ ...,
+ options={"hidden": True},
+ template="{{{numerical_value}}} {{{unit_symbol}}}",
+ title="Value",
+ title_={"de": "Wert"},
+ watch={
+ "numerical_value": "statement.numerical_value",
+ "unit_symbol": "statement.unit_symbol",
+ },
+ )
class Item(Entity):
+ class Config:
+ schema_extra = {
+ "@context": [
+ "/wiki/Category:Entity?action=raw&slot=jsonschema",
+ {
+ "type": {"@id": "Property:HasType", "@type": "@id"},
+ "type*": {"@id": "Property:HasSchema", "@type": "@id"},
+ },
+ ],
+ "title": "Item",
+ }
+
type: Optional[List[str]] = Field(
- ["Category:Item"], min_items=1, title="Types/Categories"
+ ["Category:Item"],
+ eval_template=[
+ {
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{#type}} [[:{{{.}}}]]
{{/type}}",
+ }
+ ],
+ min_items=1,
+ options={
+ "collapsed": True,
+ "conditional_visible": {"modes": ["default", "query"]},
+ },
+ propertyOrder=-1000,
+ title="Types/Categories",
+ title_={"de": "Typen/Kategorien"},
)
entry_access: Optional[AccessRestrictions] = Field(
- None, title="Access restrictions"
+ None,
+ eval_template={
+ "$comment": "See https://www.mediawiki.org/wiki/Extension:Semantic_ACL",
+ "type": "mustache-wikitext",
+ "mode": "render",
+ "value": "{{entry_access.read.level}} {{=<% %>=}} {{#set: |Visible to= {{#switch: <%={{ }}=%> {{{entry_access.read.level}}} {{=<% %>=}} |public=public |internal=users |restricted=whitelist |#default=}} }} <%={{ }}=%>",
+ },
+ title="Access restrictions",
+ title_={"de": "Zugriffsbeschränkungen"},
)
+class IntangibleItem(Item):
+ """
+ A utility class that serves as the umbrella for a number of 'intangible' things such as terms, quantities, structured values, etc.
+ """
+
+ class Config:
+ schema_extra = {
+ "@context": ["/wiki/Category:Item?action=raw&slot=jsonschema"],
+ "uuid": "cbb09a36-3367-40c6-a2cd-62db9bf647ec",
+ "title": "IntangibleItem",
+ "title*": {"en": "Intangible Item", "de": "Immaterieller Gegenstand"},
+ "description": "A utility class that serves as the umbrella for a number of 'intangible' things such as terms, quantities, structured values, etc.",
+ "description*": {
+ "en": "A utility class that serves as the umbrella for a number of 'intangible' things such as terms, quantities, structured values, etc.",
+ "de": 'Eine Gebrauchsklasse, die als Dach für eine Reihe von "immateriellen" Dingen wie Begriffe, Mengen, strukturierte Werte usw. dient.',
+ },
+ }
+
+ type: Optional[Any] = ["Category:OSWcbb09a36336740c6a2cd62db9bf647ec"]
+
+
+class DefinedTerm(IntangibleItem):
+ """
+ Definition of a noun or compound word describing a specific concept
+ """
+
+ class Config:
+ schema_extra = {
+ "@context": [
+ "/wiki/Category:OSWcbb09a36336740c6a2cd62db9bf647ec?action=raw&slot=jsonschema"
+ ],
+ "uuid": "a5812d3b-5119-416c-8da1-606cbe7054eb",
+ "title": "DefinedTerm",
+ "title*": {"en": "Defined Term", "de": "Definierter Begriff"},
+ "description": "Definition of a noun or compound word describing a specific concept",
+ "description*": {
+ "en": "Definition of a noun or compound word describing a specific concept",
+ "de": "Definition eines Ausdruckes der ein spezifisches Konzept repräsentiert",
+ },
+ }
+
+ type: Optional[Any] = ["Category:OSWa5812d3b5119416c8da1606cbe7054eb"]
+
+
+class Keyword(DefinedTerm):
+ """
+ Used as a user defined tag to label entitites
+ """
+
+ class Config:
+ schema_extra = {
+ "@context": [
+ "/wiki/Category:OSWa5812d3b5119416c8da1606cbe7054eb?action=raw&slot=jsonschema"
+ ],
+ "uuid": "09f6cdd5-4bc5-4de7-86ea-fced5f675cbe",
+ "title": "Keyword",
+ "title*": {"en": "Keyword", "de": "Schlüsselwort"},
+ "description": "Used as a user defined tag to label entitites",
+ "description*": {
+ "en": "Used as a user defined tag to label entitites",
+ "de": "Wird als nutzerdefiniertes Etikett / Tag genutzt um Entitäten zu labeln",
+ },
+ }
+
+ type: Optional[Any] = ["Category:OSW09f6cdd54bc54de786eafced5f675cbe"]
+
+
+Entity.update_forward_refs()
+ObjectStatement.update_forward_refs()
+DataStatement.update_forward_refs()
+QuantityStatement.update_forward_refs()
+Item.update_forward_refs()
+IntangibleItem.update_forward_refs()
+DefinedTerm.update_forward_refs()
+Keyword.update_forward_refs()
+# generated by datamodel-codegen:
+# filename: Entity.json
+# timestamp: 2025-05-18T15:16:03+00:00
+
+
+from enum import Enum
+from typing import Any, List, Optional, Set, Union
+from uuid import UUID
+
+from opensemantic import OswBaseModel
+from pydantic.v1 import Field, constr
+
Entity.update_forward_refs()
ObjectStatement.update_forward_refs()
DataStatement.update_forward_refs()
QuantityStatement.update_forward_refs()
Item.update_forward_refs()
+IntangibleItem.update_forward_refs()
+DefinedTerm.update_forward_refs()
+Keyword.update_forward_refs()
diff --git a/src/osw/model/static.py b/src/osw/model/static.py
deleted file mode 100644
index 8b18fd7..0000000
--- a/src/osw/model/static.py
+++ /dev/null
@@ -1,390 +0,0 @@
-"""
-This module is to be imported in the dynamically created and updated entity.py module.
-"""
-
-from typing import TYPE_CHECKING, Literal, Optional, Type, TypeVar, Union
-from uuid import UUID, uuid4
-from warnings import warn
-
-from pydantic.v1 import BaseModel, Field, constr
-
-from osw.custom_types import NoneType
-from osw.utils.strings import pascal_case
-
-T = TypeVar("T", bound=BaseModel)
-
-# This is dirty, but required for autocompletion:
-# https://stackoverflow.com/questions/62884543/pydantic-autocompletion-in-vs-code
-# Ideally, solved by custom templates in the future:
-# https://github.com/koxudaxi/datamodel-code-generator/issues/860
-# ToDo: Still needed?
-
-if TYPE_CHECKING:
- from dataclasses import dataclass as _basemodel_decorator
-else:
- _basemodel_decorator = lambda x: x # noqa: E731
-
-
-def custom_issubclass(obj: Union[type, T], class_name: str) -> bool:
- """
- Custom issubclass function that checks if the object is a subclass of a class
- with the given name.
-
- Parameters
- ----------
- obj : object
- The object to check.
- class_name : str
- The name of the class to check against.
-
- Returns
- -------
- bool
- True if the object is a subclass of the class with the given name,
- False otherwise.
- """
-
- def check_bases(cls, name):
- if hasattr(cls, "__name__") and cls.__name__ == name:
- return True
- if not hasattr(cls, "__bases__"):
- return False
- for base in cls.__bases__:
- if check_bases(base, name):
- return True
- return False
-
- return check_bases(obj, class_name)
-
-
-def custom_isinstance(obj: Union[type, T], class_name: str) -> bool:
- """
- Custom isinstance function that checks if the object is an instance of a class with
- the given name.
-
- Parameters
- ----------
- obj : object
- The object to check.
- class_name : str
- The name of the class to check against.
-
- Returns
- -------
- bool
- True if the object is an instance of the class with the given name,
- False otherwise.
- """
- if not hasattr(obj, "__class__"):
- return False
-
- return custom_issubclass(obj.__class__, class_name)
-
-
-@_basemodel_decorator
-class OswBaseModel(BaseModel):
-
- class Config:
- """Configuration for the OswBaseModel"""
-
- # strict = False
- # extra = "ignore"
- # Additional fields are ignored
- validate_assignment = True
- # Ensures that the assignment of a value to a field is validated
- smart_union = True
- # To avoid unexpected coercing of types, the smart_union option is enabled
- # See: https://docs.pydantic.dev/1.10/usage/model_config/#smart-union
- # Not required in v2 as this will become the new default
-
- def __init__(self, **data):
- if data.get("label"):
- if not isinstance(data["label"], list):
- raise ValueError(
- "label must be a list of Label objects",
- )
- labels = []
- for label in data["label"]:
- if isinstance(label, dict):
- labels.append(Label(**label))
- else:
- # The list element should be a Label object
- labels.append(label)
- data["label"] = labels
- # Ensure that the label attribute is a list of Label objects, but use
- # custom_isinstance to avoid circular imports and ValidationError since
- # osw.model.entity defines its own Label class
- if not all(custom_isinstance(label, "Label") for label in data["label"]):
- raise ValueError(
- "label must be a list of Label objects",
- )
- if data.get("name") is None and "label" in data:
- data["name"] = pascal_case(data["label"][0].text)
- if "uuid" not in data:
- # If no uuid is provided, generate a new one
- data["uuid"] = OswBaseModel._init_uuid(**data)
- super().__init__(**data)
-
- @classmethod
- def _init_uuid(cls, **data) -> UUID:
- """Generates a random UUID for the entity if not provided during initialization.
- This method can be overridden to generate a UUID based on the data, e.g.
- for using a UUIDv5 based on the name:
- ```python
- def _get_uuid(**data) -> UUID:
- namespace_uuid = uuid.UUID("0dd6c54a-b162-4552-bab9-9942ccaf4f41")
- return uuid.uuid5(namespace_uuid, data["name"])
- ```
- """
-
- # default: random UUID
- return uuid4()
-
- def full_dict(self, **kwargs): # extent BaseClass export function
- d = super().dict(**kwargs)
- for key in ("_osl_template", "_osl_footer"):
- if hasattr(self, key):
- d[key] = getattr(self, key)
- # Include selected private properties. note: private properties are not
- # considered as discriminator
- return d
-
- def cast(
- self,
- cls: Union[Type[T], type],
- none_to_default: bool = False,
- remove_extra: bool = False,
- silent: bool = True,
- **kwargs,
- ) -> T:
- """Casting self into target class
-
- Parameters
- ----------
- cls
- target class
- kwargs
- additional attributes to be set
- none_to_default
- If True, attributes that are None will be set to their default value
- remove_extra
- If True, extra attributes that are passed to the constructor are removed
- silent
- If True, no warnings are printed
- Returns
- -------
- instance of target class
- """
-
- def empty_list_or_none(
- obj: Union[
- NoneType,
- list,
- ]
- ) -> bool:
- if obj is None:
- return True
- elif isinstance(obj, list):
- if len(obj) == 0:
- return True
- elif len([item for item in obj if item is not None]) == 0:
- return True
- return False
-
- combined_args = {**self.dict(), **kwargs}
- none_args = []
- if none_to_default:
- reduced = {}
- for k, v in combined_args.items():
- if empty_list_or_none(v):
- none_args.append(k)
- else:
- reduced[k] = v
- combined_args = reduced
- extra_args = []
- if remove_extra:
- reduced = {}
- for k, v in combined_args.items():
- if k not in cls.__fields__.keys():
- extra_args.append(k)
- else:
- reduced[k] = v
- combined_args = reduced
- if not silent:
- if none_to_default and none_args:
- warn(f"Removed attributes with None or empty list values: {none_args}")
- if remove_extra and extra_args:
- warn(f"Removed extra attributes: {extra_args}")
- if "type" in combined_args:
- del combined_args["type"]
- return cls(**combined_args)
-
- def cast_none_to_default(self, cls: Union[Type[T], type], **kwargs) -> T:
- """Casting self into target class. If the passed attribute is None or solely
- includes None values, the attribute is not passed to the instance of the
- target class, which will then fall back to the default."""
-
- return self.cast(cls, none_to_default=True, **kwargs)
-
- def get_uuid(self) -> Union[str, UUID, NoneType]:
- """Getter for the attribute 'uuid' of the entity
-
- Returns
- -------
- The uuid as a string or None if the uuid could not be determined
- """
- return getattr(self, "uuid", None)
-
- def get_osw_id(self) -> Union[str, NoneType]:
- """Determines the OSW-ID based on the entity's uuid.
-
- Returns
- -------
- The OSW-ID as a string or None if the OSW-ID could not be determined
- """
- return get_osw_id(self)
-
- def get_namespace(self) -> Union[str, NoneType]:
- """Determines the wiki namespace based on the entity's type/class
-
- Returns
- -------
- The namespace as a string or None if the namespace could not be determined
- """
- return get_namespace(self)
-
- def get_title(self) -> Union[str, NoneType]:
- """Determines the wiki page title based on the entity's data
-
- Returns
- -------
- The title as a string or None if the title could not be determined
- """
- return get_title(self)
-
- def get_iri(self) -> Union[str, NoneType]:
- """Determines the IRI / wiki full title (namespace:title) based on the entity's
- data
-
- Returns
- -------
- The full title as a string or None if the title could not be determined.
- """
- return get_full_title(self)
-
-
-def get_osw_id(entity: Union[OswBaseModel, Type[OswBaseModel]]) -> Union[str, NoneType]:
- """Determines the OSW-ID based on the entity's data - either from the entity's
- attribute 'osw_id' or 'uuid'.
-
- Parameters
- ----------
- entity
- The entity to determine the OSW-ID for
-
- Returns
- -------
- The OSW-ID as a string or None if the OSW-ID could not be determined
- """
- osw_id = getattr(entity, "osw_id", None)
- uuid = entity.get_uuid()
- from_uuid = None if uuid is None else f"OSW{str(uuid).replace('-', '')}"
- if osw_id is None:
- return from_uuid
- if osw_id != from_uuid:
- raise ValueError(f"OSW-ID does not match UUID: {osw_id} != {from_uuid}")
- return osw_id
-
-
-def get_namespace(
- entity: Union[OswBaseModel, Type[OswBaseModel]]
-) -> Union[str, NoneType]:
- """Determines the wiki namespace based on the entity's type/class
-
- Parameters
- ----------
- entity
- The entity to determine the namespace for
-
- Returns
- -------
- The namespace as a string or None if the namespace could not be determined
- """
- namespace = None
-
- if hasattr(entity, "meta") and entity.meta and entity.meta.wiki_page:
- if entity.meta.wiki_page.namespace:
- namespace = entity.meta.wiki_page.namespace
-
- if namespace is None:
- if custom_issubclass(entity, "Entity"):
- namespace = "Category"
- elif custom_isinstance(entity, "Category"):
- namespace = "Category"
- elif custom_issubclass(entity, "Characteristic"):
- namespace = "Category"
- elif custom_isinstance(entity, "Item"):
- namespace = "Item"
- elif custom_isinstance(entity, "Property"):
- namespace = "Property"
- elif custom_isinstance(entity, "WikiFile"):
- namespace = "File"
-
- return namespace
-
-
-def get_title(entity: OswBaseModel) -> Union[str, NoneType]:
- """Determines the wiki page title based on the entity's data
-
- Parameters
- ----------
- entity
- the entity to determine the title for
-
- Returns
- -------
- the title as a string or None if the title could not be determined
- """
- title = None
-
- if hasattr(entity, "meta") and entity.meta and entity.meta.wiki_page:
- if entity.meta.wiki_page.title:
- title = entity.meta.wiki_page.title
-
- if title is None:
- title = get_osw_id(entity)
-
- return title
-
-
-def get_full_title(entity: OswBaseModel) -> Union[str, NoneType]:
- """determines the wiki full title (namespace:title) based on the entity's data
-
- Parameters
- ----------
- entity
- the entity to determine the full title for
-
- Returns
- -------
- the full title as a string or None if the title could not be determined
- """
- namespace = get_namespace(entity)
- title = get_title(entity)
- if namespace is not None and title is not None:
- return namespace + ":" + title
- elif title is not None:
- return title
-
-
-class Ontology(OswBaseModel):
- iri: str
- prefix: str
- name: str
- prefix_name: str
- link: str
-
-
-class Label(OswBaseModel):
- text: constr(min_length=1) = Field(..., title="Text")
- lang: Optional[Literal["en", "de"]] = Field("en", title="Lang code")
diff --git a/src/osw/ontology.py b/src/osw/ontology.py
index 749b125..fa72dfa 100644
--- a/src/osw/ontology.py
+++ b/src/osw/ontology.py
@@ -4,13 +4,13 @@
import uuid
from typing import Dict, List, Literal, Optional, Type
+from opensemantic import OswBaseModel
from pydantic.v1 import PrivateAttr
from pyld import jsonld
from rdflib import Graph
from typing_extensions import deprecated
from osw.core import OSW, model
-from osw.model.static import OswBaseModel
from osw.utils.strings import camel_case, pascal_case
from osw.utils.wiki import get_namespace
from osw.wtsite import WtSite
diff --git a/src/osw/utils/regex.py b/src/osw/utils/regex.py
index 3012d2d..46c5a88 100644
--- a/src/osw/utils/regex.py
+++ b/src/osw/utils/regex.py
@@ -1,10 +1,9 @@
import re
from typing import Dict, List, Optional, Union
+from opensemantic import OswBaseModel
from pydantic.v1 import validator
-from osw.model.static import OswBaseModel
-
# Classes
class RegExPatternExtended(OswBaseModel):
diff --git a/src/osw/utils/wiki.py b/src/osw/utils/wiki.py
index 3149627..2d5bda8 100644
--- a/src/osw/utils/wiki.py
+++ b/src/osw/utils/wiki.py
@@ -2,7 +2,7 @@
from uuid import UUID
# Legacy imports:
-from osw.model.static import get_full_title, get_namespace, get_title # noqa: F401
+from opensemantic import get_full_title, get_namespace, get_title # noqa: F401
def get_osw_id(uuid: UUID) -> str:
diff --git a/src/osw/wiki_tools.py b/src/osw/wiki_tools.py
index 6ce979f..c32294a 100644
--- a/src/osw/wiki_tools.py
+++ b/src/osw/wiki_tools.py
@@ -3,9 +3,9 @@
import mwclient
import yaml
+from opensemantic import OswBaseModel
from pydantic.v1 import FilePath
-from osw.model.static import OswBaseModel
from osw.utils.util import parallelize
# try import functions from wikitext.py (relies on the extra dependency osw[wikitext])
diff --git a/src/osw/wtsite.py b/src/osw/wtsite.py
index d4e7a0e..9b4db05 100644
--- a/src/osw/wtsite.py
+++ b/src/osw/wtsite.py
@@ -22,6 +22,7 @@
import requests
from jsonpath_ng.ext import parse
from mwclient.page import Page as MwPage
+from opensemantic import OswBaseModel
from pydantic.v1 import FilePath
from typing_extensions import deprecated
@@ -29,7 +30,6 @@
import osw.utils.util as ut
import osw.wiki_tools as wt
from osw.auth import CredentialManager
-from osw.model.static import OswBaseModel
from osw.utils.regex_pattern import REGEX_PATTERN_LIB
from osw.utils.util import parallelize
from osw.utils.wiki import get_osw_id