Graphinate 0.5.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/workflows/publish.yml +1 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/workflows/test.yml +1 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/.gitignore +2 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/PKG-INFO +4 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/README.md +2 -0
- graphinate-0.7.0/docs/examples/social.md +15 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/intro.md +14 -10
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/usage/cli.md +8 -0
- graphinate-0.7.0/examples/code/html_dom.py +61 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/code/python_ast.py +3 -3
- graphinate-0.7.0/examples/code/tokens.py +53 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/repositories.py +2 -2
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/math/graph_atlas.py +3 -7
- graphinate-0.7.0/examples/math/requirements.txt +2 -0
- graphinate-0.7.0/examples/social/cache/13/dd/73ce25face7beb30b69b64feeb77.val +0 -0
- graphinate-0.7.0/examples/social/cache/21/9e/00846f323987ba16cfbe0127d8eb.val +0 -0
- graphinate-0.7.0/examples/social/cache/70/b6/2aefb0269adce7fedf877fa0d267.val +0 -0
- graphinate-0.7.0/examples/social/cache/87/f5/ec1739bc369e84c3fcb302bf532a.val +0 -0
- graphinate-0.7.0/examples/social/cache/ba/fe/3aca7b2c38abff60e7ce5eb486a8.val +0 -0
- graphinate-0.7.0/examples/social/cache/c7/9e/ce82b0288020b7152779df09bd73.val +0 -0
- graphinate-0.7.0/examples/social/cache/cache.db +0 -0
- graphinate-0.7.0/examples/social/cache/d2/53/3b88f2fc162561cfdbbe9abc352a.val +0 -0
- graphinate-0.7.0/examples/social/cache/e2/d5/5d079f200eabf9b625b0473f6fbe.val +0 -0
- graphinate-0.7.0/examples/social/gui.py +108 -0
- graphinate-0.7.0/examples/social/music_artists.py +149 -0
- graphinate-0.7.0/examples/social/requirements.txt +3 -0
- graphinate-0.7.0/examples/system/.ignore +796 -0
- graphinate-0.7.0/examples/system/files.py +81 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/system/requirements.txt +1 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/mkdocs.yml +6 -2
- {graphinate-0.5.1 → graphinate-0.7.0}/pyproject.toml +11 -10
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/__init__.py +2 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/builders.py +63 -104
- graphinate-0.7.0/src/graphinate/constants.py +2 -0
- graphinate-0.7.0/src/graphinate/converters.py +91 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/modeling.py +53 -32
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/__init__.py +12 -4
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/rapidoc/index.html +3 -2
- graphinate-0.7.0/src/graphinate/server/web/viewer/index.html +661 -0
- graphinate-0.5.1/src/graphinate/tools/__init__.py → graphinate-0.7.0/src/graphinate/tools.py +3 -1
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/conftest.py +6 -5
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_builders.py +26 -64
- graphinate-0.7.0/tests/graphinate/test_converters.py +60 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_modeling.py +4 -3
- graphinate-0.5.1/examples/math/requirements.txt +0 -1
- graphinate-0.5.1/src/graphinate/server/web/viewer/index.html +0 -162
- graphinate-0.5.1/src/graphinate/tools/converters.py +0 -28
- graphinate-0.5.1/src/graphinate/tools/mutators.py +0 -153
- graphinate-0.5.1/tests/graphinate/test_tools.py +0 -65
- {graphinate-0.5.1 → graphinate-0.7.0}/.coveragerc +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/dependabot.yml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/workflows/codeql.yml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/workflows/publish-docs.yaml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/.github/workflows/test-beta.yml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/.sonarcloud.properties +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/LICENSE +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/STATS.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/acknowledge.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/assets/badge/v0.json +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/assets/images/logo-128.png +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/assets/images/network_graph.png +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/assets/stylesheets/extra.css +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/dev.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/examples/code.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/examples/github.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/examples/math.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/examples/system.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/examples/web.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/gen_ref_pages.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/index.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/start.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/docs/usage/lib.md +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/code/python_dependencies.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/code/requirements.txt +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/_client.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/commits_visibilty_graph.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/followers.graphql +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/followers.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/graphql.config.yml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/repositories.graphql +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/github/requirements.txt +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/math/graphs.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/math/gui.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/math/polygonal_graph.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/system/processes.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/web/page_links.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/examples/web/requirements.txt +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/ethernet/traceroute.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/genric_graph.graphql +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/graphql.config.yml +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/house_of_graphs.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/social/albums.json +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/social/musicisians.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/text/nlp_graph.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/text/requirements.txt +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/time_series/requirements.txt +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/playground/time_series/visibility_graph.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/sonar-project.properties +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/__main__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/cli.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/color.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/materializers/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/materializers/matplotlib.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/starlette/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/starlette/views.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/elements/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/elements/index.html +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/graphiql/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/graphiql/index.html +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/rapidoc/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/static/images/logo-128.png +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/static/images/logo.svg +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/static/images/network_graph.png +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/viewer/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/voyager/__init__.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/server/web/voyager/index.html +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/src/graphinate/typing.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_cli.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_color.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_materializers.py +0 -0
- {graphinate-0.5.1 → graphinate-0.7.0}/tests/graphinate/test_server.py +0 -0
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
- name: Build package
|
|
29
29
|
run: python -m build
|
|
30
30
|
- name: Publish package
|
|
31
|
-
uses: pypa/gh-action-pypi-publish@
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
|
32
32
|
with:
|
|
33
33
|
user: __token__
|
|
34
34
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -40,7 +40,7 @@ jobs:
|
|
|
40
40
|
python -m pip install faker pytest pytest-asyncio pytest-cov pytest-randomly pytest-xdist starlette-prometheus uvicorn[standard]
|
|
41
41
|
pytest tests --cov=src --cov-branch --cov-report=xml --junitxml=test_results.xml -n auto
|
|
42
42
|
- name: Upload coverage reports to Codecov
|
|
43
|
-
uses: codecov/codecov-action@
|
|
43
|
+
uses: codecov/codecov-action@v5
|
|
44
44
|
with:
|
|
45
45
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
46
46
|
- name: Run codacy-coverage-reporter
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Graphinate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: 𝔾raphinate. Data to Graphs.
|
|
5
5
|
Project-URL: Homepage, https://erivlis.github.io/graphinate
|
|
6
6
|
Project-URL: Documentation, https://erivlis.github.io/graphinate
|
|
@@ -30,6 +30,7 @@ Requires-Python: >=3.10
|
|
|
30
30
|
Requires-Dist: click
|
|
31
31
|
Requires-Dist: inflect
|
|
32
32
|
Requires-Dist: loguru
|
|
33
|
+
Requires-Dist: mappingtools
|
|
33
34
|
Requires-Dist: matplotlib
|
|
34
35
|
Requires-Dist: networkx
|
|
35
36
|
Requires-Dist: strawberry-graphql[asgi,opentelemetry]
|
|
@@ -221,6 +222,8 @@ Options:
|
|
|
221
222
|
|
|
222
223
|
#### Server
|
|
223
224
|
|
|
225
|
+
> TIP: requires the `server` extra to be installed. e.g., `pip install graphinate[server]`
|
|
226
|
+
|
|
224
227
|
```text
|
|
225
228
|
Usage: python -m graphinate server [OPTIONS]
|
|
226
229
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Social
|
|
2
|
+
|
|
3
|
+
## Music
|
|
4
|
+
|
|
5
|
+
=== "Music"
|
|
6
|
+
|
|
7
|
+
``` python title="examples/social/music.py" linenums="1"
|
|
8
|
+
--8<-- "examples/social/music.py"
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
=== "Dependencies"
|
|
12
|
+
|
|
13
|
+
``` text title="examples/social/requirements.txt" linenums="1"
|
|
14
|
+
--8<-- "examples/social/requirements.txt"
|
|
15
|
+
```
|
|
@@ -15,21 +15,25 @@ In addition, there are several modes of output to enable examination of the Grap
|
|
|
15
15
|
|
|
16
16
|
### What is a Graph?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
of them.
|
|
20
|
-
The points of a graph are most commonly known as graph *vertices*, but may also be called *nodes* or *points*.
|
|
21
|
-
Similarly, the lines connecting the vertices of a graph are most commonly known as graph *edges*, but may also
|
|
22
|
-
be called *arcs* or *lines*.”
|
|
18
|
+
!!! quote
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
“In a mathematician's terminology, a graph is a collection of points and lines connecting some (possibly empty) subset
|
|
21
|
+
of them.
|
|
22
|
+
The points of a graph are most commonly known as graph *vertices*, but may also be called *nodes* or *points*.
|
|
23
|
+
Similarly, the lines connecting the vertices of a graph are most commonly known as graph *edges*, but may also
|
|
24
|
+
be called *arcs* or *lines*.”
|
|
25
|
+
|
|
26
|
+
— [https://mathworld.wolfram.com/Graph.html](https://mathworld.wolfram.com/Graph.html)
|
|
25
27
|
|
|
26
28
|
### What is Data?
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
fact, statistics, other basic units of meaning, or simply sequences of symbols that may be further interpreted
|
|
30
|
-
formally.”
|
|
30
|
+
!!! quote
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
“...data is a collection of discrete or continuous values that convey information, describing the quantity, quality,
|
|
33
|
+
fact, statistics, other basic units of meaning, or simply sequences of symbols that may be further interpreted
|
|
34
|
+
formally.”
|
|
35
|
+
|
|
36
|
+
— [https://en.wikipedia.org/wiki/Data](https://en.wikipedia.org/wiki/Data)
|
|
33
37
|
|
|
34
38
|
## How?
|
|
35
39
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from bs4 import BeautifulSoup, Tag
|
|
5
|
+
|
|
6
|
+
import graphinate
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_html_from_url(url="https://www.google.com"):
|
|
10
|
+
response = requests.get(url)
|
|
11
|
+
return response.text
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_html(file_path):
|
|
15
|
+
with open(file_path) as file:
|
|
16
|
+
return file.read()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def html_dom_graph_model(html_content):
|
|
20
|
+
graph_model = graphinate.model(name="HTML DOM Graph")
|
|
21
|
+
soup = BeautifulSoup(html_content, 'html.parser')
|
|
22
|
+
|
|
23
|
+
def node_type(tag: Tag):
|
|
24
|
+
return tag.name.strip('[]')
|
|
25
|
+
|
|
26
|
+
def node_key(tag: Tag):
|
|
27
|
+
return str((tag.sourceline, tag.sourcepos)) if isinstance(tag, Tag) else base64.b64encode(
|
|
28
|
+
tag.encode()).decode()
|
|
29
|
+
|
|
30
|
+
def node_label(tag: Tag):
|
|
31
|
+
return str(tag)
|
|
32
|
+
|
|
33
|
+
@graph_model.node(node_type, key=node_key, label=node_label)
|
|
34
|
+
def html_node():
|
|
35
|
+
for tag in soup.descendants:
|
|
36
|
+
if tag.name is not None:
|
|
37
|
+
yield tag
|
|
38
|
+
|
|
39
|
+
@graph_model.edge()
|
|
40
|
+
def contains():
|
|
41
|
+
for tag in soup.descendants:
|
|
42
|
+
if tag.name is not None:
|
|
43
|
+
for child in tag.children:
|
|
44
|
+
if child.name is not None:
|
|
45
|
+
yield {
|
|
46
|
+
'source': node_key(tag),
|
|
47
|
+
'target': node_key(child)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return graph_model
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == '__main__':
|
|
54
|
+
html_content = load_html_from_url()
|
|
55
|
+
dom_model = html_dom_graph_model(html_content)
|
|
56
|
+
graphinate.materialize(
|
|
57
|
+
dom_model,
|
|
58
|
+
builder=graphinate.builders.GraphQLBuilder,
|
|
59
|
+
builder_output_handler=graphinate.graphql
|
|
60
|
+
)
|
|
61
|
+
|
|
@@ -79,14 +79,14 @@ def ast_graph_model():
|
|
|
79
79
|
def target(value):
|
|
80
80
|
return endpoint(value, 'target')
|
|
81
81
|
|
|
82
|
-
@graph_model.node(
|
|
82
|
+
@graph_model.node(type_=node_type,
|
|
83
83
|
key=key,
|
|
84
84
|
label=node_label,
|
|
85
|
-
|
|
85
|
+
unique=True)
|
|
86
86
|
def ast_node(**kwargs):
|
|
87
87
|
yield from _ast_nodes([root_ast_node])
|
|
88
88
|
|
|
89
|
-
@graph_model.edge(
|
|
89
|
+
@graph_model.edge(type_='edge',
|
|
90
90
|
source=source,
|
|
91
91
|
target=target,
|
|
92
92
|
label=operator.itemgetter('type'))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import operator
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pygments import lex
|
|
6
|
+
from pygments.lexers import guess_lexer_for_filename
|
|
7
|
+
|
|
8
|
+
import graphinate
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_file(file_path):
|
|
12
|
+
with open(file_path) as file:
|
|
13
|
+
return file.read()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def tokenize_file(file_path):
|
|
17
|
+
content = load_file(file_path)
|
|
18
|
+
lexer = guess_lexer_for_filename(file_path, content)
|
|
19
|
+
return lex(content, lexer)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def token_graph_model(file_path):
|
|
23
|
+
graph_model = graphinate.model(name="Token Graph")
|
|
24
|
+
|
|
25
|
+
def token_type(v):
|
|
26
|
+
return str(v[0]).replace('.', '_')
|
|
27
|
+
|
|
28
|
+
def token_key(v):
|
|
29
|
+
return f"{v[0]}-{v[1]}"
|
|
30
|
+
|
|
31
|
+
@graph_model.node(token_type, key=token_key)
|
|
32
|
+
def token():
|
|
33
|
+
yield from tokenize_file(file_path)
|
|
34
|
+
|
|
35
|
+
@graph_model.edge(source=operator.itemgetter(0), target=operator.itemgetter(1))
|
|
36
|
+
def edge():
|
|
37
|
+
yield from itertools.pairwise(token_key(t) for t in tokenize_file(file_path))
|
|
38
|
+
|
|
39
|
+
return graph_model
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == '__main__':
|
|
43
|
+
if len(sys.argv) != 2:
|
|
44
|
+
print("Usage: python tokens.py <file_path>")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
file_path = sys.argv[1]
|
|
48
|
+
token_model = token_graph_model(file_path)
|
|
49
|
+
graphinate.materialize(
|
|
50
|
+
token_model,
|
|
51
|
+
builder=graphinate.builders.GraphQLBuilder,
|
|
52
|
+
builder_output_handler=graphinate.graphql
|
|
53
|
+
)
|
|
@@ -18,7 +18,7 @@ def repo_graph_model(): # noqa: C901
|
|
|
18
18
|
|
|
19
19
|
graph_model = graphinate.model(name='GitHub Repository Graph')
|
|
20
20
|
|
|
21
|
-
@graph_model.edge
|
|
21
|
+
@graph_model.edge()
|
|
22
22
|
def github(user_id: Optional[str] = None,
|
|
23
23
|
repository_id: Optional[str] = None,
|
|
24
24
|
commit_id: Optional[str] = None,
|
|
@@ -56,7 +56,7 @@ def repo_graph_model(): # noqa: C901
|
|
|
56
56
|
label=commit_label)
|
|
57
57
|
|
|
58
58
|
file_node = graph_model.node(parent_type='commit',
|
|
59
|
-
|
|
59
|
+
unique=True,
|
|
60
60
|
key=operator.attrgetter('filename'),
|
|
61
61
|
value=operator.attrgetter('raw_data'),
|
|
62
62
|
label=operator.itemgetter('filename'))
|
|
@@ -38,13 +38,9 @@ def model(items: list[tuple[str, nx.Graph]]) -> graphinate.GraphModel:
|
|
|
38
38
|
def nodes():
|
|
39
39
|
yield from g.nodes(data='type')
|
|
40
40
|
|
|
41
|
-
@graph_model.edge(operator.itemgetter(
|
|
42
|
-
source=operator.itemgetter(0),
|
|
43
|
-
target=operator.itemgetter(1),
|
|
44
|
-
label=operator.itemgetter(0, 1),
|
|
45
|
-
value=operator.itemgetter(0, 1))
|
|
41
|
+
@graph_model.edge(operator.itemgetter('type'))
|
|
46
42
|
def edge():
|
|
47
|
-
yield from g.edges.data(
|
|
43
|
+
yield from ({'source': e[0], 'target': e[1], **e[2]} for e in g.edges.data())
|
|
48
44
|
|
|
49
45
|
return graph_model
|
|
50
46
|
|
|
@@ -54,7 +50,7 @@ if __name__ == '__main__':
|
|
|
54
50
|
|
|
55
51
|
graph_atlas = graphs.atlas()
|
|
56
52
|
|
|
57
|
-
listbox_chooser = ListboxChooser('Choose Graph', graph_atlas)
|
|
53
|
+
listbox_chooser = ListboxChooser('Choose Graph/s', graph_atlas)
|
|
58
54
|
choices = list(listbox_chooser.get_choices())
|
|
59
55
|
model = model(choices)
|
|
60
56
|
|
|
Binary file
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import tkinter as tk
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModalWindow(tk.Tk):
|
|
7
|
+
def __init__(self, title: str):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.title(title)
|
|
10
|
+
self.configure_window()
|
|
11
|
+
|
|
12
|
+
def configure_window(self):
|
|
13
|
+
if platform.system().lower() == 'windows':
|
|
14
|
+
self.wm_attributes('-toolwindow', 'True')
|
|
15
|
+
self.wm_attributes('-topmost', 'True')
|
|
16
|
+
self.resizable(False, False)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RadiobuttonChooser(ModalWindow):
|
|
20
|
+
"""
|
|
21
|
+
Usage Example:
|
|
22
|
+
```python
|
|
23
|
+
radiobutton_chooser = RadiobuttonChooser("Choose an option", {"Option 1": 1, "Option 2": 2})
|
|
24
|
+
choice, value = radiobutton_chooser.get_choice()
|
|
25
|
+
print(choice, value)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, window_title: str, options: dict, default=None):
|
|
30
|
+
super().__init__(window_title)
|
|
31
|
+
self.exit_button = None
|
|
32
|
+
self.choice_var = tk.StringVar(self, None)
|
|
33
|
+
self.default = default
|
|
34
|
+
self.options = options
|
|
35
|
+
self.create_widgets()
|
|
36
|
+
self.mainloop()
|
|
37
|
+
|
|
38
|
+
def create_widgets(self):
|
|
39
|
+
frame = ttk.Frame(self, borderwidth=5, relief='solid')
|
|
40
|
+
frame.pack(padx=4, pady=4)
|
|
41
|
+
|
|
42
|
+
ttk.Label(frame, text="Output Mode:").pack()
|
|
43
|
+
|
|
44
|
+
for option in self.options:
|
|
45
|
+
ttk.Radiobutton(
|
|
46
|
+
frame,
|
|
47
|
+
text=option,
|
|
48
|
+
variable=self.choice_var,
|
|
49
|
+
value=option,
|
|
50
|
+
command=self.enable_exit_button,
|
|
51
|
+
padding=4
|
|
52
|
+
).pack(side=tk.TOP, anchor="w")
|
|
53
|
+
|
|
54
|
+
self.exit_button = ttk.Button(self, text="OK", command=self.destroy, state=tk.DISABLED)
|
|
55
|
+
self.exit_button.pack(pady=4, side=tk.BOTTOM)
|
|
56
|
+
|
|
57
|
+
def enable_exit_button(self):
|
|
58
|
+
self.exit_button['state'] = tk.NORMAL
|
|
59
|
+
|
|
60
|
+
def get_choice(self):
|
|
61
|
+
return self.choice_var.get(), self.options.get(self.choice_var.get(), self.default)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ListboxChooser(ModalWindow):
|
|
65
|
+
"""
|
|
66
|
+
Usage Example:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
listbox_chooser = ListboxChooser("Choose options", {"Option 1": 1, "Option 2": 2, "Option 3": 3})
|
|
70
|
+
for choice, value in listbox_chooser.get_choices():
|
|
71
|
+
print(choice, value)
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, window_title: str, options: dict, default=None):
|
|
76
|
+
super().__init__(window_title)
|
|
77
|
+
self.exit_button = None
|
|
78
|
+
self.choices = list(options.keys())
|
|
79
|
+
self.options = options
|
|
80
|
+
self.default = default
|
|
81
|
+
self.selection_var = tk.Variable(self)
|
|
82
|
+
self.create_widgets()
|
|
83
|
+
self.mainloop()
|
|
84
|
+
|
|
85
|
+
def create_widgets(self):
|
|
86
|
+
frame = ttk.Frame(self)
|
|
87
|
+
frame.pack(padx=2, pady=2)
|
|
88
|
+
|
|
89
|
+
listbox = tk.Listbox(frame, listvariable=tk.Variable(self, value=self.choices), selectmode="multiple",
|
|
90
|
+
height=min(len(self.choices), 50), width=max(len(c) for c in self.choices))
|
|
91
|
+
listbox.pack(side=tk.LEFT, fill=tk.BOTH)
|
|
92
|
+
|
|
93
|
+
scrollbar = ttk.Scrollbar(frame, command=listbox.yview)
|
|
94
|
+
scrollbar.pack(side=tk.RIGHT, fill=tk.BOTH)
|
|
95
|
+
listbox.config(yscrollcommand=scrollbar.set)
|
|
96
|
+
|
|
97
|
+
listbox.bind("<<ListboxSelect>>", self.on_select)
|
|
98
|
+
|
|
99
|
+
self.exit_button = ttk.Button(self, text="OK", command=self.destroy, state=tk.DISABLED)
|
|
100
|
+
self.exit_button.pack(pady=4, side=tk.BOTTOM)
|
|
101
|
+
|
|
102
|
+
def on_select(self, event):
|
|
103
|
+
self.exit_button['state'] = tk.NORMAL
|
|
104
|
+
self.selection_var.set(event.widget.curselection())
|
|
105
|
+
|
|
106
|
+
def get_choices(self):
|
|
107
|
+
for choice in self.selection_var.get():
|
|
108
|
+
yield self.choices[choice], self.options.get(self.choices[choice], self.default)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import operator
|
|
3
|
+
import pathlib
|
|
4
|
+
from functools import reduce
|
|
5
|
+
from time import sleep
|
|
6
|
+
|
|
7
|
+
import diskcache
|
|
8
|
+
import musicbrainzngs
|
|
9
|
+
|
|
10
|
+
import graphinate
|
|
11
|
+
|
|
12
|
+
# logging.basicConfig(level=logging.INFO)
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def initialize_musicbrainz():
|
|
17
|
+
musicbrainzngs.set_useragent(
|
|
18
|
+
"MusicArtistGraph",
|
|
19
|
+
"0.1.0",
|
|
20
|
+
"https://github.com/erivlis/graphinate"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
initialize_musicbrainz()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def cache_dir():
|
|
28
|
+
current_script_path = pathlib.Path(__file__).resolve()
|
|
29
|
+
parent_dir = current_script_path.parent
|
|
30
|
+
return (parent_dir / 'cache').as_posix()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def music_graph_model(name: str, max_depth: int = 0):
|
|
34
|
+
graph_model = graphinate.model(f"{name.capitalize()} Graph")
|
|
35
|
+
|
|
36
|
+
artists_cache = diskcache.Cache(directory=cache_dir(), eviction_policy='none')
|
|
37
|
+
|
|
38
|
+
result = musicbrainzngs.search_artists(query=name, strict=True, artist=name)
|
|
39
|
+
sleep(1)
|
|
40
|
+
root_artist = result.get('artist-list', [])[0] if result else None
|
|
41
|
+
|
|
42
|
+
def artists(parent_artist, artist, depth):
|
|
43
|
+
logger.info(f"Current depth: {depth}")
|
|
44
|
+
artist_id = artist.get('id')
|
|
45
|
+
if artist_id not in artists_cache:
|
|
46
|
+
artists_cache[artist_id] = musicbrainzngs.get_artist_by_id(id=artist_id, includes=['artist-rels']).get(
|
|
47
|
+
'artist')
|
|
48
|
+
sleep(0.1)
|
|
49
|
+
|
|
50
|
+
artist = artists_cache.get(artist_id)
|
|
51
|
+
|
|
52
|
+
yield parent_artist, artist
|
|
53
|
+
|
|
54
|
+
if depth < max_depth:
|
|
55
|
+
related_artist_ids = set()
|
|
56
|
+
for item in artist.get('artist-relation-list', []):
|
|
57
|
+
related_artist = item.get('artist')
|
|
58
|
+
related_artist_id = related_artist.get('id')
|
|
59
|
+
if related_artist_id not in related_artist_ids:
|
|
60
|
+
related_artist_ids.add(related_artist_id)
|
|
61
|
+
yield from artists(artist, related_artist, depth + 1)
|
|
62
|
+
|
|
63
|
+
def artist_type(value):
|
|
64
|
+
return value.get('type', '_UNKNOWN_')
|
|
65
|
+
|
|
66
|
+
@graph_model.node(artist_type,
|
|
67
|
+
key=operator.itemgetter('id'),
|
|
68
|
+
label=operator.itemgetter('name'),
|
|
69
|
+
multiplicity=graphinate.Multiplicity.FIRST)
|
|
70
|
+
def node():
|
|
71
|
+
yielded = set()
|
|
72
|
+
for a, b in artists(None, root_artist, 0):
|
|
73
|
+
if a and ((a_id := a.get('id')) not in yielded):
|
|
74
|
+
yielded.add(a_id)
|
|
75
|
+
yield a
|
|
76
|
+
if b and ((b_id := b.get('id')) not in yielded):
|
|
77
|
+
yielded.add(b_id)
|
|
78
|
+
yield b
|
|
79
|
+
|
|
80
|
+
@graph_model.edge()
|
|
81
|
+
def edge():
|
|
82
|
+
for a, b in artists(None, root_artist, 0):
|
|
83
|
+
if a:
|
|
84
|
+
yield {'source': a.get('id'), 'target': b.get('id')}
|
|
85
|
+
|
|
86
|
+
return graph_model
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == '__main__':
|
|
90
|
+
from gui import ListboxChooser
|
|
91
|
+
|
|
92
|
+
artist_names = [
|
|
93
|
+
'Alice in Chains',
|
|
94
|
+
'Beatles',
|
|
95
|
+
'Caravan',
|
|
96
|
+
'Charles Mingus',
|
|
97
|
+
'Dave Brubeck',
|
|
98
|
+
'Dave Douglas',
|
|
99
|
+
'David Bowie',
|
|
100
|
+
'Deep Purple',
|
|
101
|
+
'Dire Straits',
|
|
102
|
+
'Emerson, Lake & Palmer',
|
|
103
|
+
'Foo Fighters',
|
|
104
|
+
'Frank Zappa',
|
|
105
|
+
'Genesis',
|
|
106
|
+
'Gentle Giant',
|
|
107
|
+
'Herbie Hancock',
|
|
108
|
+
'Jethro Tull',
|
|
109
|
+
'John Coltrane',
|
|
110
|
+
'John Scofield',
|
|
111
|
+
'John Zorn',
|
|
112
|
+
'Ken Vandermark',
|
|
113
|
+
'King Crimson',
|
|
114
|
+
'Led Zeppelin',
|
|
115
|
+
'Mahavishnu Orchestra',
|
|
116
|
+
'Miles Davis',
|
|
117
|
+
'Nirvana',
|
|
118
|
+
'Ornette Coleman',
|
|
119
|
+
'Paul McCartney',
|
|
120
|
+
'Pearl Jam',
|
|
121
|
+
'Pink Floyd',
|
|
122
|
+
'Police',
|
|
123
|
+
'Porcupine Tree',
|
|
124
|
+
'Radiohead',
|
|
125
|
+
'Red Hot Chili Peppers',
|
|
126
|
+
'Return to Forever',
|
|
127
|
+
'Rush',
|
|
128
|
+
'Smashing Pumpkins',
|
|
129
|
+
'Soft Machine',
|
|
130
|
+
'Soundgarden',
|
|
131
|
+
'Stone Temple Pilots',
|
|
132
|
+
'System of a Down',
|
|
133
|
+
'Thelonious Monk',
|
|
134
|
+
'Weather Report',
|
|
135
|
+
'Wings',
|
|
136
|
+
'Yes',
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
listbox_chooser = ListboxChooser('Choose Artist/s', {name: name for name in artist_names})
|
|
140
|
+
|
|
141
|
+
models = (music_graph_model(a, 2) for _, a in listbox_chooser.get_choices())
|
|
142
|
+
|
|
143
|
+
model = reduce(operator.add, models)
|
|
144
|
+
|
|
145
|
+
graphinate.materialize(
|
|
146
|
+
model,
|
|
147
|
+
builder=graphinate.builders.GraphQLBuilder,
|
|
148
|
+
builder_output_handler=graphinate.graphql
|
|
149
|
+
)
|