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.
Files changed (211) hide show
  1. graphinate-0.15.2/.github/workflows/codspeed.yml +35 -0
  2. graphinate-0.15.2/.github/workflows/publish.yml +31 -0
  3. {graphinate-0.15.0 → graphinate-0.15.2}/CHANGELOG.md +12 -0
  4. {graphinate-0.15.0 → graphinate-0.15.2}/PKG-INFO +6 -3
  5. {graphinate-0.15.0 → graphinate-0.15.2}/README.md +4 -1
  6. graphinate-0.15.2/benchmarks/conftest.py +72 -0
  7. graphinate-0.15.2/benchmarks/test_builders_benchmark.py +88 -0
  8. graphinate-0.15.2/benchmarks/test_color_benchmark.py +28 -0
  9. graphinate-0.15.2/benchmarks/test_converters_benchmark.py +52 -0
  10. graphinate-0.15.2/benchmarks/test_modeling_benchmark.py +53 -0
  11. {graphinate-0.15.0 → graphinate-0.15.2}/pyproject.toml +2 -1
  12. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/graphql.py +15 -11
  13. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/cli.py +34 -4
  14. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/graphql.py +8 -3
  15. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_graphql.py +21 -0
  16. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_graphql_server.py +8 -3
  17. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_cli.py +38 -10
  18. graphinate-0.15.0/.github/workflows/publish.yml +0 -31
  19. {graphinate-0.15.0 → graphinate-0.15.2}/.coveragerc +0 -0
  20. {graphinate-0.15.0 → graphinate-0.15.2}/.deepsource.toml +0 -0
  21. {graphinate-0.15.0 → graphinate-0.15.2}/.github/dependabot.yml +0 -0
  22. {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/codeql.yml +0 -0
  23. {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/publish-docs.yaml +0 -0
  24. {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/test-beta.yml +0 -0
  25. {graphinate-0.15.0 → graphinate-0.15.2}/.github/workflows/test.yml +0 -0
  26. {graphinate-0.15.0 → graphinate-0.15.2}/.gitignore +0 -0
  27. {graphinate-0.15.0 → graphinate-0.15.2}/.python-version +0 -0
  28. {graphinate-0.15.0 → graphinate-0.15.2}/.sonarcloud.properties +0 -0
  29. {graphinate-0.15.0 → graphinate-0.15.2}/AGENTS.md +0 -0
  30. {graphinate-0.15.0 → graphinate-0.15.2}/LICENSE +0 -0
  31. {graphinate-0.15.0 → graphinate-0.15.2}/PERSONA.md +0 -0
  32. {graphinate-0.15.0 → graphinate-0.15.2}/PRINCIPLES.md +0 -0
  33. {graphinate-0.15.0 → graphinate-0.15.2}/STYLEGUIDE.md +0 -0
  34. {graphinate-0.15.0 → graphinate-0.15.2}/TELEMETRY.md +0 -0
  35. {graphinate-0.15.0 → graphinate-0.15.2}/TOOLS.md +0 -0
  36. {graphinate-0.15.0 → graphinate-0.15.2}/benchmarks/color_benchmark.py +0 -0
  37. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Builders_Analysis.md +0 -0
  38. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/CLI_Analysis.md +0 -0
  39. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Core_Analysis.md +0 -0
  40. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/GraphModel_Analysis.md +0 -0
  41. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Renderers_Analysis.md +0 -0
  42. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Server_Analysis.md +0 -0
  43. {graphinate-0.15.0 → graphinate-0.15.2}/design/analysis/Viewer_Analysis.md +0 -0
  44. {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts.md +0 -0
  45. {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts_v1.md +0 -0
  46. {graphinate-0.15.0 → graphinate-0.15.2}/design/art_prompts_v2.md +0 -0
  47. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Builders_Grading.md +0 -0
  48. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/CLI_Grading.md +0 -0
  49. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Core_Grading.md +0 -0
  50. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Modeling_Grading.md +0 -0
  51. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Renderers_Grading.md +0 -0
  52. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Server_Grading.md +0 -0
  53. {graphinate-0.15.0 → graphinate-0.15.2}/design/grading/Viewer_Grading.md +0 -0
  54. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/01_The_Foundations.md +0 -0
  55. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/02_The_Fantastical.md +0 -0
  56. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/03_The_Analog.md +0 -0
  57. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/04_The_Avant_Garde.md +0 -0
  58. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/05_The_Pop_Culture.md +0 -0
  59. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/06_The_Modern.md +0 -0
  60. {graphinate-0.15.0 → graphinate-0.15.2}/design/prompts/07_Graphinate_2.0.md +0 -0
  61. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-000_Graphinate_Enhancement_Proposals.md +0 -0
  62. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-001_The_Graphinate_Mission.md +0 -0
  63. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-002_Modularization_Strategy.md +0 -0
  64. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-003_Roadmap.md +0 -0
  65. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-007_Theoretical_Foundations.md +0 -0
  66. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-010_Explicit_Dependency_Injection.md +0 -0
  67. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-011_Standardized_Element_Factory.md +0 -0
  68. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-012_Robust_Model_Composition.md +0 -0
  69. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-013_Stable_ID_Serialization.md +0 -0
  70. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-014_Explicit_GraphQL_Schema_Generation.md +0 -0
  71. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-015_Refactored_Graph_Population_Strategies.md +0 -0
  72. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-016_CLI_Argument_Parsing_and_Path_Safety.md +0 -0
  73. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-017_Decoupled_Server_Architecture.md +0 -0
  74. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-018_Relay_Support.md +0 -0
  75. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-019_Offline_Viewer_Support.md +0 -0
  76. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-020_Modular_Frontend_Architecture.md +0 -0
  77. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-022_2D_Visualization_Mode.md +0 -0
  78. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-023_Async_Support.md +0 -0
  79. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-024_The_Graph_Engine.md +0 -0
  80. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-025_Signed_ID_Serialization.md +0 -0
  81. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-026_Pluggable_Visualization_and_Notebook_Support.md +0 -0
  82. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-027_Advanced_ThreeJS_Visualizations.md +0 -0
  83. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-028_MCP_Server_Integration.md +0 -0
  84. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-029_Extraction_Strategies.md +0 -0
  85. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-030_Node_Type_Taxonomy.md +0 -0
  86. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-031_High_Performance_Vectorized_Operations.md +0 -0
  87. {graphinate-0.15.0 → graphinate-0.15.2}/design/proposals/GEP-032_GraphModel_Compilation_and_Validation_Lifecycle.md +0 -0
  88. {graphinate-0.15.0 → graphinate-0.15.2}/docs/acknowledgements.md +0 -0
  89. {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/modeling.md +0 -0
  90. {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/optimization.md +0 -0
  91. {graphinate-0.15.0 → graphinate-0.15.2}/docs/advanced/secure_ids.md +0 -0
  92. {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/badge/v0.json +0 -0
  93. {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/images/logo-128.png +0 -0
  94. {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/images/network_graph.png +0 -0
  95. {graphinate-0.15.0 → graphinate-0.15.2}/docs/assets/stylesheets/extra.css +0 -0
  96. {graphinate-0.15.0 → graphinate-0.15.2}/docs/development.md +0 -0
  97. {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/class_diagram.md +0 -0
  98. {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/component_diagram.md +0 -0
  99. {graphinate-0.15.0 → graphinate-0.15.2}/docs/diagrams/function_call_graph.md +0 -0
  100. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/code.md +0 -0
  101. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/github.md +0 -0
  102. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/math.md +0 -0
  103. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/social.md +0 -0
  104. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/system.md +0 -0
  105. {graphinate-0.15.0 → graphinate-0.15.2}/docs/examples/web.md +0 -0
  106. {graphinate-0.15.0 → graphinate-0.15.2}/docs/gen_ref_pages.py +0 -0
  107. {graphinate-0.15.0 → graphinate-0.15.2}/docs/index.md +0 -0
  108. {graphinate-0.15.0 → graphinate-0.15.2}/docs/introduction.md +0 -0
  109. {graphinate-0.15.0 → graphinate-0.15.2}/docs/start.md +0 -0
  110. {graphinate-0.15.0 → graphinate-0.15.2}/docs/tutorial.md +0 -0
  111. {graphinate-0.15.0 → graphinate-0.15.2}/docs/usage/cli.md +0 -0
  112. {graphinate-0.15.0 → graphinate-0.15.2}/docs/usage/lib.md +0 -0
  113. {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/git_commits.py +0 -0
  114. {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/python_ast.py +0 -0
  115. {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/python_dependencies.py +0 -0
  116. {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/requirements.txt +0 -0
  117. {graphinate-0.15.0 → graphinate-0.15.2}/examples/code/tokens.py +0 -0
  118. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/_client.py +0 -0
  119. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/commits_visibilty_graph.py +0 -0
  120. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/followers.graphql +0 -0
  121. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/followers.py +0 -0
  122. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/graphql.config.yml +0 -0
  123. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/repositories.graphql +0 -0
  124. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/repositories.py +0 -0
  125. {graphinate-0.15.0 → graphinate-0.15.2}/examples/github/requirements.txt +0 -0
  126. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/_test_materializers.py +0 -0
  127. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/graph_atlas.py +0 -0
  128. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/graphs.py +0 -0
  129. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/gui.py +0 -0
  130. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/materializers.py +0 -0
  131. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/polygonal_graph.py +0 -0
  132. {graphinate-0.15.0 → graphinate-0.15.2}/examples/math/requirements.txt +0 -0
  133. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/13/dd/73ce25face7beb30b69b64feeb77.val +0 -0
  134. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/21/9e/00846f323987ba16cfbe0127d8eb.val +0 -0
  135. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/70/b6/2aefb0269adce7fedf877fa0d267.val +0 -0
  136. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/87/f5/ec1739bc369e84c3fcb302bf532a.val +0 -0
  137. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/ba/fe/3aca7b2c38abff60e7ce5eb486a8.val +0 -0
  138. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/c7/9e/ce82b0288020b7152779df09bd73.val +0 -0
  139. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/cache.db +0 -0
  140. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/d2/53/3b88f2fc162561cfdbbe9abc352a.val +0 -0
  141. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/cache/e2/d5/5d079f200eabf9b625b0473f6fbe.val +0 -0
  142. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/gui.py +0 -0
  143. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/music_artists.py +0 -0
  144. {graphinate-0.15.0 → graphinate-0.15.2}/examples/social/requirements.txt +0 -0
  145. {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/.ignore +0 -0
  146. {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/files.py +0 -0
  147. {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/processes.py +0 -0
  148. {graphinate-0.15.0 → graphinate-0.15.2}/examples/system/requirements.txt +0 -0
  149. {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/html_dom.py +0 -0
  150. {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/page_links.py +0 -0
  151. {graphinate-0.15.0 → graphinate-0.15.2}/examples/web/requirements.txt +0 -0
  152. {graphinate-0.15.0 → graphinate-0.15.2}/playground/check_repo.py +0 -0
  153. {graphinate-0.15.0 → graphinate-0.15.2}/playground/ethernet/traceroute.py +0 -0
  154. {graphinate-0.15.0 → graphinate-0.15.2}/playground/genric_graph.graphql +0 -0
  155. {graphinate-0.15.0 → graphinate-0.15.2}/playground/graphql.config.yml +0 -0
  156. {graphinate-0.15.0 → graphinate-0.15.2}/playground/house_of_graphs.py +0 -0
  157. {graphinate-0.15.0 → graphinate-0.15.2}/playground/octagonal_graph.py +0 -0
  158. {graphinate-0.15.0 → graphinate-0.15.2}/playground/science/caffeine.py +0 -0
  159. {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/db.xml +0 -0
  160. {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/music.py +0 -0
  161. {graphinate-0.15.0 → graphinate-0.15.2}/playground/social/requirements.txt +0 -0
  162. {graphinate-0.15.0 → graphinate-0.15.2}/playground/text/nlp_graph.py +0 -0
  163. {graphinate-0.15.0 → graphinate-0.15.2}/playground/text/requirements.txt +0 -0
  164. {graphinate-0.15.0 → graphinate-0.15.2}/playground/time_series/requirements.txt +0 -0
  165. {graphinate-0.15.0 → graphinate-0.15.2}/playground/time_series/visibility_graph.py +0 -0
  166. {graphinate-0.15.0 → graphinate-0.15.2}/playground/word_graph.py +0 -0
  167. {graphinate-0.15.0 → graphinate-0.15.2}/properdocs.yml +0 -0
  168. {graphinate-0.15.0 → graphinate-0.15.2}/sonar-project.properties +0 -0
  169. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/.agents/skills/graphinate/SKILL.md +0 -0
  170. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/__init__.py +0 -0
  171. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/__main__.py +0 -0
  172. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/_secure.py +0 -0
  173. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/__init__.py +0 -0
  174. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/builder.py +0 -0
  175. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/d3.py +0 -0
  176. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/mermaid.py +0 -0
  177. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/builders/networkx.py +0 -0
  178. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/color.py +0 -0
  179. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/constants.py +0 -0
  180. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/converters.py +0 -0
  181. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/enums.py +0 -0
  182. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/modeling.py +0 -0
  183. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/__init__.py +0 -0
  184. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/renderers/matplotlib.py +0 -0
  185. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/__init__.py +0 -0
  186. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/starlette/__init__.py +0 -0
  187. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/starlette/views.py +0 -0
  188. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/__init__.py +0 -0
  189. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/elements/index.html +0 -0
  190. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/graphiql/index.html +0 -0
  191. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/rapidoc/index.html +0 -0
  192. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/logo-128.png +0 -0
  193. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/logo.svg +0 -0
  194. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/static/images/network_graph.png +0 -0
  195. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/viewer/index.html +0 -0
  196. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/server/web/voyager/index.html +0 -0
  197. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/tools.py +0 -0
  198. {graphinate-0.15.0 → graphinate-0.15.2}/src/graphinate/typing.py +0 -0
  199. {graphinate-0.15.0 → graphinate-0.15.2}/tests/conftest.py +0 -0
  200. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_build.py +0 -0
  201. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_d3.py +0 -0
  202. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_mermaid.py +0 -0
  203. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/builders/test_networkx.py +0 -0
  204. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_matplotlib_draw.py +0 -0
  205. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/renderers/test_matplotlib_plot.py +0 -0
  206. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/server/test_starlette.py +0 -0
  207. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_color.py +0 -0
  208. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_converters.py +0 -0
  209. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_modeling.py +0 -0
  210. {graphinate-0.15.0 → graphinate-0.15.2}/tests/graphinate/test_modeling_add.py +0 -0
  211. {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.4
1
+ Metadata-Version: 2.5
2
2
  Name: Graphinate
3
- Version: 0.15.0
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
- <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>
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
- <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>
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.0"
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
- for v in node_types:
270
- self.GraphNodeType._member_names_.append(v)
271
- self.GraphNodeType._member_map_[v] = v
272
- self.GraphNodeType._value2member_map_[v] = v
273
-
274
- self.GraphNodeType.__strawberry_definition__.values.append(
275
- EnumValue(
276
- name=v,
277
- value=v,
278
- description=f"Graph Node Type: {v}"
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
- uvicorn.run(app, host='0.0.0.0', port=port)
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):