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

Add linting and formatting to linkml-runtime #347

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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
39 changes: 39 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Lint

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

jobs:
poetry:
name: Poetry Lockfile Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: poetry
- name: Check pyproject.toml and poetry.lock
run: poetry check

lint:
name: Lint Code
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install tox
run: python -m pip install tox
- name: Run code quality checks
run: tox -e lint
8 changes: 5 additions & 3 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.12"]
python-version: ["3.9", "3.10", "3.12", "3.13"]
exclude:
- os: windows-latest
python-version: "3.8"
python-version: "3.10"
- os: windows-latest
python-version: "3.12"

runs-on: ${{ matrix.os }}

Expand All @@ -34,7 +36,7 @@ jobs:
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pypi-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Python
uses: actions/[email protected]
with:
python-version: 3.8
python-version: 3.12

- name: Install Poetry
run: pipx install poetry
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-upstream.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ]
exclude:
- os: windows-latest
python-version: "3.9"
- os: windows-latest
python-version: "3.10"
- os: windows-latest
python-version: "3.11"
- os: windows-latest
python-version: "3.12"
runs-on: ${{ matrix.os }}
env:
POETRY_VIRTUALENVS_IN_PROJECT: true
Expand Down
42 changes: 26 additions & 16 deletions linkml_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
from pathlib import Path

from rdflib import OWL, RDF, RDFS, SKOS, XSD

from linkml_runtime.utils.curienamespace import CurieNamespace
from linkml_runtime.utils.schemaview import SchemaView
from rdflib import RDF, RDFS, SKOS, XSD, OWL

__all__ = [
"SchemaView",
]

# use importlib.metadata to read the version provided
# by the package during installation. Do not hardcode
# the version in the code
import importlib.metadata as importlib_metadata

LINKML = CurieNamespace('linkml', 'https://w3id.org/linkml/')
TCCM = CurieNamespace('tccm', 'https://ontologies.r.us/tccm/')
OWL = CurieNamespace('owl', OWL)
RDF = CurieNamespace('rdf', RDF)
RDFS = CurieNamespace('rdfs', RDFS)
SKOS = CurieNamespace('skos', SKOS)
XSD = CurieNamespace('xsd', XSD)
LINKML = CurieNamespace("linkml", "https://w3id.org/linkml/")
TCCM = CurieNamespace("tccm", "https://ontologies.r.us/tccm/")
OWL = CurieNamespace("owl", OWL)
RDF = CurieNamespace("rdf", RDF)
RDFS = CurieNamespace("rdfs", RDFS)
SKOS = CurieNamespace("skos", SKOS)
XSD = CurieNamespace("xsd", XSD)

__version__ = importlib_metadata.version(__name__)

Expand All @@ -35,24 +41,28 @@


URI_TO_LOCAL = {
'https://w3id.org/linkml/annotations.yaml': str(LINKML_ANNOTATIONS),
'https://w3id.org/linkml/array.yaml': str(LINKML_ARRAY),
'https://w3id.org/linkml/extensions.yaml': str(LINKML_EXTENSIONS),
'https://w3id.org/linkml/mappings.yaml': str(LINKML_MAPPINGS),
'https://w3id.org/linkml/meta.yaml': str(MAIN_SCHEMA_PATH),
'https://w3id.org/linkml/types.yaml': str(LINKML_TYPES),
'https://w3id.org/linkml/units.yaml': str(LINKML_UNITS),
'https://w3id.org/linkml/validation.yaml': str(LINKML_VALIDATION),
"https://w3id.org/linkml/annotations.yaml": str(LINKML_ANNOTATIONS),
"https://w3id.org/linkml/array.yaml": str(LINKML_ARRAY),
"https://w3id.org/linkml/extensions.yaml": str(LINKML_EXTENSIONS),
"https://w3id.org/linkml/mappings.yaml": str(LINKML_MAPPINGS),
"https://w3id.org/linkml/meta.yaml": str(MAIN_SCHEMA_PATH),
"https://w3id.org/linkml/types.yaml": str(LINKML_TYPES),
"https://w3id.org/linkml/units.yaml": str(LINKML_UNITS),
"https://w3id.org/linkml/validation.yaml": str(LINKML_VALIDATION),
}


class MappingError(ValueError):
"""
An error when mapping elements of a LinkML model to runtime objects
"""

pass


class DataNotFoundError(ValueError):
"""
An error in which data cannot be found
"""

pass
2 changes: 1 addition & 1 deletion linkml_runtime/dumpers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from linkml_runtime.dumpers.csv_dumper import CSVDumper
from linkml_runtime.dumpers.json_dumper import JSONDumper
from linkml_runtime.dumpers.rdf_dumper import RDFDumper
from linkml_runtime.dumpers.rdflib_dumper import RDFLibDumper
from linkml_runtime.dumpers.tsv_dumper import TSVDumper
from linkml_runtime.dumpers.yaml_dumper import YAMLDumper
from linkml_runtime.dumpers.csv_dumper import CSVDumper

json_dumper = JSONDumper()
rdf_dumper = RDFDumper()
Expand Down
26 changes: 14 additions & 12 deletions linkml_runtime/dumpers/delimited_file_dumper.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import io
import yaml
import json
from abc import ABC, abstractmethod
from typing import Union

from json_flattener import GlobalConfig, flatten_to_csv
from pydantic import BaseModel

from linkml_runtime.dumpers.dumper_root import Dumper
from linkml_runtime.dumpers.json_dumper import JSONDumper
from linkml_runtime.utils.yamlutils import YAMLRoot
from linkml_runtime.linkml_model.meta import SlotDefinitionName, SchemaDefinition
from linkml_runtime.linkml_model.meta import SchemaDefinition, SlotDefinitionName
from linkml_runtime.utils.csvutils import get_configmap
from linkml_runtime.utils.schemaview import SchemaView

from linkml_runtime.utils.csvutils import GlobalConfig, get_configmap
from json_flattener import flatten_to_csv
from linkml_runtime.utils.yamlutils import YAMLRoot


class DelimitedFileDumper(Dumper, ABC):
Expand All @@ -22,12 +21,15 @@ class DelimitedFileDumper(Dumper, ABC):
def delimiter(self):
pass

def dumps(self, element: Union[BaseModel, YAMLRoot],
index_slot: SlotDefinitionName = None,
schema: SchemaDefinition = None,
schemaview: SchemaView = None,
**kwargs) -> str:
""" Return element formatted as CSV lines """
def dumps(
self,
element: Union[BaseModel, YAMLRoot],
index_slot: SlotDefinitionName = None,
schema: SchemaDefinition = None,
schemaview: SchemaView = None,
**kwargs,
) -> str:
"""Return element formatted as CSV lines"""
json_dumper = JSONDumper()
element_j = json.loads(json_dumper.dumps(element))
objs = element_j[index_slot]
Expand Down
7 changes: 4 additions & 3 deletions linkml_runtime/dumpers/dumper_root.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from abc import ABC, abstractmethod
from typing import Union

from linkml_runtime.utils.yamlutils import YAMLRoot
from pydantic import BaseModel

from linkml_runtime.utils.yamlutils import YAMLRoot


class Dumper(ABC):
""" Abstract base class for all dumpers """
"""Abstract base class for all dumpers"""

def dump(self, element: Union[BaseModel, YAMLRoot], to_file: str, **_) -> None:
"""
Expand All @@ -15,7 +16,7 @@ def dump(self, element: Union[BaseModel, YAMLRoot], to_file: str, **_) -> None:
:param to_file: file to dump to
:@param _: method specific arguments
"""
with open(to_file, 'w', encoding='UTF-8') as output_file:
with open(to_file, "w", encoding="UTF-8") as output_file:
output_file.write(self.dumps(element, **_))

@abstractmethod
Expand Down
32 changes: 17 additions & 15 deletions linkml_runtime/dumpers/json_dumper.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import json
from decimal import Decimal
from typing import Dict, Union
from pydantic import BaseModel
from typing import Union

from deprecated.classic import deprecated
from jsonasobj2 import JsonObj
from pydantic import BaseModel

from linkml_runtime.dumpers.dumper_root import Dumper
from linkml_runtime.utils import formatutils
from linkml_runtime.utils.context_utils import CONTEXTS_PARAM_TYPE
from linkml_runtime.utils.formatutils import remove_empty_items
from linkml_runtime.utils.yamlutils import YAMLRoot, as_json_object
from jsonasobj2 import JsonObj


class JSONDumper(Dumper):

def dump(self, element: Union[BaseModel, YAMLRoot], to_file: str, contexts: CONTEXTS_PARAM_TYPE = None,
**kwargs) -> None:
def dump(
self, element: Union[BaseModel, YAMLRoot], to_file: str, contexts: CONTEXTS_PARAM_TYPE = None, **kwargs
) -> None:
"""
Write element as json to to_file
:param element: LinkML object to be serialized as YAML
Expand All @@ -30,7 +31,7 @@ def dump(self, element: Union[BaseModel, YAMLRoot], to_file: str, contexts: CONT
* A list containing elements of any type named above
"""
if isinstance(element, BaseModel):
element = element.dict()
element = element.model_dump()
super().dump(element, to_file, contexts=contexts, **kwargs)

def dumps(self, element: Union[BaseModel, YAMLRoot], contexts: CONTEXTS_PARAM_TYPE = None, inject_type=True) -> str:
Expand All @@ -50,33 +51,34 @@ def dumps(self, element: Union[BaseModel, YAMLRoot], contexts: CONTEXTS_PARAM_TY

def default(o):
if isinstance(o, BaseModel):
return remove_empty_items(o.dict(), hide_protected_keys=True)
return remove_empty_items(o.model_dump(), hide_protected_keys=True)
if isinstance(o, YAMLRoot):
return remove_empty_items(o, hide_protected_keys=True)
elif isinstance(o, Decimal):
# https://stackoverflow.com/questions/1960516/python-json-serialize-a-decimal-object
return str(o)
else:
return json.JSONDecoder().decode(o)

if isinstance(element, BaseModel):
element = element.dict()
return json.dumps(as_json_object(element, contexts, inject_type=inject_type),
default=default,
ensure_ascii=False,
indent=' ')
element = element.model_dump()
return json.dumps(
as_json_object(element, contexts, inject_type=inject_type), default=default, ensure_ascii=False, indent=" "
)

@staticmethod
@deprecated("Use `utils/formatutils/remove_empty_items` instead")
def remove_empty_items(obj: Dict) -> Dict:
def remove_empty_items(obj: dict) -> dict:
"""
Remove empty items from obj
:param obj:
:return: copy of dictionary with empty lists/dicts and Nones removed
"""
return formatutils.remove_empty_items(obj, hide_protected_keys=True)

def to_json_object(self, element: Union[BaseModel, YAMLRoot], contexts: CONTEXTS_PARAM_TYPE = None,
inject_type=True) -> JsonObj:
def to_json_object(
self, element: Union[BaseModel, YAMLRoot], contexts: CONTEXTS_PARAM_TYPE = None, inject_type=True
) -> JsonObj:
"""
As dumps(), except returns a JsonObj, not a string

Expand Down
Loading
Loading