Graphinate 0.15.0__tar.gz → 0.15.2__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.15.2/.github/workflows/codspeed.yml +35 -0
- graphinate-0.15.2/.github/workflows/publish.yml +31 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/CHANGELOG.md +12 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/PKG-INFO +6 -3
- {graphinate-0.15.0 → graphinate-0.15.2}/README.md +4 -1
- graphinate-0.15.2/benchmarks/conftest.py +72 -0
- graphinate-0.15.2/benchmarks/test_builders_benchmark.py +88 -0
- graphinate-0.15.2/benchmarks/test_color_benchmark.py +28 -0
- graphinate-0.15.2/benchmarks/test_converters_benchmark.py +52 -0
- graphinate-0.15.2/benchmarks/test_modeling_benchmark.py +53 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/pyproject.toml +2 -1
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/graphql.py +15 -11
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/cli.py +34 -4
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/graphql.py +8 -3
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_graphql.py +21 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_graphql_server.py +8 -3
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_cli.py +38 -10
- graphinate-0.15.0/.github/workflows/publish.yml +0 -31
- {graphinate-0.15.0 → graphinate-0.15.2}/.coveragerc +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.deepsource.toml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.github/dependabot.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/codeql.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/publish-docs.yaml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/test-beta.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/test.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.gitignore +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.python-version +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/.sonarcloud.properties +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/AGENTS.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/LICENSE +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/PERSONA.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/PRINCIPLES.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/STYLEGUIDE.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/TELEMETRY.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/TOOLS.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/benchmarks/color_benchmark.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Builders_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/CLI_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Core_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/GraphModel_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Renderers_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Server_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Viewer_Analysis.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts_v1.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts_v2.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Builders_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/CLI_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Core_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Modeling_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Renderers_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Server_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Viewer_Grading.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/01_The_Foundations.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/02_The_Fantastical.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/03_The_Analog.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/04_The_Avant_Garde.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/05_The_Pop_Culture.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/06_The_Modern.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/07_Graphinate_2.0.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-000_Graphinate_Enhancement_Proposals.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-001_The_Graphinate_Mission.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-002_Modularization_Strategy.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-003_Roadmap.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-007_Theoretical_Foundations.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-010_Explicit_Dependency_Injection.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-011_Standardized_Element_Factory.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-012_Robust_Model_Composition.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-013_Stable_ID_Serialization.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-014_Explicit_GraphQL_Schema_Generation.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-015_Refactored_Graph_Population_Strategies.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-016_CLI_Argument_Parsing_and_Path_Safety.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-017_Decoupled_Server_Architecture.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-018_Relay_Support.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-019_Offline_Viewer_Support.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-020_Modular_Frontend_Architecture.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-022_2D_Visualization_Mode.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-023_Async_Support.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-024_The_Graph_Engine.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-025_Signed_ID_Serialization.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-026_Pluggable_Visualization_and_Notebook_Support.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-027_Advanced_ThreeJS_Visualizations.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-028_MCP_Server_Integration.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-029_Extraction_Strategies.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-030_Node_Type_Taxonomy.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-031_High_Performance_Vectorized_Operations.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-032_GraphModel_Compilation_and_Validation_Lifecycle.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/acknowledgements.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/modeling.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/optimization.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/secure_ids.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/badge/v0.json +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/images/logo-128.png +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/images/network_graph.png +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/stylesheets/extra.css +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/development.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/class_diagram.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/component_diagram.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/function_call_graph.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/code.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/github.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/math.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/social.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/system.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/web.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/gen_ref_pages.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/index.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/introduction.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/start.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/tutorial.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/usage/cli.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/docs/usage/lib.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/git_commits.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/python_ast.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/python_dependencies.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/tokens.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/_client.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/commits_visibilty_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/followers.graphql +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/followers.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/graphql.config.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/repositories.graphql +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/repositories.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/_test_materializers.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/graph_atlas.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/graphs.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/gui.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/materializers.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/polygonal_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/13/dd/73ce25face7beb30b69b64feeb77.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/21/9e/00846f323987ba16cfbe0127d8eb.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/70/b6/2aefb0269adce7fedf877fa0d267.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/87/f5/ec1739bc369e84c3fcb302bf532a.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/ba/fe/3aca7b2c38abff60e7ce5eb486a8.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/c7/9e/ce82b0288020b7152779df09bd73.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/cache.db +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/d2/53/3b88f2fc162561cfdbbe9abc352a.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/e2/d5/5d079f200eabf9b625b0473f6fbe.val +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/gui.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/music_artists.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/.ignore +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/files.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/processes.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/html_dom.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/page_links.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/check_repo.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/ethernet/traceroute.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/genric_graph.graphql +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/graphql.config.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/house_of_graphs.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/octagonal_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/science/caffeine.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/db.xml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/music.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/text/nlp_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/text/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/time_series/requirements.txt +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/time_series/visibility_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/playground/word_graph.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/properdocs.yml +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/sonar-project.properties +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/.agents/skills/graphinate/SKILL.md +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/__main__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/_secure.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/builder.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/d3.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/mermaid.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/networkx.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/color.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/constants.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/converters.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/enums.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/modeling.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/matplotlib.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/starlette/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/starlette/views.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/__init__.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/elements/index.html +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/graphiql/index.html +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/rapidoc/index.html +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/logo-128.png +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/logo.svg +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/network_graph.png +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/viewer/index.html +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/voyager/index.html +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/tools.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/typing.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/conftest.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_build.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_d3.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_mermaid.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_networkx.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_matplotlib_draw.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_matplotlib_plot.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/server/test_starlette.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_color.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_converters.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_modeling.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_modeling_add.py +0 -0
- {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_server.py +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# This workflow runs the benchmark suite and reports the results to CodSpeed.
|
|
2
|
+
# For more information see: https://codspeed.io/docs/integrations/ci/github-actions
|
|
3
|
+
|
|
4
|
+
name: CodSpeed
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "main" ]
|
|
11
|
+
# `workflow_dispatch` allows CodSpeed to trigger backtest
|
|
12
|
+
# performance analysis in order to generate initial data.
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
id-token: write # for OpenID Connect authentication with CodSpeed
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
benchmarks:
|
|
21
|
+
name: Run benchmarks
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v7
|
|
25
|
+
- name: Set up UV and Python
|
|
26
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.14"
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: uv sync --all-extras --group test
|
|
31
|
+
- name: Run benchmarks
|
|
32
|
+
uses: CodSpeedHQ/action@v5
|
|
33
|
+
with:
|
|
34
|
+
mode: simulation
|
|
35
|
+
run: uv run pytest benchmarks --codspeed
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# This workflow will build and publish the Python package to PyPI using uv
|
|
2
|
+
# For more information see: https://docs.astral.sh/uv/
|
|
3
|
+
|
|
4
|
+
name: Publish
|
|
5
|
+
on:
|
|
6
|
+
release:
|
|
7
|
+
types: [published]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
deploy:
|
|
14
|
+
name: Deploy to PyPI
|
|
15
|
+
environment: pypi
|
|
16
|
+
permissions:
|
|
17
|
+
# Required for Trusted Publishing / OIDC authentication
|
|
18
|
+
id-token: write
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v7
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
26
|
+
with:
|
|
27
|
+
python-version: "3.14"
|
|
28
|
+
- name: Build package
|
|
29
|
+
run: uv build
|
|
30
|
+
- name: Publish package
|
|
31
|
+
run: uv publish
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v0.15.2 (2026-08-21)
|
|
2
|
+
|
|
3
|
+
### Fix
|
|
4
|
+
|
|
5
|
+
- correct debug mode support to CLI and GraphQL server configuration
|
|
6
|
+
|
|
7
|
+
## v0.15.1 (2026-08-09)
|
|
8
|
+
|
|
9
|
+
### Fix
|
|
10
|
+
|
|
11
|
+
- GraphQL builder robustly populates enums and handles empty models.
|
|
12
|
+
|
|
1
13
|
## v0.15.0 (2026-08-07)
|
|
2
14
|
|
|
3
15
|
### Feat
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: Graphinate
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.2
|
|
4
4
|
Summary: Graphinate. Data to Graphs.
|
|
5
5
|
Project-URL: Homepage, https://erivlis.github.io/graphinate
|
|
6
6
|
Project-URL: Documentation, https://erivlis.github.io/graphinate
|
|
@@ -88,6 +88,7 @@ Description-Content-Type: text/markdown
|
|
|
88
88
|
<td>
|
|
89
89
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/test.yml"><img alt="Test" src="https://github.com/erivlis/graphinate/actions/workflows/test.yml/badge.svg"></a>
|
|
90
90
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/test-beta.yml"><img alt="Test (Beta)" src="https://github.com/erivlis/graphinate/actions/workflows/test-beta.yml/badge.svg"></a>
|
|
91
|
+
<a href="https://github.com/erivlis/graphinate/actions/workflows/codspeed.yml"><img alt="CodSpeed" src="https://github.com/erivlis/graphinate/actions/workflows/codspeed.yml/badge.svg"></a>
|
|
91
92
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/publish.yml"><img alt="Publish" src="https://github.com/erivlis/graphinate/actions/workflows/publish.yml/badge.svg"></a>
|
|
92
93
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/publish-docs.yaml"><img alt="Publish Docs" src="https://github.com/erivlis/graphinate/actions/workflows/publish-docs.yaml/badge.svg"></a>
|
|
93
94
|
</td>
|
|
@@ -109,13 +110,15 @@ Description-Content-Type: text/markdown
|
|
|
109
110
|
<a href="https://app.deepsource.com/gh/erivlis/graphinate/" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/erivlis/graphinate.svg/?label=active+issues&show_trend=true&token=5n11jDEmcZB-6eyGR34rQaxF"/></a>
|
|
110
111
|
<a href="https://app.deepsource.com/gh/erivlis/graphinate/" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/erivlis/graphinate.svg/?label=resolved+issues&show_trend=true&token=5n11jDEmcZB-6eyGR34rQaxF"/></a>
|
|
111
112
|
<a href="https://snyk.io/test/github/erivlis/graphinate"><img alt="Snyk" src="https://snyk.io/test/github/erivlis/Graphinate/badge.svg"></a>
|
|
113
|
+
<a href="https://app.codspeed.io/erivlis/graphinate?utm_source=badge"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed"/></a>
|
|
112
114
|
</td>
|
|
113
115
|
</tr>
|
|
114
116
|
<tr>
|
|
115
117
|
<td>Mentions</td>
|
|
116
118
|
<td>
|
|
117
119
|
<a href="https://www.youtube.com/live/k01G0b0Y0Jg?si=030OT8sK3BqPyy8r&t=1028"><img alt="PythonBytes Podcast" src="https://img.shields.io/badge/Python_Bytes-Ep. 361-D7F9FF?logo=applepodcasts&labelColor=blue"></a>
|
|
118
|
-
|
|
120
|
+
<!-- a href="https://pythonhub.dev/digest/2024-03-10/"><img alt="Static Badge" src="https://img.shields.io/badge/PythonHub-2024.03.10-gold?labelColor=blue"></a -->
|
|
121
|
+
<a href="https://pythonhub.dev/digest/2024-03-10/"><img alt="Python Hub" src="https://custom-icon-badges.demolab.com/badge/Python%20Hub-2024.03.10-gold?logo=pythonhub&labelColor=grey"></a>
|
|
119
122
|
</td>
|
|
120
123
|
</tr>
|
|
121
124
|
<tr>
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
<td>
|
|
42
42
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/test.yml"><img alt="Test" src="https://github.com/erivlis/graphinate/actions/workflows/test.yml/badge.svg"></a>
|
|
43
43
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/test-beta.yml"><img alt="Test (Beta)" src="https://github.com/erivlis/graphinate/actions/workflows/test-beta.yml/badge.svg"></a>
|
|
44
|
+
<a href="https://github.com/erivlis/graphinate/actions/workflows/codspeed.yml"><img alt="CodSpeed" src="https://github.com/erivlis/graphinate/actions/workflows/codspeed.yml/badge.svg"></a>
|
|
44
45
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/publish.yml"><img alt="Publish" src="https://github.com/erivlis/graphinate/actions/workflows/publish.yml/badge.svg"></a>
|
|
45
46
|
<a href="https://github.com/erivlis/graphinate/actions/workflows/publish-docs.yaml"><img alt="Publish Docs" src="https://github.com/erivlis/graphinate/actions/workflows/publish-docs.yaml/badge.svg"></a>
|
|
46
47
|
</td>
|
|
@@ -62,13 +63,15 @@
|
|
|
62
63
|
<a href="https://app.deepsource.com/gh/erivlis/graphinate/" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/erivlis/graphinate.svg/?label=active+issues&show_trend=true&token=5n11jDEmcZB-6eyGR34rQaxF"/></a>
|
|
63
64
|
<a href="https://app.deepsource.com/gh/erivlis/graphinate/" target="_blank"><img alt="DeepSource" title="DeepSource" src="https://app.deepsource.com/gh/erivlis/graphinate.svg/?label=resolved+issues&show_trend=true&token=5n11jDEmcZB-6eyGR34rQaxF"/></a>
|
|
64
65
|
<a href="https://snyk.io/test/github/erivlis/graphinate"><img alt="Snyk" src="https://snyk.io/test/github/erivlis/Graphinate/badge.svg"></a>
|
|
66
|
+
<a href="https://app.codspeed.io/erivlis/graphinate?utm_source=badge"><img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" alt="CodSpeed"/></a>
|
|
65
67
|
</td>
|
|
66
68
|
</tr>
|
|
67
69
|
<tr>
|
|
68
70
|
<td>Mentions</td>
|
|
69
71
|
<td>
|
|
70
72
|
<a href="https://www.youtube.com/live/k01G0b0Y0Jg?si=030OT8sK3BqPyy8r&t=1028"><img alt="PythonBytes Podcast" src="https://img.shields.io/badge/Python_Bytes-Ep. 361-D7F9FF?logo=applepodcasts&labelColor=blue"></a>
|
|
71
|
-
|
|
73
|
+
<!-- a href="https://pythonhub.dev/digest/2024-03-10/"><img alt="Static Badge" src="https://img.shields.io/badge/PythonHub-2024.03.10-gold?labelColor=blue"></a -->
|
|
74
|
+
<a href="https://pythonhub.dev/digest/2024-03-10/"><img alt="Python Hub" src="https://custom-icon-badges.demolab.com/badge/Python%20Hub-2024.03.10-gold?logo=pythonhub&labelColor=grey"></a>
|
|
72
75
|
</td>
|
|
73
76
|
</tr>
|
|
74
77
|
<tr>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Shared fixtures for the Graphinate benchmark suite.
|
|
2
|
+
|
|
3
|
+
The models defined here are deterministic (no randomness, no I/O) so that
|
|
4
|
+
measurements stay comparable from one run to the next.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import networkx as nx
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
import graphinate
|
|
11
|
+
from graphinate.builders import NetworkxBuilder
|
|
12
|
+
|
|
13
|
+
COUNTRY_COUNT = 10
|
|
14
|
+
CITY_COUNT = 200
|
|
15
|
+
MESH_NODE_COUNT = 400
|
|
16
|
+
MESH_DEGREE = 3
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def hierarchical_model(country_count: int = COUNTRY_COUNT, city_count: int = CITY_COUNT) -> graphinate.GraphModel:
|
|
20
|
+
"""A two level parent/child model, exercising nested node generation."""
|
|
21
|
+
countries = tuple(f'country-{i}' for i in range(country_count))
|
|
22
|
+
cities = {f'city-{i}': countries[i % country_count] for i in range(city_count)}
|
|
23
|
+
|
|
24
|
+
graph_model = graphinate.model(name='Map')
|
|
25
|
+
|
|
26
|
+
@graph_model.node(unique=False)
|
|
27
|
+
def country(country_id=None, **kwargs):
|
|
28
|
+
if country_id is not None and country_id in countries:
|
|
29
|
+
yield country_id
|
|
30
|
+
else:
|
|
31
|
+
yield from countries
|
|
32
|
+
|
|
33
|
+
@graph_model.node(parent_type='country', unique=False)
|
|
34
|
+
def city(country_id=None, city_id=None, **kwargs):
|
|
35
|
+
if city_id is not None:
|
|
36
|
+
if cities.get(city_id) == country_id or country_id is None:
|
|
37
|
+
yield city_id
|
|
38
|
+
elif country_id is None:
|
|
39
|
+
yield from cities
|
|
40
|
+
else:
|
|
41
|
+
yield from (k for k, v in cities.items() if v == country_id)
|
|
42
|
+
|
|
43
|
+
return graph_model
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def mesh_model(node_count: int = MESH_NODE_COUNT, degree: int = MESH_DEGREE) -> graphinate.GraphModel:
|
|
47
|
+
"""An edge only model producing a circulant graph, exercising edge population."""
|
|
48
|
+
graph_model = graphinate.model(name='Mesh')
|
|
49
|
+
|
|
50
|
+
@graph_model.edge()
|
|
51
|
+
def edge():
|
|
52
|
+
for i in range(node_count):
|
|
53
|
+
for offset in range(1, degree + 1):
|
|
54
|
+
yield {'source': i, 'target': (i + offset) % node_count}
|
|
55
|
+
|
|
56
|
+
return graph_model
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def hierarchical_graph_model() -> graphinate.GraphModel:
|
|
61
|
+
return hierarchical_model()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def mesh_graph_model() -> graphinate.GraphModel:
|
|
66
|
+
return mesh_model()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def mesh_graph() -> nx.Graph:
|
|
71
|
+
"""A materialized NetworkX graph, used to benchmark post-processing steps."""
|
|
72
|
+
return NetworkxBuilder(mesh_model()).build()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Benchmarks for the builders, the core of Graphinate's data pipeline."""
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
import pytest
|
|
5
|
+
import strawberry
|
|
6
|
+
|
|
7
|
+
import graphinate
|
|
8
|
+
from graphinate import GraphType
|
|
9
|
+
from graphinate.builders import D3Builder, GraphQLBuilder, MermaidBuilder, NetworkxBuilder
|
|
10
|
+
|
|
11
|
+
GRAPHQL_QUERY = """
|
|
12
|
+
query Graph {
|
|
13
|
+
graph {
|
|
14
|
+
name
|
|
15
|
+
nodeTypeCounts { name value }
|
|
16
|
+
edgeTypeCounts { name value }
|
|
17
|
+
nodeCount
|
|
18
|
+
edgeCount
|
|
19
|
+
order
|
|
20
|
+
size
|
|
21
|
+
}
|
|
22
|
+
nodes { id type label color }
|
|
23
|
+
edges { source { id } target { id } type label weight }
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.benchmark
|
|
29
|
+
def test_networkx_builder__mesh(mesh_graph_model):
|
|
30
|
+
graph = NetworkxBuilder(mesh_graph_model).build()
|
|
31
|
+
assert graph.number_of_nodes() > 0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.benchmark
|
|
35
|
+
def test_networkx_builder__hierarchical(hierarchical_graph_model):
|
|
36
|
+
graph = NetworkxBuilder(hierarchical_graph_model).build()
|
|
37
|
+
assert graph.number_of_nodes() > 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.benchmark
|
|
41
|
+
def test_networkx_builder__digraph(mesh_graph_model):
|
|
42
|
+
graph = NetworkxBuilder(mesh_graph_model, graph_type=GraphType.DiGraph).build()
|
|
43
|
+
assert isinstance(graph, nx.DiGraph)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.benchmark
|
|
47
|
+
def test_d3_builder__python(mesh_graph_model):
|
|
48
|
+
d3_graph = D3Builder(mesh_graph_model).build()
|
|
49
|
+
assert d3_graph['nodes']
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.benchmark
|
|
53
|
+
def test_d3_builder__json(mesh_graph_model):
|
|
54
|
+
d3_graph = D3Builder(mesh_graph_model).build(values_format='json')
|
|
55
|
+
assert d3_graph['nodes']
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.benchmark
|
|
59
|
+
def test_d3_builder__from_networkx(mesh_graph):
|
|
60
|
+
d3_graph = D3Builder.from_networkx(mesh_graph)
|
|
61
|
+
assert d3_graph['nodes']
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.benchmark
|
|
65
|
+
def test_mermaid_builder(mesh_graph_model):
|
|
66
|
+
diagram = MermaidBuilder(mesh_graph_model).build()
|
|
67
|
+
assert diagram
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.benchmark
|
|
71
|
+
def test_graphql_builder__schema(hierarchical_graph_model):
|
|
72
|
+
schema = GraphQLBuilder(hierarchical_graph_model).build()
|
|
73
|
+
assert isinstance(schema, strawberry.Schema)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_graphql_builder__query(benchmark, hierarchical_graph_model):
|
|
77
|
+
schema: strawberry.Schema = GraphQLBuilder(hierarchical_graph_model).build()
|
|
78
|
+
|
|
79
|
+
result = benchmark(schema.execute_sync, GRAPHQL_QUERY)
|
|
80
|
+
|
|
81
|
+
assert result.errors is None
|
|
82
|
+
assert result.data['nodes']
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.benchmark
|
|
86
|
+
def test_build_facade(mesh_graph_model):
|
|
87
|
+
graph = graphinate.build(NetworkxBuilder, mesh_graph_model)
|
|
88
|
+
assert graph.number_of_nodes() > 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Benchmarks for the color helpers used when finalizing every graph."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from graphinate.color import color_hex, convert_colors_to_hex, node_color_mapping
|
|
6
|
+
|
|
7
|
+
# node_color_mapping is lru_cached on the graph instance, bypass the cache so that
|
|
8
|
+
# the mapping logic is actually measured on every iteration.
|
|
9
|
+
_node_color_mapping = node_color_mapping.__wrapped__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_node_color_mapping(benchmark, mesh_graph):
|
|
13
|
+
mapping = benchmark(_node_color_mapping, mesh_graph)
|
|
14
|
+
|
|
15
|
+
assert len(mapping) == mesh_graph.number_of_nodes()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_convert_colors_to_hex(benchmark, mesh_graph):
|
|
19
|
+
benchmark(convert_colors_to_hex, mesh_graph)
|
|
20
|
+
|
|
21
|
+
assert all(isinstance(data['color'], str) for _, data in mesh_graph.nodes(data=True))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.benchmark
|
|
25
|
+
def test_color_hex():
|
|
26
|
+
for i in range(1000):
|
|
27
|
+
value = (i % 256) / 255
|
|
28
|
+
color_hex((value, 1 - value, value / 2, 1.0))
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Benchmarks for the converters used on every GraphQL node and edge identifier."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from graphinate.converters import (
|
|
8
|
+
decode_edge_id,
|
|
9
|
+
decode_node_id,
|
|
10
|
+
edge_label_converter,
|
|
11
|
+
encode_edge_id,
|
|
12
|
+
encode_node_id,
|
|
13
|
+
node_label_converter,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
NODE_IDS = [(f'country-{i % 10}', f'city-{i}') for i in range(500)]
|
|
17
|
+
EDGE_IDS = [((source,), (target,)) for source, target in itertools.pairwise(NODE_IDS)]
|
|
18
|
+
ENCODED_NODE_IDS = [encode_node_id(node_id) for node_id in NODE_IDS]
|
|
19
|
+
ENCODED_EDGE_IDS = [encode_edge_id(edge_id) for edge_id in EDGE_IDS]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.benchmark
|
|
23
|
+
def test_encode_node_id():
|
|
24
|
+
for node_id in NODE_IDS:
|
|
25
|
+
encode_node_id(node_id)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.benchmark
|
|
29
|
+
def test_decode_node_id():
|
|
30
|
+
for encoded in ENCODED_NODE_IDS:
|
|
31
|
+
decode_node_id(encoded)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.mark.benchmark
|
|
35
|
+
def test_encode_edge_id():
|
|
36
|
+
for edge_id in EDGE_IDS:
|
|
37
|
+
encode_edge_id(edge_id)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.benchmark
|
|
41
|
+
def test_decode_edge_id():
|
|
42
|
+
for encoded in ENCODED_EDGE_IDS:
|
|
43
|
+
decode_edge_id(encoded)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.benchmark
|
|
47
|
+
def test_label_converters():
|
|
48
|
+
for node_id in NODE_IDS:
|
|
49
|
+
node_label_converter(node_id)
|
|
50
|
+
|
|
51
|
+
for edge_id in EDGE_IDS:
|
|
52
|
+
edge_label_converter(edge_id)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Benchmarks for the modeling layer: element creation and model registration."""
|
|
2
|
+
|
|
3
|
+
import operator
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from graphinate.modeling import GraphModel, elements, extractor
|
|
8
|
+
|
|
9
|
+
PAYLOADS = [{'id': i, 'name': f'item-{i}', 'kind': 'even' if i % 2 == 0 else 'odd'} for i in range(2000)]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.mark.benchmark
|
|
13
|
+
def test_elements__static_type():
|
|
14
|
+
generated = elements(PAYLOADS, 'item', key=operator.itemgetter('id'), label=operator.itemgetter('name'))
|
|
15
|
+
assert sum(1 for _ in generated) == len(PAYLOADS)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.benchmark
|
|
19
|
+
def test_elements__dynamic_type():
|
|
20
|
+
generated = elements(
|
|
21
|
+
PAYLOADS,
|
|
22
|
+
operator.itemgetter('kind'),
|
|
23
|
+
key=operator.itemgetter('id'),
|
|
24
|
+
label=operator.itemgetter('name'),
|
|
25
|
+
)
|
|
26
|
+
assert sum(1 for _ in generated) == len(PAYLOADS)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.benchmark
|
|
30
|
+
def test_extractor():
|
|
31
|
+
for payload in PAYLOADS:
|
|
32
|
+
extractor(payload, 'name')
|
|
33
|
+
extractor(payload, operator.itemgetter('id'))
|
|
34
|
+
extractor(payload)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.benchmark
|
|
38
|
+
def test_model_registration():
|
|
39
|
+
graph_model = GraphModel(name='Registration')
|
|
40
|
+
|
|
41
|
+
@graph_model.node()
|
|
42
|
+
def country(**kwargs):
|
|
43
|
+
yield from ()
|
|
44
|
+
|
|
45
|
+
@graph_model.node(parent_type='country')
|
|
46
|
+
def city(country_id=None, **kwargs):
|
|
47
|
+
yield from ()
|
|
48
|
+
|
|
49
|
+
@graph_model.edge()
|
|
50
|
+
def edge(**kwargs):
|
|
51
|
+
yield from ()
|
|
52
|
+
|
|
53
|
+
assert graph_model.node_types == {'country', 'city'}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "Graphinate"
|
|
3
|
-
version = "0.15.
|
|
3
|
+
version = "0.15.2"
|
|
4
4
|
description = "Graphinate. Data to Graphs."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Eran Rivlis", email = "eran@rivlis.info" },
|
|
@@ -99,6 +99,7 @@ test = [
|
|
|
99
99
|
"httpx",
|
|
100
100
|
"pytest",
|
|
101
101
|
"pytest-asyncio",
|
|
102
|
+
"pytest-codspeed",
|
|
102
103
|
"pytest-cov",
|
|
103
104
|
"pytest-mock",
|
|
104
105
|
"pytest-randomly",
|
|
@@ -266,18 +266,22 @@ class GraphQLBuilder(NetworkxBuilder):
|
|
|
266
266
|
def _populate_graph_node_type_enum(self, node_types: list[str]):
|
|
267
267
|
from strawberry.types.enum import EnumValue
|
|
268
268
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
self.GraphNodeType.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
269
|
+
types_to_populate = node_types if node_types else ['node']
|
|
270
|
+
|
|
271
|
+
for v in types_to_populate:
|
|
272
|
+
if v not in self.GraphNodeType._member_names_:
|
|
273
|
+
self.GraphNodeType._member_names_.append(v)
|
|
274
|
+
self.GraphNodeType._member_map_[v] = v
|
|
275
|
+
self.GraphNodeType._value2member_map_[v] = v
|
|
276
|
+
|
|
277
|
+
self.GraphNodeType.__strawberry_definition__.values.append(
|
|
278
|
+
EnumValue(
|
|
279
|
+
name=v,
|
|
280
|
+
value=v,
|
|
281
|
+
description=f"Graph Node Type: {v}"
|
|
282
|
+
)
|
|
279
283
|
)
|
|
280
|
-
|
|
284
|
+
|
|
281
285
|
|
|
282
286
|
@property
|
|
283
287
|
@functools.lru_cache
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import json
|
|
3
|
+
import sys
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from types import ModuleType
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
import rich_click as click
|
|
9
|
+
from loguru import logger
|
|
8
10
|
from strawberry import Schema
|
|
9
11
|
|
|
10
12
|
from . import GraphModel, builders, graphql
|
|
11
13
|
from .renderers.graphql import DEFAULT_PORT
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
def _configure_logging(debug: bool = False) -> None:
|
|
17
|
+
logger.remove()
|
|
18
|
+
log_level = 'DEBUG' if debug else 'INFO'
|
|
19
|
+
logger.add(sys.stderr, level=log_level)
|
|
20
|
+
|
|
21
|
+
|
|
14
22
|
def _get_kwargs(ctx: click.Context) -> dict:
|
|
15
23
|
return dict([item.strip('--').split('=') for item in ctx.args if item.startswith("--")]) # NOSONAR
|
|
16
24
|
|
|
@@ -79,15 +87,29 @@ model_option = click.option('-m', '--model',
|
|
|
79
87
|
|
|
80
88
|
|
|
81
89
|
@click.group()
|
|
90
|
+
@click.option(
|
|
91
|
+
'--debug/--no-debug',
|
|
92
|
+
default=False,
|
|
93
|
+
envvar='GRAPHINATE_DEBUG',
|
|
94
|
+
help='Enable or disable debug mode (can also be set via GRAPHINATE_DEBUG env var).'
|
|
95
|
+
)
|
|
82
96
|
@click.pass_context
|
|
83
|
-
def cli(ctx: click.Context) -> None:
|
|
97
|
+
def cli(ctx: click.Context, debug: bool) -> None:
|
|
84
98
|
ctx.ensure_object(dict)
|
|
99
|
+
ctx.obj['debug'] = debug
|
|
100
|
+
_configure_logging(debug)
|
|
85
101
|
|
|
86
102
|
|
|
87
103
|
@cli.command()
|
|
88
104
|
@model_option
|
|
105
|
+
@click.option('--debug/--no-debug', envvar='GRAPHINATE_DEBUG', default=None,
|
|
106
|
+
help='Enable or disable debug mode (can also be set via GRAPHINATE_DEBUG env var).')
|
|
89
107
|
@click.pass_context
|
|
90
|
-
def save(ctx: click.Context, model: GraphModel) -> None:
|
|
108
|
+
def save(ctx: click.Context, model: GraphModel, debug: bool | None = None) -> None:
|
|
109
|
+
if debug is not None:
|
|
110
|
+
ctx.obj['debug'] = debug
|
|
111
|
+
_configure_logging(debug)
|
|
112
|
+
|
|
91
113
|
file_path = Path(f"{model.name}.d3_graph.json")
|
|
92
114
|
|
|
93
115
|
if file_path.is_absolute():
|
|
@@ -109,8 +131,16 @@ def save(ctx: click.Context, model: GraphModel) -> None:
|
|
|
109
131
|
@model_option
|
|
110
132
|
@click.option('-p', '--port', type=int, default=DEFAULT_PORT, help='Port number.')
|
|
111
133
|
@click.option('-b', '--browse', type=bool, default=False, help='Open server address in browser.')
|
|
134
|
+
@click.option('--debug/--no-debug', envvar='GRAPHINATE_DEBUG', default=None,
|
|
135
|
+
help='Enable or disable debug mode (can also be set via GRAPHINATE_DEBUG env var).')
|
|
112
136
|
@click.pass_context
|
|
113
|
-
def server(ctx: click.Context, model: GraphModel, port: int, browse: bool) -> None:
|
|
137
|
+
def server(ctx: click.Context, model: GraphModel, port: int, browse: bool, debug: bool | None = None) -> None:
|
|
138
|
+
if debug is None:
|
|
139
|
+
debug = ctx.obj.get('debug', False)
|
|
140
|
+
else:
|
|
141
|
+
ctx.obj['debug'] = debug
|
|
142
|
+
_configure_logging(debug)
|
|
143
|
+
|
|
114
144
|
message = """
|
|
115
145
|
██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗██║███╗ ██╗ █████╗ ████████╗███████╗
|
|
116
146
|
██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║██║████╗ ██║██╔══██╗╚══██╔══╝██╔════╝
|
|
@@ -120,4 +150,4 @@ def server(ctx: click.Context, model: GraphModel, port: int, browse: bool) -> No
|
|
|
120
150
|
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚══════╝"""
|
|
121
151
|
click.echo(message)
|
|
122
152
|
schema: Schema = builders.GraphQLBuilder(model).build()
|
|
123
|
-
graphql.server(schema, port=port, browse=browse, **_get_kwargs(ctx))
|
|
153
|
+
graphql.server(schema, port=port, browse=browse, debug=debug, **_get_kwargs(ctx))
|
|
@@ -69,6 +69,7 @@ def _graphql_app(graphql_schema: strawberry.Schema) -> GraphQL:
|
|
|
69
69
|
|
|
70
70
|
def _starlette_app(graphql_app: strawberry.asgi.GraphQL | None = None,
|
|
71
71
|
port: int = DEFAULT_PORT,
|
|
72
|
+
debug: bool = False,
|
|
72
73
|
**kwargs: Any) -> Starlette:
|
|
73
74
|
def open_url(endpoint):
|
|
74
75
|
webbrowser.open(f'http://localhost:{port}/{endpoint}')
|
|
@@ -97,6 +98,7 @@ def _starlette_app(graphql_app: strawberry.asgi.GraphQL | None = None,
|
|
|
97
98
|
])
|
|
98
99
|
|
|
99
100
|
app = Starlette(
|
|
101
|
+
debug=debug,
|
|
100
102
|
lifespan=lifespan,
|
|
101
103
|
routes=app_routes
|
|
102
104
|
)
|
|
@@ -108,21 +110,24 @@ def _starlette_app(graphql_app: strawberry.asgi.GraphQL | None = None,
|
|
|
108
110
|
return app
|
|
109
111
|
|
|
110
112
|
|
|
111
|
-
def server(graphql_schema: strawberry.Schema, port: int = DEFAULT_PORT, **kwargs: Any):
|
|
113
|
+
def server(graphql_schema: strawberry.Schema, port: int = DEFAULT_PORT, debug: bool = False, **kwargs: Any):
|
|
112
114
|
"""
|
|
113
115
|
Args:
|
|
114
116
|
graphql_schema: The Strawberry GraphQL schema.
|
|
115
117
|
port: The port number to run the server on. Defaults to 8072.
|
|
118
|
+
debug: Whether to run in debug mode. Defaults to False.
|
|
116
119
|
|
|
117
120
|
Returns:
|
|
118
121
|
"""
|
|
119
122
|
|
|
120
123
|
graphql_app = _graphql_app(graphql_schema)
|
|
121
124
|
|
|
122
|
-
app = _starlette_app(graphql_app, port=port, **kwargs)
|
|
125
|
+
app = _starlette_app(graphql_app, port=port, debug=debug, **kwargs)
|
|
123
126
|
|
|
124
127
|
import uvicorn
|
|
125
|
-
|
|
128
|
+
uvicorn_log_level = 'debug' if debug else 'info'
|
|
129
|
+
uvicorn.run(app, host='0.0.0.0', port=port, log_level=kwargs.get('log_level', uvicorn_log_level))
|
|
130
|
+
|
|
126
131
|
|
|
127
132
|
|
|
128
133
|
__all__ = ['server']
|
|
@@ -136,3 +136,24 @@ def test_graphql_builder__ast_model__graph_query(ast_graph_model, graphql_query)
|
|
|
136
136
|
assert actual_graph['graph']['name'] == 'AST Graph'
|
|
137
137
|
node_types_counts = {c['name']: c['value'] for c in actual_graph['graph']['nodeTypeCounts']}
|
|
138
138
|
assert node_types_counts
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_graphql_builder_empty_graph_model():
|
|
142
|
+
empty_model = graphinate.model('Empty Model')
|
|
143
|
+
builder = graphinate.builders.GraphQLBuilder(empty_model)
|
|
144
|
+
|
|
145
|
+
import strawberry
|
|
146
|
+
schema: strawberry.Schema = builder.build()
|
|
147
|
+
assert schema is not None
|
|
148
|
+
|
|
149
|
+
result = schema.execute_sync("{ graph { name nodeCount edgeCount } nodes { id } }")
|
|
150
|
+
assert result.errors is None
|
|
151
|
+
assert result.data == {
|
|
152
|
+
"graph": {
|
|
153
|
+
"name": "Empty Model",
|
|
154
|
+
"nodeCount": 0,
|
|
155
|
+
"edgeCount": 0,
|
|
156
|
+
},
|
|
157
|
+
"nodes": [],
|
|
158
|
+
}
|
|
159
|
+
|
|
@@ -238,13 +238,18 @@ def test_root_redirect_and_schema_endpoints(client: TestClient):
|
|
|
238
238
|
def test_server_runs_uvicorn(fake_schema, monkeypatch):
|
|
239
239
|
import uvicorn
|
|
240
240
|
called = []
|
|
241
|
-
def mock_run(app, host, port):
|
|
242
|
-
called.append((host, port))
|
|
241
|
+
def mock_run(app, host, port, log_level='info'):
|
|
242
|
+
called.append((host, port, log_level, app.debug))
|
|
243
243
|
|
|
244
244
|
monkeypatch.setattr(uvicorn, "run", mock_run)
|
|
245
245
|
graphql.server(fake_schema, port=9000)
|
|
246
246
|
assert len(called) == 1
|
|
247
|
-
assert called[0] == ("0.0.0.0", 9000)
|
|
247
|
+
assert called[0] == ("0.0.0.0", 9000, "info", False)
|
|
248
|
+
|
|
249
|
+
called.clear()
|
|
250
|
+
graphql.server(fake_schema, port=9001, debug=True)
|
|
251
|
+
assert called[0] == ("0.0.0.0", 9001, "debug", True)
|
|
252
|
+
|
|
248
253
|
|
|
249
254
|
|
|
250
255
|
def test_package_not_found_fallback(monkeypatch):
|