Skip to content

Commit 1096e1f

Browse files
authored
Drop Python 3.8 support (#785)
* Drop Python 3.8 support 3.8 is EOL * Remove * Remove 3.8 backwards compat handling * Use constants for HTTP response codes and methods * Changelog 3.8 EOL + document tox envs
1 parent aef74f9 commit 1096e1f

File tree

70 files changed

+429
-428
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+429
-428
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Remember that many community members have become regular contributors and some a
5555

5656
Setting up the Python development environment:
5757

58-
* Install Python 3.8+
58+
* Install Python 3.9+
5959
* [Install pip](https://pip.pypa.io/en/stable/installation/)
6060
* Install the project's Python dependencies:
6161
```bash

changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
## Breaking changes
55

6+
* Drop support for Python `3.8` as its EOL
7+
68

79
## New features
810

doc/modules/ROOT/pages/installation.adoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ The Python client supports the following versions of the other three components:
1919
| Python Client | GDS version | Python version | Neo4j Python Driver version
2020
.1+<.^| 1.13
2121
.1+<.^| >= 2.6, < 2.11
22-
.4+<.^| >= 3.8, < 3.13
22+
.1+<.^| >= 3.9, < 3.13
2323
.3+<.^| >= 4.4.12, < 6.0.0
2424

2525
.1+<.^| 1.12
2626
.1+<.^| >= 2.6, < 2.11
27+
.3+<.^| >= 3.8, < 3.13
2728

2829
.1+<.^| 1.11
2930
.1+<.^| >= 2.6, < 2.10

doc/tests/test_client_only_endpoints.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import os
22
import re
33
import sys
4-
from typing import List
54

65
from neo4j import GraphDatabase
76

@@ -34,14 +33,14 @@
3433
FUNCTION_DEF_PATTERN = re.compile(r"def\s+(\w+)\s*\(")
3534

3635

37-
def find_single_client_only_functions(py_file_path: str) -> List[str]:
36+
def find_single_client_only_functions(py_file_path: str) -> list[str]:
3837
with open(py_file_path) as f:
3938
contents = f.read()
4039
matches = re.finditer(CLIENT_ONLY_PATTERN, contents)
4140
return [f"{match.group(1)}.{match.group(3)}" for match in matches]
4241

4342

44-
def find_client_only_functions() -> List[str]:
43+
def find_client_only_functions() -> list[str]:
4544
client_only_functions = []
4645
for root, dirs, files in os.walk(PROJECT_DIR):
4746
for file in files:
@@ -52,7 +51,7 @@ def find_client_only_functions() -> List[str]:
5251
return client_only_functions
5352

5453

55-
def find_covered_server_endpoints() -> List[str]:
54+
def find_covered_server_endpoints() -> list[str]:
5655
driver = GraphDatabase.driver(URI, auth=AUTH)
5756
with driver.session() as session:
5857
all_server_endpoints = session.run("CALL gds.list() YIELD name", {}).data()
@@ -62,7 +61,7 @@ def find_covered_server_endpoints() -> List[str]:
6261
return [ep["name"] for ep in all_server_endpoints if ep["name"] not in IGNORED_SERVER_ENDPOINTS]
6362

6463

65-
def check_rst_files(endpoints: List[str]) -> None:
64+
def check_rst_files(endpoints: list[str]) -> None:
6665
not_mentioned = set(endpoints)
6766

6867
for root, _, files in os.walk(RST_DIR):

graphdatascience/algo/algo_proc_runner.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC
2-
from typing import Any, Dict, Tuple
2+
from typing import Any
33

44
from pandas import DataFrame, Series
55

@@ -12,7 +12,7 @@
1212

1313
class AlgoProcRunner(IllegalAttrChecker, ABC):
1414
@graph_type_check
15-
def _run_procedure(self, G: Graph, config: Dict[str, Any], with_logging: bool = True) -> DataFrame:
15+
def _run_procedure(self, G: Graph, config: dict[str, Any], with_logging: bool = True) -> DataFrame:
1616
params = CallParameters(graph_name=G.name(), config=config)
1717

1818
return self._query_runner.call_procedure(endpoint=self._namespace, params=params, logging=with_logging)
@@ -35,7 +35,7 @@ def __call__(self, G: Graph, **config: Any) -> "Series[Any]":
3535

3636
class GraphSageRunner(AlgoProcRunner):
3737
@graph_type_check
38-
def __call__(self, G: Graph, **config: Any) -> Tuple[GraphSageModel, "Series[Any]"]:
38+
def __call__(self, G: Graph, **config: Any) -> tuple[GraphSageModel, "Series[Any]"]:
3939
result = self._run_procedure(G, config).squeeze()
4040
model_name = result["modelInfo"]["modelName"]
4141

graphdatascience/call_parameters.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Any, OrderedDict
1+
from collections import OrderedDict
2+
from typing import Any
23

34

45
class CallParameters(OrderedDict[str, Any]):

graphdatascience/error/endpoint_suggester.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
from typing import List
2-
31
import textdistance
42

53
from ..ignored_server_endpoints import IGNORED_SERVER_ENDPOINTS
64

75

8-
def generate_suggestive_error_message(requested_endpoint: str, all_endpoints: List[str]) -> str:
6+
def generate_suggestive_error_message(requested_endpoint: str, all_endpoints: list[str]) -> str:
97
MIN_SIMILARITY_FOR_SUGGESTION = 0.9
108

119
closest_endpoint = None

graphdatascience/graph/base_graph_proc_runner.py

+9-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import os
22
import pathlib
3-
import sys
43
import warnings
5-
from typing import Any, Dict, List, Optional, Union
4+
from typing import Any, List, Optional, Union
65

76
import pandas as pd
87
from multimethod import multimethod
@@ -53,28 +52,22 @@ def __init__(self, query_runner: Any, namespace: str, server_version: ServerVers
5352

5453
@staticmethod
5554
def _path(package: str, resource: str) -> pathlib.Path:
56-
if sys.version_info >= (3, 9):
57-
from importlib.resources import files
55+
from importlib.resources import files
5856

59-
# files() returns a Traversable, but usages require a Path object
60-
return pathlib.Path(str(files(package) / resource))
61-
else:
62-
from importlib.resources import path
63-
64-
# we dont want to use a context manager here, so we need to call __enter__ manually
65-
return path(package, resource).__enter__()
57+
# files() returns a Traversable, but usages require a Path object
58+
return pathlib.Path(str(files(package) / resource))
6659

6760
@client_only_endpoint("gds.graph")
6861
@compatible_with("construct", min_inclusive=ServerVersion(2, 1, 0))
6962
def construct(
7063
self,
7164
graph_name: str,
72-
nodes: Union[DataFrame, List[DataFrame]],
73-
relationships: Optional[Union[DataFrame, List[DataFrame]]] = None,
65+
nodes: Union[DataFrame, list[DataFrame]],
66+
relationships: Optional[Union[DataFrame, list[DataFrame]]] = None,
7467
concurrency: int = 4,
75-
undirected_relationship_types: Optional[List[str]] = None,
68+
undirected_relationship_types: Optional[list[str]] = None,
7669
) -> Graph:
77-
nodes = nodes if isinstance(nodes, List) else [nodes]
70+
nodes = nodes if isinstance(nodes, list) else [nodes]
7871

7972
if isinstance(relationships, DataFrame):
8073
relationships = [relationships]
@@ -365,7 +358,7 @@ def _handle_properties(
365358
G: Graph,
366359
properties: Strings,
367360
entities: Strings,
368-
config: Dict[str, Any],
361+
config: dict[str, Any],
369362
) -> DataFrame:
370363
params = CallParameters(
371364
graph_name=G.name(),

graphdatascience/graph/graph_alpha_proc_runner.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from typing import List, Optional, Union
2+
from typing import Optional, Union
33

44
from pandas import DataFrame
55

@@ -36,13 +36,13 @@ def nodeLabel(self) -> GraphLabelRunner:
3636
def construct(
3737
self,
3838
graph_name: str,
39-
nodes: Union[DataFrame, List[DataFrame]],
40-
relationships: Union[DataFrame, List[DataFrame]],
39+
nodes: Union[DataFrame, list[DataFrame]],
40+
relationships: Union[DataFrame, list[DataFrame]],
4141
concurrency: int = 4,
42-
undirected_relationship_types: Optional[List[str]] = None,
42+
undirected_relationship_types: Optional[list[str]] = None,
4343
) -> Graph:
44-
nodes = nodes if isinstance(nodes, List) else [nodes]
45-
relationships = relationships if isinstance(relationships, List) else [relationships]
44+
nodes = nodes if isinstance(nodes, list) else [nodes]
45+
relationships = relationships if isinstance(relationships, list) else [relationships]
4646

4747
errors = []
4848

graphdatascience/graph/graph_beta_proc_runner.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, List, Union
1+
from typing import Any, Union
22

33
from ..call_parameters import CallParameters
44
from ..error.illegal_attr_checker import IllegalAttrChecker
@@ -9,7 +9,7 @@
99
from .graph_object import Graph
1010
from .graph_project_runner import GraphProjectBetaRunner
1111

12-
Strings = Union[str, List[str]]
12+
Strings = Union[str, list[str]]
1313

1414

1515
class GraphBetaProcRunner(UncallableNamespace, IllegalAttrChecker):

graphdatascience/graph/graph_cypher_runner.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import re
44
from itertools import chain, zip_longest
5-
from typing import Any, Dict, Optional
5+
from typing import Any, Optional
66

77
from pandas import Series
88

@@ -45,7 +45,7 @@ def project(
4545

4646
GraphCypherRunner._verify_query_ends_with_return_clause(self._namespace, query)
4747

48-
result: Optional[Dict[str, Any]] = self._query_runner.run_cypher(query, params, database, False).squeeze()
48+
result: Optional[dict[str, Any]] = self._query_runner.run_cypher(query, params, database, False).squeeze()
4949

5050
if not result:
5151
raise ValueError("Projected graph cannot be empty.")

graphdatascience/graph/graph_entity_ops_runner.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from functools import reduce
2-
from typing import Any, Dict, List, Type, Union
2+
from typing import Any, Type, Union
33
from warnings import filterwarnings
44

55
import pandas as pd
@@ -18,15 +18,15 @@
1818
from .graph_object import Graph
1919
from .graph_type_check import graph_type_check
2020

21-
Strings = Union[str, List[str]]
21+
Strings = Union[str, list[str]]
2222

2323

2424
class TopologyDataFrame(DataFrame):
2525
@property
2626
def _constructor(self) -> "Type[TopologyDataFrame]":
2727
return TopologyDataFrame
2828

29-
def by_rel_type(self) -> Dict[str, List[List[int]]]:
29+
def by_rel_type(self) -> dict[str, list[list[int]]]:
3030
# Pandas 2.2.0 deprecated an internal API used by DF.take(indices)
3131
filterwarnings(
3232
"ignore",
@@ -55,7 +55,7 @@ def _handle_properties(
5555
G: Graph,
5656
properties: Strings,
5757
entities: Strings,
58-
config: Dict[str, Any],
58+
config: dict[str, Any],
5959
) -> DataFrame:
6060
params = CallParameters(
6161
graph_name=G.name(),
@@ -78,7 +78,7 @@ def stream(
7878
G: Graph,
7979
node_property: str,
8080
node_labels: Strings = ["*"],
81-
db_node_properties: List[str] = [],
81+
db_node_properties: list[str] = [],
8282
**config: Any,
8383
) -> DataFrame:
8484
self._namespace += ".stream"
@@ -96,10 +96,10 @@ class GraphNodePropertiesRunner(GraphEntityOpsBaseRunner):
9696
def stream(
9797
self,
9898
G: Graph,
99-
node_properties: List[str],
99+
node_properties: list[str],
100100
node_labels: Strings = ["*"],
101101
separate_property_columns: bool = False,
102-
db_node_properties: List[str] = [],
102+
db_node_properties: list[str] = [],
103103
**config: Any,
104104
) -> DataFrame:
105105
self._namespace += ".stream"
@@ -113,11 +113,11 @@ def stream(
113113
@staticmethod
114114
def _process_result(
115115
query_runner: QueryRunner,
116-
node_properties: List[str],
116+
node_properties: list[str],
117117
separate_property_columns: bool,
118-
db_node_properties: List[str],
118+
db_node_properties: list[str],
119119
result: DataFrame,
120-
config: Dict[str, Any],
120+
config: dict[str, Any],
121121
) -> DataFrame:
122122
# new format was requested, but the query was run via Cypher
123123
if separate_property_columns and "propertyValue" in result.keys():
@@ -161,7 +161,7 @@ def _process_result(
161161
return result
162162

163163
@staticmethod
164-
def _build_query(db_node_properties: List[str]) -> str:
164+
def _build_query(db_node_properties: list[str]) -> str:
165165
query_prefix = "MATCH (n) WHERE id(n) IN $ids RETURN id(n) AS nodeId"
166166

167167
def add_property(query: str, prop: str) -> str:
@@ -176,7 +176,7 @@ def write(self, G: Graph, node_properties: Strings, node_labels: Strings = ["*"]
176176

177177
@compatible_with("drop", min_inclusive=ServerVersion(2, 2, 0))
178178
@graph_type_check
179-
def drop(self, G: Graph, node_properties: List[str], **config: Any) -> "Series[Any]":
179+
def drop(self, G: Graph, node_properties: list[str], **config: Any) -> "Series[Any]":
180180
self._namespace += ".drop"
181181
params = CallParameters(
182182
graph_name=G.name(),
@@ -205,7 +205,7 @@ class GraphRelationshipPropertiesRunner(GraphEntityOpsBaseRunner):
205205
def stream(
206206
self,
207207
G: Graph,
208-
relationship_properties: List[str],
208+
relationship_properties: list[str],
209209
relationship_types: Strings = ["*"],
210210
separate_property_columns: bool = False,
211211
**config: Any,
@@ -239,7 +239,7 @@ def write(
239239
self,
240240
G: Graph,
241241
relationship_type: str,
242-
relationship_properties: List[str],
242+
relationship_properties: list[str],
243243
**config: Any,
244244
) -> "Series[Any]":
245245
self._namespace += ".write"
@@ -319,7 +319,7 @@ def drop(
319319

320320
@compatible_with("stream", min_inclusive=ServerVersion(2, 5, 0))
321321
@graph_type_check
322-
def stream(self, G: Graph, relationship_types: List[str] = ["*"], **config: Any) -> TopologyDataFrame:
322+
def stream(self, G: Graph, relationship_types: list[str] = ["*"], **config: Any) -> TopologyDataFrame:
323323
self._namespace += ".stream"
324324
params = CallParameters(graph_name=G.name(), relationship_types=relationship_types, config=config)
325325
result = self._query_runner.call_procedure(endpoint=self._namespace, params=params)
@@ -336,7 +336,7 @@ def toUndirected(self) -> ToUndirectedRunner:
336336
class GraphRelationshipsBetaRunner(GraphEntityOpsBaseRunner):
337337
@compatible_with("stream", min_inclusive=ServerVersion(2, 2, 0))
338338
@graph_type_check
339-
def stream(self, G: Graph, relationship_types: List[str] = ["*"], **config: Any) -> TopologyDataFrame:
339+
def stream(self, G: Graph, relationship_types: list[str] = ["*"], **config: Any) -> TopologyDataFrame:
340340
self._namespace += ".stream"
341341
params = CallParameters(graph_name=G.name(), relationship_types=relationship_types, config=config)
342342

graphdatascience/graph/graph_export_runner.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict
1+
from typing import Any
22

33
from pandas import Series
44

@@ -15,7 +15,7 @@ def __call__(self, G: Graph, **config: Any) -> "Series[Any]":
1515
return self._export_call(G, config)
1616

1717
@graph_type_check
18-
def _export_call(self, G: Graph, config: Dict[str, Any]) -> "Series[Any]":
18+
def _export_call(self, G: Graph, config: dict[str, Any]) -> "Series[Any]":
1919
params = CallParameters(graph_name=G.name(), config=config)
2020
return self._query_runner.call_procedure(endpoint=self._namespace, params=params).squeeze() # type: ignore
2121

@@ -39,7 +39,7 @@ def __call__(self, G: Graph, **config: Any) -> "Series[Any]":
3939
return self._export_call(G, config)
4040

4141
@graph_type_check
42-
def _export_call(self, G: Graph, config: Dict[str, Any]) -> "Series[Any]":
42+
def _export_call(self, G: Graph, config: dict[str, Any]) -> "Series[Any]":
4343
params = CallParameters(graph_name=G.name(), config=config)
4444
return self._query_runner.call_procedure(endpoint=self._namespace, params=params).squeeze() # type: ignore
4545

0 commit comments

Comments
 (0)