Skip to content

Commit 46f3623

Browse files
committed
Merge enhancement into master
2 parents 71c2565 + ee1888d commit 46f3623

9 files changed

Lines changed: 190 additions & 62 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: CI/CD Pipeline
2+
3+
on:
4+
push:
5+
tags: '*.*.*'
6+
release:
7+
types: [ published ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version:
15+
- '3.8'
16+
- '3.9'
17+
- '3.10'
18+
- '3.11'
19+
- '3.12'
20+
- '3.13'
21+
- '3.14'
22+
23+
steps:
24+
- uses: actions/checkout@v3
25+
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v4
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
31+
- name: Install dependencies
32+
run: |
33+
python -m pip install --upgrade pip
34+
python -m pip install pytest pytest-cov
35+
# pip install -e ".[test]"
36+
37+
- name: Run tests
38+
run: |
39+
# python -m pytest tests/ --cov=lib --cov-report=xml
40+
export PYTHONPATH="$(pwd -P)/lib:$PYTHONPATH"
41+
python -m pytest tests/unit/md/python/graph.py --cov=lib --cov-report=term
42+
43+
# - name: Upload coverage to Codecov
44+
# uses: codecov/codecov-action@v3
45+
# if: matrix.python-version == '3.10'
46+
# with:
47+
# file: ./coverage.xml
48+
# fail_ci_if_error: true
49+
50+
security:
51+
runs-on: ubuntu-latest
52+
steps:
53+
- uses: actions/checkout@v3
54+
55+
- name: Set up Python
56+
uses: actions/setup-python@v4
57+
with:
58+
python-version: '3.10'
59+
60+
- name: Install safety
61+
run: pip install safety
62+
63+
- name: Check dependencies for vulnerabilities
64+
run: safety check --full-report
65+
66+
- name: Install bandit
67+
run: pip install bandit
68+
69+
- name: Static security analysis
70+
run: bandit -r lib/ -f json -o bandit-report.json --skip B101
71+
72+
type-check:
73+
runs-on: ubuntu-latest
74+
steps:
75+
- uses: actions/checkout@v3
76+
77+
- name: Set up Python
78+
uses: actions/setup-python@v4
79+
with:
80+
python-version: '3.10'
81+
82+
- name: Install mypy
83+
run: pip install mypy
84+
85+
- name: Type checking
86+
run: |
87+
export PYTHONPATH="$(pwd -P)/lib:$PYTHONPATH"
88+
mypy lib/ --disable-error-code import-untyped
89+
90+
build:
91+
needs: [test, security, type-check]
92+
runs-on: ubuntu-latest
93+
94+
steps:
95+
- uses: actions/checkout@v3
96+
97+
- name: Set up Python
98+
uses: actions/setup-python@v4
99+
with:
100+
python-version: '3.10'
101+
102+
- name: Install build dependencies
103+
run: pip install build wheel
104+
105+
- name: Build wheel package
106+
run: python -m build --wheel
107+
108+
- name: List artifacts
109+
run: ls -la dist/
110+
111+
- name: Upload wheel to GitHub Releases
112+
uses: svenstaro/upload-release-action@v2
113+
with:
114+
repo_token: ${{ secrets.GITHUB_TOKEN }}
115+
file: dist/*.whl
116+
tag: ${{ github.ref }}
117+
overwrite: true
118+
file_glob: true

changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.0] - 2025-12-28
9+
### Added
10+
11+
- [x] feature: add CI/CD github workflow configuration
12+
13+
### Changed
14+
15+
- [x] enhancement: clarify generics types to explicit, fix `mypy` errors
16+
817
## [1.0.0] - 2024-09-01
918

1019
- Initial implementation
1120

21+
[1.1.0]: https://github.com/md-py/md.python.graph/releases/compare/1.0.0...1.1.0
1222
[1.0.0]: https://github.com/md-py/md.python.graph/releases/tag/1.0.0
4.63 KB
Loading

docs/_static/architecture-overview.class-diagram.puml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ package md.python.graph {
1515
+ CYCLE_DETECTED = 1
1616
}
1717

18-
interface TopologicalSortInterface {
19-
+ sort(graph: GraphType) -> Iterable[NodeType]
18+
interface TopologicalSortInterface <NodeType of typing.Hashable> {
19+
+ sort(graph: GraphType[NodeType]) -> Iterable[NodeType]
2020
}
2121

22-
class AscendingTopologicalSort {
23-
+ sort(graph: GraphType) -> Iterable[NodeType]
22+
class AscendingTopologicalSort <NodeType of typing.Hashable> {
23+
+ sort(graph: GraphType[NodeType]) -> Iterable[NodeType]
2424
}
2525

26-
class DescendingTopologicalSort {
27-
+ sort(graph: GraphType) -> Iterable[NodeType]
26+
class DescendingTopologicalSort <NodeType of typing.Hashable> {
27+
+ sort(graph: GraphType[NodeType]) -> Iterable[NodeType]
2828
}
2929

30-
AscendingTopologicalSort .left.> TopologicalSortException : raises
30+
AscendingTopologicalSort .left.> TopologicalSortException : raises >
3131
AscendingTopologicalSort -up-|> TopologicalSortInterface
3232
DescendingTopologicalSort -up-|> TopologicalSortInterface
3333
}

docs/_static/architecture-overview.class-diagram.svg

Lines changed: 17 additions & 17 deletions
Loading

docs/index.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ graph type, and provides few useful tools out from box.
1515
NodeType = typing.TypeVar('NodeType', bound=typing.Hashable)
1616
GraphType = typing.Mapping[NodeType, typing.Collection[NodeType]]
1717
GraphPathType = typing.Iterable[NodeType]
18-
TopologicalSortType = typing.Callable[[GraphType], typing.Iterable[NodeType]]
18+
TopologicalSortType = typing.Callable[[GraphType[NodeType]], typing.Iterable[NodeType]]
1919

2020
# Implementation
21-
def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]: ...
21+
def topological_sort_ascending(graph: GraphType[NodeType]) -> typing.Iterable[NodeType]: ...
2222

2323
def topological_sort_descending(
24-
graph: GraphType,
24+
graph: GraphType[NodeType],
2525
initial_node: typing.Iterable[NodeType] = None
2626
) -> typing.Iterable[NodeType]: ...
2727

28-
def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
29-
typing.List[GraphPathType],
30-
typing.List[GraphPathType],
28+
def get_paths(graph: GraphType[NodeType], include_subtree: bool = False) -> typing.Tuple[
29+
typing.List[GraphPathType[NodeType]],
30+
typing.List[GraphPathType[NodeType]],
3131
]: ...
3232
```
3333

@@ -54,7 +54,7 @@ but defines algorithm direction of nodes traversing.
5454
#### Ascending topological sorting
5555

5656
```python3
57-
def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]: ...
57+
def topological_sort_ascending(graph: GraphType[NodeType]) -> typing.Iterable[NodeType]: ...
5858
```
5959

6060
Ascending topological sorting performs sorting from the bottom to the top,
@@ -117,7 +117,7 @@ a cycle (see [get_path](#graph-paths-retrieval) below, for example).
117117

118118
```python3
119119
def topological_sort_descending(
120-
graph: GraphType,
120+
graph: GraphType[NodeType],
121121
initial_node: typing.Iterable[NodeType] = None
122122
) -> typing.Iterable[NodeType]: ...
123123
```
@@ -188,9 +188,9 @@ and default implementation:
188188
### Graph paths retrieval
189189

190190
```python3
191-
def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
192-
typing.List[GraphPathType],
193-
typing.List[GraphPathType],
191+
def get_paths(graph: GraphType[NodeType], include_subtree: bool = False) -> typing.Tuple[
192+
typing.List[GraphPathType[NodeType]],
193+
typing.List[GraphPathType[NodeType]],
194194
]: ...
195195
```
196196

lib/md/python/graph.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# Metadata
55
__author__ = 'https://md.land/md'
6-
__version__ = '1.0.0'
6+
__version__ = '1.1.0'
77
__all__ = (
88
# Metadata
99
'__author__',
@@ -40,13 +40,13 @@ class GraphExceptionInterface:
4040
class TopologicalSortException(RuntimeError, GraphExceptionInterface):
4141
CYCLE_DETECTED = 1
4242

43-
def __init__(self, *args, code: int = 0, graph: GraphType = None, **kwargs) -> None:
44-
super().__init__(*args, **kwargs)
43+
def __init__(self, *args, code: int = 0, graph: typing.Optional[GraphType] = None) -> None:
44+
super().__init__(*args)
4545
self.code = code
4646
self.graph = graph
4747

4848
@classmethod
49-
def as_cycle_detected(class_, graph: GraphType = None) -> 'TopologicalSortException':
49+
def as_cycle_detected(class_, graph: typing.Optional[GraphType] = None) -> 'TopologicalSortException':
5050
return class_(
5151
'Unable to perform topological sort, graph contains a cycle',
5252
code=class_.CYCLE_DETECTED,
@@ -55,8 +55,8 @@ def as_cycle_detected(class_, graph: GraphType = None) -> 'TopologicalSortExcept
5555

5656

5757
# Contract
58-
class TopologicalSortInterface:
59-
def sort(self, graph: GraphType) -> typing.Iterable[NodeType]:
58+
class TopologicalSortInterface(typing.Generic[NodeType]):
59+
def sort(self, graph: GraphType[NodeType]) -> typing.Iterable[NodeType]:
6060
"""
6161
Performs graph topological sorting and returns sequence of nodes
6262
:param graph: Generic graph structure represented by Mapping of Hashable nodes
@@ -66,7 +66,7 @@ def sort(self, graph: GraphType) -> typing.Iterable[NodeType]:
6666

6767

6868
# Implementation
69-
def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]:
69+
def topological_sort_ascending(graph: GraphType[NodeType]) -> typing.Iterable[NodeType]:
7070
"""
7171
Performs graph topological sorting and returns sequence of nodes (from the bottom)
7272
@@ -82,7 +82,7 @@ def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]:
8282
for node, related_node_collection in graph.items():
8383
normalized_graph[node] = set(related_node_collection)
8484

85-
leave_node_set = set()
85+
leave_node_set: typing.Set[NodeType] = set()
8686

8787
# 2. Search for nodes which are not explicitly defined as an empty graph
8888
for related_node_set in normalized_graph.values():
@@ -100,7 +100,7 @@ def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]:
100100
break
101101

102102
try:
103-
yield from sorted(leave_node_set)
103+
yield from sorted(leave_node_set) # type: ignore
104104
except TypeError:
105105
yield from leave_node_set
106106

@@ -116,8 +116,8 @@ def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]:
116116

117117

118118
def topological_sort_descending(
119-
graph: GraphType,
120-
initial_node: typing.Iterable[NodeType] = None
119+
graph: GraphType[NodeType],
120+
initial_node: typing.Optional[typing.Iterable[NodeType]] = None
121121
) -> typing.Iterable[NodeType]:
122122
"""
123123
Performs graph topological sorting and returns sequence of nodes (from the top)
@@ -160,9 +160,9 @@ def topological_sort_descending(
160160
yield pending_node
161161

162162

163-
def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
164-
typing.List[GraphPathType], # path list without cycle
165-
typing.List[GraphPathType], # path list with cycle
163+
def get_paths(graph: GraphType[NodeType], include_subtree: bool = False) -> typing.Tuple[
164+
typing.Sequence[GraphPathType[NodeType]], # path list without cycle
165+
typing.Sequence[GraphPathType[NodeType]], # path list with cycle
166166
]:
167167
"""
168168
Returns two-sized tuple of graph paths list:
@@ -209,9 +209,9 @@ def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
209209
# 2. Build result
210210
node_list = elder_node_heap
211211
if include_subtree:
212-
node_list = node_path_map.keys()
212+
node_list = list(node_path_map.keys())
213213

214-
path_list: typing.List[GraphPathType] = []
214+
path_list: typing.List[GraphPathType[NodeType]] = []
215215
cycle_path_list = []
216216

217217
for root_node in node_list:
@@ -223,11 +223,11 @@ def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
223223
return path_list, cycle_path_list
224224

225225

226-
class AscendingTopologicalSort(TopologicalSortInterface, typing.Generic[NodeType]):
227-
def sort(self, graph: GraphType) -> typing.Iterable[NodeType]:
226+
class AscendingTopologicalSort(TopologicalSortInterface[NodeType]):
227+
def sort(self, graph: GraphType[NodeType]) -> typing.Iterable[NodeType]:
228228
return topological_sort_ascending(graph=graph)
229229

230230

231-
class DescendingTopologicalSort(TopologicalSortInterface, typing.Generic[NodeType]):
232-
def sort(self, graph: GraphType) -> typing.Iterable[NodeType]:
231+
class DescendingTopologicalSort(TopologicalSortInterface[NodeType]):
232+
def sort(self, graph: GraphType[NodeType]) -> typing.Iterable[NodeType]:
233233
return topological_sort_descending(graph=graph)

readme.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,19 @@ graph type, and provides few useful tools out from box.
1414
NodeType = typing.TypeVar('NodeType', bound=typing.Hashable)
1515
GraphType = typing.Mapping[NodeType, typing.Collection[NodeType]]
1616
GraphPathType = typing.Iterable[NodeType]
17-
TopologicalSortType = typing.Callable[[GraphType], typing.Iterable[NodeType]]
17+
TopologicalSortType = typing.Callable[[GraphType[NodeType]], typing.Iterable[NodeType]]
1818

1919
# Implementation
20-
def topological_sort_ascending(graph: GraphType) -> typing.Iterable[NodeType]: ...
20+
def topological_sort_ascending(graph: GraphType[NodeType]) -> typing.Iterable[NodeType]: ...
2121

2222
def topological_sort_descending(
23-
graph: GraphType,
23+
graph: GraphType[NodeType],
2424
initial_node: typing.Iterable[NodeType] = None
2525
) -> typing.Iterable[NodeType]: ...
2626

27-
def get_paths(graph: GraphType, include_subtree: bool = False) -> typing.Tuple[
28-
typing.List[GraphPathType],
29-
typing.List[GraphPathType],
27+
def get_paths(graph: GraphType[NodeType], include_subtree: bool = False) -> typing.Tuple[
28+
typing.List[GraphPathType[NodeType]],
29+
typing.List[GraphPathType[NodeType]],
3030
]: ...
3131
```
3232

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setuptools.setup(
77
name='md.python.graph',
8-
version='1.0.0',
8+
version='1.1.0',
99
description='Set of contracts & operations over graph type',
1010
long_description=long_description,
1111
long_description_content_type='text/markdown',

0 commit comments

Comments
 (0)