graphitect 0.2.0__py3-none-any.whl
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.
- graphify/__init__.py +30 -0
- graphify/__main__.py +757 -0
- graphify/_minhash.py +107 -0
- graphify/affected.py +318 -0
- graphify/always_on/agents-md.md +12 -0
- graphify/always_on/antigravity-rules.md +14 -0
- graphify/always_on/claude-md.md +9 -0
- graphify/always_on/gemini-md.md +9 -0
- graphify/always_on/kiro-steering.md +5 -0
- graphify/always_on/vscode-instructions.md +17 -0
- graphify/analyze.py +769 -0
- graphify/benchmark.py +152 -0
- graphify/build.py +2300 -0
- graphify/cache.py +1746 -0
- graphify/callflow_html.py +2051 -0
- graphify/cargo_introspect.py +109 -0
- graphify/cli.py +4745 -0
- graphify/cluster.py +409 -0
- graphify/command-kilo.md +15 -0
- graphify/cross_repo_calls.py +216 -0
- graphify/cross_repo_types.py +75 -0
- graphify/csharp_dispatch.py +154 -0
- graphify/dedup.py +1213 -0
- graphify/detect.py +2566 -0
- graphify/diagnostics.py +406 -0
- graphify/export.py +1349 -0
- graphify/exporters/__init__.py +1 -0
- graphify/exporters/base.py +14 -0
- graphify/exporters/graphdb.py +173 -0
- graphify/exporters/html.py +637 -0
- graphify/extract.py +7856 -0
- graphify/extractors/MIGRATION.md +107 -0
- graphify/extractors/__init__.py +66 -0
- graphify/extractors/apex.py +215 -0
- graphify/extractors/base.py +85 -0
- graphify/extractors/bash.py +579 -0
- graphify/extractors/blade.py +53 -0
- graphify/extractors/commonlisp.py +540 -0
- graphify/extractors/csharp.py +448 -0
- graphify/extractors/dart.py +564 -0
- graphify/extractors/dm.py +494 -0
- graphify/extractors/elixir.py +241 -0
- graphify/extractors/engine.py +6509 -0
- graphify/extractors/fortran.py +311 -0
- graphify/extractors/go.py +527 -0
- graphify/extractors/json_config.py +240 -0
- graphify/extractors/julia.py +289 -0
- graphify/extractors/markdown.py +408 -0
- graphify/extractors/models.py +131 -0
- graphify/extractors/objc.py +566 -0
- graphify/extractors/ocaml.py +289 -0
- graphify/extractors/pascal.py +688 -0
- graphify/extractors/pascal_forms.py +196 -0
- graphify/extractors/powershell.py +522 -0
- graphify/extractors/razor.py +192 -0
- graphify/extractors/resolution.py +3584 -0
- graphify/extractors/robot.py +296 -0
- graphify/extractors/rust.py +470 -0
- graphify/extractors/sln.py +92 -0
- graphify/extractors/sql.py +720 -0
- graphify/extractors/terraform.py +181 -0
- graphify/extractors/verilog.py +329 -0
- graphify/extractors/zig.py +181 -0
- graphify/file_slice.py +246 -0
- graphify/global_graph.py +194 -0
- graphify/google_workspace.py +237 -0
- graphify/hooks.py +933 -0
- graphify/ids.py +93 -0
- graphify/ingest.py +358 -0
- graphify/install.py +2366 -0
- graphify/llm.py +3544 -0
- graphify/manifest.py +4 -0
- graphify/manifest_ingest.py +311 -0
- graphify/mcp_ingest.py +386 -0
- graphify/multigraph_compat.py +212 -0
- graphify/pascal_resolution.py +129 -0
- graphify/paths.py +436 -0
- graphify/pg_introspect.py +165 -0
- graphify/prs.py +770 -0
- graphify/querylog.py +80 -0
- graphify/reflect.py +882 -0
- graphify/report.py +346 -0
- graphify/resolver_registry.py +85 -0
- graphify/ruby_resolution.py +242 -0
- graphify/scip_ingest.py +363 -0
- graphify/security.py +460 -0
- graphify/semantic_cleanup.py +336 -0
- graphify/serve.py +2608 -0
- graphify/skill-agents.md +710 -0
- graphify/skill-aider.md +1283 -0
- graphify/skill-amp.md +710 -0
- graphify/skill-claw.md +713 -0
- graphify/skill-codex.md +710 -0
- graphify/skill-copilot.md +713 -0
- graphify/skill-devin.md +1410 -0
- graphify/skill-droid.md +710 -0
- graphify/skill-kilo.md +722 -0
- graphify/skill-kiro.md +713 -0
- graphify/skill-opencode.md +705 -0
- graphify/skill-pi.md +713 -0
- graphify/skill-trae.md +711 -0
- graphify/skill-vscode.md +709 -0
- graphify/skill-windows.md +755 -0
- graphify/skill.md +713 -0
- graphify/skills/agents/references/add-watch.md +56 -0
- graphify/skills/agents/references/exports.md +87 -0
- graphify/skills/agents/references/extraction-spec.md +70 -0
- graphify/skills/agents/references/github-and-merge.md +46 -0
- graphify/skills/agents/references/hooks.md +33 -0
- graphify/skills/agents/references/query.md +311 -0
- graphify/skills/agents/references/transcribe.md +52 -0
- graphify/skills/agents/references/update.md +210 -0
- graphify/skills/amp/references/add-watch.md +56 -0
- graphify/skills/amp/references/exports.md +87 -0
- graphify/skills/amp/references/extraction-spec.md +70 -0
- graphify/skills/amp/references/github-and-merge.md +46 -0
- graphify/skills/amp/references/hooks.md +33 -0
- graphify/skills/amp/references/query.md +311 -0
- graphify/skills/amp/references/transcribe.md +52 -0
- graphify/skills/amp/references/update.md +210 -0
- graphify/skills/claude/references/add-watch.md +56 -0
- graphify/skills/claude/references/exports.md +87 -0
- graphify/skills/claude/references/extraction-spec.md +70 -0
- graphify/skills/claude/references/github-and-merge.md +46 -0
- graphify/skills/claude/references/hooks.md +33 -0
- graphify/skills/claude/references/query.md +311 -0
- graphify/skills/claude/references/transcribe.md +52 -0
- graphify/skills/claude/references/update.md +210 -0
- graphify/skills/claw/references/add-watch.md +56 -0
- graphify/skills/claw/references/exports.md +87 -0
- graphify/skills/claw/references/extraction-spec.md +31 -0
- graphify/skills/claw/references/github-and-merge.md +46 -0
- graphify/skills/claw/references/hooks.md +33 -0
- graphify/skills/claw/references/query.md +311 -0
- graphify/skills/claw/references/transcribe.md +52 -0
- graphify/skills/claw/references/update.md +210 -0
- graphify/skills/codex/references/add-watch.md +56 -0
- graphify/skills/codex/references/exports.md +87 -0
- graphify/skills/codex/references/extraction-spec.md +31 -0
- graphify/skills/codex/references/github-and-merge.md +46 -0
- graphify/skills/codex/references/hooks.md +33 -0
- graphify/skills/codex/references/query.md +311 -0
- graphify/skills/codex/references/transcribe.md +52 -0
- graphify/skills/codex/references/update.md +210 -0
- graphify/skills/copilot/references/add-watch.md +56 -0
- graphify/skills/copilot/references/exports.md +87 -0
- graphify/skills/copilot/references/extraction-spec.md +70 -0
- graphify/skills/copilot/references/github-and-merge.md +46 -0
- graphify/skills/copilot/references/hooks.md +33 -0
- graphify/skills/copilot/references/query.md +311 -0
- graphify/skills/copilot/references/transcribe.md +52 -0
- graphify/skills/copilot/references/update.md +210 -0
- graphify/skills/droid/references/add-watch.md +56 -0
- graphify/skills/droid/references/exports.md +87 -0
- graphify/skills/droid/references/extraction-spec.md +70 -0
- graphify/skills/droid/references/github-and-merge.md +46 -0
- graphify/skills/droid/references/hooks.md +33 -0
- graphify/skills/droid/references/query.md +311 -0
- graphify/skills/droid/references/transcribe.md +52 -0
- graphify/skills/droid/references/update.md +210 -0
- graphify/skills/kilo/references/add-watch.md +56 -0
- graphify/skills/kilo/references/exports.md +87 -0
- graphify/skills/kilo/references/extraction-spec.md +70 -0
- graphify/skills/kilo/references/github-and-merge.md +46 -0
- graphify/skills/kilo/references/hooks.md +33 -0
- graphify/skills/kilo/references/query.md +311 -0
- graphify/skills/kilo/references/transcribe.md +52 -0
- graphify/skills/kilo/references/update.md +210 -0
- graphify/skills/kiro/references/add-watch.md +56 -0
- graphify/skills/kiro/references/exports.md +87 -0
- graphify/skills/kiro/references/extraction-spec.md +31 -0
- graphify/skills/kiro/references/github-and-merge.md +46 -0
- graphify/skills/kiro/references/hooks.md +33 -0
- graphify/skills/kiro/references/query.md +311 -0
- graphify/skills/kiro/references/transcribe.md +52 -0
- graphify/skills/kiro/references/update.md +210 -0
- graphify/skills/opencode/references/add-watch.md +56 -0
- graphify/skills/opencode/references/exports.md +87 -0
- graphify/skills/opencode/references/extraction-spec.md +70 -0
- graphify/skills/opencode/references/github-and-merge.md +46 -0
- graphify/skills/opencode/references/hooks.md +33 -0
- graphify/skills/opencode/references/query.md +311 -0
- graphify/skills/opencode/references/transcribe.md +52 -0
- graphify/skills/opencode/references/update.md +210 -0
- graphify/skills/pi/references/add-watch.md +56 -0
- graphify/skills/pi/references/exports.md +87 -0
- graphify/skills/pi/references/extraction-spec.md +31 -0
- graphify/skills/pi/references/github-and-merge.md +46 -0
- graphify/skills/pi/references/hooks.md +33 -0
- graphify/skills/pi/references/query.md +311 -0
- graphify/skills/pi/references/transcribe.md +52 -0
- graphify/skills/pi/references/update.md +210 -0
- graphify/skills/trae/references/add-watch.md +56 -0
- graphify/skills/trae/references/exports.md +87 -0
- graphify/skills/trae/references/extraction-spec.md +70 -0
- graphify/skills/trae/references/github-and-merge.md +46 -0
- graphify/skills/trae/references/hooks.md +35 -0
- graphify/skills/trae/references/query.md +311 -0
- graphify/skills/trae/references/transcribe.md +52 -0
- graphify/skills/trae/references/update.md +210 -0
- graphify/skills/vscode/references/add-watch.md +56 -0
- graphify/skills/vscode/references/exports.md +87 -0
- graphify/skills/vscode/references/extraction-spec.md +70 -0
- graphify/skills/vscode/references/github-and-merge.md +46 -0
- graphify/skills/vscode/references/hooks.md +33 -0
- graphify/skills/vscode/references/query.md +311 -0
- graphify/skills/vscode/references/transcribe.md +52 -0
- graphify/skills/vscode/references/update.md +210 -0
- graphify/skills/windows/references/add-watch.md +56 -0
- graphify/skills/windows/references/exports.md +87 -0
- graphify/skills/windows/references/extraction-spec.md +70 -0
- graphify/skills/windows/references/github-and-merge.md +46 -0
- graphify/skills/windows/references/hooks.md +33 -0
- graphify/skills/windows/references/query.md +311 -0
- graphify/skills/windows/references/transcribe.md +52 -0
- graphify/skills/windows/references/update.md +210 -0
- graphify/symbol_resolution.py +556 -0
- graphify/transcribe.py +186 -0
- graphify/tree_html.py +603 -0
- graphify/validate.py +95 -0
- graphify/watch.py +2280 -0
- graphify/wiki.py +405 -0
- graphitect/__init__.py +28 -0
- graphitect/__main__.py +4 -0
- graphitect/_vendor/__init__.py +2 -0
- graphitect/_vendor/archify/LICENSE +22 -0
- graphitect/_vendor/archify/SKILL.md +137 -0
- graphitect/_vendor/archify/THIRD_PARTY_NOTICES.md +69 -0
- graphitect/_vendor/archify/assets/JetBrainsMono-OFL.txt +93 -0
- graphitect/_vendor/archify/assets/template.html +14935 -0
- graphitect/_vendor/archify/bin/archify.mjs +2091 -0
- graphitect/_vendor/archify/bin/open-artifact.mjs +86 -0
- graphitect/_vendor/archify/bin/preview.mjs +653 -0
- graphitect/_vendor/archify/bin/visual-check.mjs +829 -0
- graphitect/_vendor/archify/brand-marks/README.md +31 -0
- graphitect/_vendor/archify/brand-marks/catalog.json +131 -0
- graphitect/_vendor/archify/delta/architecture-delta.mjs +1221 -0
- graphitect/_vendor/archify/examples/agent-run.lifecycle.json +60 -0
- graphitect/_vendor/archify/examples/agent-tool-call.workflow.json +94 -0
- graphitect/_vendor/archify/examples/async-job-roundtrip.sequence.json +61 -0
- graphitect/_vendor/archify/examples/brand-aware-delivery.architecture.json +47 -0
- graphitect/_vendor/archify/examples/cache-miss-request.sequence.json +82 -0
- graphitect/_vendor/archify/examples/checkout-platform.base.architecture.json +31 -0
- graphitect/_vendor/archify/examples/checkout-platform.head.architecture.json +31 -0
- graphitect/_vendor/archify/examples/dataflow-product-analytics.html +15045 -0
- graphitect/_vendor/archify/examples/deployment-release.lifecycle.json +49 -0
- graphitect/_vendor/archify/examples/event-stream.dataflow.json +57 -0
- graphitect/_vendor/archify/examples/incident-response.workflow.json +64 -0
- graphitect/_vendor/archify/examples/lifecycle-agent-run.html +14980 -0
- graphitect/_vendor/archify/examples/product-analytics.dataflow.json +76 -0
- graphitect/_vendor/archify/examples/production-deployment.architecture.json +71 -0
- graphitect/_vendor/archify/examples/release-delivery.workflow.json +62 -0
- graphitect/_vendor/archify/examples/sequence-cache-miss-request.html +15060 -0
- graphitect/_vendor/archify/examples/web-app-rendered.html +15009 -0
- graphitect/_vendor/archify/examples/web-app.architecture.json +46 -0
- graphitect/_vendor/archify/examples/workflow-agent-tool-call-rendered.html +15051 -0
- graphitect/_vendor/archify/migrations/workflow-v2.mjs +279 -0
- graphitect/_vendor/archify/package-lock.json +149 -0
- graphitect/_vendor/archify/package.json +39 -0
- graphitect/_vendor/archify/recipes/scenarios.mjs +391 -0
- graphitect/_vendor/archify/references/authoring-contract.md +243 -0
- graphitect/_vendor/archify/references/brand-marks.md +65 -0
- graphitect/_vendor/archify/references/delivery-contract.md +120 -0
- graphitect/_vendor/archify/references/viewer-runtime.md +45 -0
- graphitect/_vendor/archify/renderers/architecture/grid.mjs +62 -0
- graphitect/_vendor/archify/renderers/architecture/render-architecture.mjs +1078 -0
- graphitect/_vendor/archify/renderers/dataflow/README.md +104 -0
- graphitect/_vendor/archify/renderers/dataflow/render-dataflow.mjs +483 -0
- graphitect/_vendor/archify/renderers/lifecycle/README.md +115 -0
- graphitect/_vendor/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
- graphitect/_vendor/archify/renderers/sequence/README.md +114 -0
- graphitect/_vendor/archify/renderers/sequence/render-sequence.mjs +464 -0
- graphitect/_vendor/archify/renderers/shared/brand-marks.mjs +563 -0
- graphitect/_vendor/archify/renderers/shared/cli.mjs +218 -0
- graphitect/_vendor/archify/renderers/shared/desktop-readability.mjs +26 -0
- graphitect/_vendor/archify/renderers/shared/diagnostics.mjs +127 -0
- graphitect/_vendor/archify/renderers/shared/engineering-profiles.mjs +157 -0
- graphitect/_vendor/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
- graphitect/_vendor/archify/renderers/shared/generated-validators.mjs +13 -0
- graphitect/_vendor/archify/renderers/shared/geometry.mjs +1423 -0
- graphitect/_vendor/archify/renderers/shared/i18n.mjs +595 -0
- graphitect/_vendor/archify/renderers/shared/layout-report.mjs +40 -0
- graphitect/_vendor/archify/renderers/shared/legend.mjs +217 -0
- graphitect/_vendor/archify/renderers/shared/output-path.mjs +340 -0
- graphitect/_vendor/archify/renderers/shared/repository-evidence.mjs +238 -0
- graphitect/_vendor/archify/renderers/shared/repository-location.mjs +58 -0
- graphitect/_vendor/archify/renderers/shared/text-fit.mjs +49 -0
- graphitect/_vendor/archify/renderers/shared/utils.mjs +232 -0
- graphitect/_vendor/archify/renderers/shared/validator.mjs +86 -0
- graphitect/_vendor/archify/renderers/workflow/README.md +223 -0
- graphitect/_vendor/archify/renderers/workflow/render-workflow.mjs +35 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
- graphitect/_vendor/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
- graphitect/_vendor/archify/schemas/README.md +211 -0
- graphitect/_vendor/archify/schemas/architecture.schema.json +178 -0
- graphitect/_vendor/archify/schemas/common.schema.json +115 -0
- graphitect/_vendor/archify/schemas/dataflow.schema.json +243 -0
- graphitect/_vendor/archify/schemas/lifecycle.schema.json +266 -0
- graphitect/_vendor/archify/schemas/sequence.schema.json +223 -0
- graphitect/_vendor/archify/schemas/workflow.schema.json +428 -0
- graphitect/_vendor/archify/scripts/check-render-output.mjs +836 -0
- graphitect/_vendor/archify/scripts/check-update.mjs +1667 -0
- graphitect/_vendor/archify/scripts/generate-brand-marks.mjs +141 -0
- graphitect/_vendor/archify/scripts/generate-validators.mjs +66 -0
- graphitect/_vendor/archify/scripts/render-examples.mjs +26 -0
- graphitect/_vendor/archify/scripts/update-contract.mjs +182 -0
- graphitect/_vendor/archify/skill-release.json +10 -0
- graphitect/cli.py +981 -0
- graphitect/deliver/__init__.py +5 -0
- graphitect/deliver/archify_adapter.py +1877 -0
- graphitect/deliver/archify_ir.py +160 -0
- graphitect/deliver/archify_repair.py +135 -0
- graphitect/deliver/doc_compiler.py +916 -0
- graphitect/ground/__init__.py +5 -0
- graphitect/ground/describe_source.py +27 -0
- graphitect/ground/fullread_source.py +56 -0
- graphitect/ground/graphify_source.py +107 -0
- graphitect/models.py +118 -0
- graphitect/skill/SKILL.md +80 -0
- graphitect/skill/agents/openai.yaml +4 -0
- graphitect/synthesize/__init__.py +5 -0
- graphitect/synthesize/engine.py +281 -0
- graphitect/synthesize/llm_backend.py +331 -0
- graphitect/synthesize/questions.py +139 -0
- graphitect/synthesize/rubric.py +104 -0
- graphitect-0.2.0.dist-info/METADATA +284 -0
- graphitect-0.2.0.dist-info/RECORD +336 -0
- graphitect-0.2.0.dist-info/WHEEL +5 -0
- graphitect-0.2.0.dist-info/entry_points.txt +2 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE +21 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-ARCHIFY-MIT +22 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-APACHE-2.0 +202 -0
- graphitect-0.2.0.dist-info/licenses/LICENSE-GRAPHIFY-MIT +21 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-ARCHIFY-THIRD-PARTY.md +69 -0
- graphitect-0.2.0.dist-info/licenses/NOTICE-GRAPHIFY +8 -0
- graphitect-0.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""exporters package."""
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Shared constants/helpers for the graphify exporters package.
|
|
2
|
+
|
|
3
|
+
Symbols used by more than one exporter live here so each exporter module can be
|
|
4
|
+
split out of graphify/export.py without a circular import (export.py and the
|
|
5
|
+
per-format modules both import from here, never from each other).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# Categorical palette for community coloring, shared by the HTML, SVG, and
|
|
10
|
+
# Obsidian exporters. Moved verbatim from graphify/export.py.
|
|
11
|
+
COMMUNITY_COLORS = [
|
|
12
|
+
"#4E79A7", "#F28E2B", "#E15759", "#76B7B2", "#59A14F",
|
|
13
|
+
"#EDC948", "#B07AA1", "#FF9DA7", "#9C755F", "#BAB0AC",
|
|
14
|
+
]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""graphdb — moved verbatim from graphify/export.py."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from graphify.analyze import _node_community_map
|
|
5
|
+
import networkx as nx
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def push_to_neo4j(
|
|
10
|
+
G: nx.Graph,
|
|
11
|
+
uri: str,
|
|
12
|
+
user: str,
|
|
13
|
+
password: str,
|
|
14
|
+
communities: dict[int, list[str]] | None = None,
|
|
15
|
+
) -> dict[str, int]:
|
|
16
|
+
"""Push graph directly to a running Neo4j instance via the Python driver.
|
|
17
|
+
|
|
18
|
+
Requires: pip install neo4j
|
|
19
|
+
|
|
20
|
+
Uses MERGE so re-running is safe - nodes and edges are upserted, not duplicated.
|
|
21
|
+
Returns a dict with counts of nodes and edges pushed.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
from neo4j import GraphDatabase
|
|
25
|
+
except ImportError as e:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
"neo4j driver not installed. Run: pip install neo4j"
|
|
28
|
+
) from e
|
|
29
|
+
|
|
30
|
+
node_community = _node_community_map(communities) if communities else {}
|
|
31
|
+
|
|
32
|
+
def _safe_rel(relation: str) -> str:
|
|
33
|
+
return re.sub(r"[^A-Z0-9_]", "_", relation.upper().replace(" ", "_").replace("-", "_")) or "RELATED_TO"
|
|
34
|
+
|
|
35
|
+
def _safe_label(label: str) -> str:
|
|
36
|
+
"""Sanitize a Neo4j node label to prevent Cypher injection."""
|
|
37
|
+
sanitized = re.sub(r"[^A-Za-z0-9_]", "", label)
|
|
38
|
+
return sanitized if sanitized else "Entity"
|
|
39
|
+
|
|
40
|
+
driver = GraphDatabase.driver(uri, auth=(user, password))
|
|
41
|
+
nodes_pushed = 0
|
|
42
|
+
edges_pushed = 0
|
|
43
|
+
|
|
44
|
+
with driver.session() as session:
|
|
45
|
+
for node_id, data in G.nodes(data=True):
|
|
46
|
+
props = {
|
|
47
|
+
k: v for k, v in data.items()
|
|
48
|
+
if isinstance(v, (str, int, float, bool)) and not k.startswith("_")
|
|
49
|
+
}
|
|
50
|
+
props["id"] = node_id
|
|
51
|
+
cid = node_community.get(node_id)
|
|
52
|
+
if cid is not None:
|
|
53
|
+
props["community"] = cid
|
|
54
|
+
ftype = _safe_label(data.get("file_type", "Entity").capitalize())
|
|
55
|
+
session.run(
|
|
56
|
+
f"MERGE (n:{ftype} {{id: $id}}) SET n += $props",
|
|
57
|
+
id=node_id,
|
|
58
|
+
props=props,
|
|
59
|
+
)
|
|
60
|
+
nodes_pushed += 1
|
|
61
|
+
|
|
62
|
+
for u, v, data in G.edges(data=True):
|
|
63
|
+
rel = _safe_rel(data.get("relation", "RELATED_TO"))
|
|
64
|
+
props = {
|
|
65
|
+
k: v for k, v in data.items()
|
|
66
|
+
if isinstance(v, (str, int, float, bool)) and not k.startswith("_")
|
|
67
|
+
}
|
|
68
|
+
session.run(
|
|
69
|
+
f"MATCH (a {{id: $src}}), (b {{id: $tgt}}) "
|
|
70
|
+
f"MERGE (a)-[r:{rel}]->(b) SET r += $props",
|
|
71
|
+
src=u,
|
|
72
|
+
tgt=v,
|
|
73
|
+
props=props,
|
|
74
|
+
)
|
|
75
|
+
edges_pushed += 1
|
|
76
|
+
|
|
77
|
+
driver.close()
|
|
78
|
+
return {"nodes": nodes_pushed, "edges": edges_pushed}
|
|
79
|
+
|
|
80
|
+
def push_to_falkordb(
|
|
81
|
+
G: nx.Graph,
|
|
82
|
+
uri: str,
|
|
83
|
+
user: str | None = None,
|
|
84
|
+
password: str | None = None,
|
|
85
|
+
communities: dict[int, list[str]] | None = None,
|
|
86
|
+
graph_name: str = "graphify",
|
|
87
|
+
) -> dict[str, int]:
|
|
88
|
+
"""Push graph directly to a running FalkorDB instance via the Python SDK.
|
|
89
|
+
|
|
90
|
+
Requires: pip install falkordb
|
|
91
|
+
|
|
92
|
+
FalkorDB is OpenCypher-compatible, so the MERGE/SET upsert queries are
|
|
93
|
+
identical to push_to_neo4j. Differences from the Neo4j path:
|
|
94
|
+
- connects with FalkorDB(host, port, username, password) instead of a bolt
|
|
95
|
+
driver; only the host/port are read from the URI, so the scheme is
|
|
96
|
+
informational - "falkordb://localhost:6379", "redis://localhost:6379"
|
|
97
|
+
and a bare "localhost:6379" are all equivalent (default port 6379).
|
|
98
|
+
- a named graph is selected via db.select_graph(graph_name) (default
|
|
99
|
+
"graphify"); FalkorDB keys each graph by name in the same instance.
|
|
100
|
+
- queries run via graph.query(cypher, params) - there is no session object.
|
|
101
|
+
- auth is optional (FalkorDB runs without credentials by default), so user
|
|
102
|
+
and password may be None.
|
|
103
|
+
- no APOC: the Neo4j path does not use APOC either, so nothing to port.
|
|
104
|
+
|
|
105
|
+
Uses MERGE so re-running is safe - nodes and edges are upserted, not
|
|
106
|
+
duplicated. Returns a dict with counts of nodes and edges pushed.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
from falkordb import FalkorDB
|
|
110
|
+
except ImportError as e:
|
|
111
|
+
raise ImportError(
|
|
112
|
+
"falkordb SDK not installed. Run: pip install falkordb"
|
|
113
|
+
) from e
|
|
114
|
+
|
|
115
|
+
from urllib.parse import urlparse
|
|
116
|
+
|
|
117
|
+
node_community = _node_community_map(communities) if communities else {}
|
|
118
|
+
|
|
119
|
+
def _safe_rel(relation: str) -> str:
|
|
120
|
+
return re.sub(r"[^A-Z0-9_]", "_", relation.upper().replace(" ", "_").replace("-", "_")) or "RELATED_TO"
|
|
121
|
+
|
|
122
|
+
def _safe_label(label: str) -> str:
|
|
123
|
+
"""Sanitize a FalkorDB node label to prevent Cypher injection."""
|
|
124
|
+
sanitized = re.sub(r"[^A-Za-z0-9_]", "", label)
|
|
125
|
+
return sanitized if sanitized else "Entity"
|
|
126
|
+
|
|
127
|
+
parsed = urlparse(uri if "://" in uri else f"redis://{uri}")
|
|
128
|
+
# FalkorDB auth is optional. Only send credentials when a password is
|
|
129
|
+
# provided; otherwise connect anonymously and ignore any bolt-style default
|
|
130
|
+
# username (e.g. Neo4j's "neo4j"), which FalkorDB rejects as an unknown ACL
|
|
131
|
+
# user. Credentials embedded in the URI take precedence over the args.
|
|
132
|
+
connect_user = parsed.username or (user if password else None)
|
|
133
|
+
connect_password = parsed.password or (password or None)
|
|
134
|
+
db = FalkorDB(
|
|
135
|
+
host=parsed.hostname or "localhost",
|
|
136
|
+
port=parsed.port or 6379,
|
|
137
|
+
username=connect_user,
|
|
138
|
+
password=connect_password,
|
|
139
|
+
)
|
|
140
|
+
graph = db.select_graph(graph_name)
|
|
141
|
+
nodes_pushed = 0
|
|
142
|
+
edges_pushed = 0
|
|
143
|
+
|
|
144
|
+
for node_id, data in G.nodes(data=True):
|
|
145
|
+
props = {
|
|
146
|
+
k: v for k, v in data.items()
|
|
147
|
+
if isinstance(v, (str, int, float, bool)) and not k.startswith("_")
|
|
148
|
+
}
|
|
149
|
+
props["id"] = node_id
|
|
150
|
+
cid = node_community.get(node_id)
|
|
151
|
+
if cid is not None:
|
|
152
|
+
props["community"] = cid
|
|
153
|
+
ftype = _safe_label(data.get("file_type", "Entity").capitalize())
|
|
154
|
+
graph.query(
|
|
155
|
+
f"MERGE (n:{ftype} {{id: $id}}) SET n += $props",
|
|
156
|
+
{"id": node_id, "props": props},
|
|
157
|
+
)
|
|
158
|
+
nodes_pushed += 1
|
|
159
|
+
|
|
160
|
+
for u, v, data in G.edges(data=True):
|
|
161
|
+
rel = _safe_rel(data.get("relation", "RELATED_TO"))
|
|
162
|
+
props = {
|
|
163
|
+
k: v for k, v in data.items()
|
|
164
|
+
if isinstance(v, (str, int, float, bool)) and not k.startswith("_")
|
|
165
|
+
}
|
|
166
|
+
graph.query(
|
|
167
|
+
f"MATCH (a {{id: $src}}), (b {{id: $tgt}}) "
|
|
168
|
+
f"MERGE (a)-[r:{rel}]->(b) SET r += $props",
|
|
169
|
+
{"src": u, "tgt": v, "props": props},
|
|
170
|
+
)
|
|
171
|
+
edges_pushed += 1
|
|
172
|
+
|
|
173
|
+
return {"nodes": nodes_pushed, "edges": edges_pushed}
|