diff --git a/src/datajoint/builtin_codecs/attach.py b/src/datajoint/builtin_codecs/attach.py index aa10f2424..9aff7bbde 100644 --- a/src/datajoint/builtin_codecs/attach.py +++ b/src/datajoint/builtin_codecs/attach.py @@ -106,7 +106,8 @@ def decode(self, stored: bytes, *, key: dict | None = None) -> str: # Write to download path config = (key or {}).get("_config") if config is None: - from ..settings import config + from ..settings import config # type: ignore[assignment] + assert config is not None download_path = Path(config.get("download_path", ".")) download_path.mkdir(parents=True, exist_ok=True) local_path = download_path / filename diff --git a/src/datajoint/builtin_codecs/filepath.py b/src/datajoint/builtin_codecs/filepath.py index a0400499b..8a44287f6 100644 --- a/src/datajoint/builtin_codecs/filepath.py +++ b/src/datajoint/builtin_codecs/filepath.py @@ -102,7 +102,8 @@ def encode(self, value: Any, *, key: dict | None = None, store_name: str | None config = (key or {}).get("_config") if config is None: - from ..settings import config + from ..settings import config # type: ignore[assignment] + assert config is not None path = str(value) diff --git a/src/datajoint/declare.py b/src/datajoint/declare.py index 6af24ae55..4edb0c22f 100644 --- a/src/datajoint/declare.py +++ b/src/datajoint/declare.py @@ -370,7 +370,7 @@ def prepare_declare( adapter, fk_attribute_map, ) - elif re.match(r"^(unique\s+)?index\s*.*$", line, re.I): # index + elif re.match(r"^(unique\s+)?index\s*\(.*\)$", line, re.I): # index compile_index(line, index_sql, adapter) else: name, sql, store, comment = compile_attribute(line, in_key, foreign_key_sql, context, adapter) diff --git a/src/datajoint/hash_registry.py b/src/datajoint/hash_registry.py index 331c836cd..d33c916ba 100644 --- a/src/datajoint/hash_registry.py +++ b/src/datajoint/hash_registry.py @@ -32,14 +32,19 @@ datajoint.gc : Garbage collection for orphaned storage items. """ +from __future__ import annotations + import base64 import hashlib import logging -from typing import Any +from typing import TYPE_CHECKING, Any from .errors import DataJointError from .storage import StorageBackend +if TYPE_CHECKING: + from .settings import Config + logger = logging.getLogger(__name__.split(".")[0]) @@ -130,7 +135,7 @@ def build_hash_path( return f"_hash/{schema_name}/{content_hash}" -def get_store_backend(store_name: str | None = None, config=None) -> StorageBackend: +def get_store_backend(store_name: str | None = None, config: Config | None = None) -> StorageBackend: """ Get a StorageBackend for hash-addressed storage. @@ -147,13 +152,14 @@ def get_store_backend(store_name: str | None = None, config=None) -> StorageBack StorageBackend instance. """ if config is None: - from .settings import config + from .settings import config # type: ignore[assignment] + assert config is not None # get_store_spec handles None by using stores.default spec = config.get_store_spec(store_name) return StorageBackend(spec) -def get_store_subfolding(store_name: str | None = None, config=None) -> tuple[int, ...] | None: +def get_store_subfolding(store_name: str | None = None, config: Config | None = None) -> tuple[int, ...] | None: """ Get the subfolding configuration for a store. @@ -170,7 +176,8 @@ def get_store_subfolding(store_name: str | None = None, config=None) -> tuple[in Subfolding pattern (e.g., (2, 2)) or None for flat storage. """ if config is None: - from .settings import config + from .settings import config # type: ignore[assignment] + assert config is not None spec = config.get_store_spec(store_name) subfolding = spec.get("subfolding") if subfolding is not None: @@ -182,7 +189,7 @@ def put_hash( data: bytes, schema_name: str, store_name: str | None = None, - config=None, + config: Config | None = None, ) -> dict[str, Any]: """ Store content using hash-addressed storage. @@ -231,7 +238,7 @@ def put_hash( } -def get_hash(metadata: dict[str, Any], config=None) -> bytes: +def get_hash(metadata: dict[str, Any], config: Config | None = None) -> bytes: """ Retrieve content using stored metadata. @@ -275,7 +282,7 @@ def get_hash(metadata: dict[str, Any], config=None) -> bytes: def delete_path( path: str, store_name: str | None = None, - config=None, + config: Config | None = None, ) -> bool: """ Delete content at the specified path from storage. diff --git a/tests/integration/test_declare.py b/tests/integration/test_declare.py index 2379f1a9e..19e711e96 100644 --- a/tests/integration/test_declare.py +++ b/tests/integration/test_declare.py @@ -339,14 +339,20 @@ class WithSuchALongPartNameThatItCrashesMySQL(dj.Part): schema_any(WhyWouldAnyoneCreateATableNameThisLong) -def test_regex_mismatch(schema_any): +def test_index_attribute_name(schema_any): + """Attributes named 'index' should not be misclassified as index declarations (#1411).""" + class IndexAttribute(dj.Manual): definition = """ - index: int + index : int + --- + index_value : float """ - with pytest.raises(dj.DataJointError): - schema_any(IndexAttribute) + schema_any(IndexAttribute) + assert "index" in IndexAttribute.heading.attributes + assert "index_value" in IndexAttribute.heading.attributes + IndexAttribute.drop() def test_table_name_with_underscores(schema_any):