diff --git a/demos/demos_databases_apis/tigergraph/fraud.ipynb b/demos/demos_databases_apis/tigergraph/fraud_raw_REST_calls.ipynb similarity index 88% rename from demos/demos_databases_apis/tigergraph/fraud.ipynb rename to demos/demos_databases_apis/tigergraph/fraud_raw_REST_calls.ipynb index 75cf29222a..4a4ab9b39e 100644 --- a/demos/demos_databases_apis/tigergraph/fraud.ipynb +++ b/demos/demos_databases_apis/tigergraph/fraud_raw_REST_calls.ipynb @@ -4,9 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Tigergraph<>Graphistry Fraud Demo\n", + "# Tigergraph<>Graphistry Fraud Demo: Raw REST\n", "\n", - "Accesses Tigergraph's fraud demo" + "Accesses Tigergraph's fraud demo directly via manual REST calls" ] }, { @@ -18,9 +18,6 @@ "height": 34 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "LDqOCGCFv5hV", "outputId": "ce308f8c-c843-43b1-ee99-539cb78ceeaf" }, @@ -44,8 +41,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "fX4NU9ZGH6Ln" }, "outputs": [], @@ -104,8 +99,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "LUEA1fmFOjCD" }, "source": [ @@ -116,8 +109,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "rY8Ip6WcOnPl" }, "source": [ @@ -130,9 +121,6 @@ "metadata": { "colab": {}, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "grR4ZNOAIIJn" }, "outputs": [], @@ -148,8 +136,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "UUTIiNkmIMQc" }, "outputs": [], @@ -161,8 +147,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "mXT2bD2UOp3o" }, "source": [ @@ -175,9 +159,6 @@ "metadata": { "colab": {}, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "6fP3qhDCOwi3" }, "outputs": [], @@ -193,8 +174,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "6Z6ER8VGVi9t" }, "outputs": [], @@ -206,8 +185,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "SKepDGbKZLGI" }, "source": [ @@ -221,8 +198,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "SNvvtNvGZM92" }, "outputs": [], @@ -237,8 +212,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "JD8M0c7OlYh7" }, "source": [ @@ -252,8 +225,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "kdFC1-wglZIT" }, "outputs": [], @@ -273,8 +244,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "TczzpZRulnb4" }, "outputs": [], @@ -289,8 +258,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "0uOagyc1lqRR" }, "outputs": [], @@ -316,8 +283,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "Gc_Ot3E2lwOr" }, "outputs": [], @@ -341,16 +306,16 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/demos/demos_databases_apis/tigergraph/social.ipynb b/demos/demos_databases_apis/tigergraph/social_raw_REST_calls.ipynb similarity index 89% rename from demos/demos_databases_apis/tigergraph/social.ipynb rename to demos/demos_databases_apis/tigergraph/social_raw_REST_calls.ipynb index febce51d25..4b499e583f 100644 --- a/demos/demos_databases_apis/tigergraph/social.ipynb +++ b/demos/demos_databases_apis/tigergraph/social_raw_REST_calls.ipynb @@ -4,12 +4,10 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "ufnpFtXZvhXl" }, "source": [ - "# Graphistry Tutorial: Notebooks + TigerGraph\n", + "# Graphistry Tutorial: Notebooks + TigerGraph via raw REST calls\n", "\n", "\n", "* Connect to Graphistry, TigerGraph\n", @@ -24,8 +22,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "vBYi8AdBwaIB" }, "source": [ @@ -39,8 +35,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "MApRHslPwjDD" }, "outputs": [], @@ -61,8 +55,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "akqSnA63wUF1" }, "source": [ @@ -78,9 +70,6 @@ "height": 255 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "g0pcHrm0xGFi", "outputId": "794ccc69-a5bb-49c5-c253-0bd63463b035" }, @@ -96,8 +85,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "slYnm6W4xJED" }, "outputs": [], @@ -115,9 +102,6 @@ "height": 34 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "LDqOCGCFv5hV", "outputId": "963dff36-8f73-4756-8e0d-af23ff3ea9a3" }, @@ -142,9 +126,6 @@ "height": 543 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "yr0JmMMmxiXZ", "outputId": "2d252a88-79f9-4ea1-bc1f-58cc4aef980c" }, @@ -164,8 +145,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "jlQxZuqeztIP" }, "source": [ @@ -181,9 +160,6 @@ "height": 255 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "lAGwcRgEz9aF", "outputId": "5b1c10c7-7a97-4476-f6f3-246926e9aa33" }, @@ -198,8 +174,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "-JNvt5kYXOLK" }, "source": [ @@ -213,8 +187,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "fX4NU9ZGH6Ln" }, "outputs": [], @@ -277,9 +249,6 @@ "height": 225 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "lnByP_zLXTW9", "outputId": "11ce110a-5d28-401e-cb9c-c4c73065ae5a" }, @@ -294,8 +263,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "LUEA1fmFOjCD" }, "source": [ @@ -311,9 +278,6 @@ "height": 560 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "6kVoTbQt21du", "outputId": "8b14514f-7a8e-4764-eb28-f379c1b3eec7" }, @@ -330,8 +294,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "iCeSWQdPbYIC" }, "source": [ @@ -384,8 +346,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "no9tdOo9oz7r" }, "source": [ @@ -407,9 +367,6 @@ "height": 359 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "07EGd4ZkLL6F", "outputId": "6ae6c716-5e51-4f1b-d84e-e9798f8194d6" }, @@ -428,9 +385,6 @@ "height": 747 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "0RFQ-vVMyiK0", "outputId": "a5d3ee2c-b5a1-4a11-c726-5524a9005eeb" }, @@ -450,9 +404,6 @@ "height": 921 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "QBT6jLz4DcBl", "outputId": "da20c136-4bdb-4795-a367-ab58215fb79a" }, @@ -465,8 +416,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "fMVqtAhOz0-9" }, "source": [ @@ -482,9 +431,6 @@ "height": 713 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "1Qzdw9GQ8thS", "outputId": "ac2970ee-10ad-41fb-d85b-1009c3395804" }, @@ -501,8 +447,6 @@ "cell_type": "markdown", "metadata": { "colab_type": "text", - "deletable": true, - "editable": true, "id": "xRLwg4FDfhBw" }, "source": [ @@ -518,9 +462,6 @@ "height": 159 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "00eblOG9Zkvc", "outputId": "b0fdcc4a-a2d2-4c7a-c02d-4854c7c7b015" }, @@ -543,9 +484,6 @@ "height": 51 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "559q1NaQkiLL", "outputId": "e4965d2c-c709-4dce-ff93-5d320f12a9e5" }, @@ -563,9 +501,6 @@ "height": 142 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "tOa2YzcRkl7z", "outputId": "9f1c1878-004f-47f1-952a-bd74a857c2e4" }, @@ -596,9 +531,6 @@ "height": 543 }, "colab_type": "code", - "collapsed": false, - "deletable": true, - "editable": true, "id": "ecMn0Qf_gQSD", "outputId": "8232bca0-1d6c-4071-f8bc-79d7044fcc98" }, @@ -623,8 +555,6 @@ "colab": {}, "colab_type": "code", "collapsed": true, - "deletable": true, - "editable": true, "id": "oogreipqgRTD" }, "outputs": [], @@ -646,16 +576,16 @@ "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.13" + "pygments_lexer": "ipython3", + "version": "3.7.3" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/demos/demos_databases_apis/tigergraph/tigergraph_pygraphistry_bindings.ipynb b/demos/demos_databases_apis/tigergraph/tigergraph_pygraphistry_bindings.ipynb new file mode 100644 index 0000000000..187ea557b9 --- /dev/null +++ b/demos/demos_databases_apis/tigergraph/tigergraph_pygraphistry_bindings.ipynb @@ -0,0 +1,211 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tigergraph Bindings: Demo of IT Infra Analysis\n", + "\n", + "Uses bindings built into PyGraphistry for Tigergraph:\n", + "\n", + "* Configure DB connection\n", + "* Call dynamic endpoints for user-defined endpoints\n", + "* Call interpreted-mode query\n", + "* Visualize results" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import and connect" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "import graphistry\n", + "\n", + "# !pip install graphistry -q\n", + "# graphistry.register(key='...', protocol='https', server='www.acme.com', ...)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "g = graphistry.tigergraph(\n", + " protocol='http', server='www.acme.org',\n", + " user='tigergraph', pwd='tigergraph', \n", + " db='Storage', #optional\n", + " #web_port = 14240, api_port = 9000, verbose=True\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Dynamic user-defined GSQL endpoints: Call, analyze, & plot" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# edges: 241\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g2 = g.gsql_endpoint(\n", + " 'StorageImpact', {'vertexType': 'Service', 'input': 61921, 'input.type': 'Pool'},\n", + " #{'edges': '@@edgeList', 'nodes': '@@nodeList'}\n", + ")\n", + "\n", + "print('# edges:', len(g2._edges))\n", + "\n", + "g2.plot()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## On-the-fly GSQL interpreted queries: Call, analyze, & plot" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "# edges: 241\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g3 = g.gsql(\"\"\"\n", + " INTERPRET QUERY () FOR GRAPH Storage { \n", + " \n", + " OrAccum @@stop;\n", + " ListAccum @@edgeList;\n", + " SetAccum @@set;\n", + " \n", + " @@set += to_vertex(\"61921\", \"Pool\");\n", + "\n", + " Start = @@set;\n", + "\n", + " while Start.size() > 0 and @@stop == false do\n", + "\n", + " Start = select t from Start:s-(:e)-:t\n", + " where e.goUpper == TRUE\n", + " accum @@edgeList += e\n", + " having t.type != \"Service\";\n", + " end;\n", + "\n", + " print @@edgeList;\n", + " }\n", + " \"\"\", \n", + " #{'edges': '@@edgeList', 'nodes': '@@nodeList'} # can skip by default\n", + ") \n", + "\n", + "print('# edges:', len(g3._edges))\n", + "\n", + "g3.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/graphistry/__init__.py b/graphistry/__init__.py index 331e724b74..ebe3d7ada0 100644 --- a/graphistry/__init__.py +++ b/graphistry/__init__.py @@ -2,4 +2,9 @@ __version__ = get_versions()['version'] del get_versions -from graphistry.pygraphistry import register, bind, edges, nodes, graph, settings, hypergraph, bolt, cypher +from graphistry.pygraphistry import ( +register, bind, edges, nodes, graph, settings, +hypergraph, +bolt, cypher, +tigergraph, gsql, gsql_endpoint +) \ No newline at end of file diff --git a/graphistry/hyper.py b/graphistry/hyper.py index 56e9fc793b..d5cc6c30d1 100644 --- a/graphistry/hyper.py +++ b/graphistry/hyper.py @@ -105,7 +105,7 @@ def format_hyperedges(events, entity_types, defs, drop_na, drop_edge_attrs): else []) + [defs['EDGETYPE'], defs['ATTRIBID'], defs['EVENTID']] + ([defs['CATEGORY']] if is_using_categories else []) )) - out = pd.concat(subframes, ignore_index=True).reset_index(drop=True)[ result_cols ] + out = pd.concat(subframes, ignore_index=True, sort=False).reset_index(drop=True)[ result_cols ] return out else: return pd.DataFrame([]) @@ -168,7 +168,7 @@ def format_hypernodes(events, defs, drop_na): return event_nodes def hyperbinding(g, defs, entities, event_entities, edges, source, destination): - nodes = pd.concat([entities, event_entities], ignore_index=True).reset_index(drop=True) + nodes = pd.concat([entities, event_entities], ignore_index=True, sort=False).reset_index(drop=True) return { 'entities': entities, 'events': event_entities, @@ -184,7 +184,8 @@ def flatten_objs_inplace(df, cols): for c in cols: name = df[c].dtype.name if name == 'category': - df[c] = df[c].where(df[c].isnull(), df[c].astype(str)) + #Avoid warning + df[c] = df[c].astype(str).where(~df[c].isnull(), df[c]) elif name == 'object': df[c] = df[c].where(df[c].isnull(), df[c].astype(str)) diff --git a/graphistry/plotter.py b/graphistry/plotter.py index dd11a95b3e..3befec3904 100644 --- a/graphistry/plotter.py +++ b/graphistry/plotter.py @@ -10,6 +10,8 @@ from .pygraphistry import util from .pygraphistry import bolt_util +from .plugins.tigergraph import Tigeristry + class Plotter(object): """Graph plotting class. @@ -48,6 +50,7 @@ def __init__(self): self._url_params = {'info': 'true'} # Integrations self._bolt_driver = None + self._tigergraph = None def __repr__(self): @@ -680,3 +683,165 @@ def cypher(self, query, params={}): )\ .nodes(nodes)\ .edges(edges) + + def tigergraph(self, + protocol = 'http', + server = 'localhost', + web_port = 14240, + api_port = 9000, + db = None, + user = 'tigergraph', + pwd = 'tigergraph', + verbose = False + ): + """Register Tigergraph connection setting defaults + + :param protocol: Protocol used to contact the database. + :type protocol: Optional string. + :param server: Domain of the database + :type server: Optional string. + :param web_port: + :type web_port: Optional integer. + :param api_port: + :type api_port: Optional integer. + :param db: Name of the database + :type db: Optional string. + :param user: + :type user: Optional string. + :param pwd: + :type pwd: Optional string. + :param verbose: Whether to print operations + :type verbose: Optional bool. + :returns: Plotter. + :rtype: Plotter. + + + **Example: Standard** + :: + + import graphistry + tg = graphistry.tigergraph(protocol='https', server='acme.com', db='my_db', user='alice', pwd='tigergraph2') + + """ + res = copy.copy(self) + res._tigergraph = Tigeristry(self, protocol, server, web_port, api_port, db, user, pwd, verbose) + return res + + + def gsql_endpoint(self, method_name, args = {}, bindings = {}, db = None, dry_run = False): + """Invoke Tigergraph stored procedure at a user-definend endpoint and return transformed Plottable + + :param method_name: Stored procedure name + :type method_name: String. + :param args: Named endpoint arguments + :type args: Optional dictionary. + :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList + :type bindings: Optional dictionary. + :param db: Name of the database, defaults to value set in .tigergraph(...) + :type db: Optional string. + :param dry_run: Return target URL without running + :type dry_run: Bool, defaults to False + :returns: Plotter. + :rtype: Plotter. + + **Example: Minimal** + :: + + import graphistry + tg = graphistry.tigergraph(db='my_db') + tg.gsql_endpoint('neighbors').plot() + + **Example: Full** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql_endpoint('neighbors', {'k': 2}, {'edges': 'my_edge_list'}, 'my_db').plot() + + **Example: Read data** + :: + + import graphistry + tg = graphistry.tigergraph() + out = tg.gsql_endpoint('neighbors') + (nodes_df, edges_df) = (out._nodes, out._edges) + + """ + return self._tigergraph.gsql_endpoint(self, method_name, args, bindings, db, dry_run) + + def gsql(self, query, bindings = {}, dry_run = False): + """Run Tigergraph query in interpreted mode and return transformed Plottable + + :param query: Code to run + :type query: String. + :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList + :type bindings: Optional dictionary. + :param dry_run: Return target URL without running + :type dry_run: Bool, defaults to False + :returns: Plotter. + :rtype: Plotter. + + **Example: Minimal** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql(\"\"\" +INTERPRET QUERY () FOR GRAPH Storage { + + OrAccum @@stop; + ListAccum @@edgeList; + SetAccum @@set; + + @@set += to_vertex("61921", "Pool"); + + Start = @@set; + + while Start.size() > 0 and @@stop == false do + + Start = select t from Start:s-(:e)-:t + where e.goUpper == TRUE + accum @@edgeList += e + having t.type != "Service"; + end; + + print @@edgeList; + } + \"\"\").plot() + + **Example: Full** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql(\"\"\" +INTERPRET QUERY () FOR GRAPH Storage { + + OrAccum @@stop; + ListAccum @@edgeList; + SetAccum @@set; + + @@set += to_vertex("61921", "Pool"); + + Start = @@set; + + while Start.size() > 0 and @@stop == false do + + Start = select t from Start:s-(:e)-:t + where e.goUpper == TRUE + accum @@edgeList += e + having t.type != "Service"; + end; + + print @@my_edge_list; + } + \"\"\", {'edges': 'my_edge_list'}).plot() + """ + return self._tigergraph.gsql(self, query, bindings, dry_run) + + + + + + + diff --git a/graphistry/plugins/__init__.py b/graphistry/plugins/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/graphistry/plugins/tigergraph.py b/graphistry/plugins/tigergraph.py new file mode 100644 index 0000000000..c70e014a48 --- /dev/null +++ b/graphistry/plugins/tigergraph.py @@ -0,0 +1,199 @@ +import requests +import pandas as pd + +def merge_dicts(x, y): + return dict(list(x.items()) + list(y.items())) + +class Tigeristry(object): + """Tigergraph bindings class + + * Initialize with DB cfg + * Register named stored procedures and graphistry bindings + * Call stored procedures + * Call interpreted queries + + """ + + # ----------------- Helpers ----------------------- + + def __log(self, v): + if self.tiger_config['verbose']: + print(v) + + # () -> 'http://site.com:9000' + def __base_url(self, mode = 'api'): + port = self.tiger_config['web_port'] if mode == 'web' else self.tiger_config['api_port'] + who = \ + (self.tiger_config['user'] + ':' + self.tiger_config['pwd'] + '@') \ + if (not (self.tiger_config['user'] is None) and not (self.tiger_config['pwd'] is None)) \ + else '' + return self.tiger_config['protocol'] + '://'+ who + self.tiger_config['server'] + ':' + str(port) + + def __check_initialized(self, graphistry): + if (graphistry is None) or (graphistry._tigergraph is None): + raise Exception("First register a tigergraph db via .tigergraph() or .register(tigergraph=)") + + + # -------------------------------------------------- + + + def __init__( + self, + graphistry, + protocol = 'http', + server = 'localhost', + web_port = 14240, + api_port = 9000, + db = None, + user = 'tigergraph', + pwd = 'tigergraph', + verbose = False + ): + + self.tiger_config = { + 'protocol': protocol, + 'server': server, + 'web_port': web_port, + 'api_port': api_port, + 'db': db, + 'user': user, + 'pwd': pwd, + 'verbose': verbose + } + + self.__log('TG config: ' + str({k: v for k, v in self.tiger_config.items() if k not in ['pwd']})) + + + # -------------------------------------------------- + + def __verify_and_unwrap_json_result(self, json): + + if json is None: + raise Exception("No response!") + elif not 'error' in json: + raise Exception("Unexpected response format, no validity indicator", json) + elif json['error']: + raise Exception("Database returned error", json['message'] if 'message' in json else 'No message') + elif not ('results' in json): + raise Exception("No field results in database response") + + return json['results'] + + # str * ?dict * ?str => json graph + def __gsql_endpoint(self, method_name, args = {}, db = None, dry_run = False): + + db = self.tiger_config['db'] if db is None else db + if db is None: + raise Exception("Must specify db in Tigeristry constructor or .__call()") + + base_url = self.__base_url('api') + url = base_url + '/query/' + db + '/' + method_name + if len(args.items()) > 0: + url = url + '?' + '&'.join( [str(k) + '=' + str(v) for k, v in args.items()] ) + self.__log(url) + + if dry_run: + return url + + resp = requests.get(url) + self.__log(resp) + json = resp.json() + + return self.__verify_and_unwrap_json_result(json) + + def __json_to_graphistry(self, graphistry, json, bindings): + edges_df = pd.DataFrame({'from_id': [], 'to_id': []}) + edge_key = bindings['edges'] + edges = [x for x in json if edge_key in x] + if len(edges) > 0 and (edge_key in edges[0]): + edges = edges[0][edge_key] + edges_df = pd.DataFrame(edges) + try: + edges_df = edges_df.drop(columns=['attributes']) + attrs = [x['attributes'] for x in edges] + edges_df = pd.merge( edges_df, pd.DataFrame(attrs), left_index=True, right_index=True ) + except: + self.__log('Failed to extract edge attrs') + g = graphistry.bind(source='from_id', destination='to_id').edges(edges_df) + + nodes_df = pd.DataFrame({'type': [], 'node_id': []}) + node_key = bindings['nodes'] + nodes = [x for x in json if node_key in x] + if len(nodes) > 0 and (node_key in nodes[0]): + nodes = nodes[0][node_key] + nodes_df = pd.DataFrame(nodes) + try: + nodes_df = nodes_df.drop(columns=['attributes']) + attrs = [x['attributes'] for x in nodes] + nodes_df = pd.merge( nodes_df, pd.DataFrame(attrs), left_index=True, right_index=True ) + except: + self.__log('Failed to extract node attrs') + else: + nodes_df = pd.DataFrame({'node_id': edges_df['from_id'].append(edges_df['to_id'])}) \ + .drop_duplicates().reset_index(drop=True) + from_types = nodes_df.merge(edges_df[['from_id', 'from_type']].rename(columns={'from_id': 'node_id', 'from_type': 'type'}), on='node_id', how='left') + to_types = nodes_df.merge(edges_df[['to_id', 'to_type']].rename(columns={'to_id': 'node_id', 'to_type': 'type'}), on='node_id', how='left') + nodes_df = nodes_df.merge( + pd.DataFrame({'type': + from_types.merge(to_types, left_index=True, right_index=True)\ + .apply( + lambda row: row['type_x'] if not pd.isna(row['type_x']) else row['type_y'], + axis=1)}), + left_index=True, right_index=True) + g = g.bind(node='node_id').nodes(nodes_df) + return g + + def __gsql(self, query, dry_run = False): + base_url = self.__base_url('web') + url = base_url + '/gsqlserver/interpreted_query' + self.__log(url) + if dry_run == True: + return url + response = requests.post(url, data=query) + json = response.json() + return self.__verify_and_unwrap_json_result(json) + + + # -------------------------------------------------- + + # Tigeristry * Plotter * string * ?dict * ?dict * ?string => Plotter + def gsql_endpoint(self, graphistry, method_name, args = {}, bindings = {}, db = None, dry_run = False): + + self.__check_initialized(graphistry) + + json = self.__gsql_endpoint(method_name, args, db, dry_run) + + if dry_run == True: + url = json + return url + + bindings = merge_dicts( + { + 'edges': '@@edgeList', + 'nodes': '@@nodeList' + }, + bindings + ) + + return self.__json_to_graphistry(graphistry, json, bindings) + + # Tigeristry * Plotter * string * ?dict => Plotter + def gsql(self, graphistry, query, bindings = {}, dry_run = False): + + self.__check_initialized(graphistry) + + json = self.__gsql(query, dry_run) + + if dry_run == True: + url = json + return url + + bindings = merge_dicts( + { + 'edges': '@@edgeList', + 'nodes': '@@nodeList' + }, + bindings + ) + + return self.__json_to_graphistry(graphistry, json, bindings) \ No newline at end of file diff --git a/graphistry/pygraphistry.py b/graphistry/pygraphistry.py index 782a17b110..f0a7dd907c 100644 --- a/graphistry/pygraphistry.py +++ b/graphistry/pygraphistry.py @@ -345,6 +345,169 @@ def bind(node=None, source=None, destination=None, point_title, point_label, point_color, point_size) + @staticmethod + def tigergraph( + protocol = 'http', + server = 'localhost', + web_port = 14240, + api_port = 9000, + db = None, + user = 'tigergraph', + pwd = 'tigergraph', + verbose = False + ): + """Register Tigergraph connection setting defaults + + :param protocol: Protocol used to contact the database. + :type protocol: Optional string. + :param server: Domain of the database + :type server: Optional string. + :param web_port: + :type web_port: Optional integer. + :param api_port: + :type api_port: Optional integer. + :param db: Name of the database + :type db: Optional string. + :param user: + :type user: Optional string. + :param pwd: + :type pwd: Optional string. + :param verbose: Whether to print operations + :type verbose: Optional bool. + :returns: Plotter. + :rtype: Plotter. + + + **Example: Standard** + :: + + import graphistry + tg = graphistry.tigergraph(protocol='https', server='acme.com', db='my_db', user='alice', pwd='tigergraph2') + + """ + from . import plotter + return plotter.Plotter().tigergraph(protocol, server, web_port, api_port, db, user, pwd, verbose) + + + @staticmethod + def gsql_endpoint(self, method_name, args = {}, bindings = None, db = None, dry_run = False): + """Invoke Tigergraph stored procedure at a user-definend endpoint and return transformed Plottable + + :param method_name: Stored procedure name + :type method_name: String. + :param args: Named endpoint arguments + :type args: Optional dictionary. + :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList + :type bindings: Optional dictionary. + :param db: Name of the database, defaults to value set in .tigergraph(...) + :type db: Optional string. + :param dry_run: Return target URL without running + :type dry_run: Bool, defaults to False + :returns: Plotter. + :rtype: Plotter. + + **Example: Minimal** + :: + + import graphistry + tg = graphistry.tigergraph(db='my_db') + tg.gsql_endpoint('neighbors').plot() + + **Example: Full** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql_endpoint('neighbors', {'k': 2}, {'edges': 'my_edge_list'}, 'my_db').plot() + + **Example: Read data** + :: + + import graphistry + tg = graphistry.tigergraph() + out = tg.gsql_endpoint('neighbors') + (nodes_df, edges_df) = (out._nodes, out._edges) + + """ + from . import plotter + return plotter.Plotter().gsql_endpoint(method_name, args, bindings, db, dry_run) + + + + @staticmethod + def gsql(query, bindings = None, dry_run = False): + """Run Tigergraph query in interpreted mode and return transformed Plottable + + :param query: Code to run + :type query: String. + :param bindings: Mapping defining names of returned 'edges' and/or 'nodes', defaults to @@nodeList and @@edgeList + :type bindings: Optional dictionary. + :param dry_run: Return target URL without running + :type dry_run: Bool, defaults to False + :returns: Plotter. + :rtype: Plotter. + + **Example: Minimal** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql(\"\"\" +INTERPRET QUERY () FOR GRAPH Storage { + + OrAccum @@stop; + ListAccum @@edgeList; + SetAccum @@set; + + @@set += to_vertex("61921", "Pool"); + + Start = @@set; + + while Start.size() > 0 and @@stop == false do + + Start = select t from Start:s-(:e)-:t + where e.goUpper == TRUE + accum @@edgeList += e + having t.type != "Service"; + end; + + print @@edgeList; + } + \"\"\").plot() + + **Example: Full** + :: + + import graphistry + tg = graphistry.tigergraph() + tg.gsql(\"\"\" +INTERPRET QUERY () FOR GRAPH Storage { + + OrAccum @@stop; + ListAccum @@edgeList; + SetAccum @@set; + + @@set += to_vertex("61921", "Pool"); + + Start = @@set; + + while Start.size() > 0 and @@stop == false do + + Start = select t from Start:s-(:e)-:t + where e.goUpper == TRUE + accum @@edgeList += e + having t.type != "Service"; + end; + + print @@my_edge_list; + } + \"\"\", {'edges': 'my_edge_list'}).plot() + """ + from . import plotter + return plotter.Plotter().gsql(query, bindings, dry_run) + + + @staticmethod def nodes(nodes): from . import plotter @@ -544,6 +707,9 @@ def _check_key_and_version(): hypergraph = PyGraphistry.hypergraph bolt = PyGraphistry.bolt cypher = PyGraphistry.cypher +tigergraph = PyGraphistry.tigergraph +gsql_endpoint = PyGraphistry.gsql_endpoint +gsql = PyGraphistry.gsql class NumpyJSONEncoder(json.JSONEncoder): diff --git a/graphistry/tests/test_tigergraph.py b/graphistry/tests/test_tigergraph.py new file mode 100644 index 0000000000..795dc606f9 --- /dev/null +++ b/graphistry/tests/test_tigergraph.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +import unittest +import graphistry +from mock import patch +from common import NoAuthTestCase + +class TestTiger(NoAuthTestCase): + def test_tg_init_plain(self): + tg = graphistry.tigergraph() + self.assertTrue(type(tg) == graphistry.plotter.Plotter) + + def test_tg_init_many(self): + tg = graphistry.tigergraph( + protocol = 'https', + server = '127.0.0.1', + web_port = 10000, + api_port = 11000, + db = 'z', + user = 'tigergraph1', + pwd = 'tigergraph2', + verbose = False + ) + self.assertTrue(type(tg) == graphistry.plotter.Plotter) + + def test_tg_endpoint_url_simple(self): + tg = graphistry.tigergraph( + protocol = 'https', + server = '127.0.0.1', + web_port = 10000, + api_port = 11000, + db = 'z', + user = 'tigergraph1', + pwd = 'tigergraph2', + verbose = False + ) + self.assertEqual( + tg.gsql_endpoint('x', dry_run = True), + 'https://tigergraph1:tigergraph2@127.0.0.1:11000/query/z/x' + ) + + def test_tg_endpoint_url_1_arg(self): + tg = graphistry.tigergraph( + protocol = 'https', + server = '127.0.0.1', + web_port = 10000, + api_port = 11000, + db = 'z', + user = 'tigergraph1', + pwd = 'tigergraph2', + verbose = False + ) + self.assertEqual( + tg.gsql_endpoint('x', {'f': 1}, dry_run = True), + 'https://tigergraph1:tigergraph2@127.0.0.1:11000/query/z/x?f=1' + ) + + def test_tg_endpoint_url_3_arg(self): + tg = graphistry.tigergraph( + protocol = 'https', + server = '127.0.0.1', + web_port = 10000, + api_port = 11000, + db = 'z', + user = 'tigergraph1', + pwd = 'tigergraph2', + verbose = False + ) + #27 does not preserve order + self.assertEqual( + len(tg.gsql_endpoint('x', {'f': 1, 'ggg': 2, 'h': 33}, dry_run = True)), + len('https://tigergraph1:tigergraph2@127.0.0.1:11000/query/z/x?f=1&ggg=2&h=33') + ) + + def test_tg_gsql(self): + tg = graphistry.tigergraph( + protocol = 'https', + server = '127.0.0.1', + web_port = 10000, + api_port = 11000, + db = 'z', + user = 'tigergraph1', + pwd = 'tigergraph2', + verbose = False + ) + self.assertEqual( + tg.gsql('x', dry_run = True), + 'https://tigergraph1:tigergraph2@127.0.0.1:10000/gsqlserver/interpreted_query' + ) +