graphdatascience 1.10__tar.gz → 1.11a1__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 (141) hide show
  1. {graphdatascience-1.10/graphdatascience.egg-info → graphdatascience-1.11a1}/PKG-INFO +1 -1
  2. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/base_graph_proc_runner.py +1 -2
  3. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph_data_science.py +9 -9
  4. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/arrow_query_runner.py +10 -3
  5. graphdatascience-1.11a1/graphdatascience/session/__init__.py +16 -0
  6. graphdatascience-1.11a1/graphdatascience/session/algorithm_category.py +14 -0
  7. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/session/aura_api.py +101 -106
  8. graphdatascience-1.11a1/graphdatascience/session/aura_api_responses.py +166 -0
  9. graphdatascience-1.11a1/graphdatascience/session/aurads_sessions.py +202 -0
  10. graphdatascience-1.11a1/graphdatascience/session/dedicated_sessions.py +137 -0
  11. graphdatascience-1.11a1/graphdatascience/session/gds_sessions.py +107 -0
  12. graphdatascience-1.11a1/graphdatascience/session/session_info.py +40 -0
  13. graphdatascience-1.11a1/graphdatascience/session/session_sizes.py +31 -0
  14. graphdatascience-1.11a1/graphdatascience/version.py +1 -0
  15. {graphdatascience-1.10 → graphdatascience-1.11a1/graphdatascience.egg-info}/PKG-INFO +1 -1
  16. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience.egg-info/SOURCES.txt +5 -0
  17. graphdatascience-1.10/graphdatascience/session/__init__.py +0 -13
  18. graphdatascience-1.10/graphdatascience/session/gds_sessions.py +0 -238
  19. graphdatascience-1.10/graphdatascience/session/session_sizes.py +0 -33
  20. graphdatascience-1.10/graphdatascience/version.py +0 -1
  21. {graphdatascience-1.10 → graphdatascience-1.11a1}/LICENSE +0 -0
  22. {graphdatascience-1.10 → graphdatascience-1.11a1}/MANIFEST.in +0 -0
  23. {graphdatascience-1.10 → graphdatascience-1.11a1}/README.md +0 -0
  24. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/__init__.py +0 -0
  25. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/algo/__init__.py +0 -0
  26. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/algo/algo_endpoints.py +0 -0
  27. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/algo/algo_proc_runner.py +0 -0
  28. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/algo/single_mode_algo_endpoints.py +0 -0
  29. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/call_builder.py +0 -0
  30. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/call_parameters.py +0 -0
  31. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/caller_base.py +0 -0
  32. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/endpoints.py +0 -0
  33. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/__init__.py +0 -0
  34. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/client_only_endpoint.py +0 -0
  35. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/cypher_warning_handler.py +0 -0
  36. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/endpoint_suggester.py +0 -0
  37. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/gds_not_installed.py +0 -0
  38. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/illegal_attr_checker.py +0 -0
  39. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/unable_to_connect.py +0 -0
  40. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/error/uncallable_namespace.py +0 -0
  41. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/__init__.py +0 -0
  42. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_alpha_proc_runner.py +0 -0
  43. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_beta_proc_runner.py +0 -0
  44. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_create_result.py +0 -0
  45. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_cypher_runner.py +0 -0
  46. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_endpoints.py +0 -0
  47. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_entity_ops_runner.py +0 -0
  48. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_export_runner.py +0 -0
  49. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_object.py +0 -0
  50. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_proc_runner.py +0 -0
  51. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_project_runner.py +0 -0
  52. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_remote_proc_runner.py +0 -0
  53. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_remote_project_runner.py +0 -0
  54. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_sample_runner.py +0 -0
  55. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/graph_type_check.py +0 -0
  56. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/nx_loader.py +0 -0
  57. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/graph/ogb_loader.py +0 -0
  58. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/ignored_server_endpoints.py +0 -0
  59. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/__init__.py +0 -0
  60. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/graphsage_model.py +0 -0
  61. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/link_prediction_model.py +0 -0
  62. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model.py +0 -0
  63. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model_alpha_proc_runner.py +0 -0
  64. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model_beta_proc_runner.py +0 -0
  65. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model_endpoints.py +0 -0
  66. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model_proc_runner.py +0 -0
  67. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/model_resolver.py +0 -0
  68. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/node_classification_model.py +0 -0
  69. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/node_regression_model.py +0 -0
  70. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/pipeline_model.py +0 -0
  71. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/model/simple_rel_embedding_model.py +0 -0
  72. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/__init__.py +0 -0
  73. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/classification_training_pipeline.py +0 -0
  74. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/lp_pipeline_create_runner.py +0 -0
  75. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/lp_training_pipeline.py +0 -0
  76. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/nc_pipeline_create_runner.py +0 -0
  77. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/nc_training_pipeline.py +0 -0
  78. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/nr_pipeline_create_runner.py +0 -0
  79. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/nr_training_pipeline.py +0 -0
  80. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/pipeline_alpha_proc_runner.py +0 -0
  81. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/pipeline_beta_proc_runner.py +0 -0
  82. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/pipeline_endpoints.py +0 -0
  83. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/pipeline_proc_runner.py +0 -0
  84. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/pipeline/training_pipeline.py +0 -0
  85. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/py.typed +0 -0
  86. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/__init__.py +0 -0
  87. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/arrow_endpoint_version.py +0 -0
  88. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/arrow_graph_constructor.py +0 -0
  89. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/aura_db_arrow_query_runner.py +0 -0
  90. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/cypher_graph_constructor.py +0 -0
  91. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/graph_constructor.py +0 -0
  92. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/neo4j_query_runner.py +0 -0
  93. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/query_runner/query_runner.py +0 -0
  94. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/__init__.py +0 -0
  95. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/cora/__init__.py +0 -0
  96. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/cora/cora_nodes.parquet.gzip +0 -0
  97. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/cora/cora_rels.parquet.gzip +0 -0
  98. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/cora/serialize_cora.py +0 -0
  99. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/__init__.py +0 -0
  100. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_acted_in.parquet.gzip +0 -0
  101. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_actors.parquet.gzip +0 -0
  102. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_directed_in.parquet.gzip +0 -0
  103. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_directors.parquet.gzip +0 -0
  104. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_movies_with_genre.parquet.gzip +0 -0
  105. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/imdb_movies_without_genre.parquet.gzip +0 -0
  106. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/imdb/serialize_imdb.py +0 -0
  107. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/karate/__init__.py +0 -0
  108. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/karate/karate_club.parquet.gzip +0 -0
  109. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/__init__.py +0 -0
  110. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/artist_nodes.parquet.gzip +0 -0
  111. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/serialize_lastfm.py +0 -0
  112. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/user_friend_df_directed.parquet.gzip +0 -0
  113. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/user_listen_artist_rels.parquet.gzip +0 -0
  114. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/user_nodes.parquet.gzip +0 -0
  115. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/resources/lastfm/user_tag_artist_rels.parquet.gzip +0 -0
  116. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/server_version/__init__.py +0 -0
  117. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/server_version/compatible_with.py +0 -0
  118. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/server_version/server_version.py +0 -0
  119. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/session/aura_graph_data_science.py +0 -0
  120. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/session/dbms_connection_info.py +0 -0
  121. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/session/region_suggester.py +0 -0
  122. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/session/schema.py +0 -0
  123. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/system/__init__.py +0 -0
  124. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/system/config_endpoints.py +0 -0
  125. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/system/system_endpoints.py +0 -0
  126. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/topological_lp/__init__.py +0 -0
  127. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/topological_lp/topological_lp_alpha_runner.py +0 -0
  128. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/topological_lp/topological_lp_endpoints.py +0 -0
  129. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/utils/__init__.py +0 -0
  130. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/utils/util_endpoints.py +0 -0
  131. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience/utils/util_proc_runner.py +0 -0
  132. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience.egg-info/dependency_links.txt +0 -0
  133. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience.egg-info/not-zip-safe +0 -0
  134. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience.egg-info/requires.txt +0 -0
  135. {graphdatascience-1.10 → graphdatascience-1.11a1}/graphdatascience.egg-info/top_level.txt +0 -0
  136. {graphdatascience-1.10 → graphdatascience-1.11a1}/pyproject.toml +0 -0
  137. {graphdatascience-1.10 → graphdatascience-1.11a1}/requirements/base/base.txt +0 -0
  138. {graphdatascience-1.10 → graphdatascience-1.11a1}/requirements/base/networkx.txt +0 -0
  139. {graphdatascience-1.10 → graphdatascience-1.11a1}/requirements/base/ogb.txt +0 -0
  140. {graphdatascience-1.10 → graphdatascience-1.11a1}/setup.cfg +0 -0
  141. {graphdatascience-1.10 → graphdatascience-1.11a1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphdatascience
3
- Version: 1.10
3
+ Version: 1.11a1
4
4
  Summary: A Python client for the Neo4j Graph Data Science (GDS) library
5
5
  Home-page: https://neo4j.com/product/graph-data-science/
6
6
  Author: Neo4j
@@ -517,8 +517,7 @@ class BaseGraphProcRunner(UncallableNamespace, IllegalAttrChecker):
517
517
  ).squeeze()
518
518
 
519
519
  @multimethod
520
- def removeNodeProperties(self) -> None:
521
- ...
520
+ def removeNodeProperties(self) -> None: ...
522
521
 
523
522
  @removeNodeProperties.register
524
523
  @graph_type_check
@@ -44,20 +44,20 @@ class GraphDataScience(DirectEndpoints, UncallableNamespace):
44
44
  A username, password pair for database authentication.
45
45
  aura_ds : bool, default False
46
46
  A flag that indicates that that the client is used to connect
47
- to a Neo4j Aura instance.
47
+ to a Neo4j AuraDS instance.
48
48
  database: Optional[str], default None
49
49
  The Neo4j database to query against.
50
50
  arrow : Union[str, bool], default True
51
- Arrow connection information. Either a flag that indicates whether the client should use Apache Arrow
52
- for data streaming if it is available on the server. True means discover the connection URI from the server.
53
- A connection URI (str) can also be provided.
51
+ Arrow connection information. This is either a bool or a string.
52
+ If it is a string, it will be interpreted as a connection URL to a GDS Arrow Server.
53
+ If it is a bool,
54
+ True will make the client discover the connection URI to the GDS Arrow server via the Neo4j endpoint,
55
+ while False will make the client use Bolt for all operations.
54
56
  arrow_disable_server_verification : bool, default True
55
- A flag that indicates that, if the flight client is connecting with
56
- TLS, that it skips server verification. If this is enabled, all
57
- other TLS settings are overridden.
57
+ A flag that overrides other TLS settings and disables server verification for TLS connections.
58
58
  arrow_tls_root_certs : Optional[bytes], default None
59
- PEM-encoded certificates that are used for the connecting to the
60
- Arrow Flight server.
59
+ PEM-encoded certificates that are used for the connection to the
60
+ GDS Arrow Flight server.
61
61
  bookmarks : Optional[Any], default None
62
62
  The Neo4j bookmarks to require a certain state before the next query gets executed.
63
63
  """
@@ -282,7 +282,7 @@ class ArrowQueryRunner(QueryRunner):
282
282
 
283
283
  if self._arrow_endpoint_version == ArrowEndpointVersion.V1:
284
284
  payload = {
285
- "name": "GET_MESSAGE",
285
+ "name": "GET_COMMAND",
286
286
  "version": ArrowEndpointVersion.V1.version(),
287
287
  "body": payload,
288
288
  }
@@ -377,10 +377,17 @@ class AuthMiddleware(ClientMiddleware): # type: ignore
377
377
  self._factory = factory
378
378
 
379
379
  def received_headers(self, headers: Dict[str, Any]) -> None:
380
- auth_header: str = headers.get("Authorization", None)
380
+ auth_header = headers.get("authorization", None)
381
381
  if not auth_header:
382
382
  return
383
- [auth_type, token] = auth_header.split(" ", 1)
383
+
384
+ # the result is always a list
385
+ header_value = auth_header[0]
386
+
387
+ if not isinstance(header_value, str):
388
+ raise ValueError(f"Incompatible header value received from server: `{header_value}`")
389
+
390
+ auth_type, token = header_value.split(" ", 1)
384
391
  if auth_type == "Bearer":
385
392
  self._factory.set_token(token)
386
393
 
@@ -0,0 +1,16 @@
1
+ from .algorithm_category import AlgorithmCategory
2
+ from .dbms_connection_info import DbmsConnectionInfo
3
+ from .gds_sessions import AuraAPICredentials, GdsSessions
4
+ from .schema import GdsPropertyTypes
5
+ from .session_info import SessionInfo
6
+ from .session_sizes import SessionMemory
7
+
8
+ __all__ = [
9
+ "GdsSessions",
10
+ "SessionInfo",
11
+ "DbmsConnectionInfo",
12
+ "AuraAPICredentials",
13
+ "SessionMemory",
14
+ "GdsPropertyTypes",
15
+ "AlgorithmCategory",
16
+ ]
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AlgorithmCategory(Enum):
5
+ """
6
+ Enumeration of supported algorithm categories used for size estimation.
7
+ """
8
+
9
+ CENTRALITY = "centrality"
10
+ COMMUNITY_DETECTION = "community-detection"
11
+ MACHINE_LEARNING = "machine-learning"
12
+ NODE_EMBEDDING = "node-embedding"
13
+ PATH_FINDING = "path-finding"
14
+ SIMILARITY = "similarity"
@@ -1,121 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
- import dataclasses
4
3
  import logging
5
4
  import os
6
5
  import time
7
- from collections import defaultdict
8
- from dataclasses import dataclass
9
- from typing import Any, List, NamedTuple, Optional, Set
6
+ from typing import Any, List, Optional
10
7
  from urllib.parse import urlparse
11
8
 
12
9
  import requests as req
13
10
  from requests import HTTPError
14
11
 
12
+ from graphdatascience.session.algorithm_category import AlgorithmCategory
13
+ from graphdatascience.session.aura_api_responses import (
14
+ EstimationDetails,
15
+ InstanceCreateDetails,
16
+ InstanceDetails,
17
+ InstanceSpecificDetails,
18
+ SessionDetails,
19
+ TenantDetails,
20
+ WaitResult,
21
+ )
15
22
  from graphdatascience.version import __version__
16
23
 
17
24
 
18
- @dataclass(repr=True, frozen=True)
19
- class InstanceDetails:
20
- id: str
21
- name: str
22
- tenant_id: str
23
- cloud_provider: str
24
-
25
- @classmethod
26
- def fromJson(cls, json: dict[str, Any]) -> InstanceDetails:
27
- return cls(
28
- id=json["id"],
29
- name=json["name"],
30
- tenant_id=json["tenant_id"],
31
- cloud_provider=json["cloud_provider"],
32
- )
33
-
34
-
35
- @dataclass(repr=True, frozen=True)
36
- class InstanceSpecificDetails(InstanceDetails):
37
- status: str
38
- connection_url: str
39
- memory: str
40
- type: str
41
- region: str
42
-
43
- @classmethod
44
- def fromJson(cls, json: dict[str, Any]) -> InstanceSpecificDetails:
45
- return cls(
46
- id=json["id"],
47
- name=json["name"],
48
- tenant_id=json["tenant_id"],
49
- cloud_provider=json["cloud_provider"],
50
- status=json["status"],
51
- connection_url=json.get("connection_url", ""),
52
- memory=json.get("memory", ""),
53
- type=json["type"],
54
- region=json["region"],
55
- )
56
-
57
-
58
- @dataclass(repr=True, frozen=True)
59
- class InstanceCreateDetails:
60
- id: str
61
- username: str
62
- password: str
63
- connection_url: str
64
-
65
- @classmethod
66
- def from_json(cls, json: dict[str, Any]) -> InstanceCreateDetails:
67
- fields = dataclasses.fields(cls)
68
- if any(f.name not in json for f in fields):
69
- raise RuntimeError(f"Missing required field. Expected `{[f.name for f in fields]}` but got `{json}`")
70
-
71
- return cls(**{f.name: json[f.name] for f in fields})
72
-
73
-
74
- class WaitResult(NamedTuple):
75
- connection_url: str
76
- error: str
77
-
78
- @classmethod
79
- def from_error(cls, error: str) -> WaitResult:
80
- return cls(connection_url="", error=error)
81
-
82
- @classmethod
83
- def from_connection_url(cls, connection_url: str) -> WaitResult:
84
- return cls(connection_url=connection_url, error="")
85
-
86
-
87
- @dataclass(repr=True, frozen=True)
88
- class TenantDetails:
89
- id: str
90
- ds_type: str
91
- regions_per_provider: dict[str, Set[str]]
92
-
93
- @classmethod
94
- def from_json(cls, json: dict[str, Any]) -> TenantDetails:
95
- regions_per_provider = defaultdict(set)
96
- instance_types = set()
97
- ds_type = None
98
-
99
- for configs in json["instance_configurations"]:
100
- type = configs["type"]
101
- if type.split("-")[1] == "ds":
102
- regions_per_provider[configs["cloud_provider"]].add(configs["region"])
103
- ds_type = type
104
- instance_types.add(configs["type"])
105
-
106
- id = json["id"]
107
- if not ds_type:
108
- raise RuntimeError(
109
- f"Tenant with id `{id}` cannot create DS instances. Available instances are `{instance_types}`."
110
- )
111
-
112
- return cls(
113
- id=id,
114
- ds_type=ds_type,
115
- regions_per_provider=regions_per_provider,
116
- )
117
-
118
-
119
25
  class AuraApi:
120
26
  class AuraAuthToken:
121
27
  access_token: str
@@ -156,6 +62,79 @@ class AuraApi:
156
62
 
157
63
  return host.split(".")[0].split("-")[0]
158
64
 
65
+ def create_session(self, name: str, dbid: str, pwd: str, memory: str) -> SessionDetails:
66
+ response = req.post(
67
+ f"{self._base_uri}/v1beta5/data-science/sessions",
68
+ headers=self._build_header(),
69
+ json={"name": name, "instance_id": dbid, "password": pwd, "memory": memory},
70
+ )
71
+
72
+ response.raise_for_status()
73
+
74
+ return SessionDetails.fromJson(response.json())
75
+
76
+ def list_session(self, session_id: str, dbid: str) -> Optional[SessionDetails]:
77
+ response = req.get(
78
+ f"{self._base_uri}/v1beta5/data-science/sessions/{session_id}?instanceId={dbid}",
79
+ headers=self._build_header(),
80
+ )
81
+
82
+ if response.status_code == 404:
83
+ return None
84
+
85
+ response.raise_for_status()
86
+
87
+ return SessionDetails.fromJson(response.json())
88
+
89
+ def list_sessions(self, dbid: str) -> List[SessionDetails]:
90
+ response = req.get(
91
+ f"{self._base_uri}/v1beta5/data-science/sessions?instanceId={dbid}",
92
+ headers=self._build_header(),
93
+ )
94
+
95
+ response.raise_for_status()
96
+
97
+ return [SessionDetails.fromJson(s) for s in response.json()]
98
+
99
+ def wait_for_session_running(
100
+ self, session_id: str, dbid: str, sleep_time: float = 0.2, max_sleep_time: float = 300
101
+ ) -> WaitResult:
102
+ waited_time = 0.0
103
+ while waited_time <= max_sleep_time:
104
+ session = self.list_session(session_id, dbid)
105
+ if session is None:
106
+ return WaitResult.from_error(f"Session `{session_id}` for database `{dbid}` not found -- please retry")
107
+ elif session.status == "Ready" and session.host: # check host needed until dns based routing
108
+ return WaitResult.from_connection_url(session.bolt_connection_url())
109
+ else:
110
+ self._logger.debug(
111
+ f"Session `{session_id}` is not yet running. "
112
+ f"Current status: {session.status} Host: {session.host}. "
113
+ f"Retrying in {sleep_time} seconds..."
114
+ )
115
+ waited_time += sleep_time
116
+ time.sleep(sleep_time)
117
+
118
+ return WaitResult.from_error(
119
+ f"Session `{session_id}` for database `{dbid}` is not running after {waited_time} seconds"
120
+ )
121
+
122
+ def delete_session(self, session_id: str, dbid: str) -> bool:
123
+ response = req.delete(
124
+ f"{self._base_uri}/v1beta5/data-science/sessions/{session_id}",
125
+ headers=self._build_header(),
126
+ json={"instance_id": dbid},
127
+ )
128
+
129
+ if response.status_code == 404:
130
+ return False
131
+ elif response.status_code == 202:
132
+ return True
133
+
134
+ response.raise_for_status()
135
+
136
+ return False
137
+
159
138
  def create_instance(self, name: str, memory: str, cloud_provider: str, region: str) -> InstanceCreateDetails:
160
139
  tenant_details = self.tenant_details()
161
140
 
@@ -247,6 +226,21 @@ class AuraApi:
247
226
 
248
227
  return WaitResult.from_error(f"Instance is not running after waiting for {waited_time} seconds")
249
228
 
229
+ def estimate_size(
230
+ self, node_count: int, relationship_count: int, algorithm_categories: List[AlgorithmCategory]
231
+ ) -> EstimationDetails:
232
+ data = {
233
+ "node_count": node_count,
234
+ "relationship_count": relationship_count,
235
+ "algorithm_categories": [i.value for i in algorithm_categories],
236
+ "instance_type": "dsenterprise",
237
+ }
238
+
239
+ response = req.post(f"{self._base_uri}/v1/instances/sizing", headers=self._build_header(), json=data)
240
+ response.raise_for_status()
241
+
242
+ return EstimationDetails.from_json(response.json()["data"])
243
+
250
244
  def _get_tenant_id(self) -> str:
251
245
  response = req.get(
252
246
  f"{self._base_uri}/v1/tenants",
@@ -257,8 +251,9 @@ class AuraApi:
257
251
  raw_data = response.json()["data"]
258
252
 
259
253
  if len(raw_data) != 1:
254
+ tenants_dict = {d["id"]: d["name"] for d in raw_data}
260
255
  raise RuntimeError(
261
- f"This account has access to multiple tenants `{raw_data}`. Please specify which one to use."
256
+ f"This account has access to multiple tenants: `{tenants_dict}`. Please specify which one to use."
262
257
  )
263
258
 
264
259
  return raw_data[0]["id"] # type: ignore
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import sys
5
+ from collections import defaultdict
6
+ from dataclasses import dataclass
7
+ from datetime import datetime, timezone
8
+ from typing import Any, NamedTuple, Optional, Set
9
+
10
+
11
+ @dataclass(repr=True, frozen=True)
12
+ class SessionDetails:
13
+ id: str
14
+ name: str
15
+ instance_id: str
16
+ memory: str
17
+ status: str
18
+ host: Optional[str]
19
+ expiry_date: Optional[datetime]
20
+ created_at: datetime
21
+
22
+ @classmethod
23
+ def fromJson(cls, json: dict[str, Any]) -> SessionDetails:
24
+ expiry_date = json.get("expiry_date")
25
+
26
+ return cls(
27
+ id=json["id"],
28
+ name=json["name"],
29
+ instance_id=json["instance_id"],
30
+ memory=json["memory"],
31
+ status=json["status"],
32
+ host=json.get("host"),
33
+ expiry_date=TimeParser.fromisoformat(expiry_date) if expiry_date else None,
34
+ created_at=TimeParser.fromisoformat(json["created_at"]),
35
+ )
36
+
37
+ def bolt_connection_url(self) -> str:
38
+ return f"neo4j+ssc://{self.host}" # TODO use neo4j+s
39
+
40
+
41
+ @dataclass(repr=True, frozen=True)
42
+ class InstanceDetails:
43
+ id: str
44
+ name: str
45
+ tenant_id: str
46
+ cloud_provider: str
47
+
48
+ @classmethod
49
+ def fromJson(cls, json: dict[str, Any]) -> InstanceDetails:
50
+ return cls(
51
+ id=json["id"],
52
+ name=json["name"],
53
+ tenant_id=json["tenant_id"],
54
+ cloud_provider=json["cloud_provider"],
55
+ )
56
+
57
+
58
+ @dataclass(repr=True, frozen=True)
59
+ class InstanceSpecificDetails(InstanceDetails):
60
+ status: str
61
+ connection_url: str
62
+ memory: str
63
+ type: str
64
+ region: str
65
+
66
+ @classmethod
67
+ def fromJson(cls, json: dict[str, Any]) -> InstanceSpecificDetails:
68
+ return cls(
69
+ id=json["id"],
70
+ name=json["name"],
71
+ tenant_id=json["tenant_id"],
72
+ cloud_provider=json["cloud_provider"],
73
+ status=json["status"],
74
+ connection_url=json.get("connection_url", ""),
75
+ memory=json.get("memory", ""),
76
+ type=json["type"],
77
+ region=json["region"],
78
+ )
79
+
80
+
81
+ @dataclass(repr=True, frozen=True)
82
+ class InstanceCreateDetails:
83
+ id: str
84
+ username: str
85
+ password: str
86
+ connection_url: str
87
+
88
+ @classmethod
89
+ def from_json(cls, json: dict[str, Any]) -> InstanceCreateDetails:
90
+ fields = dataclasses.fields(cls)
91
+ if any(f.name not in json for f in fields):
92
+ raise RuntimeError(f"Missing required field. Expected `{[f.name for f in fields]}` but got `{json}`")
93
+
94
+ return cls(**{f.name: json[f.name] for f in fields})
95
+
96
+
97
+ @dataclass(repr=True, frozen=True)
98
+ class EstimationDetails:
99
+ min_required_memory: str
100
+ recommended_size: str
101
+ did_exceed_maximum: bool
102
+
103
+ @classmethod
104
+ def from_json(cls, json: dict[str, Any]) -> EstimationDetails:
105
+ fields = dataclasses.fields(cls)
106
+ if any(f.name not in json for f in fields):
107
+ raise RuntimeError(f"Missing required field. Expected `{[f.name for f in fields]}` but got `{json}`")
108
+
109
+ return cls(**{f.name: json[f.name] for f in fields})
110
+
111
+
112
+ class WaitResult(NamedTuple):
113
+ connection_url: str
114
+ error: str
115
+
116
+ @classmethod
117
+ def from_error(cls, error: str) -> WaitResult:
118
+ return cls(connection_url="", error=error)
119
+
120
+ @classmethod
121
+ def from_connection_url(cls, connection_url: str) -> WaitResult:
122
+ return cls(connection_url=connection_url, error="")
123
+
124
+
125
+ @dataclass(repr=True, frozen=True)
126
+ class TenantDetails:
127
+ id: str
128
+ ds_type: str
129
+ regions_per_provider: dict[str, Set[str]]
130
+
131
+ @classmethod
132
+ def from_json(cls, json: dict[str, Any]) -> TenantDetails:
133
+ regions_per_provider = defaultdict(set)
134
+ instance_types = set()
135
+ ds_type = None
136
+
137
+ for configs in json["instance_configurations"]:
138
+ type = configs["type"]
139
+ if type.split("-")[1] == "ds":
140
+ regions_per_provider[configs["cloud_provider"]].add(configs["region"])
141
+ ds_type = type
142
+ instance_types.add(configs["type"])
143
+
144
+ id = json["id"]
145
+ if not ds_type:
146
+ raise RuntimeError(
147
+ f"Tenant with id `{id}` cannot create DS instances. Available instances are `{instance_types}`."
148
+ )
149
+
150
+ return cls(
151
+ id=id,
152
+ ds_type=ds_type,
153
+ regions_per_provider=regions_per_provider,
154
+ )
155
+
156
+
157
+ # datetime.fromisoformat only works with Python version > 3.9
158
+ class TimeParser:
159
+
160
+ @staticmethod
161
+ def fromisoformat(date: str) -> datetime:
162
+ if sys.version_info >= (3, 11):
163
+ return datetime.fromisoformat(date)
164
+ else:
165
+ # Aura API example: 1970-01-01T00:00:00Z
166
+ return datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)