Skip to content

Commit 4a5edb8

Browse files
committed
Merge branch 'master' into dev/dev-skrub
2 parents 9b093f8 + 105487b commit 4a5edb8

File tree

14 files changed

+2806
-3
lines changed

14 files changed

+2806
-3
lines changed

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
77

88
## [Development]
99

10+
## [0.36.0 - 2025-02-05]
11+
1012
### Breaking
1113

1214
* `from_cugraph` returns using the src/dst bindings of `cugraph.Graph` object instead of base `Plottable`
@@ -42,6 +44,45 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
4244
* ci tests dgl
4345
* plugin tests check for module imports
4446

47+
## [0.35.10 - 2025-01-24]
48+
49+
### Fixes:
50+
51+
* Spanner: better handling of spanner_config issues: #634, #644
52+
53+
## [0.35.9 - 2025-01-22]
54+
55+
### Docs
56+
57+
* Spanner: minor changes to html and markdown in notebook for proper rendering in readthedocs
58+
59+
## [0.35.8 - 2025-01-22]
60+
61+
### Docs
62+
63+
* Spanner: fix for plots rendering in readthedocs demo notebooks
64+
65+
66+
## [0.35.7 - 2025-01-22]
67+
68+
### Feat
69+
70+
* added support for Google Spanner Graph and Google Spanner `spanner_gql_to_g` and `spanner_query_to_df`
71+
* added new Google Spanner Graph demo notebook
72+
73+
## [0.35.6 - 2025-01-11]
74+
75+
### Docs
76+
77+
* Fix typo in new shaping tutorial
78+
* Privacy-preserving analytics
79+
80+
## [0.35.5 - 2025-01-10]
81+
82+
### Docs
83+
84+
* New tutorial on graph shaping
85+
4586
## [0.35.4 - 2024-12-28]
4687

4788
### Fixes

demos/demos_databases_apis/spanner/google_spanner_finance_graph.ipynb

Lines changed: 1278 additions & 0 deletions
Large diffs are not rendered by default.

demos/more_examples/simple/table_to_graph_three_ways.ipynb

Lines changed: 869 additions & 0 deletions
Large diffs are not rendered by default.

docs/source/conf.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import docutils.nodes, os, logging, re, sys
1414
from docutils import nodes
1515
from packaging.version import Version
16+
from sphinx.application import Sphinx
1617

1718

1819
sys.path.insert(0, os.path.abspath("../.."))
@@ -759,12 +760,38 @@ def assert_external_images_removed(app, doctree, fromdocname):
759760
assert "://" not in image_uri, f"Failed to remove external image: {image_uri}"
760761

761762

762-
def setup(app):
763+
def setup(app: Sphinx):
763764
"""
764765
Connect the replace_iframe_src function to the doctree-resolved event.
765766
"""
767+
766768
app.connect("doctree-resolved", ignore_svg_images_for_latex)
767769
app.connect("doctree-resolved", remove_external_images_for_latex)
768770
app.connect('doctree-resolved', replace_iframe_src)
769771
app.connect("doctree-resolved", assert_external_images_removed)
770-
app.add_css_file('graphistry.css', priority=900)
772+
773+
def on_builder(app: Sphinx) -> None:
774+
if not hasattr(app, 'builder'):
775+
print('No app.builder found for app type=', type(app))
776+
# use dir to enumerate field names & types
777+
attr_and_types: str = '\n'.join([f'{name}: {type(getattr(app, name))}' for name in dir(app)])
778+
print(f'attr_and_types:\n---\n{attr_and_types}\n---\n')
779+
return
780+
781+
if (app.builder.name == "html" or app.builder.name == "readthedocs"):
782+
app.add_css_file('graphistry.css', priority=900)
783+
app.add_js_file("https://plausible.io/js/script.hash.outbound-links.js", **{
784+
"defer": "true",
785+
"data-domain": "pygraphistry.readthedocs.io",
786+
})
787+
app.add_js_file(None, body="""
788+
window.plausible = window.plausible || function() {
789+
(window.plausible.q = window.plausible.q || []).push(arguments)
790+
}
791+
""")
792+
return
793+
794+
print('No custom handling for app.builder.name=', app.builder.name)
795+
796+
app.connect('builder-inited', on_builder)
797+

docs/source/notebooks/intro.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ Getting Started
1111
For developers <../demos/for_developers.ipynb>
1212

1313
CSV upload miniapp <../demos/upload_csv_miniapp.ipynb>
14+
15+
Visually analyze any table as a graph: Our 3 favorite shapings <../demos/more_examples/simple/table_to_graph_three_ways.ipynb>

docs/source/notebooks/plugins.connectors.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Plugins - Data Providers
2121
Titan <../demos/demos_databases_apis/gremlin-tinkerpop/TitanDemo.ipynb>
2222
Neo4j - Official <../demos/demos_databases_apis/neo4j/official/graphistry_bolt_tutorial_public.ipynb>
2323
Neo4j - Contributed <../demos/demos_databases_apis/neo4j/contributed/Neo4jTwitter.ipynb>
24+
Google Spanner - Finance Graph <../demos/demos_databases_apis/spanner/google_spanner_finance_graph.ipynb>
2425
SQL - Postgres <../demos/demos_databases_apis/sql/postgres.ipynb>
2526
Tigergraph: Bindings <../demos/demos_databases_apis/tigergraph/tigergraph_pygraphistry_bindings.ipynb>
2627
Tigergraph: Fraud <../demos/demos_databases_apis/tigergraph/fraud_raw_REST_calls.ipynb>

docs/source/plugins.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Graph
2323
* `Gremlin <https://tinkerpop.apache.org>`_ (:class:`graphistry.gremlin.GremlinMixin`)
2424
* `Memgraph <https://memgraph.com>`_ (:meth:`graphistry.PlotterBase.PlotterBase.cypher`)
2525
* `Neo4j <https://neo4j.com>`_ (:meth:`graphistry.PlotterBase.PlotterBase.cypher`)
26+
* `Google Spanner Graph <https://cloud.google.com/spanner/docs/graph/overview>`_ (:meth:`graphistry.PlotterBase.PlotterBase.spanner_gql_to_g`)
2627
* `TigerGraph <https://www.tigergraph.com>`_ (:meth:`graphistry.PlotterBase.PlotterBase.gsql`)
2728
* `Trovares <https://trovares.com>`_
2829

graphistry/Plottable.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ class Plottable(object):
6565
_complex_encodings : dict
6666
_bolt_driver : Any
6767
_tigergraph : Any
68-
68+
_spannergraph: Any
69+
6970
_dataset_id: Optional[str]
7071
_url: Optional[str]
7172
_nodes_file_id: Optional[str]

graphistry/PlotterBase.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
end_node_id_key,
3636
to_bolt_driver)
3737

38+
3839
from .arrow_uploader import ArrowUploader
3940
from .nodexlistry import NodeXLGraphistry
4041
from .tigeristry import Tigeristry
@@ -176,6 +177,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
176177
# Integrations
177178
self._bolt_driver : Any = None
178179
self._tigergraph : Any = None
180+
self._spannergraph: Any
179181

180182
# feature engineering
181183
self._node_embedding = None
@@ -2271,7 +2273,31 @@ def bolt(self, driver):
22712273
res = copy.copy(self)
22722274
res._bolt_driver = to_bolt_driver(driver)
22732275
return res
2276+
2277+
def spanner_init(self: Plottable, spanner_config: Dict[str, str]) -> Plottable:
2278+
"""
2279+
Initializes a SpannerGraph object with the provided configuration and connects to the instance db
2280+
2281+
spanner_config dict must contain the include the following keys, credentials_file is optional:
2282+
- "project_id": The GCP project ID.
2283+
- "instance_id": The Spanner instance ID.
2284+
- "database_id": The Spanner database ID.
2285+
- "credentials_file": json file API key for service accounts
2286+
2287+
:param spanner_config A dictionary containing the Spanner configuration.
2288+
:type (Dict[str, str])
2289+
:return: Plottable with a Spanner connection
2290+
:rtype: Plottable
2291+
:raises ValueError: If any of the required keys in `spanner_config` are missing or have invalid values.
2292+
2293+
"""
2294+
from .plugins.spannergraph import SpannerGraph
22742295

2296+
res = copy.copy(self)
2297+
2298+
res._spannergraph = SpannerGraph(res, spanner_config)
2299+
logger.debug("Created SpannerGraph object: {res._spannergraph}")
2300+
return res
22752301

22762302
def infer_labels(self):
22772303
"""
@@ -2460,6 +2486,114 @@ def cypher(self, query: str, params: Dict[str, Any] = {}) -> Plottable:
24602486
)\
24612487
.nodes(nodes)\
24622488
.edges(edges)
2489+
2490+
2491+
def spanner_gql_to_g(self: Plottable, query: str) -> Plottable:
2492+
"""
2493+
Submit GQL query to google spanner graph database and return Plottable with nodes and edges populated
2494+
2495+
GQL must be a path query with a syntax similar to the following, it's recommended to return the path with
2496+
SAFE_TO_JSON(p), TO_JSON() can also be used, but not recommend. LIMIT is optional, but for large graphs with millions
2497+
of edges or more, it's best to filter either in the query or use LIMIT so as not to exhaust GPU memory.
2498+
2499+
query=f'''GRAPH my_graph
2500+
MATCH p = (a)-[b]->(c) LIMIT 100000 return SAFE_TO_JSON(p) as path'''
2501+
2502+
:param query: GQL query string
2503+
:type query: Str
2504+
2505+
:returns: Plottable with the results of GQL query as a graph
2506+
:rtype: Plottable
2507+
2508+
**Example: calling spanner_gql_to_g
2509+
::
2510+
2511+
import graphistry
2512+
2513+
# credentials_file is optional, all others are required
2514+
SPANNER_CONF = { "project_id": PROJECT_ID,
2515+
"instance_id": INSTANCE_ID,
2516+
"database_id": DATABASE_ID,
2517+
"credentials_file": CREDENTIALS_FILE }
2518+
2519+
graphistry.register(..., spanner_config=SPANNER_CONF)
2520+
2521+
query=f'''GRAPH my_graph
2522+
MATCH p = (a)-[b]->(c) LIMIT 100000 return SAFE_TO_JSON(p) as path'''
2523+
2524+
g = graphistry.spanner_gql_to_g(query)
2525+
2526+
g.plot()
2527+
2528+
"""
2529+
from .pygraphistry import PyGraphistry
2530+
from .plugins.spannergraph import SpannerGraph
2531+
2532+
res = copy.copy(self)
2533+
2534+
if not hasattr(res, '_spannergraph'):
2535+
spanner_config = PyGraphistry._config.get("spanner", None)
2536+
2537+
if spanner_config is not None:
2538+
logger.debug(f"Spanner Config: {spanner_config}")
2539+
else:
2540+
raise ValueError('spanner_config not defined. Pass spanner_config via register() and retry query.')
2541+
2542+
res = res.spanner_init(spanner_config) # type: ignore[attr-defined]
2543+
2544+
return res._spannergraph.gql_to_graph(res, query)
2545+
2546+
def spanner_query_to_df(self: Plottable, query: str) -> pd.DataFrame:
2547+
"""
2548+
2549+
Submit query to google spanner database and return a df of the results
2550+
2551+
query can be SQL or GQL as long as table of results are returned
2552+
2553+
query='SELECT * from Account limit 10000'
2554+
2555+
:param query: query string
2556+
:type query: Str
2557+
2558+
:returns: Pandas DataFrame with the results of query
2559+
:rtype: pd.DataFrame
2560+
2561+
**Example: calling spanner_query_to_df
2562+
::
2563+
2564+
import graphistry
2565+
2566+
# credentials_file is optional, all others are required
2567+
SPANNER_CONF = { "project_id": PROJECT_ID,
2568+
"instance_id": INSTANCE_ID,
2569+
"database_id": DATABASE_ID,
2570+
"credentials_file": CREDENTIALS_FILE }
2571+
2572+
graphistry.register(..., spanner_config=SPANNER_CONF)
2573+
2574+
query='SELECT * from Account limit 10000'
2575+
2576+
df = graphistry.spanner_query_to_df(query)
2577+
2578+
g.plot()
2579+
2580+
"""
2581+
2582+
from .pygraphistry import PyGraphistry
2583+
2584+
res = copy.copy(self)
2585+
2586+
if not hasattr(res, '_spannergraph'):
2587+
spanner_config = PyGraphistry._config["spanner"]
2588+
if spanner_config is not None:
2589+
logger.debug(f"Spanner Config: {spanner_config}")
2590+
else:
2591+
logger.warning('PyGraphistry._config["spanner"] is None')
2592+
2593+
res = res.spanner_init(PyGraphistry._config["spanner"]) # type: ignore[attr-defined]
2594+
2595+
return res._spannergraph.query_to_df(query)
2596+
24632597

24642598
def nodexl(self, xls_or_url, source='default', engine=None, verbose=False):
24652599

graphistry/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
bolt,
3232
cypher,
3333
tigergraph,
34+
spanner_gql_to_g,
35+
spanner_query_to_df,
36+
spanner_init,
3437
gsql,
3538
gsql_endpoint,
3639
cosmos,

0 commit comments

Comments
 (0)