Skip to content

Commit 72ec0b4

Browse files
committed
wip 2
1 parent 73d89c6 commit 72ec0b4

41 files changed

Lines changed: 784 additions & 307 deletions

File tree

Some content is hidden

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

pyproject.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ members = ["python/loris_*"]
5656
loris_bids_reader = { workspace = true }
5757
loris_dicom_importer = { workspace = true }
5858
loris_ephys_chunker = { workspace = true }
59+
loris_ephys_server = { workspace = true }
60+
loris_server = { workspace = true }
5961
loris_utils = { workspace = true }
6062

6163
[tool.ruff]
@@ -65,8 +67,8 @@ line-length = 120
6567
preview = true
6668

6769
[tool.ruff.lint]
68-
ignore = ["E202", "E203", "E221", "E241", "E251", "E272"]
69-
select = ["E", "EXE", "F", "I", "N", "RUF", "UP", "W"]
70+
ignore = ["E202", "E203", "E221", "E241", "E251", "E272", "FAST003"]
71+
select = ["E", "EXE", "F", "FAST", "I", "N", "RUF", "UP", "W"]
7072

7173
[tool.ruff.lint.pycodestyle]
7274
max-doc-length = 100
@@ -91,11 +93,14 @@ include = [
9193
"python/lib/get_session_info.py",
9294
"python/lib/logging.py",
9395
"python/lib/make_env.py",
96+
"python/lib/user.py",
9497
"python/scripts/import_bids_dataset.py",
9598
"python/loris_bids_reader",
9699
"python/loris_dicom_importer",
97100
"python/loris_ephys_chunker",
101+
"python/loris_ephys_server",
98102
"python/loris_utils",
103+
"python/loris_server",
99104
]
100105
exclude = [
101106
"python/loris_ephys_chunker/src/loris_ephys_chunker/protocol_buffers",

python/lib/config.py

Lines changed: 40 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
from lib.logging import log_error_exit
88

99

10+
def get_jwt_secret_key_config(env: Env) -> str:
11+
"""
12+
Get the LORIS JWT secret key from the in-database configuration.
13+
"""
14+
15+
return _get_config_value(env, 'JWTKey')
16+
17+
1018
def get_patient_id_dicom_header_config(env: Env) -> Literal['PatientID', 'PatientName']:
1119
"""
1220
Get the DICOM header in which to look for the patient ID from the in-database configuration, or
@@ -34,22 +42,7 @@ def get_data_dir_path_config(env: Env) -> Path:
3442
"""
3543

3644
data_dir_path = Path(_get_config_value(env, 'dataDirBasepath'))
37-
38-
if not data_dir_path.is_dir():
39-
log_error_exit(
40-
env,
41-
(
42-
f"The LORIS base data directory path configuration value '{data_dir_path}' does not refer to an"
43-
" existing directory."
44-
)
45-
)
46-
47-
if not os.access(data_dir_path, os.R_OK) or not os.access(data_dir_path, os.W_OK):
48-
log_error_exit(
49-
env,
50-
f"Missing read or write permission on the LORIS base data directory '{data_dir_path}'.",
51-
)
52-
45+
check_loris_directory(env, data_dir_path, "data")
5346
return data_dir_path
5447

5548

@@ -60,22 +53,7 @@ def get_dicom_archive_dir_path_config(env: Env) -> Path:
6053
"""
6154

6255
dicom_archive_dir_path = Path(_get_config_value(env, 'tarchiveLibraryDir'))
63-
64-
if not dicom_archive_dir_path.is_dir():
65-
log_error_exit(
66-
env,
67-
(
68-
f"The LORIS DICOM archive directory path configuration value '{dicom_archive_dir_path}' does not refer"
69-
" to an existing directory."
70-
),
71-
)
72-
73-
if not os.access(dicom_archive_dir_path, os.R_OK) or not os.access(dicom_archive_dir_path, os.W_OK):
74-
log_error_exit(
75-
env,
76-
f"Missing read or write permission on the LORIS DICOM archive directory '{dicom_archive_dir_path}'.",
77-
)
78-
56+
check_loris_directory(env, dicom_archive_dir_path, "DICOM archive")
7957
return dicom_archive_dir_path
8058

8159

@@ -87,75 +65,66 @@ def get_default_bids_visit_label_config(env: Env) -> str | None:
8765
return _try_get_config_value(env, 'default_bids_vl')
8866

8967

90-
def get_eeg_viz_enabled_config(env: Env) -> bool:
68+
def get_ephys_visualization_enabled_config(env: Env) -> bool:
9169
"""
92-
Get whether the EEG visualization is enabled from the in-database configuration.
70+
Get whether the electrophysiology visualization is enabled from the in-database configuration.
9371
"""
9472

9573
eeg_viz_enabled = _try_get_config_value(env, 'useEEGBrowserVisualizationComponents')
9674
return eeg_viz_enabled == 'true' or eeg_viz_enabled == '1'
9775

9876

99-
def get_eeg_chunks_dir_path_config(env: Env) -> Path | None:
77+
def get_ephys_chunks_dir_path_config(env: Env) -> Path | None:
10078
"""
101-
Get the EEG chunks directory path configuration value from the in-database configuration.
79+
Get the electrophysiology chunks directory path configuration value from the in-database
80+
configuration.
10281
"""
10382

104-
eeg_chunks_path = _try_get_config_value(env, 'EEGChunksPath')
105-
if eeg_chunks_path is None:
83+
ephys_chunks_path = _try_get_config_value(env, 'EEGChunksPath')
84+
if ephys_chunks_path is None:
10685
return None
10786

108-
eeg_chunks_path = Path(eeg_chunks_path)
87+
ephys_chunks_path = Path(ephys_chunks_path)
88+
check_loris_directory(env, ephys_chunks_path, "electrophysiology chunks")
89+
return ephys_chunks_path
10990

110-
if not eeg_chunks_path.is_dir():
111-
log_error_exit(
112-
env,
113-
(
114-
f"The configuration value for the LORIS EEG chunks directory path '{eeg_chunks_path}' does not refer to"
115-
" an existing directory."
116-
),
117-
)
11891

119-
if not os.access(eeg_chunks_path, os.R_OK) or not os.access(eeg_chunks_path, os.W_OK):
120-
log_error_exit(
121-
env,
122-
f"Missing read or write permission on the LORIS EEG chunks directory '{eeg_chunks_path}'.",
123-
)
124-
125-
return eeg_chunks_path
126-
127-
128-
def get_eeg_pre_package_download_dir_path_config(env: Env) -> Path | None:
92+
def get_ephys_archive_dir_path_config(env: Env) -> Path | None:
12993
"""
130-
Get the EEG pre-packaged download path configuration value from the in-database configuration.
94+
Get the electrophysiology archive directory path configuration value from the in-database
95+
configuration.
13196
"""
13297

133-
eeg_pre_package_path = _try_get_config_value(env, 'prePackagedDownloadPath')
134-
if eeg_pre_package_path is None:
98+
ephys_archive_dir_path = _try_get_config_value(env, 'prePackagedDownloadPath')
99+
if ephys_archive_dir_path is None:
135100
return None
136101

137-
eeg_pre_package_path = Path(eeg_pre_package_path)
102+
ephys_archive_dir_path = Path(ephys_archive_dir_path)
103+
check_loris_directory(env, ephys_archive_dir_path, "electrophysiology archive")
104+
return ephys_archive_dir_path
138105

139-
if not eeg_pre_package_path.is_dir():
106+
107+
def check_loris_directory(env: Env, dir_path: Path, display_name: str):
108+
"""
109+
Check that a LORIS directory exists and is readable and writable, or exit the program with an
110+
error otherwise.
111+
"""
112+
113+
if not dir_path.is_dir():
140114
log_error_exit(
141115
env,
142116
(
143-
"The configuration value for the LORIS EEG pre-packaged download directory path"
144-
f" '{eeg_pre_package_path}' does not refer to an existing directory."
117+
f"The LORIS {display_name} directory path configuration value '{dir_path}' does not refer to an"
118+
" existing directory."
145119
),
146120
)
147121

148-
if not os.access(eeg_pre_package_path, os.R_OK) or not os.access(eeg_pre_package_path, os.W_OK):
122+
if not os.access(dir_path, os.R_OK) or not os.access(dir_path, os.W_OK):
149123
log_error_exit(
150124
env,
151-
(
152-
"Missing read or write permission on the LORIS EEG pre-packaged download directory"
153-
f" '{eeg_pre_package_path}'."
154-
),
125+
f"Missing read or write permission on the {display_name} directory '{dir_path}'.",
155126
)
156127

157-
return eeg_pre_package_path
158-
159128

160129
def _get_config_value(env: Env, setting_name: str) -> str:
161130
"""

python/lib/db/models/user.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
from datetime import date
22

3-
from sqlalchemy.orm import Mapped, mapped_column
3+
from sqlalchemy.orm import Mapped, mapped_column, relationship
44

5+
import lib.db.models.project as db_project
6+
import lib.db.models.site as db_site
7+
import lib.db.models.user_project # type: ignore # noqa: F401
8+
import lib.db.models.user_site # type: ignore # noqa: F401
59
from lib.db.base import Base
610
from lib.db.decorators.int_bool import IntBool
711
from lib.db.decorators.y_n_bool import YNBool
@@ -11,7 +15,7 @@ class DbUser(Base):
1115
__tablename__ = 'users'
1216

1317
id : Mapped[int] = mapped_column('ID', primary_key=True)
14-
user_id : Mapped[str] = mapped_column('UserID')
18+
username : Mapped[str] = mapped_column('UserID')
1519
password : Mapped[str | None] = mapped_column('Password')
1620
real_name : Mapped[str | None] = mapped_column('Real_name')
1721
first_name : Mapped[str | None] = mapped_column('First_name')
@@ -41,3 +45,13 @@ class DbUser(Base):
4145
active_from : Mapped[date | None] = mapped_column('active_from')
4246
active_to : Mapped[date | None] = mapped_column('active_to')
4347
account_request_date : Mapped[date | None] = mapped_column('account_request_date')
48+
49+
projects: Mapped[list['db_project.DbProject']] = relationship('DbProject', secondary='user_project_rel')
50+
"""
51+
The projects to which this user belongs to.
52+
"""
53+
54+
sites: Mapped[list['db_site.DbSite']] = relationship('DbSite', secondary='user_psc_rel')
55+
"""
56+
The sites to which this user belongs to.
57+
"""
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from sqlalchemy import ForeignKey
2+
from sqlalchemy.orm import Mapped, mapped_column
3+
4+
from lib.db.base import Base
5+
6+
7+
class DbUserProject(Base):
8+
"""
9+
Relationship between users and projects.
10+
"""
11+
12+
__tablename__ = 'user_project_rel'
13+
14+
user_id : Mapped[int] = mapped_column('UserID', ForeignKey('users.ID'), primary_key=True)
15+
project_id : Mapped[int] = mapped_column('ProjectID', ForeignKey('Project.ProjectID'), primary_key=True)

python/lib/db/models/user_site.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from sqlalchemy import ForeignKey
2+
from sqlalchemy.orm import Mapped, mapped_column
3+
4+
from lib.db.base import Base
5+
6+
7+
class DbUserSite(Base):
8+
"""
9+
Relationship between users and sites.
10+
"""
11+
12+
__tablename__ = 'user_psc_rel'
13+
14+
user_id: Mapped[int] = mapped_column('UserID', ForeignKey('users.ID'), primary_key=True)
15+
site_id: Mapped[int] = mapped_column('CenterID', ForeignKey('psc.CenterID'), primary_key=True)

python/lib/db/queries/user.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from sqlalchemy import select
2+
from sqlalchemy.orm import Session as Database
3+
4+
from lib.db.models.user import DbUser
5+
6+
7+
def try_get_user_with_id(db: Database, user_id: int) -> DbUser | None:
8+
"""
9+
Get a user from the database using its ID, or return `None` if no user is found.
10+
"""
11+
12+
return db.execute(select(DbUser)
13+
.where(DbUser.id == user_id)
14+
).scalar_one_or_none()
15+
16+
17+
def try_get_user_with_username(db: Database, username: str) -> DbUser | None:
18+
"""
19+
Get a user from the database using its username, or return `None` if no user is found.
20+
"""
21+
22+
return db.execute(select(DbUser)
23+
.where(DbUser.username == username)
24+
).scalar_one_or_none()

python/lib/eeg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import lib.exitcode
1616
import lib.utilities as utilities
17-
from lib.config import get_eeg_viz_enabled_config
17+
from lib.config import get_ephys_visualization_enabled_config
1818
from lib.db.models.physio_file import DbPhysioFile
1919
from lib.db.models.session import DbSession
2020
from lib.db.queries.physio_file import try_get_physio_file_with_path
@@ -206,7 +206,7 @@ def register_data(self, derivatives=False, detect=True):
206206
import_physio_file_archive(self.env, eeg_file, files_to_archive)
207207

208208
# create data chunks for React visualization
209-
if get_eeg_viz_enabled_config(self.env):
209+
if get_ephys_visualization_enabled_config(self.env):
210210
create_physio_channels_chunks(self.env, eeg_file)
211211

212212
def fetch_and_insert_eeg_files(self, derivatives=False, detect=True):

python/lib/import_bids_dataset/archive.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from loris_utils.crypto import compute_file_blake2b_hash
55
from loris_utils.path import remove_path_extension
66

7-
from lib.config import get_data_dir_path_config, get_eeg_pre_package_download_dir_path_config
7+
from lib.config import get_data_dir_path_config, get_ephys_archive_dir_path_config
88
from lib.db.models.physio_event_archive import DbPhysioEventArchive
99
from lib.db.models.physio_file import DbPhysioFile
1010
from lib.db.models.physio_file_archive import DbPhysioFileArchive
@@ -70,9 +70,9 @@ def get_archive_path(env: Env, file_path: Path) -> Path:
7070
"""
7171

7272
archive_rel_path = remove_path_extension(file_path).with_suffix('.tgz')
73-
archives_dir_path = get_eeg_pre_package_download_dir_path_config(env)
74-
if archives_dir_path is not None:
73+
archive_dir_path = get_ephys_archive_dir_path_config(env)
74+
if archive_dir_path is not None:
7575
data_dir_path = get_data_dir_path_config(env)
76-
return (archives_dir_path / 'raw' / archive_rel_path.name).relative_to(data_dir_path)
76+
return (archive_dir_path / 'raw' / archive_rel_path.name).relative_to(data_dir_path)
7777
else:
7878
return archive_rel_path

python/lib/import_bids_dataset/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
from lib.import_bids_dataset.env import BidsImportEnv
2525
from lib.import_bids_dataset.events import get_root_events_metadata
26-
from lib.import_bids_dataset.meg import import_bids_meg_data_type
26+
from lib.import_bids_dataset.meg.ctf import import_bids_meg_data_type
2727
from lib.import_bids_dataset.mri import import_bids_mri_data_type
2828
from lib.import_bids_dataset.print import print_bids_import_summary
2929
from lib.logging import log, log_error_exit, log_warning
@@ -93,7 +93,7 @@ def import_bids_dataset(env: Env, args: Args, legacy_db: Database):
9393

9494
import_env = BidsImportEnv(
9595
data_dir_path = data_dir_path,
96-
loris_bids_path = loris_bids_path,
96+
loris_bids_path = loris_bids_path.relative_to(data_dir_path) if loris_bids_path is not None else None,
9797
total_files_count = acquisitions_count,
9898
)
9999

0 commit comments

Comments
 (0)