pylantir 0.2.1__py3-none-any.whl → 0.2.3__py3-none-any.whl
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.
- pylantir/redcap_to_db.py +32 -19
- {pylantir-0.2.1.dist-info → pylantir-0.2.3.dist-info}/METADATA +4 -2
- {pylantir-0.2.1.dist-info → pylantir-0.2.3.dist-info}/RECORD +6 -6
- {pylantir-0.2.1.dist-info → pylantir-0.2.3.dist-info}/WHEEL +0 -0
- {pylantir-0.2.1.dist-info → pylantir-0.2.3.dist-info}/entry_points.txt +0 -0
- {pylantir-0.2.1.dist-info → pylantir-0.2.3.dist-info}/licenses/LICENSE +0 -0
pylantir/redcap_to_db.py
CHANGED
|
@@ -55,14 +55,23 @@ def fetch_redcap_entries(redcap_fields: list, interval: float) -> list:
|
|
|
55
55
|
datetime_interval = datetime_now - timedelta(seconds=interval)
|
|
56
56
|
records = project.export_records(fields=redcap_fields, date_begin=datetime_interval, date_end=datetime_now, format_type="df")
|
|
57
57
|
|
|
58
|
+
# Clean up PyCap Project immediately after export to free API client cache
|
|
59
|
+
del project
|
|
60
|
+
gc.collect()
|
|
61
|
+
|
|
58
62
|
if records.empty:
|
|
59
63
|
lgr.warning("No records retrieved from REDCap.")
|
|
64
|
+
# Explicitly clean up the empty DataFrame to release any allocated buffers
|
|
65
|
+
del records
|
|
66
|
+
gc.collect()
|
|
60
67
|
return []
|
|
61
68
|
|
|
62
69
|
filtered_records = []
|
|
63
70
|
|
|
64
71
|
# Group by 'record_id' (index level 0)
|
|
65
|
-
|
|
72
|
+
# Convert to list to avoid holding groupby iterator reference
|
|
73
|
+
record_groups = list(records.groupby(level=0))
|
|
74
|
+
for record_id, group in record_groups:
|
|
66
75
|
|
|
67
76
|
# Try to get baseline (non-repeated instrument) values
|
|
68
77
|
baseline_rows = group[group['redcap_repeat_instrument'].isna()]
|
|
@@ -90,6 +99,11 @@ def fetch_redcap_entries(redcap_fields: list, interval: float) -> list:
|
|
|
90
99
|
|
|
91
100
|
filtered_records.append(record)
|
|
92
101
|
|
|
102
|
+
# Explicitly clean up DataFrame and groupby list to free memory
|
|
103
|
+
del record_groups
|
|
104
|
+
del records
|
|
105
|
+
gc.collect()
|
|
106
|
+
|
|
93
107
|
return filtered_records
|
|
94
108
|
|
|
95
109
|
# TODO: Implement age binning for paricipants
|
|
@@ -164,11 +178,11 @@ def cleanup_memory_and_connections():
|
|
|
164
178
|
lgr.debug("Disposing database connection pool")
|
|
165
179
|
engine.pool.dispose()
|
|
166
180
|
|
|
167
|
-
# 3. Force Python garbage collection
|
|
168
|
-
#
|
|
169
|
-
collected =
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
# 3. Force Python garbage collection targeting all generations
|
|
182
|
+
# Target generation 2 (oldest) first to catch long-lived objects
|
|
183
|
+
collected = gc.collect(generation=2) # Oldest generation
|
|
184
|
+
collected += gc.collect(generation=1) # Middle generation
|
|
185
|
+
collected += gc.collect(generation=0) # Youngest generation
|
|
172
186
|
|
|
173
187
|
# 4. Clear any cached SQLAlchemy metadata
|
|
174
188
|
if hasattr(engine, 'pool'):
|
|
@@ -178,20 +192,17 @@ def cleanup_memory_and_connections():
|
|
|
178
192
|
# Get memory usage after cleanup
|
|
179
193
|
memory_after = get_memory_usage()
|
|
180
194
|
|
|
181
|
-
# Log cleanup results
|
|
182
|
-
if memory_before and memory_after:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
else:
|
|
191
|
-
lgr.info(f"Memory cleanup completed. Collected {collected} objects. "
|
|
192
|
-
f"Memory stats: {memory_after}")
|
|
195
|
+
# Log cleanup results with simplified, focused metrics
|
|
196
|
+
if memory_before and memory_after and 'rss_mb' in memory_before:
|
|
197
|
+
freed = memory_before['rss_mb'] - memory_after['rss_mb']
|
|
198
|
+
lgr.info(
|
|
199
|
+
f"Memory cleanup: Before={memory_before['rss_mb']:.1f}MB, "
|
|
200
|
+
f"After={memory_after['rss_mb']:.1f}MB, "
|
|
201
|
+
f"Freed={freed:.1f}MB, "
|
|
202
|
+
f"Objects={collected}"
|
|
203
|
+
)
|
|
193
204
|
else:
|
|
194
|
-
lgr.info(f"Memory cleanup
|
|
205
|
+
lgr.info(f"Memory cleanup: Collected {collected} objects")
|
|
195
206
|
|
|
196
207
|
except Exception as e:
|
|
197
208
|
lgr.error(f"Error during cleanup: {e}")
|
|
@@ -328,6 +339,8 @@ def sync_redcap_to_db(
|
|
|
328
339
|
finally:
|
|
329
340
|
# Always ensure session is properly closed
|
|
330
341
|
if session:
|
|
342
|
+
# Detach all ORM objects from session to clear identity map
|
|
343
|
+
session.expunge_all()
|
|
331
344
|
session.close()
|
|
332
345
|
|
|
333
346
|
# Perform cleanup after sync
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pylantir
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Python - DICOM Modality WorkList with Optional API
|
|
5
5
|
Author-email: Milton Camacho <miltoncamachoicc@gmail.com>
|
|
6
6
|
Requires-Python: >=3.11.1
|
|
@@ -86,13 +86,15 @@ pip install pylantir[api]
|
|
|
86
86
|
```
|
|
87
87
|
Includes: FastAPI, Uvicorn, JWT authentication, password hashing
|
|
88
88
|
|
|
89
|
-
#### Memory Monitoring
|
|
89
|
+
#### Memory Monitoring (Recommended for Production)
|
|
90
90
|
For enhanced memory usage monitoring and cleanup during REDCap synchronization:
|
|
91
91
|
```bash
|
|
92
92
|
pip install pylantir[monitoring]
|
|
93
93
|
```
|
|
94
94
|
Includes: psutil for system resource monitoring
|
|
95
95
|
|
|
96
|
+
**Note**: While memory cleanup functions work without psutil, you need it installed to see cleanup effectiveness in logs. Without psutil, logs will show high-water mark memory values that don't decrease, even though cleanup is working. For production deployments, installing `[monitoring]` is **highly recommended** to validate memory stability.
|
|
97
|
+
|
|
96
98
|
#### Big Data Processing
|
|
97
99
|
For Spark-based data processing capabilities:
|
|
98
100
|
```bash
|
|
@@ -8,13 +8,13 @@ pylantir/db_setup.py,sha256=ljL3u6RTj1W7JqAsi_iYvV0rRUtwRut66X4IDAMQmCQ,3572
|
|
|
8
8
|
pylantir/models.py,sha256=bKgI0EN1VSYanPTOvEhEY2Zzqa0gDYLpVnE_KNQ6PEc,1780
|
|
9
9
|
pylantir/mwl_server.py,sha256=GMJDcK0u_KM3oa6UqQ87NxMVye2pvG2cdkcI9k_iExg,10338
|
|
10
10
|
pylantir/populate_db.py,sha256=KIbkVA-EAuTlDArXMFOHkjMmVfjlsTApj7S1wpUu1bM,2207
|
|
11
|
-
pylantir/redcap_to_db.py,sha256=
|
|
11
|
+
pylantir/redcap_to_db.py,sha256=rzA9VZl2qG67Ff82lxKgpXFVVkrmxBlr7Fd5OZORrVU,19476
|
|
12
12
|
pylantir/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
pylantir/cli/run.py,sha256=4JI9cRwoKYGG53DDP32ZBfXlZp_5hl0C3A7FiM3JyJw,21885
|
|
14
14
|
pylantir/config/config_example_with_cors.json,sha256=4F6YCOYMIPOZVnEdrWw3txIPWUyA3hRBIRs-zS5h2Vk,2885
|
|
15
15
|
pylantir/config/mwl_config.json,sha256=I1nPzXofmw2JviS7hEOw01ExdL6pGdgH46rcKYu6ofo,1437
|
|
16
|
-
pylantir-0.2.
|
|
17
|
-
pylantir-0.2.
|
|
18
|
-
pylantir-0.2.
|
|
19
|
-
pylantir-0.2.
|
|
20
|
-
pylantir-0.2.
|
|
16
|
+
pylantir-0.2.3.dist-info/entry_points.txt,sha256=vxaxvfGppLqRt9_4sqNDdP6b2jlgpcHIwP7UQfrM1T0,50
|
|
17
|
+
pylantir-0.2.3.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
|
18
|
+
pylantir-0.2.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
19
|
+
pylantir-0.2.3.dist-info/METADATA,sha256=_QLhbm0N61fQGwKtlZHPONSlchoJGCm1Dgh4yIi0nVU,19888
|
|
20
|
+
pylantir-0.2.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|