Skip to content
Merged
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 pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "definit"
version = "0.5.0"
version = "0.6.0"
description = "Hierarchy of precise definitions."
readme = "README.md"
requires-python = ">=3.12"
Expand Down
35 changes: 35 additions & 0 deletions src/definit/dag/dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,38 @@ def visit(node_key: DefinitionKey) -> bool:
return False

return True

def get_definition_levels(self) -> list[tuple[Definition, int]]:
"""Return the definition levels.

Each definition is paired with its level in the DAG.
Level 0 corresponds to definitions with no dependencies.
"""
visited: set[DefinitionKey] = set()
added: set[DefinitionKey] = set()
levels: dict[DefinitionKey, int] = {}
definitions_with_levels: list[tuple[Definition, int]] = []

def visit(node_key: DefinitionKey) -> int:
if node_key in visited:
return levels.get(node_key, 0)

visited.add(node_key)
max_level = 0

for neighbor in self._edges.get(node_key, []):
neighbor_level = visit(neighbor)
max_level = max(max_level, neighbor_level + 1)

levels[node_key] = max_level

if node_key not in added:
definitions_with_levels.append((self._definitions[node_key], max_level))
added.add(node_key)

return max_level

for node_key in self._definitions.keys():
visit(node_key)

return definitions_with_levels
7 changes: 7 additions & 0 deletions src/definit/db/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@


class DatabaseAbstract(ABC):
@abstractmethod
def get_dag(self, definition_keys: set[DefinitionKey]) -> DAG:
"""
Get the DAG for a set of definition keys.
"""
...

@abstractmethod
def get_dag_for_definition(self, root: DefinitionKey) -> DAG:
"""
Expand Down
54 changes: 33 additions & 21 deletions src/definit/db/md.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self, data_md_path: Path, load_cache: bool = False) -> None:
def get_dag_for_definition(self, root: DefinitionKey) -> DAG:
self._assure_cache_loaded()
definitions = {root}
return self._get_dag(definitions=definitions)
return self.get_dag(definition_keys=definitions)

def get_index(self, field: Field | None = None) -> set[DefinitionKey]:
index: set[DefinitionKey] = set()
Expand All @@ -67,6 +67,31 @@ def get_definition(self, definition_key: DefinitionKey) -> Definition:
parent_definition_key=None,
)

def get_dag(self, definition_keys: set[DefinitionKey]) -> DAG:
dag = DAG()

for definition_key in definition_keys:
self._update_dag_in_place(definition_key=definition_key, dag=dag)

definition_keys = {key for key in definition_keys}
dag_definition_keys = {definition.key for definition in dag.nodes}
missing_definition_keys = definition_keys - dag_definition_keys

if missing_definition_keys:
_logger.warning(f"Following definitions are not a part of DAG: {missing_definition_keys}")

additional_definition_keys = dag_definition_keys - definition_keys

if additional_definition_keys:
msg = f"Following definitions are part of DAG but not requested: {additional_definition_keys}"
raise DataParserMdException(msg)

if not dag.has_dag_structure():
msg = f"DAG has an invalid structure: {dag.edges}"
raise DataParserMdException(msg)

return dag

##### Static methods #####

@staticmethod
Expand All @@ -88,25 +113,6 @@ def serialize(definitions: list[Definition], db_path: Path) -> None:
def _get_full_path(self, absolute_path: Path) -> str:
return absolute_path.relative_to(self._definitions_path).as_posix()

def _get_dag(self, definitions: set[DefinitionKey]) -> DAG:
dag = DAG()

for definition in definitions:
self._update_dag_in_place(definition_key=definition, dag=dag)

definition_keys = {key for key in definitions}
dag_definition_keys = {definition.key for definition in dag.nodes}
missing_definition_keys = definition_keys - dag_definition_keys

if missing_definition_keys:
_logger.warning(f"Following definitions are not a part of DAG: {missing_definition_keys}")

if not dag.has_dag_structure():
msg = f"DAG has an invalid structure: {dag.edges}"
raise DataParserMdException(msg)

return dag

def _assure_cache_loaded(self) -> None:
if self._definition_uid_to_absolute_path:
return # cache already loaded
Expand Down Expand Up @@ -167,7 +173,13 @@ def _update_dag_in_place(
matches = re.findall(r"\[(.*?)\]\((.*?)\)", definition.content)

for _, child_uid in matches:
child_definition_key = DefinitionKey.from_full_path(child_uid)
absolute_path = self._definition_uid_to_absolute_path[child_uid]
full_path = self._get_full_path(absolute_path=absolute_path).removesuffix(".md")
child_definition_key = DefinitionKey.from_full_path(full_path=full_path)

if child_definition_key == definition_key:
continue # Skip self-references

child_definition = self._get_definition(
definition_key=child_definition_key,
parent_definition_key=definition_key,
Expand Down
10 changes: 8 additions & 2 deletions src/definit/definition/definition_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,24 @@ def from_full_path(full_path: str) -> "DefinitionKey":

def get_reference(self, phrase: str | None = None) -> str:
if phrase is None:
phrase = self.name
phrase = self._fixed_name

return f"[{phrase}]({self.uid})"

def get_index_reference(self) -> str:
return f"[{self.name}]({self.full_path})"
return f"[{self._fixed_name}]({self.full_path})"

# Internal methods

def __hash__(self) -> int:
return hash(self.uid)

def __eq__(self, value: object) -> bool:
if not isinstance(value, DefinitionKey):
return NotImplemented

return self.__hash__() == hash(value)

@cached_property
def _fixed_name(self) -> str:
return self.name.replace(" ", "_").replace("'", "").replace("-", "_").lower()
Loading