ethoscopy 2.0.3__tar.gz → 2.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/CLAUDE.md +35 -0
- ethoscopy-2.0.4/Docker/.claude/settings.local.json +41 -0
- ethoscopy-2.0.4/Docker/README.md +227 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub.sqlite +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/PKG-INFO +1 -1
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/pyproject.toml +1 -1
- ethoscopy-2.0.4/scripts/README.md +121 -0
- ethoscopy-2.0.4/scripts/convert_databases.sh +51 -0
- ethoscopy-2.0.4/scripts/convert_wal_to_delete.py +240 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/load.py +64 -7
- ethoscopy-2.0.3/Docker/.claude/settings.local.json +0 -19
- ethoscopy-2.0.3/Docker/README.md +0 -85
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.claude/settings.local.json +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.codecov.yml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.github/workflows/ci.yml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.github/workflows/release.yml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.gitignore +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/.pre-commit-config.yaml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/.env +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/.env.keycloak +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/Dockerfile +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/config/jupyterhub.sqlite +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/config/jupyterhub_cookie_secret +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/docker-compose.yml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/install_r_packages.r +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub-proxy.pid +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub_config.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/Docker/jupyterhub_data/jupyterhub_cookie_secret +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/LICENSE +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/QWEN.md +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/README.md +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/TESTING.md +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/_config.yml +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/pytest.ini +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/run_tests.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/setup.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/__init__.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/analyse.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_HMM_class.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_class.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_core.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_draw.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_periodogram_class.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_plotly.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/behavpy_seaborn.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/metadata_db.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/__init__.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/circadian_bars.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/general_functions.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/get_HMM.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/get_tutorials.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/hmm_functions.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/periodogram_functions.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/src/ethoscopy/misc/validate_datetime.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/__init__.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/conftest.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/data/README.md +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/data/test_ethoscope.db +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_analyse.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_baseline_enhancements.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_behavpy.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_behavpy_core_simple.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_compatibility_classes.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_general_functions.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_get_tutorials.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load_comprehensive.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tests/test_load_optimizations.py +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/1_Overview_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/2_HMM_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/3_Circadian_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/4_Navigating_db_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/5_Ethoscopy_catch22_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/6_Ethoscopy_to_hctsa_tutorial.ipynb +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/ethoscope_db.csv +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/jones_et_al_metadata.csv +0 -0
- {ethoscopy-2.0.3 → ethoscopy-2.0.4}/tutorial_notebook/notebook_paper.ipynb +0 -0
|
@@ -45,6 +45,41 @@ pip install -e ".[dev]"
|
|
|
45
45
|
- Build: `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`
|
|
46
46
|
- Run: `docker compose up -d` (from Docker/ directory)
|
|
47
47
|
|
|
48
|
+
### Troubleshooting: Database "Malformed" Errors
|
|
49
|
+
|
|
50
|
+
**Problem**: Intermittent "database disk image is malformed" errors when loading ethoscope data in Docker
|
|
51
|
+
|
|
52
|
+
**Root Cause**: SQLite databases in WAL (Write-Ahead Logging) mode on read-only Docker mounts
|
|
53
|
+
|
|
54
|
+
**Solution Options**:
|
|
55
|
+
1. **Convert databases to DELETE mode** (recommended for immediate fix)
|
|
56
|
+
```bash
|
|
57
|
+
# Using the conversion script (from project root)
|
|
58
|
+
python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
|
|
59
|
+
|
|
60
|
+
# Or using bash wrapper
|
|
61
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
2. **Improved connection handling** (v2.0.4+)
|
|
65
|
+
- The `_connect_db()` function in `load.py` now detects WAL mode automatically
|
|
66
|
+
- Uses `mode=ro&nolock=1` URI parameters for WAL databases on read-only mounts
|
|
67
|
+
- Includes retry logic in `read_single_roi_optimized()` for resilience
|
|
68
|
+
|
|
69
|
+
**Key Implementation Details** (load.py):
|
|
70
|
+
- Lines 18-70: `_connect_db()` - Smart connection with WAL detection and appropriate parameters
|
|
71
|
+
- Lines 943-979: Retry logic in `read_single_roi_optimized()` - Handles transient errors with fresh connections
|
|
72
|
+
|
|
73
|
+
**Testing After Changes**:
|
|
74
|
+
```python
|
|
75
|
+
import ethoscopy as etho
|
|
76
|
+
metadata = etho.link_meta_index('metadata.csv', '/mnt/ethoscope_results')
|
|
77
|
+
data = etho.load_ethoscope(metadata, reference_hour=9.0, FUN=etho.sleep_annotation)
|
|
78
|
+
# Should load all ROIs without "malformed" errors
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**See Also**: `Docker/README.md` for detailed database preparation instructions
|
|
82
|
+
|
|
48
83
|
## Architecture and Code Structure
|
|
49
84
|
|
|
50
85
|
### Core Architecture
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(ls:*)",
|
|
5
|
+
"Bash(git checkout:*)",
|
|
6
|
+
"Bash(docker images:*)",
|
|
7
|
+
"Read(//tmp/**)",
|
|
8
|
+
"Bash(JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.1 docker compose build:*)",
|
|
9
|
+
"Bash(tee:*)",
|
|
10
|
+
"Bash(docker compose:*)",
|
|
11
|
+
"Bash(find:*)",
|
|
12
|
+
"Bash(sqlite3:*)",
|
|
13
|
+
"Bash(python3:*)",
|
|
14
|
+
"Bash(docker exec:*)",
|
|
15
|
+
"Bash(docker cp:*)",
|
|
16
|
+
"Bash(ip addr:*)",
|
|
17
|
+
"Bash(systemctl status:*)",
|
|
18
|
+
"Bash(docker ps:*)",
|
|
19
|
+
"Bash(wg show:*)",
|
|
20
|
+
"Bash(ip route:*)",
|
|
21
|
+
"Bash(ip rule:*)",
|
|
22
|
+
"Bash(docker inspect:*)",
|
|
23
|
+
"Bash(docker logs:*)",
|
|
24
|
+
"Bash(nc:*)",
|
|
25
|
+
"Bash(timeout 3 bash -c 'cat < /dev/tcp/192.168.254.10/8082')",
|
|
26
|
+
"Bash(docker network inspect:*)",
|
|
27
|
+
"Bash(ss:*)",
|
|
28
|
+
"Bash(host:*)",
|
|
29
|
+
"Bash(chmod:*)",
|
|
30
|
+
"Bash(tree:*)",
|
|
31
|
+
"Bash(source:*)",
|
|
32
|
+
"Bash(pip install:*)",
|
|
33
|
+
"Bash(git add:*)",
|
|
34
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix: resolve \"database disk image is malformed\" errors for WAL-mode databases\n\nAdd comprehensive solution for SQLite WAL-mode database errors on read-only Docker mounts:\n\n- Improve connection handling in load.py:\n - Detect WAL mode databases and use appropriate connection parameters\n - Add retry logic with fresh connections for transient errors\n - Use mode=ro&nolock=1 for WAL databases on read-only filesystems\n\n- Add database conversion scripts:\n - convert_wal_to_delete.py: Python script to convert databases from WAL to DELETE mode\n - convert_databases.sh: Bash wrapper for easy bulk conversion\n - scripts/README.md: Comprehensive documentation for database utilities\n\n- Update documentation:\n - Docker/README.md: Add \"Database Preparation for Docker\" section\n - CLAUDE.md: Add troubleshooting section for database errors\n\n- Bump version to 2.0.4\n\nThis resolves intermittent loading failures for ROIs when ethoscope databases\nin WAL mode are mounted read-only in Docker containers.\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
35
|
+
"Bash(git push:*)",
|
|
36
|
+
"Bash(git tag:*)",
|
|
37
|
+
"Bash(python -m build:*)"
|
|
38
|
+
],
|
|
39
|
+
"deny": []
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Ethoscopelab docker instance
|
|
2
|
+
|
|
3
|
+
Note: Most users will **not** need to recreate this image. These instructions are just provided as reference.
|
|
4
|
+
The ethoscopelab docker instance lives on dockerhub at the following address: [https://hub.docker.com/r/ggilestro/ethoscope-lab](https://hub.docker.com/r/ggilestro/ethoscope-lab) and this is what regular users should download and run. Follow instructions there and on the [ethoscopy manual](https://bookstack.lab.gilest.ro/books/ethoscopy/page/getting-started).
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Docker files that were used to create the ethoscope-lab docker instance
|
|
8
|
+
|
|
9
|
+
The files in this folder can be used to recreate the image as uploaded on dockerhub.
|
|
10
|
+
|
|
11
|
+
The command to use to recreate that image is `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`. This creates the image with the specified tag. For Docker Hub deployment, push the image: `docker push ggilestro/ethoscope-lab:1.0`. To also create a latest tag: `docker tag ggilestro/ethoscope-lab:1.0 ggilestro/ethoscope-lab:latest && docker push ggilestro/ethoscope-lab:latest`. You can verify your local images with `docker images | grep ethoscope-lab`.
|
|
12
|
+
|
|
13
|
+
After creation, the image can be run using the enclosed `docker-compose.yml` file, replacing values as fit.
|
|
14
|
+
|
|
15
|
+
## Add new users to the JupyterHub
|
|
16
|
+
|
|
17
|
+
To add new users to the JupyterHub instance:
|
|
18
|
+
|
|
19
|
+
### 1. Modify the jupyterhub_config.py file
|
|
20
|
+
|
|
21
|
+
Edit the `allowed_users` set on lines 14-19 to include your new usernames:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
c.Authenticator.allowed_users = {
|
|
25
|
+
'amadabhushi', 'ggilestro', 'mjoyce', 'lguo',
|
|
26
|
+
'labguest1', 'labguest2', 'labguest3', 'labguest4',
|
|
27
|
+
'labguest5', 'labguest6', 'labguest7', 'labguest8',
|
|
28
|
+
'ethoscopelab', 'newuser1', 'newuser2' # Add your new users here
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To make a user an admin, add them to the `admin_users` set on line 22:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
c.Authenticator.admin_users = {'ggilestro', 'newadmin'}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 2. Restart the Docker container
|
|
39
|
+
|
|
40
|
+
After modifying the config file:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
docker compose down
|
|
44
|
+
docker compose up -d
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Notes:**
|
|
48
|
+
- All users share the same password: `ethoscope` (line 11)
|
|
49
|
+
- The system uses DummyAuthenticator for simple shared-password authentication
|
|
50
|
+
- Each user gets their own home directory at `/home/{username}`
|
|
51
|
+
- Home directories are created automatically when users first log in
|
|
52
|
+
|
|
53
|
+
## Mounting Home Directories as Volumes
|
|
54
|
+
|
|
55
|
+
To persist user data and notebooks across container restarts, you should mount user home directories as Docker volumes. This is done by modifying the `docker-compose.yml` file.
|
|
56
|
+
|
|
57
|
+
### Benefits of mounting home directories:
|
|
58
|
+
|
|
59
|
+
1. **Data Persistence**: User notebooks, data files, and configurations survive container restarts and updates
|
|
60
|
+
2. **Backup and Recovery**: Easy to backup user data by copying the mounted directories
|
|
61
|
+
3. **Performance**: Direct access to host filesystem, avoiding container storage overhead
|
|
62
|
+
4. **Sharing**: Users can access their files from the host system if needed
|
|
63
|
+
|
|
64
|
+
### Example volume configuration:
|
|
65
|
+
|
|
66
|
+
Add volumes to your `docker-compose.yml`:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
services:
|
|
70
|
+
jupyterhub:
|
|
71
|
+
volumes:
|
|
72
|
+
- ./user_data:/home # Maps host ./user_data to container /home
|
|
73
|
+
- ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or for individual user directories:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
volumes:
|
|
80
|
+
- ./users/ggilestro:/home/ggilestro
|
|
81
|
+
- ./users/amadabhushi:/home/amadabhushi
|
|
82
|
+
- ./users/shared:/home/shared # Shared directory for all users
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This ensures all user work is preserved even when containers are recreated or updated.
|
|
86
|
+
|
|
87
|
+
## Database Preparation for Docker
|
|
88
|
+
|
|
89
|
+
When mounting ethoscope database files into Docker containers with read-only permissions (`:ro`), you may encounter "database disk image is malformed" errors. This occurs when SQLite databases are in WAL (Write-Ahead Logging) mode but mounted on read-only filesystems where WAL companion files (`.db-wal`, `.db-shm`) cannot be accessed.
|
|
90
|
+
|
|
91
|
+
### Understanding the Issue
|
|
92
|
+
|
|
93
|
+
**Why this happens:**
|
|
94
|
+
- Ethoscope databases may be created in **WAL mode** for better write performance
|
|
95
|
+
- WAL mode requires companion files (`.db-wal` and `.db-shm`) alongside the main `.db` file
|
|
96
|
+
- Docker read-only mounts (`:ro`) prevent SQLite from accessing these files properly
|
|
97
|
+
- This causes intermittent "database disk image is malformed" errors during data loading
|
|
98
|
+
|
|
99
|
+
**Symptoms:**
|
|
100
|
+
- Intermittent loading failures for specific ROIs
|
|
101
|
+
- Error message: "database disk image is malformed"
|
|
102
|
+
- Same ROI may succeed on some loads and fail on others
|
|
103
|
+
- More common with large databases (>1 GB)
|
|
104
|
+
|
|
105
|
+
### Solution: Convert Databases to DELETE Mode
|
|
106
|
+
|
|
107
|
+
Before mounting databases in Docker, convert them from WAL to DELETE mode:
|
|
108
|
+
|
|
109
|
+
#### Using the Python Script (Recommended)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# From the ethoscopy project root
|
|
113
|
+
python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --dry-run
|
|
114
|
+
|
|
115
|
+
# Convert all databases with detailed output
|
|
116
|
+
python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
|
|
117
|
+
|
|
118
|
+
# Force conversion even for recently modified files (use with caution)
|
|
119
|
+
python3 scripts/convert_wal_to_delete.py /mnt/ethoscope_data/results --force
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Using the Bash Wrapper
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# From the ethoscopy project root
|
|
126
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### Manual Conversion Using sqlite3
|
|
130
|
+
|
|
131
|
+
For individual databases:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
sqlite3 /path/to/database.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
For bulk conversion:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
find /mnt/ethoscope_data/results -name "*.db" -type f -exec sqlite3 {} "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;" \;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Verification
|
|
144
|
+
|
|
145
|
+
After conversion, verify the database is in DELETE mode:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
sqlite3 /path/to/database.db "PRAGMA journal_mode;"
|
|
149
|
+
# Should output: delete
|
|
150
|
+
|
|
151
|
+
sqlite3 /path/to/database.db "PRAGMA integrity_check;"
|
|
152
|
+
# Should output: ok
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Best Practices
|
|
156
|
+
|
|
157
|
+
1. **Run conversion BEFORE starting Docker containers**
|
|
158
|
+
```bash
|
|
159
|
+
# From the ethoscopy project root, convert databases first
|
|
160
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
161
|
+
|
|
162
|
+
# Then start containers
|
|
163
|
+
cd Docker && docker compose up -d
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
2. **Do NOT convert databases while ethoscopes are actively writing**
|
|
167
|
+
- The conversion script skips files modified in the last 24 hours by default
|
|
168
|
+
- Use `--force` to override this safety check if needed
|
|
169
|
+
|
|
170
|
+
3. **Test on a backup first**
|
|
171
|
+
```bash
|
|
172
|
+
# Create a test copy
|
|
173
|
+
cp /path/to/database.db /tmp/test.db
|
|
174
|
+
|
|
175
|
+
# Test conversion
|
|
176
|
+
sqlite3 /tmp/test.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
|
|
177
|
+
|
|
178
|
+
# Verify it works
|
|
179
|
+
sqlite3 /tmp/test.db "PRAGMA integrity_check;"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
4. **Consider converting at the ethoscope source** (upstream fix)
|
|
183
|
+
- Configure ethoscopes to use DELETE mode by default
|
|
184
|
+
- Prevents the need for post-processing
|
|
185
|
+
|
|
186
|
+
### Troubleshooting
|
|
187
|
+
|
|
188
|
+
**Q: I still get "malformed" errors after conversion**
|
|
189
|
+
|
|
190
|
+
A: The ethoscopy library now includes improved connection handling that should work with both WAL and DELETE mode databases. If you still encounter issues:
|
|
191
|
+
1. Verify the database was actually converted: `sqlite3 database.db "PRAGMA journal_mode;"`
|
|
192
|
+
2. Check database integrity: `sqlite3 database.db "PRAGMA integrity_check;"`
|
|
193
|
+
3. Ensure you're using ethoscopy version 2.0.4 or later
|
|
194
|
+
|
|
195
|
+
**Q: Can I convert databases while Docker is running?**
|
|
196
|
+
|
|
197
|
+
A: Yes, but you should restart the container after conversion:
|
|
198
|
+
```bash
|
|
199
|
+
# From the ethoscopy project root
|
|
200
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
201
|
+
cd Docker && docker compose restart ethoscope-lab
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Q: Will this affect data quality or analysis?**
|
|
205
|
+
|
|
206
|
+
A: No. The conversion only changes how SQLite manages the database internally. All data remains identical and analysis results are unaffected.
|
|
207
|
+
|
|
208
|
+
**Q: How do I check if a database is in WAL mode?**
|
|
209
|
+
|
|
210
|
+
A:
|
|
211
|
+
```bash
|
|
212
|
+
sqlite3 database.db "PRAGMA journal_mode;"
|
|
213
|
+
```
|
|
214
|
+
Output will be either `wal` or `delete` (or `truncate`, `persist`, `memory`).
|
|
215
|
+
|
|
216
|
+
### Emergency Quick Fix
|
|
217
|
+
|
|
218
|
+
If you need to work immediately and can't run the full conversion:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# SSH to host machine (not inside container)
|
|
222
|
+
# Convert just the failing database
|
|
223
|
+
sqlite3 /path/to/failing/database.db "PRAGMA wal_checkpoint(TRUNCATE); PRAGMA journal_mode=DELETE;"
|
|
224
|
+
|
|
225
|
+
# Restart container
|
|
226
|
+
docker compose restart ethoscope-lab
|
|
227
|
+
```
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ethoscopy"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.4"
|
|
4
4
|
description = "\"A python based toolkit to download and anlyse data from the Ethoscope hardware system.\""
|
|
5
5
|
authors = [{name = "Lblackhurst29",email = "lblackhurst29@gmail.com"}]
|
|
6
6
|
readme = "README.md"
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Ethoscopy Database Utility Scripts
|
|
2
|
+
|
|
3
|
+
This directory contains utility scripts for managing and maintaining ethoscope SQLite databases.
|
|
4
|
+
|
|
5
|
+
## Database Journal Mode Conversion
|
|
6
|
+
|
|
7
|
+
### convert_wal_to_delete.py
|
|
8
|
+
|
|
9
|
+
Converts SQLite databases from WAL (Write-Ahead Logging) mode to DELETE mode. This is essential when mounting databases read-only in Docker containers, as WAL mode requires write access to create companion `.db-wal` and `.db-shm` files.
|
|
10
|
+
|
|
11
|
+
**Usage:**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Dry run to preview what would be converted
|
|
15
|
+
python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --dry-run
|
|
16
|
+
|
|
17
|
+
# Convert all databases with detailed output
|
|
18
|
+
python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --verbose
|
|
19
|
+
|
|
20
|
+
# Force conversion even for recently modified databases (use with caution)
|
|
21
|
+
python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --force
|
|
22
|
+
|
|
23
|
+
# Specify custom file pattern
|
|
24
|
+
python3 scripts/convert_wal_to_delete.py /path/to/ethoscope_results --pattern "*.sqlite"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Features:**
|
|
28
|
+
- Recursively searches for all `.db` files
|
|
29
|
+
- Checks current journal mode before conversion
|
|
30
|
+
- Safety check: skips files modified within last 24 hours (unless `--force` used)
|
|
31
|
+
- Performs WAL checkpoint to merge uncommitted data
|
|
32
|
+
- Verifies database integrity after conversion
|
|
33
|
+
- Provides detailed progress and summary statistics
|
|
34
|
+
|
|
35
|
+
### convert_databases.sh
|
|
36
|
+
|
|
37
|
+
Bash wrapper script that provides a simple interface to the Python conversion script.
|
|
38
|
+
|
|
39
|
+
**Usage:**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Simple one-line conversion
|
|
43
|
+
./scripts/convert_databases.sh /path/to/ethoscope_results
|
|
44
|
+
|
|
45
|
+
# Pass additional flags to the Python script
|
|
46
|
+
./scripts/convert_databases.sh /path/to/ethoscope_results --verbose
|
|
47
|
+
./scripts/convert_databases.sh /path/to/ethoscope_results --dry-run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## When to Use These Scripts
|
|
51
|
+
|
|
52
|
+
### Problem Symptoms
|
|
53
|
+
- Intermittent "database disk image is malformed" errors
|
|
54
|
+
- Errors occur when loading ethoscope data in Docker with read-only mounts
|
|
55
|
+
- Same ROI sometimes succeeds and sometimes fails to load
|
|
56
|
+
- More common with large databases (>1 GB)
|
|
57
|
+
|
|
58
|
+
### Solution Workflow
|
|
59
|
+
|
|
60
|
+
1. **Before starting Docker containers:**
|
|
61
|
+
```bash
|
|
62
|
+
# Convert databases
|
|
63
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
64
|
+
|
|
65
|
+
# Start containers
|
|
66
|
+
cd Docker && docker compose up -d
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. **If databases are already mounted:**
|
|
70
|
+
```bash
|
|
71
|
+
# Convert databases
|
|
72
|
+
./scripts/convert_databases.sh /mnt/ethoscope_data/results
|
|
73
|
+
|
|
74
|
+
# Restart containers to use new connections
|
|
75
|
+
cd Docker && docker compose restart ethoscope-lab
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Safety Notes
|
|
79
|
+
|
|
80
|
+
- **Do NOT convert databases while ethoscopes are actively writing to them**
|
|
81
|
+
- The script automatically skips files modified within the last 24 hours
|
|
82
|
+
- Use `--force` flag to override this safety check if needed
|
|
83
|
+
- Always test on a backup or single database first
|
|
84
|
+
- Conversion does not modify data, only the internal journal mode
|
|
85
|
+
|
|
86
|
+
### Verification
|
|
87
|
+
|
|
88
|
+
Check if a database is in WAL mode:
|
|
89
|
+
```bash
|
|
90
|
+
sqlite3 database.db "PRAGMA journal_mode;"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Verify conversion was successful:
|
|
94
|
+
```bash
|
|
95
|
+
sqlite3 database.db "PRAGMA journal_mode;" # Should return: delete
|
|
96
|
+
sqlite3 database.db "PRAGMA integrity_check;" # Should return: ok
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Technical Background
|
|
100
|
+
|
|
101
|
+
**Why this is needed:**
|
|
102
|
+
|
|
103
|
+
- Ethoscope databases may be created in WAL mode for better write performance
|
|
104
|
+
- WAL mode requires companion files (`.db-wal`, `.db-shm`) for proper operation
|
|
105
|
+
- Docker read-only mounts (`:ro`) prevent SQLite from accessing these files
|
|
106
|
+
- Without proper WAL file access, SQLite can report "database disk image is malformed"
|
|
107
|
+
- Converting to DELETE mode eliminates the need for companion files
|
|
108
|
+
|
|
109
|
+
**What the conversion does:**
|
|
110
|
+
|
|
111
|
+
1. Executes `PRAGMA wal_checkpoint(TRUNCATE)` - Merges WAL data into main database
|
|
112
|
+
2. Executes `PRAGMA journal_mode=DELETE` - Switches to DELETE mode
|
|
113
|
+
3. Verifies database integrity with `PRAGMA integrity_check`
|
|
114
|
+
|
|
115
|
+
The conversion is safe and does not modify any data - it only changes how SQLite manages internal transactions.
|
|
116
|
+
|
|
117
|
+
## See Also
|
|
118
|
+
|
|
119
|
+
- `Docker/README.md` - Complete documentation on database preparation for Docker
|
|
120
|
+
- `CLAUDE.md` - Troubleshooting section for developer reference
|
|
121
|
+
- `src/ethoscopy/load.py` - Implementation of improved connection handling
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Wrapper script to convert all ethoscope databases from WAL to DELETE mode
|
|
3
|
+
# Run this BEFORE docker compose up to prepare databases for read-only mounting
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ./convert_databases.sh /mnt/ethoscope_data/results
|
|
7
|
+
# ./convert_databases.sh /mnt/ethoscope_data/results --dry-run
|
|
8
|
+
|
|
9
|
+
set -e
|
|
10
|
+
|
|
11
|
+
# Default path if not provided
|
|
12
|
+
DATA_PATH="${1:-/mnt/ethoscope_data/results}"
|
|
13
|
+
|
|
14
|
+
# Check if path exists
|
|
15
|
+
if [ ! -d "$DATA_PATH" ]; then
|
|
16
|
+
echo "ERROR: Directory does not exist: $DATA_PATH"
|
|
17
|
+
echo "Usage: $0 <path_to_ethoscope_results> [--dry-run]"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Converting SQLite databases in: $DATA_PATH"
|
|
22
|
+
echo "This may take a while for large datasets..."
|
|
23
|
+
echo ""
|
|
24
|
+
|
|
25
|
+
# Check if Python script exists
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
PYTHON_SCRIPT="$SCRIPT_DIR/convert_wal_to_delete.py"
|
|
28
|
+
|
|
29
|
+
if [ ! -f "$PYTHON_SCRIPT" ]; then
|
|
30
|
+
echo "ERROR: Python script not found: $PYTHON_SCRIPT"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Pass all arguments to the Python script
|
|
35
|
+
python3 "$PYTHON_SCRIPT" "$@"
|
|
36
|
+
|
|
37
|
+
exit_code=$?
|
|
38
|
+
|
|
39
|
+
if [ $exit_code -eq 0 ]; then
|
|
40
|
+
echo ""
|
|
41
|
+
echo "✓ Database conversion completed successfully"
|
|
42
|
+
echo ""
|
|
43
|
+
echo "You can now start Docker containers with:"
|
|
44
|
+
echo " cd $(dirname "$SCRIPT_DIR") && docker compose up -d"
|
|
45
|
+
else
|
|
46
|
+
echo ""
|
|
47
|
+
echo "✗ Database conversion failed with exit code $exit_code"
|
|
48
|
+
echo "Please check the errors above and try again"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exit $exit_code
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Convert SQLite databases from WAL mode to DELETE mode for read-only Docker mounts.
|
|
4
|
+
|
|
5
|
+
This script walks through a directory tree, finds all SQLite database files,
|
|
6
|
+
and converts them from WAL (Write-Ahead Logging) mode to DELETE mode.
|
|
7
|
+
This is necessary for ethoscope databases that will be mounted read-only in Docker.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python convert_wal_to_delete.py /path/to/ethoscope_results
|
|
11
|
+
python convert_wal_to_delete.py /path/to/ethoscope_results --dry-run
|
|
12
|
+
python convert_wal_to_delete.py /path/to/ethoscope_results --verbose
|
|
13
|
+
python convert_wal_to_delete.py /path/to/ethoscope_results --force
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sqlite3
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from datetime import datetime, timedelta
|
|
21
|
+
from typing import Tuple, List
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def check_journal_mode(db_path: Path) -> str:
|
|
25
|
+
"""Check the current journal mode of a SQLite database."""
|
|
26
|
+
try:
|
|
27
|
+
conn = sqlite3.connect(f'file:{db_path}?mode=ro', uri=True)
|
|
28
|
+
cursor = conn.cursor()
|
|
29
|
+
cursor.execute("PRAGMA journal_mode;")
|
|
30
|
+
mode = cursor.fetchone()[0]
|
|
31
|
+
conn.close()
|
|
32
|
+
return mode
|
|
33
|
+
except Exception as e:
|
|
34
|
+
return f"ERROR: {str(e)}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def convert_database(db_path: Path, force: bool = False, verbose: bool = False) -> Tuple[bool, str]:
|
|
38
|
+
"""
|
|
39
|
+
Convert a database from WAL to DELETE mode.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
db_path: Path to the database file
|
|
43
|
+
force: If True, skip safety checks for recently modified files
|
|
44
|
+
verbose: If True, print detailed progress
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Tuple of (success, message)
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Check if file was recently modified (within last 24 hours)
|
|
51
|
+
if not force:
|
|
52
|
+
mtime = datetime.fromtimestamp(db_path.stat().st_mtime)
|
|
53
|
+
if datetime.now() - mtime < timedelta(hours=24):
|
|
54
|
+
return False, "SKIPPED: Database modified within last 24 hours (use --force to override)"
|
|
55
|
+
|
|
56
|
+
# Check current journal mode
|
|
57
|
+
current_mode = check_journal_mode(db_path)
|
|
58
|
+
if "ERROR" in current_mode:
|
|
59
|
+
return False, f"FAILED: Could not read journal mode: {current_mode}"
|
|
60
|
+
|
|
61
|
+
if current_mode.lower() == 'delete':
|
|
62
|
+
return True, "SKIPPED: Already in DELETE mode"
|
|
63
|
+
|
|
64
|
+
if verbose:
|
|
65
|
+
print(f" Current mode: {current_mode}")
|
|
66
|
+
|
|
67
|
+
# Open database in read-write mode for conversion
|
|
68
|
+
conn = sqlite3.connect(str(db_path))
|
|
69
|
+
cursor = conn.cursor()
|
|
70
|
+
|
|
71
|
+
# Checkpoint the WAL file to merge changes back to main database
|
|
72
|
+
if verbose:
|
|
73
|
+
print(f" Checkpointing WAL...")
|
|
74
|
+
cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
|
|
75
|
+
|
|
76
|
+
# Convert to DELETE mode
|
|
77
|
+
if verbose:
|
|
78
|
+
print(f" Converting to DELETE mode...")
|
|
79
|
+
cursor.execute("PRAGMA journal_mode=DELETE;")
|
|
80
|
+
new_mode = cursor.fetchone()[0]
|
|
81
|
+
|
|
82
|
+
# Verify integrity
|
|
83
|
+
if verbose:
|
|
84
|
+
print(f" Verifying integrity...")
|
|
85
|
+
cursor.execute("PRAGMA integrity_check;")
|
|
86
|
+
integrity = cursor.fetchone()[0]
|
|
87
|
+
|
|
88
|
+
conn.close()
|
|
89
|
+
|
|
90
|
+
if integrity.lower() != 'ok':
|
|
91
|
+
return False, f"FAILED: Integrity check failed: {integrity}"
|
|
92
|
+
|
|
93
|
+
if new_mode.lower() != 'delete':
|
|
94
|
+
return False, f"FAILED: Conversion unsuccessful, mode is {new_mode}"
|
|
95
|
+
|
|
96
|
+
return True, f"SUCCESS: Converted from {current_mode} to {new_mode}"
|
|
97
|
+
|
|
98
|
+
except sqlite3.OperationalError as e:
|
|
99
|
+
return False, f"FAILED: Database locked or in use: {str(e)}"
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return False, f"FAILED: {str(e)}"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def find_databases(root_path: Path, pattern: str = "*.db") -> List[Path]:
|
|
105
|
+
"""Find all database files in the directory tree."""
|
|
106
|
+
return list(root_path.rglob(pattern))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def main():
|
|
110
|
+
parser = argparse.ArgumentParser(
|
|
111
|
+
description="Convert SQLite databases from WAL mode to DELETE mode",
|
|
112
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
113
|
+
epilog="""
|
|
114
|
+
Examples:
|
|
115
|
+
# Dry run to see what would be converted
|
|
116
|
+
python convert_wal_to_delete.py /mnt/ethoscope_data/results --dry-run
|
|
117
|
+
|
|
118
|
+
# Convert all databases with verbose output
|
|
119
|
+
python convert_wal_to_delete.py /mnt/ethoscope_data/results --verbose
|
|
120
|
+
|
|
121
|
+
# Force conversion even for recently modified databases
|
|
122
|
+
python convert_wal_to_delete.py /mnt/ethoscope_data/results --force
|
|
123
|
+
"""
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
'root_path',
|
|
128
|
+
type=Path,
|
|
129
|
+
help='Root directory to search for database files'
|
|
130
|
+
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
'--dry-run',
|
|
133
|
+
action='store_true',
|
|
134
|
+
help='Show what would be done without making changes'
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
'--verbose',
|
|
138
|
+
action='store_true',
|
|
139
|
+
help='Print detailed progress information'
|
|
140
|
+
)
|
|
141
|
+
parser.add_argument(
|
|
142
|
+
'--force',
|
|
143
|
+
action='store_true',
|
|
144
|
+
help='Force conversion even for recently modified databases'
|
|
145
|
+
)
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
'--pattern',
|
|
148
|
+
type=str,
|
|
149
|
+
default='*.db',
|
|
150
|
+
help='File pattern to match (default: *.db)'
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
args = parser.parse_args()
|
|
154
|
+
|
|
155
|
+
# Validate root path
|
|
156
|
+
if not args.root_path.exists():
|
|
157
|
+
print(f"ERROR: Path does not exist: {args.root_path}", file=sys.stderr)
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
if not args.root_path.is_dir():
|
|
161
|
+
print(f"ERROR: Path is not a directory: {args.root_path}", file=sys.stderr)
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
print(f"Searching for database files in: {args.root_path}")
|
|
165
|
+
print(f"Pattern: {args.pattern}")
|
|
166
|
+
|
|
167
|
+
if args.dry_run:
|
|
168
|
+
print("DRY RUN MODE - No changes will be made")
|
|
169
|
+
|
|
170
|
+
print()
|
|
171
|
+
|
|
172
|
+
# Find all database files
|
|
173
|
+
databases = find_databases(args.root_path, args.pattern)
|
|
174
|
+
|
|
175
|
+
if not databases:
|
|
176
|
+
print("No database files found.")
|
|
177
|
+
sys.exit(0)
|
|
178
|
+
|
|
179
|
+
print(f"Found {len(databases)} database file(s)")
|
|
180
|
+
print()
|
|
181
|
+
|
|
182
|
+
# Process each database
|
|
183
|
+
stats = {
|
|
184
|
+
'total': len(databases),
|
|
185
|
+
'converted': 0,
|
|
186
|
+
'skipped': 0,
|
|
187
|
+
'failed': 0,
|
|
188
|
+
'already_delete': 0
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for i, db_path in enumerate(databases, 1):
|
|
192
|
+
rel_path = db_path.relative_to(args.root_path)
|
|
193
|
+
size_mb = db_path.stat().st_size / (1024 * 1024)
|
|
194
|
+
|
|
195
|
+
print(f"[{i}/{len(databases)}] {rel_path} ({size_mb:.1f} MB)")
|
|
196
|
+
|
|
197
|
+
if args.dry_run:
|
|
198
|
+
mode = check_journal_mode(db_path)
|
|
199
|
+
print(f" Current mode: {mode}")
|
|
200
|
+
if mode.lower() == 'wal':
|
|
201
|
+
print(f" Would convert from WAL to DELETE")
|
|
202
|
+
elif mode.lower() == 'delete':
|
|
203
|
+
print(f" Already in DELETE mode")
|
|
204
|
+
stats['skipped'] += 1
|
|
205
|
+
else:
|
|
206
|
+
success, message = convert_database(db_path, args.force, args.verbose)
|
|
207
|
+
print(f" {message}")
|
|
208
|
+
|
|
209
|
+
if success:
|
|
210
|
+
if "Already in DELETE mode" in message:
|
|
211
|
+
stats['already_delete'] += 1
|
|
212
|
+
else:
|
|
213
|
+
stats['converted'] += 1
|
|
214
|
+
elif "SKIPPED" in message:
|
|
215
|
+
stats['skipped'] += 1
|
|
216
|
+
else:
|
|
217
|
+
stats['failed'] += 1
|
|
218
|
+
|
|
219
|
+
print()
|
|
220
|
+
|
|
221
|
+
# Print summary
|
|
222
|
+
print("=" * 60)
|
|
223
|
+
print("SUMMARY")
|
|
224
|
+
print("=" * 60)
|
|
225
|
+
print(f"Total databases found: {stats['total']}")
|
|
226
|
+
|
|
227
|
+
if args.dry_run:
|
|
228
|
+
print(f"Would be processed: {stats['skipped']}")
|
|
229
|
+
else:
|
|
230
|
+
print(f"Successfully converted: {stats['converted']}")
|
|
231
|
+
print(f"Already in DELETE mode: {stats['already_delete']}")
|
|
232
|
+
print(f"Skipped: {stats['skipped']}")
|
|
233
|
+
print(f"Failed: {stats['failed']}")
|
|
234
|
+
|
|
235
|
+
if stats['failed'] > 0:
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == '__main__':
|
|
240
|
+
main()
|
|
@@ -21,8 +21,8 @@ def _connect_db(path):
|
|
|
21
21
|
|
|
22
22
|
When the database directory is read-only (e.g., mounted with :ro in Docker),
|
|
23
23
|
SQLite cannot create journal/WAL files and will fail to open the database.
|
|
24
|
-
This function detects read-only filesystems and uses
|
|
25
|
-
|
|
24
|
+
This function detects read-only filesystems and uses appropriate connection
|
|
25
|
+
parameters to handle WAL-mode databases safely.
|
|
26
26
|
|
|
27
27
|
Args:
|
|
28
28
|
path (str): Path to the SQLite database file
|
|
@@ -31,8 +31,9 @@ def _connect_db(path):
|
|
|
31
31
|
sqlite3.Connection: Database connection object
|
|
32
32
|
|
|
33
33
|
Note:
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
For WAL-mode databases on read-only mounts, this function uses mode=ro
|
|
35
|
+
with nolock=1 to prevent "database disk image is malformed" errors.
|
|
36
|
+
Any uncommitted WAL data will not be visible, which is acceptable for
|
|
36
37
|
read-only mounts where the data cannot change anyway.
|
|
37
38
|
"""
|
|
38
39
|
path_str = str(path)
|
|
@@ -40,8 +41,30 @@ def _connect_db(path):
|
|
|
40
41
|
|
|
41
42
|
# Check if we can write to the directory
|
|
42
43
|
if not os.access(dir_path, os.W_OK):
|
|
43
|
-
# Read-only filesystem -
|
|
44
|
-
|
|
44
|
+
# Read-only filesystem - check if database is in WAL mode
|
|
45
|
+
try:
|
|
46
|
+
# Try to detect WAL mode by opening in read-only mode first
|
|
47
|
+
temp_conn = sqlite3.connect(f"file:{path_str}?mode=ro", uri=True)
|
|
48
|
+
cursor = temp_conn.cursor()
|
|
49
|
+
cursor.execute("PRAGMA journal_mode;")
|
|
50
|
+
journal_mode = cursor.fetchone()[0].lower()
|
|
51
|
+
temp_conn.close()
|
|
52
|
+
|
|
53
|
+
if journal_mode == 'wal':
|
|
54
|
+
# WAL mode on read-only mount: use mode=ro with nolock
|
|
55
|
+
# This prevents "database disk image is malformed" errors
|
|
56
|
+
# by avoiding operations that require WAL/SHM files
|
|
57
|
+
return sqlite3.connect(
|
|
58
|
+
f"file:{path_str}?mode=ro&nolock=1",
|
|
59
|
+
uri=True,
|
|
60
|
+
timeout=10.0
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
# Non-WAL mode: use immutable mode for better performance
|
|
64
|
+
return sqlite3.connect(f"file:{path_str}?immutable=1", uri=True)
|
|
65
|
+
except Exception:
|
|
66
|
+
# If detection fails, fall back to immutable mode
|
|
67
|
+
return sqlite3.connect(f"file:{path_str}?immutable=1", uri=True)
|
|
45
68
|
else:
|
|
46
69
|
# Normal read-write access
|
|
47
70
|
return sqlite3.connect(path_str)
|
|
@@ -916,7 +939,41 @@ def read_single_roi_optimized(
|
|
|
916
939
|
sql_query = "SELECT * FROM ROI_{} WHERE t >= {} {}".format(
|
|
917
940
|
file["region_id"], min_time, max_time_condtion
|
|
918
941
|
)
|
|
919
|
-
|
|
942
|
+
|
|
943
|
+
# Execute query with retry logic for WAL-related errors
|
|
944
|
+
try:
|
|
945
|
+
data = pd.read_sql_query(sql_query, conn)
|
|
946
|
+
except sqlite3.DatabaseError as e:
|
|
947
|
+
# Handle "database disk image is malformed" errors
|
|
948
|
+
# This can occur with WAL-mode databases on read-only mounts
|
|
949
|
+
if "malformed" in str(e).lower() or "disk image" in str(e).lower():
|
|
950
|
+
print(f"Warning: Database error for ROI {file['region_id']}, attempting retry with fresh connection...")
|
|
951
|
+
|
|
952
|
+
# Get database path from file metadata
|
|
953
|
+
db_path = file.get("path")
|
|
954
|
+
if not db_path:
|
|
955
|
+
print(f"Error: Cannot retry - database path not found in file metadata")
|
|
956
|
+
raise
|
|
957
|
+
|
|
958
|
+
# Create a fresh connection just for the retry
|
|
959
|
+
retry_conn = None
|
|
960
|
+
try:
|
|
961
|
+
retry_conn = _connect_db(db_path)
|
|
962
|
+
data = pd.read_sql_query(sql_query, retry_conn)
|
|
963
|
+
print(f"Success: ROI {file['region_id']} loaded on retry")
|
|
964
|
+
except Exception as retry_error:
|
|
965
|
+
print(f"Error: Retry failed for ROI {file['region_id']}: {retry_error}")
|
|
966
|
+
raise
|
|
967
|
+
finally:
|
|
968
|
+
# Clean up retry connection
|
|
969
|
+
if retry_conn:
|
|
970
|
+
try:
|
|
971
|
+
retry_conn.close()
|
|
972
|
+
except Exception:
|
|
973
|
+
pass
|
|
974
|
+
else:
|
|
975
|
+
# Re-raise other database errors
|
|
976
|
+
raise
|
|
920
977
|
|
|
921
978
|
if "id" in data.columns:
|
|
922
979
|
# Check if 'id' is a primary key (reuse cursor)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(ls:*)",
|
|
5
|
-
"Bash(git checkout:*)",
|
|
6
|
-
"Bash(docker images:*)",
|
|
7
|
-
"Read(//tmp/**)",
|
|
8
|
-
"Bash(JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.1 docker compose build:*)",
|
|
9
|
-
"Bash(tee:*)",
|
|
10
|
-
"Bash(docker compose:*)",
|
|
11
|
-
"Bash(find:*)",
|
|
12
|
-
"Bash(sqlite3:*)",
|
|
13
|
-
"Bash(python3:*)",
|
|
14
|
-
"Bash(docker exec:*)",
|
|
15
|
-
"Bash(docker cp:*)"
|
|
16
|
-
],
|
|
17
|
-
"deny": []
|
|
18
|
-
}
|
|
19
|
-
}
|
ethoscopy-2.0.3/Docker/README.md
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Ethoscopelab docker instance
|
|
2
|
-
|
|
3
|
-
Note: Most users will **not** need to recreate this image. These instructions are just provided as reference.
|
|
4
|
-
The ethoscopelab docker instance lives on dockerhub at the following address: [https://hub.docker.com/r/ggilestro/ethoscope-lab](https://hub.docker.com/r/ggilestro/ethoscope-lab) and this is what regular users should download and run. Follow instructions there and on the [ethoscopy manual](https://bookstack.lab.gilest.ro/books/ethoscopy/page/getting-started).
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Docker files that were used to create the ethoscope-lab docker instance
|
|
8
|
-
|
|
9
|
-
The files in this folder can be used to recreate the image as uploaded on dockerhub.
|
|
10
|
-
|
|
11
|
-
The command to use to recreate that image is `JUPYTER_HUB_TAG=5.3.0 ETHOSCOPE_LAB_TAG=1.0 docker compose build`. This creates the image with the specified tag. For Docker Hub deployment, push the image: `docker push ggilestro/ethoscope-lab:1.0`. To also create a latest tag: `docker tag ggilestro/ethoscope-lab:1.0 ggilestro/ethoscope-lab:latest && docker push ggilestro/ethoscope-lab:latest`. You can verify your local images with `docker images | grep ethoscope-lab`.
|
|
12
|
-
|
|
13
|
-
After creation, the image can be run using the enclosed `docker-compose.yml` file, replacing values as fit.
|
|
14
|
-
|
|
15
|
-
## Add new users to the JupyterHub
|
|
16
|
-
|
|
17
|
-
To add new users to the JupyterHub instance:
|
|
18
|
-
|
|
19
|
-
### 1. Modify the jupyterhub_config.py file
|
|
20
|
-
|
|
21
|
-
Edit the `allowed_users` set on lines 14-19 to include your new usernames:
|
|
22
|
-
|
|
23
|
-
```python
|
|
24
|
-
c.Authenticator.allowed_users = {
|
|
25
|
-
'amadabhushi', 'ggilestro', 'mjoyce', 'lguo',
|
|
26
|
-
'labguest1', 'labguest2', 'labguest3', 'labguest4',
|
|
27
|
-
'labguest5', 'labguest6', 'labguest7', 'labguest8',
|
|
28
|
-
'ethoscopelab', 'newuser1', 'newuser2' # Add your new users here
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
To make a user an admin, add them to the `admin_users` set on line 22:
|
|
33
|
-
|
|
34
|
-
```python
|
|
35
|
-
c.Authenticator.admin_users = {'ggilestro', 'newadmin'}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 2. Restart the Docker container
|
|
39
|
-
|
|
40
|
-
After modifying the config file:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
docker compose down
|
|
44
|
-
docker compose up -d
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**Notes:**
|
|
48
|
-
- All users share the same password: `ethoscope` (line 11)
|
|
49
|
-
- The system uses DummyAuthenticator for simple shared-password authentication
|
|
50
|
-
- Each user gets their own home directory at `/home/{username}`
|
|
51
|
-
- Home directories are created automatically when users first log in
|
|
52
|
-
|
|
53
|
-
## Mounting Home Directories as Volumes
|
|
54
|
-
|
|
55
|
-
To persist user data and notebooks across container restarts, you should mount user home directories as Docker volumes. This is done by modifying the `docker-compose.yml` file.
|
|
56
|
-
|
|
57
|
-
### Benefits of mounting home directories:
|
|
58
|
-
|
|
59
|
-
1. **Data Persistence**: User notebooks, data files, and configurations survive container restarts and updates
|
|
60
|
-
2. **Backup and Recovery**: Easy to backup user data by copying the mounted directories
|
|
61
|
-
3. **Performance**: Direct access to host filesystem, avoiding container storage overhead
|
|
62
|
-
4. **Sharing**: Users can access their files from the host system if needed
|
|
63
|
-
|
|
64
|
-
### Example volume configuration:
|
|
65
|
-
|
|
66
|
-
Add volumes to your `docker-compose.yml`:
|
|
67
|
-
|
|
68
|
-
```yaml
|
|
69
|
-
services:
|
|
70
|
-
jupyterhub:
|
|
71
|
-
volumes:
|
|
72
|
-
- ./user_data:/home # Maps host ./user_data to container /home
|
|
73
|
-
- ./jupyterhub_config.py:/srv/jupyterhub/jupyterhub_config.py
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Or for individual user directories:
|
|
77
|
-
|
|
78
|
-
```yaml
|
|
79
|
-
volumes:
|
|
80
|
-
- ./users/ggilestro:/home/ggilestro
|
|
81
|
-
- ./users/amadabhushi:/home/amadabhushi
|
|
82
|
-
- ./users/shared:/home/shared # Shared directory for all users
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
This ensures all user work is preserved even when containers are recreated or updated.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|