diff --git a/README.md b/README.md index b4eb29d..cacc4a0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +> ## ⚠️ This is the `datajoint-2.x` branch of a fork +> +> Unofficial fork of [`datajoint/element-session`](https://github.com/datajoint/element-session), +> ported to **DataJoint 2.x**. Not affiliated with DataJoint. See +> [why this branch exists](#why-this-branch-exists) at the bottom. + [![PyPI version](https://badge.fury.io/py/element-session.svg)](http://badge.fury.io/py/element-session) # DataJoint Element - Session @@ -11,3 +17,75 @@ Installation and usage instructions can be found at the [Element documentation](https://docs.datajoint.com/elements/element-session/). ![element-session diagram](https://raw.githubusercontent.com/datajoint/element-session/main/images/session_diagram.svg) + + +--- + +## Why this branch exists + +Upstream `element-session` was last committed on 2025-05-20 and targets DataJoint +`>=0.13`. DataJoint **2.0.0** shipped 2026-02-03 as a self-described complete +rewrite; the current release is 2.3.2. Because the element's dependency pin +admits 2.x, `pip install element-session` on a fresh environment today resolves +DataJoint 2.x and produces an installation in which the element cannot be +imported. No element had a 2.x branch and there was no open migration issue or +PR anywhere in the `datajoint` org when this fork was created. + +**This branch is the complete migration, not a backward-compatible subset.** +An earlier version of this fork split the work into a `compat-fixes` branch +(changes that also work on 0.14.x) and this `datajoint-2.x` branch (adding the +2.x-only changes on top). The upstream maintainer's guidance, after reviewing +that split, was not to ship it that way: a schema is either 2.x or pre-2.x, +DataJoint no longer supports pre-2.x, and landing the backward-compatible +subset alone is actively harmful here -- it clears the import error while +leaving any `longblob`/`attach` attributes in place, which silently corrupts +data instead of loudly failing to import. See +[the migration guide](https://docs.datajoint.com/how-to/migrate-to-v20/) for +the authoritative type mapping and phase structure this follows (this is +Phase I: code only, against empty schemas, no production data touched). + +Both `compat-fixes` and `datajoint-2.x` now point at the same commit and carry +the same content, kept as two names only so nothing that already referenced +either one breaks. + +### Full scope of this branch + +| change | count | +|---|---| +| `dj.schema` -> `dj.Schema` | 2 | +| `longblob` -> `` | 2 | +| `int` -> `int32` | 2 | +| `requirements.txt`: `datajoint>=0.13.0` -> `datajoint>=2.3` | — | + +### The `longblob` change is not cosmetic + +Under DataJoint 2.x a `longblob` attribute is a **raw native column**. It +declares without error and the insert succeeds, but a numpy array written to +it comes back as `bytes`: + +``` +longblob (as this element declared it) -> returned type: bytes round-trip OK: False + (the 2.x codec) -> returned type: ndarray round-trip OK: True +``` + +Nothing raises. There is no warning beyond a generic "consider a core +DataJoint type" notice at declaration time that says nothing about data loss. +Traced upstream in datajoint/datajoint-python#1527: PyMySQL has no encoder for +`np.ndarray` and silently falls back to `str(value)`; the same declaration on +PostgreSQL raises instead of corrupting. See +[datajoint/element-session#43](https://github.com/datajoint/element-session/issues/43) +for the full round-trip evidence. + +### Using it + +``` +pip install git+https://github.com/akshay-jaggi/element-session.git@datajoint-2.x +``` + +Pin the commit rather than the branch name if you need reproducibility. Every +change was applied mechanically (regex over each table's `definition` string, +scoped so it cannot touch a docstring or a function signature) and then +reviewed line by line; no element behaviour was altered, only attribute-type +spellings that 2.x renamed, replaced, or requires as core types. + +Open PR: [datajoint/element-session#42](https://github.com/datajoint/element-session/pull/42). diff --git a/element_session/session_with_datetime.py b/element_session/session_with_datetime.py index f9e4137..80d3c0b 100644 --- a/element_session/session_with_datetime.py +++ b/element_session/session_with_datetime.py @@ -2,7 +2,7 @@ import importlib import inspect -schema = dj.schema() +schema = dj.Schema() _linking_module = None @@ -62,7 +62,7 @@ class Session(dj.Manual): -> Subject session_datetime: datetime --- - session_id=null: int + session_id=null: int32 """ class Attribute(dj.Part): @@ -80,7 +80,7 @@ class Attribute(dj.Part): attribute_name: varchar(32) --- attribute_value='': varchar(2000) - attribute_blob=null: longblob + attribute_blob=null: """ diff --git a/element_session/session_with_id.py b/element_session/session_with_id.py index dfc5330..f437312 100644 --- a/element_session/session_with_id.py +++ b/element_session/session_with_id.py @@ -1,146 +1,146 @@ -import datajoint as dj -import importlib -import inspect - -schema = dj.schema() -_linking_module = None - - -def activate( - schema_name, - create_schema: bool = True, - create_tables: bool = True, - linking_module: str = None, -): - """Activate this schema. - - Args: - schema_name (str): schema name on the database server - create_schema (bool): when True (default), create schema in the database if it - does not yet exist. - create_tables (str): when True (default), create schema tables in the database - if they do not yet exist. - linking_module (str): a module (or name) containing the required dependencies. - - Dependencies: - Upstream tables: - Subject: the subject with which an experimental session is associated - Project: the project with which experimental sessions are associated - Experimenter: the experimenter(s) participating in a given session - To supply from element-lab add `Experimenter = lab.User` - to your `workflow/pipeline.py` before `session.activate()` - """ - if isinstance(linking_module, str): - linking_module = importlib.import_module(linking_module) - assert inspect.ismodule( - linking_module - ), "The argument 'dependency' must be a module's name or a module" - - global _linking_module - _linking_module = linking_module - - schema.activate( - schema_name, - create_schema=create_schema, - create_tables=create_tables, - add_objects=linking_module.__dict__, - ) - - -@schema -class Session(dj.Manual): - """Central Session table - - Attributes: - Subject (foreign key): Key for Subject table - session_id (int): Unique numeric session ID - session_datetime (datetime, optional): date and time of the session - """ - - definition = """ - -> Subject - session_id: int - --- - session_datetime=null: datetime - """ - - class Attribute(dj.Part): - """Additional feature of interest for a session. - - Attributes: - Session (foreign key): Key for Session table - attribute_name ( varchar(32) ): Name shared across instances of attribute - attribute_value ( varchar(2000), optional ): Attribute value - attribute_blob (longblob, optional): Optional data store field - """ - - definition = """ - -> master - attribute_name: varchar(32) - --- - attribute_value='': varchar(2000) - attribute_blob=null: longblob - """ - - -@schema -class SessionDirectory(dj.Manual): - """Relative path information for files related to a given session. - - Attributes: - Session (foreign key): Key for Session table - session_dir ( varchar(256) ): Path to the data directory for a session - """ - - definition = """ - -> Session - --- - session_dir: varchar(256) # Path to the data directory for a session - """ - - -@schema -class SessionExperimenter(dj.Manual): - """Individual(s) conducting the session - - Attributes: - Session (foreign key): Key for Session table - Experimenter (foreign key): Key for Experimenter table - """ - - definition = """ - # Individual(s) conducting the session - -> Session - -> Experimenter - """ - - -@schema -class SessionNote(dj.Manual): - """Additional notes related to a given session - - Attributes: - Session (foreign key): Key for Session table - session_note ( varchar(1024) ): : Additional notes - """ - - definition = """ - -> Session - --- - session_note: varchar(1024) - """ - - -@schema -class ProjectSession(dj.Manual): - """Table linking upstream Projects with Session - - Attributes: - Project (foreign key): Key for Project table - Session (foreign key): Key for Session table - """ - - definition = """ - -> Project - -> Session - """ +import datajoint as dj +import importlib +import inspect + +schema = dj.Schema() +_linking_module = None + + +def activate( + schema_name, + create_schema: bool = True, + create_tables: bool = True, + linking_module: str = None, +): + """Activate this schema. + + Args: + schema_name (str): schema name on the database server + create_schema (bool): when True (default), create schema in the database if it + does not yet exist. + create_tables (str): when True (default), create schema tables in the database + if they do not yet exist. + linking_module (str): a module (or name) containing the required dependencies. + + Dependencies: + Upstream tables: + Subject: the subject with which an experimental session is associated + Project: the project with which experimental sessions are associated + Experimenter: the experimenter(s) participating in a given session + To supply from element-lab add `Experimenter = lab.User` + to your `workflow/pipeline.py` before `session.activate()` + """ + if isinstance(linking_module, str): + linking_module = importlib.import_module(linking_module) + assert inspect.ismodule( + linking_module + ), "The argument 'dependency' must be a module's name or a module" + + global _linking_module + _linking_module = linking_module + + schema.activate( + schema_name, + create_schema=create_schema, + create_tables=create_tables, + add_objects=linking_module.__dict__, + ) + + +@schema +class Session(dj.Manual): + """Central Session table + + Attributes: + Subject (foreign key): Key for Subject table + session_id (int): Unique numeric session ID + session_datetime (datetime, optional): date and time of the session + """ + + definition = """ + -> Subject + session_id: int32 + --- + session_datetime=null: datetime + """ + + class Attribute(dj.Part): + """Additional feature of interest for a session. + + Attributes: + Session (foreign key): Key for Session table + attribute_name ( varchar(32) ): Name shared across instances of attribute + attribute_value ( varchar(2000), optional ): Attribute value + attribute_blob (longblob, optional): Optional data store field + """ + + definition = """ + -> master + attribute_name: varchar(32) + --- + attribute_value='': varchar(2000) + attribute_blob=null: + """ + + +@schema +class SessionDirectory(dj.Manual): + """Relative path information for files related to a given session. + + Attributes: + Session (foreign key): Key for Session table + session_dir ( varchar(256) ): Path to the data directory for a session + """ + + definition = """ + -> Session + --- + session_dir: varchar(256) # Path to the data directory for a session + """ + + +@schema +class SessionExperimenter(dj.Manual): + """Individual(s) conducting the session + + Attributes: + Session (foreign key): Key for Session table + Experimenter (foreign key): Key for Experimenter table + """ + + definition = """ + # Individual(s) conducting the session + -> Session + -> Experimenter + """ + + +@schema +class SessionNote(dj.Manual): + """Additional notes related to a given session + + Attributes: + Session (foreign key): Key for Session table + session_note ( varchar(1024) ): : Additional notes + """ + + definition = """ + -> Session + --- + session_note: varchar(1024) + """ + + +@schema +class ProjectSession(dj.Manual): + """Table linking upstream Projects with Session + + Attributes: + Project (foreign key): Key for Project table + Session (foreign key): Key for Session table + """ + + definition = """ + -> Project + -> Session + """ diff --git a/requirements.txt b/requirements.txt index fc7a8e6..f971962 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -datajoint>=0.13.0 +datajoint>=2.3