From 58de0f323b9ce6f7d04af0c8bc0634b6734e42aa Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Mon, 6 Apr 2026 10:48:00 -0400 Subject: [PATCH 1/7] feat: add dbname setting for PostgreSQL connections Add database.dbname config option (env: DJ_DBNAME) to specify which PostgreSQL database to connect to. Defaults to 'postgres' if not set (existing behavior preserved). Required where the primary database has a non-default name. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/connection.py | 13 ++++++++++--- src/datajoint/settings.py | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/datajoint/connection.py b/src/datajoint/connection.py index e9eab0921..b3c29f099 100644 --- a/src/datajoint/connection.py +++ b/src/datajoint/connection.py @@ -180,7 +180,8 @@ def __init__( port = int(port) elif port is None: port = self._config["database.port"] - self.conn_info = dict(host=host, port=port, user=user, passwd=password) + dbname = self._config.get("database.dbname") + self.conn_info = dict(host=host, port=port, user=user, passwd=password, dbname=dbname) if use_tls is not False: # use_tls can be: None (auto-detect), True (enable), False (disable), or dict (custom config) if isinstance(use_tls, dict): @@ -224,7 +225,7 @@ def connect(self) -> None: warnings.filterwarnings("ignore", ".*deprecated.*") try: # Use adapter to create connection - self._conn = self.adapter.connect( + connect_kwargs = dict( host=self.conn_info["host"], port=self.conn_info["port"], user=self.conn_info["user"], @@ -232,6 +233,9 @@ def connect(self) -> None: charset=self._config["connection.charset"], use_tls=self.conn_info.get("ssl"), ) + if self.conn_info.get("dbname"): + connect_kwargs["dbname"] = self.conn_info["dbname"] + self._conn = self.adapter.connect(**connect_kwargs) except Exception as ssl_error: # If SSL fails, retry without SSL (if it was auto-detected) if self.conn_info.get("ssl_input") is None: @@ -240,7 +244,7 @@ def connect(self) -> None: "To require SSL, set use_tls=True explicitly.", ssl_error, ) - self._conn = self.adapter.connect( + connect_kwargs = dict( host=self.conn_info["host"], port=self.conn_info["port"], user=self.conn_info["user"], @@ -248,6 +252,9 @@ def connect(self) -> None: charset=self._config["connection.charset"], use_tls=False, # Explicitly disable SSL for fallback ) + if self.conn_info.get("dbname"): + connect_kwargs["dbname"] = self.conn_info["dbname"] + self._conn = self.adapter.connect(**connect_kwargs) else: raise self._is_closed = False # Mark as connected after successful connection diff --git a/src/datajoint/settings.py b/src/datajoint/settings.py index 7019d8345..4359b1739 100644 --- a/src/datajoint/settings.py +++ b/src/datajoint/settings.py @@ -196,6 +196,12 @@ class DatabaseSettings(BaseSettings): description="Database backend: 'mysql' or 'postgresql'", ) port: int | None = Field(default=None, validation_alias="DJ_PORT") + dbname: str | None = Field( + default=None, + validation_alias="DJ_DBNAME", + description="Database name for PostgreSQL connections. " + "Defaults to 'postgres' if not set.", + ) reconnect: bool = True use_tls: bool | None = Field(default=None, validation_alias="DJ_USE_TLS") database_prefix: str = Field( From bdbec08477074862312c0278abf273a5d3b94cbe Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Mon, 6 Apr 2026 11:12:11 -0400 Subject: [PATCH 2/7] refactor: extract _build_connect_kwargs, add dbname to Connection.__init__, add tests - Extract duplicated connect kwargs construction into _build_connect_kwargs() - Add dbname as explicit keyword argument to Connection.__init__() for programmatic use (explicit arg overrides config value) - Add 5 unit tests for dbname settings (default, env var, config file, dict access, override context manager) - Bump version to 2.2.1 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/connection.py | 43 ++++++++++++++---------------- src/datajoint/version.py | 2 +- tests/unit/test_settings.py | 53 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/datajoint/connection.py b/src/datajoint/connection.py index b3c29f099..fe940567b 100644 --- a/src/datajoint/connection.py +++ b/src/datajoint/connection.py @@ -168,6 +168,7 @@ def __init__( port: int | None = None, use_tls: bool | dict | None = None, *, + dbname: str | None = None, backend: str | None = None, config_override: "Config | None" = None, ) -> None: @@ -180,7 +181,8 @@ def __init__( port = int(port) elif port is None: port = self._config["database.port"] - dbname = self._config.get("database.dbname") + if dbname is None: + dbname = self._config.get("database.dbname") self.conn_info = dict(host=host, port=port, user=user, passwd=password, dbname=dbname) if use_tls is not False: # use_tls can be: None (auto-detect), True (enable), False (disable), or dict (custom config) @@ -219,23 +221,26 @@ def __repr__(self): connected = "connected" if self.is_connected else "disconnected" return "DataJoint connection ({connected}) {user}@{host}:{port}".format(connected=connected, **self.conn_info) + def _build_connect_kwargs(self, use_tls=None): + """Build kwargs dict for adapter.connect().""" + kwargs = dict( + host=self.conn_info["host"], + port=self.conn_info["port"], + user=self.conn_info["user"], + password=self.conn_info["passwd"], + charset=self._config["connection.charset"], + use_tls=use_tls if use_tls is not None else self.conn_info.get("ssl"), + ) + if self.conn_info.get("dbname"): + kwargs["dbname"] = self.conn_info["dbname"] + return kwargs + def connect(self) -> None: """Establish or re-establish connection to the database server.""" with warnings.catch_warnings(): warnings.filterwarnings("ignore", ".*deprecated.*") try: - # Use adapter to create connection - connect_kwargs = dict( - host=self.conn_info["host"], - port=self.conn_info["port"], - user=self.conn_info["user"], - password=self.conn_info["passwd"], - charset=self._config["connection.charset"], - use_tls=self.conn_info.get("ssl"), - ) - if self.conn_info.get("dbname"): - connect_kwargs["dbname"] = self.conn_info["dbname"] - self._conn = self.adapter.connect(**connect_kwargs) + self._conn = self.adapter.connect(**self._build_connect_kwargs()) except Exception as ssl_error: # If SSL fails, retry without SSL (if it was auto-detected) if self.conn_info.get("ssl_input") is None: @@ -244,17 +249,9 @@ def connect(self) -> None: "To require SSL, set use_tls=True explicitly.", ssl_error, ) - connect_kwargs = dict( - host=self.conn_info["host"], - port=self.conn_info["port"], - user=self.conn_info["user"], - password=self.conn_info["passwd"], - charset=self._config["connection.charset"], - use_tls=False, # Explicitly disable SSL for fallback + self._conn = self.adapter.connect( + **self._build_connect_kwargs(use_tls=False) ) - if self.conn_info.get("dbname"): - connect_kwargs["dbname"] = self.conn_info["dbname"] - self._conn = self.adapter.connect(**connect_kwargs) else: raise self._is_closed = False # Mark as connected after successful connection diff --git a/src/datajoint/version.py b/src/datajoint/version.py index 0b0d3f4e9..1fd91a092 100644 --- a/src/datajoint/version.py +++ b/src/datajoint/version.py @@ -1,4 +1,4 @@ # version bump auto managed by Github Actions: # label_prs.yaml(prep), release.yaml(bump), post_release.yaml(edit) # manually set this version will be eventually overwritten by the above actions -__version__ = "2.2.0" +__version__ = "2.2.1" diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 475d96df9..7d0138f5d 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -750,6 +750,59 @@ def test_similar_prefix_names_allowed(self): dj.config.stores.update(original_stores) +class TestDbnameConfiguration: + """Test database.dbname configuration.""" + + def test_dbname_default_is_none(self): + """Dbname defaults to None when not configured.""" + from datajoint.settings import DatabaseSettings + + s = DatabaseSettings() + assert s.dbname is None + + def test_dbname_env_var(self, monkeypatch): + """DJ_DBNAME environment variable sets dbname.""" + from datajoint.settings import DatabaseSettings + + monkeypatch.setenv("DJ_DBNAME", "my_database") + s = DatabaseSettings() + assert s.dbname == "my_database" + + def test_dbname_from_config_file(self, tmp_path, monkeypatch): + """Load dbname from config file.""" + import json + + from datajoint.settings import Config + + config_file = tmp_path / "test_config.json" + config_file.write_text(json.dumps({ + "database": {"dbname": "custom_db", "host": "localhost"} + })) + + monkeypatch.delenv("DJ_DBNAME", raising=False) + monkeypatch.delenv("DJ_HOST", raising=False) + + cfg = Config() + cfg.load(config_file) + assert cfg.database.dbname == "custom_db" + + def test_dbname_dict_access(self): + """Dict-style access reads and writes dbname.""" + original = dj.config.database.dbname + try: + dj.config.database.dbname = "test_db" + assert dj.config["database.dbname"] == "test_db" + finally: + dj.config.database.dbname = original + + def test_dbname_override_context_manager(self): + """Override context manager temporarily sets dbname.""" + original = dj.config.database.dbname + with dj.config.override(database__dbname="override_db"): + assert dj.config.database.dbname == "override_db" + assert dj.config.database.dbname == original + + class TestBackendConfiguration: """Test database backend configuration and port auto-detection.""" From 35e93b08723bbc09f66c6d23494c286a1fc24431 Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Mon, 6 Apr 2026 11:20:20 -0400 Subject: [PATCH 3/7] style: apply ruff-format to dbname changes Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/connection.py | 4 +--- src/datajoint/settings.py | 3 +-- tests/unit/test_settings.py | 4 +--- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/datajoint/connection.py b/src/datajoint/connection.py index fe940567b..0377e82b6 100644 --- a/src/datajoint/connection.py +++ b/src/datajoint/connection.py @@ -249,9 +249,7 @@ def connect(self) -> None: "To require SSL, set use_tls=True explicitly.", ssl_error, ) - self._conn = self.adapter.connect( - **self._build_connect_kwargs(use_tls=False) - ) + self._conn = self.adapter.connect(**self._build_connect_kwargs(use_tls=False)) else: raise self._is_closed = False # Mark as connected after successful connection diff --git a/src/datajoint/settings.py b/src/datajoint/settings.py index 4359b1739..c075c6fa6 100644 --- a/src/datajoint/settings.py +++ b/src/datajoint/settings.py @@ -199,8 +199,7 @@ class DatabaseSettings(BaseSettings): dbname: str | None = Field( default=None, validation_alias="DJ_DBNAME", - description="Database name for PostgreSQL connections. " - "Defaults to 'postgres' if not set.", + description="Database name for PostgreSQL connections. Defaults to 'postgres' if not set.", ) reconnect: bool = True use_tls: bool | None = Field(default=None, validation_alias="DJ_USE_TLS") diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 7d0138f5d..58e415cda 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -775,9 +775,7 @@ def test_dbname_from_config_file(self, tmp_path, monkeypatch): from datajoint.settings import Config config_file = tmp_path / "test_config.json" - config_file.write_text(json.dumps({ - "database": {"dbname": "custom_db", "host": "localhost"} - })) + config_file.write_text(json.dumps({"database": {"dbname": "custom_db", "host": "localhost"}})) monkeypatch.delenv("DJ_DBNAME", raising=False) monkeypatch.delenv("DJ_HOST", raising=False) From 78a94fb8384423f7e533ac498145e63f56927b77 Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Tue, 7 Apr 2026 09:30:08 -0400 Subject: [PATCH 4/7] =?UTF-8?q?Address=20PR=20review:=20rename=20database.?= =?UTF-8?q?dbname=20=E2=86=92=20database.name,=20deprecate=20database=5Fpr?= =?UTF-8?q?efix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review feedback from PR #1426: 1. Rename setting to database.name (env: DJ_DATABASE_NAME) to match section naming style and avoid stutter. Connection kwarg is database_name. Adapter still receives dbname (psycopg2's parameter). 2. Deprecate database_prefix — emit DeprecationWarning when non-empty. Will be removed in DataJoint 2.3. database.name is the replacement. 3. Revert version.py to 2.2.0 — release workflow owns version bumps. 4. Warn when database.name is set with MySQL backend (MySQL does not support database selection via this parameter). 5. Include database name in Connection.__repr__ and log message when set. Format: user@host:port/database_name Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/connection.py | 35 ++++++++++++---- src/datajoint/settings.py | 22 ++++++++-- src/datajoint/version.py | 2 +- tests/unit/test_settings.py | 83 +++++++++++++++++++++++++------------ 4 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/datajoint/connection.py b/src/datajoint/connection.py index 0377e82b6..3c3257ae9 100644 --- a/src/datajoint/connection.py +++ b/src/datajoint/connection.py @@ -168,7 +168,7 @@ def __init__( port: int | None = None, use_tls: bool | dict | None = None, *, - dbname: str | None = None, + database_name: str | None = None, backend: str | None = None, config_override: "Config | None" = None, ) -> None: @@ -181,9 +181,9 @@ def __init__( port = int(port) elif port is None: port = self._config["database.port"] - if dbname is None: - dbname = self._config.get("database.dbname") - self.conn_info = dict(host=host, port=port, user=user, passwd=password, dbname=dbname) + if database_name is None: + database_name = self._config.get("database.name") + self.conn_info = dict(host=host, port=port, user=user, passwd=password, database_name=database_name) if use_tls is not False: # use_tls can be: None (auto-detect), True (enable), False (disable), or dict (custom config) if isinstance(use_tls, dict): @@ -204,12 +204,27 @@ def __init__( backend = self._config["database.backend"] self.adapter = get_adapter(backend) + if database_name and self.adapter.backend == "mysql": + warnings.warn( + "database.name is set but the MySQL backend does not support database selection. " + "This setting only applies to PostgreSQL connections.", + UserWarning, + stacklevel=2, + ) + self.connect() if self.is_connected: - logger.info("DataJoint {version} connected to {user}@{host}:{port}".format(version=__version__, **self.conn_info)) + db = self.conn_info.get("database_name") + db_str = f"/{db}" if db else "" + logger.info( + f"DataJoint {__version__} connected to " + f"{self.conn_info['user']}@{self.conn_info['host']}:{self.conn_info['port']}{db_str}" + ) self.connection_id = self.adapter.get_connection_id(self._conn) else: - raise errors.LostConnectionError("Connection failed {user}@{host}:{port}".format(**self.conn_info)) + raise errors.LostConnectionError( + f"Connection failed {self.conn_info['user']}@{self.conn_info['host']}:{self.conn_info['port']}" + ) self._in_transaction = False self.schemas = dict() self.dependencies = Dependencies(self) @@ -219,7 +234,9 @@ def __eq__(self, other): def __repr__(self): connected = "connected" if self.is_connected else "disconnected" - return "DataJoint connection ({connected}) {user}@{host}:{port}".format(connected=connected, **self.conn_info) + db = self.conn_info.get("database_name") + db_str = f"/{db}" if db else "" + return f"DataJoint connection ({connected}) {self.conn_info['user']}@{self.conn_info['host']}:{self.conn_info['port']}{db_str}" def _build_connect_kwargs(self, use_tls=None): """Build kwargs dict for adapter.connect().""" @@ -231,8 +248,8 @@ def _build_connect_kwargs(self, use_tls=None): charset=self._config["connection.charset"], use_tls=use_tls if use_tls is not None else self.conn_info.get("ssl"), ) - if self.conn_info.get("dbname"): - kwargs["dbname"] = self.conn_info["dbname"] + if self.conn_info.get("database_name"): + kwargs["dbname"] = self.conn_info["database_name"] return kwargs def connect(self) -> None: diff --git a/src/datajoint/settings.py b/src/datajoint/settings.py index c075c6fa6..0c61cd199 100644 --- a/src/datajoint/settings.py +++ b/src/datajoint/settings.py @@ -65,6 +65,7 @@ "database.password": "DJ_PASS", "database.backend": "DJ_BACKEND", "database.port": "DJ_PORT", + "database.name": "DJ_DATABASE_NAME", "database.database_prefix": "DJ_DATABASE_PREFIX", "database.create_tables": "DJ_CREATE_TABLES", "loglevel": "DJ_LOG_LEVEL", @@ -196,9 +197,9 @@ class DatabaseSettings(BaseSettings): description="Database backend: 'mysql' or 'postgresql'", ) port: int | None = Field(default=None, validation_alias="DJ_PORT") - dbname: str | None = Field( + name: str | None = Field( default=None, - validation_alias="DJ_DBNAME", + validation_alias="DJ_DATABASE_NAME", description="Database name for PostgreSQL connections. Defaults to 'postgres' if not set.", ) reconnect: bool = True @@ -206,8 +207,7 @@ class DatabaseSettings(BaseSettings): database_prefix: str = Field( default="", validation_alias="DJ_DATABASE_PREFIX", - description="Prefix for database/schema names. " - "Not automatically applied; use dj.config.database.database_prefix when creating schemas.", + description="Deprecated. Use database.name instead.", ) create_tables: bool = Field( default=True, @@ -223,6 +223,20 @@ def set_default_port_from_backend(self) -> "DatabaseSettings": self.port = 5432 if self.backend == "postgresql" else 3306 return self + @model_validator(mode="after") + def warn_database_prefix_deprecated(self) -> "DatabaseSettings": + """Emit deprecation warning when database_prefix is set.""" + if self.database_prefix: + import warnings + + warnings.warn( + "database_prefix is deprecated and will be removed in DataJoint 2.3. " + "Use database.name to select a PostgreSQL database instead.", + DeprecationWarning, + stacklevel=2, + ) + return self + class ConnectionSettings(BaseSettings): """Connection behavior settings.""" diff --git a/src/datajoint/version.py b/src/datajoint/version.py index 1fd91a092..0b0d3f4e9 100644 --- a/src/datajoint/version.py +++ b/src/datajoint/version.py @@ -1,4 +1,4 @@ # version bump auto managed by Github Actions: # label_prs.yaml(prep), release.yaml(bump), post_release.yaml(edit) # manually set this version will be eventually overwritten by the above actions -__version__ = "2.2.1" +__version__ = "2.2.0" diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 58e415cda..49dc9f1e1 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -750,55 +750,84 @@ def test_similar_prefix_names_allowed(self): dj.config.stores.update(original_stores) -class TestDbnameConfiguration: - """Test database.dbname configuration.""" +class TestDatabaseNameConfiguration: + """Test database.name configuration.""" - def test_dbname_default_is_none(self): - """Dbname defaults to None when not configured.""" + def test_database_name_default_is_none(self): + """Database name defaults to None when not configured.""" from datajoint.settings import DatabaseSettings s = DatabaseSettings() - assert s.dbname is None + assert s.name is None - def test_dbname_env_var(self, monkeypatch): - """DJ_DBNAME environment variable sets dbname.""" + def test_database_name_env_var(self, monkeypatch): + """DJ_DATABASE_NAME environment variable sets database name.""" from datajoint.settings import DatabaseSettings - monkeypatch.setenv("DJ_DBNAME", "my_database") + monkeypatch.setenv("DJ_DATABASE_NAME", "my_database") s = DatabaseSettings() - assert s.dbname == "my_database" + assert s.name == "my_database" - def test_dbname_from_config_file(self, tmp_path, monkeypatch): - """Load dbname from config file.""" + def test_database_name_from_config_file(self, tmp_path, monkeypatch): + """Load database name from config file.""" import json from datajoint.settings import Config config_file = tmp_path / "test_config.json" - config_file.write_text(json.dumps({"database": {"dbname": "custom_db", "host": "localhost"}})) + config_file.write_text(json.dumps({"database": {"name": "custom_db", "host": "localhost"}})) - monkeypatch.delenv("DJ_DBNAME", raising=False) + monkeypatch.delenv("DJ_DATABASE_NAME", raising=False) monkeypatch.delenv("DJ_HOST", raising=False) cfg = Config() cfg.load(config_file) - assert cfg.database.dbname == "custom_db" + assert cfg.database.name == "custom_db" - def test_dbname_dict_access(self): - """Dict-style access reads and writes dbname.""" - original = dj.config.database.dbname + def test_database_name_dict_access(self): + """Dict-style access reads and writes database name.""" + original = dj.config.database.name try: - dj.config.database.dbname = "test_db" - assert dj.config["database.dbname"] == "test_db" + dj.config.database.name = "test_db" + assert dj.config["database.name"] == "test_db" finally: - dj.config.database.dbname = original - - def test_dbname_override_context_manager(self): - """Override context manager temporarily sets dbname.""" - original = dj.config.database.dbname - with dj.config.override(database__dbname="override_db"): - assert dj.config.database.dbname == "override_db" - assert dj.config.database.dbname == original + dj.config.database.name = original + + def test_database_name_override_context_manager(self): + """Override context manager temporarily sets database name.""" + original = dj.config.database.name + with dj.config.override(database__name="override_db"): + assert dj.config.database.name == "override_db" + assert dj.config.database.name == original + + def test_database_prefix_deprecation_warning(self, monkeypatch): + """Non-empty database_prefix emits DeprecationWarning.""" + import warnings + + from datajoint.settings import DatabaseSettings + + monkeypatch.setenv("DJ_DATABASE_PREFIX", "test_") + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + DatabaseSettings() + deprecation_warnings = [ + x for x in w if issubclass(x.category, DeprecationWarning) and "database_prefix" in str(x.message) + ] + assert len(deprecation_warnings) >= 1 + + def test_database_prefix_empty_no_warning(self): + """Empty database_prefix does not emit DeprecationWarning.""" + import warnings + + from datajoint.settings import DatabaseSettings + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + DatabaseSettings() + deprecation_warnings = [ + x for x in w if issubclass(x.category, DeprecationWarning) and "database_prefix" in str(x.message) + ] + assert len(deprecation_warnings) == 0 class TestBackendConfiguration: From 8e3d39bc6b7f783ad98a1f39f455cb099ece9733 Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Tue, 7 Apr 2026 09:59:22 -0400 Subject: [PATCH 5/7] style: fix line length in Connection.__repr__ Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/connection.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/datajoint/connection.py b/src/datajoint/connection.py index 3c3257ae9..160ae8449 100644 --- a/src/datajoint/connection.py +++ b/src/datajoint/connection.py @@ -234,9 +234,12 @@ def __eq__(self, other): def __repr__(self): connected = "connected" if self.is_connected else "disconnected" + user = self.conn_info["user"] + host = self.conn_info["host"] + port = self.conn_info["port"] db = self.conn_info.get("database_name") db_str = f"/{db}" if db else "" - return f"DataJoint connection ({connected}) {self.conn_info['user']}@{self.conn_info['host']}:{self.conn_info['port']}{db_str}" + return f"DataJoint connection ({connected}) {user}@{host}:{port}{db_str}" def _build_connect_kwargs(self, use_tls=None): """Build kwargs dict for adapter.connect().""" From e24605de928ae812d50a7a28fb1c0f6c0d50b043 Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Wed, 8 Apr 2026 10:02:46 -0400 Subject: [PATCH 6/7] refactor: move database_prefix deprecation warning to Schema.activate() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the DeprecationWarning from Pydantic model validator (fires at config load time with unhelpful stacklevel) to Schema.activate() where database_prefix is consumed. The warning now points to the user's dj.Schema(...) call — exactly where they need to make a change. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/schemas.py | 7 +++++++ src/datajoint/settings.py | 13 ------------- tests/unit/test_settings.py | 17 +---------------- 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/datajoint/schemas.py b/src/datajoint/schemas.py index aad69102b..ff1b0e234 100644 --- a/src/datajoint/schemas.py +++ b/src/datajoint/schemas.py @@ -172,6 +172,13 @@ def activate( self.connection = connection if self.connection is None: self.connection = _get_singleton_connection() + if self.connection._config.get("database.database_prefix"): + warnings.warn( + "database_prefix is deprecated and will be removed in DataJoint 2.3. " + "Use database.name to select a PostgreSQL database instead.", + DeprecationWarning, + stacklevel=2, + ) self.database = schema_name if create_schema is not None: self.create_schema = create_schema diff --git a/src/datajoint/settings.py b/src/datajoint/settings.py index 0c61cd199..f30f2f710 100644 --- a/src/datajoint/settings.py +++ b/src/datajoint/settings.py @@ -223,19 +223,6 @@ def set_default_port_from_backend(self) -> "DatabaseSettings": self.port = 5432 if self.backend == "postgresql" else 3306 return self - @model_validator(mode="after") - def warn_database_prefix_deprecated(self) -> "DatabaseSettings": - """Emit deprecation warning when database_prefix is set.""" - if self.database_prefix: - import warnings - - warnings.warn( - "database_prefix is deprecated and will be removed in DataJoint 2.3. " - "Use database.name to select a PostgreSQL database instead.", - DeprecationWarning, - stacklevel=2, - ) - return self class ConnectionSettings(BaseSettings): diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py index 49dc9f1e1..83bb1d67c 100644 --- a/tests/unit/test_settings.py +++ b/tests/unit/test_settings.py @@ -800,23 +800,8 @@ def test_database_name_override_context_manager(self): assert dj.config.database.name == "override_db" assert dj.config.database.name == original - def test_database_prefix_deprecation_warning(self, monkeypatch): - """Non-empty database_prefix emits DeprecationWarning.""" - import warnings - - from datajoint.settings import DatabaseSettings - - monkeypatch.setenv("DJ_DATABASE_PREFIX", "test_") - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - DatabaseSettings() - deprecation_warnings = [ - x for x in w if issubclass(x.category, DeprecationWarning) and "database_prefix" in str(x.message) - ] - assert len(deprecation_warnings) >= 1 - def test_database_prefix_empty_no_warning(self): - """Empty database_prefix does not emit DeprecationWarning.""" + """Empty database_prefix does not emit DeprecationWarning at config load.""" import warnings from datajoint.settings import DatabaseSettings From eabcfb055c72d20541c457e8f7ae39d5745c32cc Mon Sep 17 00:00:00 2001 From: Kushal Bakshi Date: Wed, 8 Apr 2026 11:00:44 -0400 Subject: [PATCH 7/7] style: remove extra blank line in settings.py Co-Authored-By: Claude Opus 4.6 (1M context) --- src/datajoint/settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/datajoint/settings.py b/src/datajoint/settings.py index f30f2f710..f5881793f 100644 --- a/src/datajoint/settings.py +++ b/src/datajoint/settings.py @@ -224,7 +224,6 @@ def set_default_port_from_backend(self) -> "DatabaseSettings": return self - class ConnectionSettings(BaseSettings): """Connection behavior settings."""