Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
87b0bea
Configure mypy settings in pyproject.toml for stricter type checking
trungdong Jun 7, 2025
1c82259
Set up GitHub Actions workflow for mypy type checks
trungdong Jun 8, 2025
ca814ea
Add type hints and improve docstrings for `identifier.py` #143
trungdong Jun 8, 2025
4e0e91e
Add type hints and refactor `serializers` module with abstract base c…
trungdong Jun 8, 2025
4bff595
Add type hints to `read` function #143
trungdong Jun 8, 2025
b5df843
Add `TYPE_CHECKING` conditional import for `ProvDocument` to avoid ci…
trungdong Jun 9, 2025
64550fa
Update abstract methods in `serializers` to accept additional keyword…
trungdong Jun 9, 2025
22abe7c
Add `TYPE_CHECKING` import in `serializers` to prevent circular depen…
trungdong Jun 9, 2025
15f6bd4
Expand supported input types for the `read` function in `prov` module
trungdong Jun 9, 2025
4c80404
Add type annotations and improve typing consistency in `model.py` #143
trungdong Jun 9, 2025
ce03334
Add type annotations in `provn.py` #143
trungdong Jun 9, 2025
6275cb3
Add type annotations to `dot.py` and `graph.py` #143
trungdong Jun 9, 2025
495d97d
Add type annotations to `convert.py` and `compare.py` #143
trungdong Jun 9, 2025
8441a6a
Add type annotations to `provjson.py` #143
trungdong Jun 9, 2025
03e76ed
Add type annotations and improve typing consistency in `provrdf.py` #143
trungdong Jun 10, 2025
104ffed
Add type annotations and improve typing consistency in `provxml.py` #143
trungdong Jun 10, 2025
eaf8530
Update mypy workflow to check all in the `src` directory
trungdong Jun 10, 2025
48dfa97
Update mypy configuration and workflow
trungdong Jun 10, 2025
70434c9
Add type ignore comments for mypy import-not-found errors in `provrdf…
trungdong Jun 10, 2025
d7ebb85
Restrict mypy workflow to the master branch only
trungdong Jun 10, 2025
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
19 changes: 19 additions & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: mypy check
on:
push:
branches: [main, master]
pull_request:
branches: [main, master, dev]

jobs:
static-type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.x'
- run: pip install mypy
- name: Type checking and install type stubs for third-party packages
run: mypy --install-types --non-interactive src

9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ disable = "C0330, C0326"

[tool.pylint.format]
max-line-length = "88"

[tool.mypy]
python_version = 3.12
exclude = ["prov/tests/*"]

[[tool.mypy.overrides]]
module = "prov.*"
disallow_untyped_defs = true
check_untyped_defs = true
12 changes: 11 additions & 1 deletion src/prov/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
from __future__ import annotations # needed for | type annotations in Python < 3.10
import os
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from prov.model import ProvDocument

__author__ = "Trung Dong Huynh"
__email__ = "trungdong@donggiang.com"
__version__ = "2.0.2"

__all__ = ["Error", "model", "read"]




class Error(Exception):
"""Base class for all errors in this package."""

pass


def read(source, format=None):
def read(source: str | bytes | os.PathLike, format: str | None = None) -> ProvDocument | None:

Check warning on line 23 in src/prov/__init__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/prov/__init__.py#L23

Redefining built-in 'format'
"""
Convenience function returning a ProvDocument instance.

Expand Down Expand Up @@ -39,6 +48,7 @@
try:
return ProvDocument.deserialize(source=source, format=format)
except:
# TODO: Specify an exception type for failing to deserialize.
pass
else:
raise TypeError(
Expand Down
1 change: 1 addition & 0 deletions src/prov/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
PROV_ROLE = PROV["role"]

PROV_QUALIFIEDNAME = PROV["QUALIFIED_NAME"]
PROV_INTERNATIONALIZEDSTRING = PROV["InternationalizedString"]

# XSD DATA TYPES
XSD_ANYURI = XSD["anyURI"]
Expand Down
61 changes: 34 additions & 27 deletions src/prov/dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@

.. moduleauthor:: Trung Dong Huynh <trungdong@donggiang.com>
"""

from __future__ import annotations # needed for | type annotations in Python < 3.10
from datetime import datetime
from html import escape
from typing import Any, Optional

from prov.graph import INFERRED_ELEMENT_CLASS
from prov.identifier import QualifiedName
from prov.model import (
ProvEntity,
ProvActivity,
Expand Down Expand Up @@ -42,13 +47,10 @@
PROV_ATTRIBUTE_QNAMES,
sorted_attributes,
ProvException,
ProvRecord,
ProvElement,
)
import pydot

try:
from html import escape
except ImportError:
from cgi import escape
import pydot # type: ignore[import]

__author__ = "Trung Dong Huynh"
__email__ = "trungdong@donggiang.com"
Expand Down Expand Up @@ -87,7 +89,7 @@
"fillcolor": "lightgray",
"color": "dimgray",
},
}
} # type: dict[Optional[type[ProvElement | ProvBundle]], dict[str, str]]
DOT_PROV_STYLE = {
# Generic node
0: {
Expand Down Expand Up @@ -166,7 +168,7 @@
ANNOTATION_END_ROW = " </TABLE>>"


def htlm_link_if_uri(value):
def htlm_link_if_uri(value: Any) -> str:
try:
uri = value.uri
return '<a href="%s">%s</a>' % (uri, str(value))
Expand All @@ -175,13 +177,13 @@ def htlm_link_if_uri(value):


def prov_to_dot(
bundle,
show_nary=True,
use_labels=False,
direction="BT",
show_element_attributes=True,
show_relation_attributes=True,
):
bundle: ProvBundle,
show_nary: bool = True,
use_labels: bool = False,
direction: str = "BT",
show_element_attributes: bool = True,
show_relation_attributes: bool = True,
) -> pydot.Dot:
"""
Convert a provenance bundle/document into a DOT graphical representation.

Expand All @@ -203,11 +205,11 @@ def prov_to_dot(
direction = "BT" # reset it to the default value
maindot = pydot.Dot(graph_type="digraph", rankdir=direction, charset="utf-8")

node_map = {}
node_map = {} # type: dict[str, pydot.Node]
count = [0, 0, 0, 0] # counters for node ids

def _bundle_to_dot(dot, bundle):
def _attach_attribute_annotation(node, record):
def _bundle_to_dot(dot: pydot.Dot | pydot.Cluster, bundle: ProvBundle) -> None:
def _attach_attribute_annotation(node: pydot.Node, record: ProvRecord) -> None:
# Adding a node to show all attributes
attributes = list(
(attr_name, value)
Expand Down Expand Up @@ -244,17 +246,17 @@ def _attach_attribute_annotation(node, record):
dot.add_node(annotations)
dot.add_edge(pydot.Edge(annotations, node, **ANNOTATION_LINK_STYLE))

def _add_bundle(bundle):
def _add_bundle(bundle: ProvBundle) -> pydot.Cluster:
count[2] += 1
subdot = pydot.Cluster(
graph_name="c%d" % count[2], URL=f'"{bundle.identifier.uri}"'
graph_name="c%d" % count[2], URL=f'"{bundle.identifier.uri}"' # type: ignore[union-attr]
)
subdot.set_label('"%s"' % str(bundle.identifier))
_bundle_to_dot(subdot, bundle)
dot.add_subgraph(subdot)
return subdot

def _add_node(record):
def _add_node(record: ProvRecord) -> pydot.Node:
count[0] += 1
node_id = "n%d" % count[0]
if use_labels:
Expand All @@ -267,12 +269,12 @@ def _add_node(record):
node_label = (
f"<{record.label}<br />"
f'<font color="#333333" point-size="10">'
f'{record.identifier}</font>>'
f"{record.identifier}</font>>"
)
else:
node_label = f'"{record.identifier}"'

uri = record.identifier.uri
uri = record.identifier.uri # type: ignore[union-attr]
style = DOT_PROV_STYLE[record.get_type()]
node = pydot.Node(node_id, label=node_label, URL='"%s"' % uri, **style)
node_map[uri] = node
Expand All @@ -282,7 +284,9 @@ def _add_node(record):
_attach_attribute_annotation(node, rec)
return node

def _add_generic_node(qname, prov_type=None):
def _add_generic_node(
qname: QualifiedName, prov_type: Optional[type[ProvElement]] = None
) -> pydot.Node:
count[0] += 1
node_id = "n%d" % count[0]
node_label = f'"{qname}"'
Expand All @@ -294,14 +298,17 @@ def _add_generic_node(qname, prov_type=None):
dot.add_node(node)
return node

def _get_bnode():
def _get_bnode() -> pydot.Node:
count[1] += 1
bnode_id = "b%d" % count[1]
bnode = pydot.Node(bnode_id, label='""', shape="point", color="gray")
dot.add_node(bnode)
return bnode

def _get_node(qname, prov_type=None):
def _get_node(
qname: Optional[QualifiedName],
prov_type: Optional[type[ProvElement]] = None,
) -> pydot.Node:
if qname is None:
return _get_bnode()
uri = qname.uri
Expand Down Expand Up @@ -382,7 +389,7 @@ def _get_node(qname, prov_type=None):
if add_attribute_annotation:
_attach_attribute_annotation(bnode, rec)
else:
# show a simple binary relations with no annotation
# show a simple binary relation with no annotation
dot.add_edge(
pydot.Edge(
_get_node(nodes[0], inferred_types[0]),
Expand Down
6 changes: 3 additions & 3 deletions src/prov/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@
}


def prov_to_graph(prov_document):
def prov_to_graph(prov_document: ProvDocument) -> nx.MultiDiGraph:
"""
Convert a :class:`~prov.model.ProvDocument` to a `MultiDiGraph
<https://networkx.github.io/documentation/stable/reference/classes/multidigraph.html>`_
instance of the `NetworkX <https://networkx.github.io/>`_ library.

:param prov_document: The :class:`~prov.model.ProvDocument` instance to convert.
"""
g = nx.MultiDiGraph()
g = nx.MultiDiGraph() # type: nx.MultiDiGraph
unified = prov_document.unified()
node_map = dict()
for element in unified.get_records(ProvElement):
Expand All @@ -89,7 +89,7 @@ def prov_to_graph(prov_document):
return g


def graph_to_prov(g):
def graph_to_prov(g: nx.MultiDiGraph) -> ProvDocument:
"""
Convert a `MultiDiGraph
<https://networkx.github.io/documentation/stable/reference/classes/multidigraph.html>`_
Expand Down
Loading