Skip to content

Commit dc1d351

Browse files
committed
Drop support for Python < 3.10 and MySQL < 8.0
- Update minimum Python version from 3.9 to 3.10 in pyproject.toml - Update ruff target-version to py310 - Remove MySQL version check in admin.py (always use ALTER USER syntax) - Modernize type hints using Python 3.10+ syntax: - Replace Union[X, Y] with X | Y - Replace Optional[X] with X | None - Replace Dict/List/Tuple with dict/list/tuple - Import Iterator from collections.abc instead of typing
1 parent 64ff27b commit dc1d351

File tree

5 files changed

+34
-40
lines changed

5 files changed

+34
-40
lines changed

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ dependencies = [
2323
"setuptools",
2424
"pydantic-settings>=2.0.0",
2525
]
26-
requires-python = ">=3.9,<3.14"
26+
requires-python = ">=3.10,<3.14"
2727
authors = [
2828
{name = "Dimitri Yatsenko", email = "[email protected]"},
2929
{name = "Thinh Nguyen", email = "[email protected]"},
@@ -102,7 +102,7 @@ dev = [
102102
[tool.ruff]
103103
# Equivalent to flake8 configuration
104104
line-length = 127
105-
target-version = "py39"
105+
target-version = "py310"
106106

107107
[tool.ruff.lint]
108108
# Enable specific rule sets equivalent to flake8 configuration
@@ -176,7 +176,7 @@ test = { features = ["test"], solve-group = "default" }
176176
[tool.pixi.tasks]
177177

178178
[tool.pixi.dependencies]
179-
python = ">=3.9,<3.14"
179+
python = ">=3.10,<3.14"
180180
graphviz = ">=13.1.2,<14"
181181

182182
[tool.pixi.activation]

src/datajoint/admin.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from getpass import getpass
33

44
import pymysql
5-
from packaging import version
65

76
from .connection import conn
87
from .settings import config
@@ -20,11 +19,7 @@ def set_password(new_password=None, connection=None, update_config=None):
2019
logger.warning("Failed to confirm the password! Aborting password change.")
2120
return
2221

23-
if version.parse(connection.query("select @@version;").fetchone()[0]) >= version.parse("5.7"):
24-
# SET PASSWORD is deprecated as of MySQL 5.7 and removed in 8+
25-
connection.query("ALTER USER user() IDENTIFIED BY '%s';" % new_password)
26-
else:
27-
connection.query("SET PASSWORD = PASSWORD('%s')" % new_password)
22+
connection.query("ALTER USER user() IDENTIFIED BY '%s';" % new_password)
2823
logger.info("Password updated.")
2924

3025
if update_config or (update_config is None and user_choice("Update local setting?") == "yes"):

src/datajoint/condition.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import re
99
import uuid
1010
from dataclasses import dataclass
11-
from typing import List, Union
1211

1312
import numpy
1413
import pandas
@@ -67,8 +66,8 @@ class Top:
6766
In SQL, this corresponds to ORDER BY ... LIMIT ... OFFSET
6867
"""
6968

70-
limit: Union[int, None] = 1
71-
order_by: Union[str, List[str]] = "KEY"
69+
limit: int | None = 1
70+
order_by: str | list[str] = "KEY"
7271
offset: int = 0
7372

7473
def __post_init__(self):

src/datajoint/settings.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import pprint
1010
from contextlib import contextmanager
1111
from enum import Enum
12-
from typing import Any, Dict, Iterator, List, Literal, Optional, Tuple
12+
from collections.abc import Iterator
13+
from typing import Any, Literal
1314

1415
from pydantic import Field, field_validator
1516
from pydantic.aliases import AliasChoices
@@ -48,11 +49,11 @@ class DatabaseSettings(BaseSettings):
4849
"""Database connection settings"""
4950

5051
host: str = "localhost"
51-
password: Optional[str] = None
52-
user: Optional[str] = None
52+
password: str | None = None
53+
user: str | None = None
5354
port: int = 3306
5455
reconnect: bool = True
55-
use_tls: Optional[bool] = None
56+
use_tls: bool | None = None
5657

5758
model_config = SettingsConfigDict(
5859
env_prefix="DJ_",
@@ -64,7 +65,7 @@ class DatabaseSettings(BaseSettings):
6465
class ConnectionSettings(BaseSettings):
6566
"""Connection settings"""
6667

67-
init_function: Optional[str] = None
68+
init_function: str | None = None
6869
charset: str = "" # pymysql uses '' as default
6970

7071
model_config = SettingsConfigDict(
@@ -91,8 +92,8 @@ class DisplaySettings(BaseSettings):
9192
class ExternalSettings(BaseSettings):
9293
"""External storage settings"""
9394

94-
aws_access_key_id: Optional[str] = None
95-
aws_secret_access_key: Optional[str] = None
95+
aws_access_key_id: str | None = None
96+
aws_secret_access_key: str | None = None
9697

9798
model_config = SettingsConfigDict(
9899
env_prefix="DJ_",
@@ -117,12 +118,12 @@ class DataJointSettings(BaseSettings):
117118
fetch_format: Literal["array", "frame"] = "array"
118119
enable_python_native_blobs: bool = True
119120
add_hidden_timestamp: bool = False
120-
filepath_checksum_size_limit: Optional[int] = None
121+
filepath_checksum_size_limit: int | None = None
121122

122123
# External stores configuration (not managed by pydantic directly)
123-
stores: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
124-
cache: Optional[str] = None
125-
query_cache: Optional[str] = None
124+
stores: dict[str, dict[str, Any]] = Field(default_factory=dict)
125+
cache: str | None = None
126+
query_cache: str | None = None
126127

127128
model_config = SettingsConfigDict(
128129
env_prefix="DJ_",
@@ -133,7 +134,7 @@ class DataJointSettings(BaseSettings):
133134

134135
@field_validator("cache", "query_cache", mode="before")
135136
@classmethod
136-
def validate_path(cls, v: Any) -> Optional[str]:
137+
def validate_path(cls, v: Any) -> str | None:
137138
"""Convert path-like objects to strings"""
138139
if v is None:
139140
return v
@@ -162,11 +163,11 @@ class ConfigWrapper(collections.abc.MutableMapping):
162163

163164
def __init__(self, settings: DataJointSettings):
164165
self._settings = settings
165-
self._original_values: Dict[str, Any] = {} # For context manager support
166-
self._extra: Dict[str, Any] = {} # Store arbitrary extra keys not in pydantic model
166+
self._original_values: dict[str, Any] = {} # For context manager support
167+
self._extra: dict[str, Any] = {} # Store arbitrary extra keys not in pydantic model
167168

168169
@property
169-
def _conf(self) -> Dict[str, Any]:
170+
def _conf(self) -> dict[str, Any]:
170171
"""Backward compatibility: expose internal config as _conf"""
171172
result = self._to_dict()
172173
result.update(self._extra)
@@ -284,9 +285,9 @@ def __len__(self) -> int:
284285
"""Return number of configuration keys"""
285286
return len(self._get_all_keys())
286287

287-
def _get_all_keys(self) -> List[str]:
288+
def _get_all_keys(self) -> list[str]:
288289
"""Get all configuration keys in flat dot notation"""
289-
keys: List[str] = []
290+
keys: list[str] = []
290291

291292
def _extract_keys(obj: Any, prefix: str = "") -> None:
292293
if isinstance(obj, BaseSettings):
@@ -318,9 +319,9 @@ def __repr__(self) -> str:
318319
"""Repr representation"""
319320
return self.__str__()
320321

321-
def _to_dict(self) -> Dict[str, Any]:
322+
def _to_dict(self) -> dict[str, Any]:
322323
"""Convert settings to a flat dict with dot notation keys"""
323-
result: Dict[str, Any] = {}
324+
result: dict[str, Any] = {}
324325

325326
def _flatten(obj: Any, prefix: str = "") -> None:
326327
if isinstance(obj, BaseSettings):
@@ -389,7 +390,7 @@ def save_global(self, verbose: bool = False) -> None:
389390
"""saves the settings in the global config file"""
390391
self.save(os.path.expanduser(os.path.join("~", GLOBALCONFIG)), verbose)
391392

392-
def get_store_spec(self, store: str) -> Dict[str, Any]:
393+
def get_store_spec(self, store: str) -> dict[str, Any]:
393394
"""
394395
find configuration of external stores for blobs and attachments
395396
"""
@@ -398,10 +399,10 @@ def get_store_spec(self, store: str) -> Dict[str, Any]:
398399
except KeyError:
399400
raise DataJointError(f"Storage {store} is requested but not configured")
400401

401-
spec: Dict[str, Any] = dict(spec) # Make a copy
402+
spec: dict[str, Any] = dict(spec) # Make a copy
402403
spec["subfolding"] = spec.get("subfolding", DEFAULT_SUBFOLDING)
403404

404-
spec_keys_by_protocol: Dict[str, Tuple[str, ...]] = { # REQUIRED in uppercase and allowed in lowercase
405+
spec_keys_by_protocol: dict[str, tuple[str, ...]] = { # REQUIRED in uppercase and allowed in lowercase
405406
"file": ("PROTOCOL", "LOCATION", "subfolding", "stage"),
406407
"s3": (
407408
"PROTOCOL",
@@ -418,7 +419,7 @@ def get_store_spec(self, store: str) -> Dict[str, Any]:
418419
}
419420

420421
try:
421-
spec_keys: Tuple[str, ...] = spec_keys_by_protocol[spec.get("protocol", "").lower()]
422+
spec_keys: tuple[str, ...] = spec_keys_by_protocol[spec.get("protocol", "").lower()]
422423
except KeyError:
423424
raise DataJointError(f'Missing or invalid protocol in dj.config["stores"]["{store}"]')
424425

@@ -441,7 +442,7 @@ def get_store_spec(self, store: str) -> Dict[str, Any]:
441442
return spec
442443

443444
@contextmanager
444-
def __call__(self, **kwargs: Any) -> Iterator["ConfigWrapper"]:
445+
def __call__(self, **kwargs: Any) -> "Iterator[ConfigWrapper]":
445446
"""
446447
The config object can also be used in a with statement to change the state of the configuration
447448
temporarily. kwargs to the context manager are the keys into config, where '.' is replaced by a
@@ -453,8 +454,8 @@ def __call__(self, **kwargs: Any) -> Iterator["ConfigWrapper"]:
453454
>>> # do dangerous stuff here
454455
"""
455456
# Save current values
456-
backup_values: Dict[str, Any] = {}
457-
converted_kwargs: Dict[str, Any] = {k.replace("__", "."): v for k, v in kwargs.items()}
457+
backup_values: dict[str, Any] = {}
458+
converted_kwargs: dict[str, Any] = {k.replace("__", "."): v for k, v in kwargs.items()}
458459

459460
try:
460461
# Save original values

src/datajoint/table.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import re
99
import uuid
1010
from pathlib import Path
11-
from typing import Union
1211

1312
import numpy as np
1413
import pandas
@@ -430,7 +429,7 @@ def delete_quick(self, get_count=False):
430429
def delete(
431430
self,
432431
transaction: bool = True,
433-
safemode: Union[bool, None] = None,
432+
safemode: bool | None = None,
434433
force_parts: bool = False,
435434
force_masters: bool = False,
436435
) -> int:

0 commit comments

Comments
 (0)