Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
- **GFQL schema effects (#1485)**: Added an internal typed schema-effect model for graph-growing GFQL calls so bound experimental `GraphSchema` snapshots are updated after successful degree, PageRank-style node-property writes, and edge-property write calls. Later local validation can see properties added by those calls without exposing a public `SchemaEffect` API or changing remote GFQL transport.
- **NetworkX Python compute API (#1619)**: Added `g.compute_networkx(...)` for the curated NetworkX algorithm subset already exposed through GFQL local Cypher, including node, edge, and `k_core` graph-returning outputs, plus updated NetworkX notebook/API docs.
- **GFQL schema accessor (#1632)**: Added experimental read-only `g.schema` for inspecting a `GraphSchema` bound through `bind(schema=...)` without reaching into private `_gfql_schema` storage. Use `g.schema is not None` for predicate checks.
- **GFQL NetworkX CALL parity (#1058)**: Expanded the local Cypher `graphistry.nx.*` CALL surface with explicit NetworkX dispatch for `degree_centrality`, `closeness_centrality`, `eigenvector_centrality`, `katz_centrality`, `connected_components`, `strongly_connected_components`, `core_number`, and multi-output `hits`, including row and `.write()` coverage.
- **NetworkX/SciPy optional dependency policy (#1618)**: Declared supported `networkx>=2.5,<4` and optional `scipy>=1.5,<2` ranges for NetworkX-backed GFQL CALL procedures, with runtime version guards and a focused lower/current-upper CI matrix.
- **GFQL schema Arrow boundary APIs (#1339)**: Added experimental public schema↔Arrow import/export helpers, graph-level Arrow declaration payloads, and opt-in `schema_validate='strict'|'autofix'` enforcement for `plot()`, `upload()`, `to_arrow()`, and `validate_arrow_schema()` when a `GraphSchema` is bound.
Expand Down
26 changes: 26 additions & 0 deletions agents/skills/review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,32 @@ Write `plans/<task>/final-report.md` with:
`both` mode:
- Complete findings artifacts first, then comment flow.

### GitHub Markdown Body Safety

When creating or updating PR descriptions, issue comments, PR comments, or
review summaries with multi-line Markdown, backticks, code fences, `$()`, or
literal `\n` sequences, do **not** pass the body inline through shell flags such
as `--body "..."`, `--body '...'`, `-f body=...`, or `-F body=...`.

Instead:

1. Write the exact body to a local Markdown artifact, preferably under
`plans/<task>/github-body-<target>.md` for durable review or `/tmp/` for a
throwaway retry.
2. Inspect the rendered source with `sed -n '1,220p' <body-file>` before
posting.
3. Use file-based GitHub CLI flags:
- `gh pr create --body-file <body-file>`
- `gh pr edit <PR> --body-file <body-file>`
- `gh issue comment <issue> --body-file <body-file>`
- `gh pr comment <PR> --body-file <body-file>`
4. After posting, verify with `gh pr view <PR> --json body` or
`gh api repos/<owner>/<repo>/issues/comments/<comment-id>` and confirm the
body contains real newlines and literal Markdown backticks.

Reason: inline shell bodies can turn Markdown backticks into command
substitution and can post literal `\n` text instead of newlines.

## Guardrails

- `fixes=deferred`: read-only; do not edit source files.
Expand Down
6 changes: 6 additions & 0 deletions docs/source/api/plotter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ the boundary. Use ``schema_validate='autofix'`` to cast compatible columns to
declared Arrow types after normal Arrow conversion. The default
``schema_validate=False`` preserves existing behavior.

Use the experimental read-only ``g.schema`` accessor to inspect the bound
``GraphSchema`` object. Check ``g.schema is not None`` when only a predicate is
needed. This reports only the local declaration attached through
``bind(schema=...)``: it does not infer a schema from data, fetch a remote
dataset schema, or serialize the schema into ``gfql_remote()`` requests.

.. toctree::
:maxdepth: 3

Expand Down
29 changes: 26 additions & 3 deletions docs/source/gfql/schema.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ check against.
)

g.gfql_validate("MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p.name")
assert g.schema is schema
assert g.schema is not None

Schema Objects
--------------
Expand All @@ -96,6 +98,22 @@ Schema Objects
makes schema-bound ``g.gfql_validate(...)`` permissive by default; callers can
still override per call with ``g.gfql_validate(..., strict=True)``.

``g.schema``
Read back the experimental ``GraphSchema`` bound with ``bind(schema=...)``.
``g.schema`` returns the bound object or ``None``. Use
``g.schema is not None`` when only a predicate is needed. Use
``bind(schema=...)`` to attach schemas, not assignment.
This is local declaration introspection only. It does not infer schemas from
data, fetch or hydrate remote dataset schemas, or serialize schemas into
``gfql_remote()`` requests in this release.

Per-type declarations such as Cat, Dog, and Car are represented by
``GraphSchema.node_types``. The stable public type identity is
``NodeType.name``; ``NodeType.labels`` are the GFQL label predicates that map
onto label columns such as ``label__Cat``. For example, Cat and Dog can both
carry an ``Animal`` label while still preserving separate Cat and Dog
property contracts.

``NodeType.to_arrow()`` and ``EdgeType.to_arrow()``
Export declarations as ``pyarrow.Schema`` objects through GFQL's row-schema
bridge. Label/type columns are included by default so exports line up with
Expand Down Expand Up @@ -210,12 +228,17 @@ The public schema is consumed by local validation APIs, including:
``gfql_remote(...)`` is different. It compiles Cypher strings locally and sends
the resulting GFQL wire payload to the server, but this release does **not**
serialize a bound ``GraphSchema`` into remote GFQL requests. Remote execution
therefore still depends on the server-side dataset schema and GFQL support. If
you want declared schema checks before a remote call, run
therefore still depends on the server-side dataset metadata and GFQL support. If
you want local declared-schema checks before a remote call, run
``g.gfql_validate(query)`` locally first, then call ``g.gfql_remote(query)``.

Remote schema transport is planned as a follow-on after the local schema
contract and serialization boundary are stable.
contract and serialization boundary are stable. The intended direction is a
versioned graph-schema envelope derived from ``GraphSchema.to_arrow()``: exact
Arrow schemas for merged node/edge tables and per-type declarations, plus a
JSON summary for dataset metadata, UI, and REST consumers. That future transport
should live beside ``gfql_query`` / ``gfql_operations`` rather than as fake data
tables.

Compatibility Notes
-------------------
Expand Down
6 changes: 6 additions & 0 deletions graphistry/Plottable.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from graphistry.models.surfaces.graphistry_frontend.url_params import URLParamsDict

if TYPE_CHECKING:
from graphistry.schema import GraphSchema
try:
from umap import UMAP
except:
Expand Down Expand Up @@ -82,6 +83,7 @@ class Plottable(Protocol):
_point_y : Optional[str]
_point_longitude : Optional[str]
_point_latitude : Optional[str]
_gfql_schema: Optional["GraphSchema"]
_height : int
_render : RenderModesConcrete
_url_params : URLParamsDict
Expand Down Expand Up @@ -370,6 +372,10 @@ def bind(
def copy(self) -> 'Plottable':
...

@property
def schema(self) -> Optional["GraphSchema"]:
...

# ### ComputeMixin

def get_indegrees(self, col: str = 'degree_in') -> 'Plottable':
Expand Down
19 changes: 18 additions & 1 deletion graphistry/PlotterBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
from .util import setup_logger
logger = setup_logger(__name__)

if TYPE_CHECKING:
from graphistry.schema import GraphSchema

_MAPPED_PROPERTY_ENCODING_METHODS: Dict[str, Tuple[str, str, str]] = {
"encode_edge_size": ("edge", "size", "edgeSizeEncoding"),
"encode_edge_weight": ("edge", "weight", "edgeWeightEncoding"),
Expand Down Expand Up @@ -267,7 +270,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
self._point_y : Optional[str] = None
self._point_longitude : Optional[str] = None
self._point_latitude : Optional[str] = None
self._gfql_schema : Any = None
self._gfql_schema : Optional["GraphSchema"] = None
# Settings
self._height : int = 500
self._render : RenderModesConcrete = resolve_render_mode(self, True)
Expand Down Expand Up @@ -1790,6 +1793,20 @@ def bind(self,

return res

@property
def schema(self) -> Optional["GraphSchema"]:
"""Return the bound experimental GFQL ``GraphSchema``, if any.

The returned object is the same schema instance supplied through
``bind(schema=...)``. The accessor is read-only: use ``bind(schema=...)``
to attach a schema to a new plotter.

This is local declaration introspection only. It does not infer a schema
from data, fetch a remote dataset schema, or serialize the schema into
``gfql_remote()`` requests in this release.
"""
return self._gfql_schema

def copy(self) -> Plottable:
return copy.copy(self)

Expand Down
24 changes: 24 additions & 0 deletions graphistry/tests/compute/gfql/test_public_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,34 @@ def test_bind_schema_is_chainable_and_used_by_preflight() -> None:
g = _graph(schema).bind(point_color="name")

assert g._gfql_schema is schema
assert g.schema is schema
report = g.gfql_validate("MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p.name AS name")
assert report["ok"] is True


def test_schema_accessor_returns_bound_schema() -> None:
schema = _schema()
g = _graph(schema)

assert g.schema is schema


def test_schema_accessor_is_read_only() -> None:
schema = _schema()
g = _graph(schema)

with pytest.raises(AttributeError):
g.schema = None # type: ignore[misc]

assert g.schema is schema


def test_schema_accessor_returns_none_when_unbound() -> None:
g = graphistry.bind()

assert g.schema is None


def test_bound_schema_arrow_boundary_strict_passes() -> None:
pa = pytest.importorskip("pyarrow")
g = _graph(_schema())
Expand Down
Loading