endoreg-db 0.8.3.0__py3-none-any.whl → 0.8.3.2__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.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

@@ -19,16 +19,12 @@ from pathlib import Path
19
19
  from typing import Union, Dict, Any, Optional, List, Tuple
20
20
  from django.db import transaction
21
21
  from endoreg_db.models import VideoFile, SensitiveMeta
22
- from endoreg_db.utils.paths import STORAGE_DIR, RAW_FRAME_DIR, VIDEO_DIR, ANONYM_VIDEO_DIR
22
+ from endoreg_db.utils.paths import STORAGE_DIR, VIDEO_DIR, ANONYM_VIDEO_DIR
23
23
  import random
24
- from lx_anonymizer.ocr import trocr_full_image_ocr
25
24
  from endoreg_db.utils.hashs import get_video_hash
26
- from endoreg_db.models.media.video.video_file_anonymize import _cleanup_raw_assets, _anonymize
27
- from typing import TYPE_CHECKING
25
+ from endoreg_db.models.media.video.video_file_anonymize import _cleanup_raw_assets
28
26
  from django.db.models.fields.files import FieldFile
29
-
30
- if TYPE_CHECKING:
31
- from endoreg_db.models import EndoscopyProcessor
27
+ from endoreg_db.models import EndoscopyProcessor
32
28
 
33
29
  # File lock configuration (matches PDF import)
34
30
  STALE_LOCK_SECONDS = 6000 # 100 minutes - reclaim locks older than this
@@ -58,15 +54,13 @@ class VideoImportService():
58
54
  self.project_root = Path(__file__).parent.parent.parent.parent
59
55
 
60
56
  # Track processed files to prevent duplicates
61
- self.processed_files = set(str(file) for file in os.listdir(ANONYM_VIDEO_DIR))
62
-
63
- self.STORAGE_DIR = STORAGE_DIR
64
-
57
+ self.processed_files = set(str(Path(ANONYM_VIDEO_DIR) / file) for file in os.listdir(ANONYM_VIDEO_DIR))
58
+
65
59
  # Central video instance and processing context
66
60
  self.current_video: Optional[VideoFile] = None
67
61
  self.processing_context: Dict[str, Any] = {}
68
62
 
69
- self.delete_source = False
63
+ self.delete_source = True
70
64
 
71
65
  self.logger = logging.getLogger(__name__)
72
66
 
@@ -166,12 +160,12 @@ class VideoImportService():
166
160
  return None
167
161
  raise
168
162
 
169
- # Create sensitive meta file, ensure raw is moved out of processing folder watched by file watcher.
170
- self._create_sensitive_file()
171
-
172
163
  # Create or retrieve video instance
173
164
  self._create_or_retrieve_video_instance()
174
165
 
166
+ # Create sensitive meta file, ensure raw is moved out of processing folder watched by file watcher.
167
+ self._create_sensitive_file()
168
+
175
169
  # Setup processing environment
176
170
  self._setup_processing_environment()
177
171
 
@@ -225,8 +219,12 @@ class VideoImportService():
225
219
 
226
220
  # Acquire file lock to prevent concurrent processing
227
221
  # Lock will be held until finally block in import_and_anonymize()
228
- self.processing_context['_lock_context'] = self._file_lock(file_path)
229
- self.processing_context['_lock_context'].__enter__()
222
+ try:
223
+ self.processing_context['_lock_context'] = self._file_lock(file_path)
224
+ self.processing_context['_lock_context'].__enter__()
225
+ except Exception:
226
+ self._cleanup_processing_context()
227
+ raise
230
228
 
231
229
  self.logger.info("Acquired file lock for: %s", file_path)
232
230
 
@@ -244,7 +242,6 @@ class VideoImportService():
244
242
 
245
243
  def _create_or_retrieve_video_instance(self):
246
244
  """Create or retrieve the VideoFile instance and move to final storage."""
247
- # Removed duplicate import of VideoFile (already imported at module level)
248
245
 
249
246
  self.logger.info("Creating VideoFile instance...")
250
247
 
@@ -275,96 +272,78 @@ class VideoImportService():
275
272
  def _move_to_final_storage(self):
276
273
  """
277
274
  Move video from raw_videos to final storage locations.
278
- - Raw video → /data/videos (raw_file_path)
275
+ - Raw video → /data/videos (raw_file_path)
279
276
  - Processed video will later → /data/anonym_videos (file_path)
280
277
  """
281
278
  from endoreg_db.utils import data_paths
282
-
283
- source_path = self.processing_context['file_path']
284
279
 
285
- videos_dir = data_paths["video"]
286
- videos_dir.mkdir(parents=True, exist_ok=True)
280
+ source_path = Path(self.processing_context["file_path"])
281
+ _current_video = self._require_current_video()
282
+ videos_dir = Path(data_paths["video"])
283
+ storage_root = Path(data_paths["storage"])
287
284
 
288
- _current_video = self.current_video
289
- assert _current_video is not None, "Current video instance is None during storage move"
285
+ videos_dir.mkdir(parents=True, exist_ok=True)
290
286
 
287
+ # --- Derive stored_raw_path safely ---
291
288
  stored_raw_path = None
292
- if hasattr(_current_video, "get_raw_file_path"):
293
- possible_path = _current_video.get_raw_file_path()
294
- if possible_path:
295
- try:
296
- stored_raw_path = Path(possible_path)
297
- except (TypeError, ValueError):
298
- stored_raw_path = None
299
-
300
- if stored_raw_path:
301
- try:
302
- storage_root = data_paths["storage"]
303
- if stored_raw_path.is_absolute():
304
- if not stored_raw_path.is_relative_to(storage_root):
289
+ try:
290
+ if hasattr(_current_video, "get_raw_file_path"):
291
+ candidate = _current_video.get_raw_file_path()
292
+ if candidate:
293
+ candidate_path = Path(candidate)
294
+ # Accept only if under storage_root
295
+ try:
296
+ candidate_path.relative_to(storage_root)
297
+ stored_raw_path = candidate_path
298
+ except ValueError:
299
+ # outside storage_root, reset
305
300
  stored_raw_path = None
306
- else:
307
- if stored_raw_path.parts and stored_raw_path.parts[0] == videos_dir.name:
308
- stored_raw_path = storage_root / stored_raw_path
309
- else:
310
- stored_raw_path = videos_dir / stored_raw_path.name
311
- except Exception:
312
- stored_raw_path = None
313
-
314
- if stored_raw_path and not stored_raw_path.suffix:
301
+ except Exception:
315
302
  stored_raw_path = None
316
303
 
304
+ # Fallback: derive from UUID + suffix
317
305
  if not stored_raw_path:
306
+ suffix = source_path.suffix or ".mp4"
318
307
  uuid_str = getattr(_current_video, "uuid", None)
319
- source_suffix = Path(source_path).suffix or ".mp4"
320
- filename = f"{uuid_str}{source_suffix}" if uuid_str else Path(source_path).name
308
+ filename = f"{uuid_str}{suffix}" if uuid_str else source_path.name
321
309
  stored_raw_path = videos_dir / filename
322
310
 
323
- delete_source = bool(self.processing_context.get('delete_source'))
311
+ delete_source = bool(self.processing_context.get("delete_source", True))
324
312
  stored_raw_path.parent.mkdir(parents=True, exist_ok=True)
325
313
 
326
- if not stored_raw_path.exists():
327
- try:
328
- if source_path.exists():
329
- if delete_source:
330
- shutil.move(str(source_path), str(stored_raw_path))
331
- self.logger.info("Moved raw video to: %s", stored_raw_path)
332
- else:
333
- shutil.copy2(str(source_path), str(stored_raw_path))
334
- self.logger.info("Copied raw video to: %s", stored_raw_path)
335
- else:
336
- raise FileNotFoundError(f"Neither stored raw path nor source path exists for {self.processing_context['file_path']}")
337
- except Exception as e:
338
- self.logger.error("Failed to place video in final storage: %s", e)
339
- raise
340
- else:
341
- # If we already have the stored copy, respect delete_source flag without touching assets unnecessarily
342
- if delete_source and source_path.exists():
314
+ # --- Move or copy raw video ---
315
+ try:
316
+ if delete_source:
317
+ # Try atomic move first, fallback to copy+unlink
343
318
  try:
319
+ os.replace(source_path, stored_raw_path)
320
+ self.logger.info("Moved raw video to: %s", stored_raw_path)
321
+ except Exception:
322
+ shutil.copy2(source_path, stored_raw_path)
344
323
  os.remove(source_path)
345
- self.logger.info("Removed original source file after storing copy: %s", source_path)
346
- except OSError as e:
347
- self.logger.warning("Failed to remove source file %s: %s", source_path, e)
324
+ self.logger.info("Copied & removed raw video to: %s", stored_raw_path)
325
+ else:
326
+ shutil.copy2(source_path, stored_raw_path)
327
+ self.logger.info("Copied raw video to: %s", stored_raw_path)
328
+ except Exception as e:
329
+ self.logger.error("Failed to move/copy video to final storage: %s", e)
330
+ raise
348
331
 
349
- # Ensure database path points to stored location (relative to storage root)
332
+ # --- Ensure DB raw_file is relative to storage root ---
350
333
  try:
351
- storage_root = data_paths["storage"]
352
- relative_path = Path(stored_raw_path).relative_to(storage_root)
353
- if _current_video.raw_file.name != str(relative_path):
354
- _current_video.raw_file.name = str(relative_path)
355
- _current_video.save(update_fields=['raw_file'])
356
- self.logger.info("Updated raw_file path to: %s", relative_path)
357
- except Exception as e:
358
- self.logger.error("Failed to ensure raw_file path is relative: %s", e)
359
- fallback_relative = Path("videos") / Path(stored_raw_path).name
360
- if _current_video.raw_file.name != fallback_relative.as_posix():
361
- _current_video.raw_file.name = fallback_relative.as_posix()
362
- _current_video.save(update_fields=['raw_file'])
363
- self.logger.info("Updated raw_file path using fallback: %s", fallback_relative.as_posix())
334
+ rel_path = stored_raw_path.relative_to(storage_root)
335
+ except Exception:
336
+ rel_path = Path("videos") / stored_raw_path.name
337
+
338
+ if _current_video.raw_file.name != rel_path.as_posix():
339
+ _current_video.raw_file.name = rel_path.as_posix()
340
+ _current_video.save(update_fields=["raw_file"])
341
+ self.logger.info("Updated raw_file path to: %s", rel_path.as_posix())
342
+
343
+ # --- Store for later stages ---
344
+ self.processing_context["raw_video_path"] = stored_raw_path
345
+ self.processing_context["video_filename"] = stored_raw_path.name
364
346
 
365
- # Store paths for later processing
366
- self.processing_context['raw_video_path'] = Path(stored_raw_path)
367
- self.processing_context['video_filename'] = Path(stored_raw_path).name
368
347
 
369
348
  def _setup_processing_environment(self):
370
349
  """Setup the processing environment without file movement."""
@@ -406,7 +385,7 @@ class VideoImportService():
406
385
  def _process_frames_and_metadata(self):
407
386
  """Process frames and extract metadata with anonymization."""
408
387
  # Check frame cleaning availability
409
- frame_cleaning_available, FrameCleaner, ReportReader = self._ensure_frame_cleaning_available()
388
+ frame_cleaning_available, frame_cleaner = self._ensure_frame_cleaning_available()
410
389
  video = self._require_current_video()
411
390
 
412
391
  raw_file_field = video.raw_file
@@ -427,7 +406,7 @@ class VideoImportService():
427
406
  from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
428
407
 
429
408
  with ThreadPoolExecutor(max_workers=1) as executor:
430
- future = executor.submit(self._perform_frame_cleaning, FrameCleaner, endoscope_data_roi_nested, endoscope_image_roi)
409
+ future = executor.submit(self._perform_frame_cleaning, endoscope_data_roi_nested, endoscope_image_roi)
431
410
  try:
432
411
  # Increased timeout to better accommodate ffmpeg + OCR
433
412
  future.result(timeout=300)
@@ -473,6 +452,9 @@ class VideoImportService():
473
452
  self.processing_context['error_reason'] = f"Frame cleaning failed: {e}, Fallback failed: {fallback_error}"
474
453
 
475
454
  def _save_anonymized_video(self):
455
+
456
+ original_raw_file_path_to_delete = None
457
+ original_raw_frame_dir_to_delete = None
476
458
  video = self._require_current_video()
477
459
  anonymized_video_path = video.get_target_anonymized_video_path()
478
460
 
@@ -760,6 +742,17 @@ class VideoImportService():
760
742
  except Exception as exc:
761
743
  self.logger.error("Failed to retrieve processor ROI information: %s", exc)
762
744
 
745
+ # Convert dict to nested list if necessary to match return type
746
+ if isinstance(endoscope_data_roi_nested, dict):
747
+ # Convert dict[str, dict[str, int | None] | None] to List[List[Dict[str, Any]]]
748
+ converted_roi = []
749
+ for key, value in endoscope_data_roi_nested.items():
750
+ if isinstance(value, dict):
751
+ converted_roi.append([value])
752
+ elif value is None:
753
+ converted_roi.append([])
754
+ endoscope_data_roi_nested = converted_roi
755
+
763
756
  return endoscope_data_roi_nested, endoscope_image_roi
764
757
 
765
758
  def _ensure_default_patient_data(self, video_instance: VideoFile | None = None) -> None:
@@ -781,8 +774,6 @@ class VideoImportService():
781
774
  sensitive_meta = SensitiveMeta.create_from_dict(default_data)
782
775
  video.sensitive_meta = sensitive_meta
783
776
  video.save(update_fields=["sensitive_meta"])
784
- state = video.get_or_create_state()
785
- state.mark_sensitive_meta_processed(save=True)
786
777
  self.logger.info("Created default SensitiveMeta for video %s", video.uuid)
787
778
  except Exception as exc:
788
779
  self.logger.error("Failed to create default SensitiveMeta for video %s: %s", video.uuid, exc)
@@ -821,67 +812,43 @@ class VideoImportService():
821
812
  Tuple of (availability_flag, FrameCleaner_class, ReportReader_class)
822
813
  """
823
814
  try:
824
- # Check if we can find the lx-anonymizer directory
825
- from importlib import resources
826
- lx_anonymizer_path = resources.files("lx_anonymizer")
815
+ # Check if we can find lx-anonymizer
816
+ from lx_anonymizer import FrameCleaner # type: ignore[import]
827
817
 
828
- # make sure lx_anonymizer_path is a Path object
829
- lx_anonymizer_path = Path(str(lx_anonymizer_path))
830
-
831
- if lx_anonymizer_path.exists():
832
- # Add to Python path temporarily
833
- if str(lx_anonymizer_path) not in sys.path:
834
- sys.path.insert(0, str(lx_anonymizer_path))
835
-
836
- # Try simple import
837
- from lx_anonymizer import FrameCleaner, ReportReader
838
-
839
- self.logger.info("Successfully imported lx_anonymizer modules")
840
-
841
- # Remove from path to avoid conflicts
842
- if str(lx_anonymizer_path) in sys.path:
843
- sys.path.remove(str(lx_anonymizer_path))
844
-
845
- return True, FrameCleaner, ReportReader
846
-
847
- else:
848
- self.logger.warning(f"lx-anonymizer path not found: {lx_anonymizer_path}")
849
-
818
+ if FrameCleaner:
819
+ return True, FrameCleaner
820
+
850
821
  except Exception as e:
851
- self.logger.warning(f"Frame cleaning not available: {e}")
822
+ self.logger.warning(f"Frame cleaning not available: {e} Please install or update lx_anonymizer.")
852
823
 
853
- return False, None, None
824
+ return False, None
854
825
 
855
826
 
856
827
 
857
- def _perform_frame_cleaning(self, FrameCleaner, endoscope_data_roi_nested, endoscope_image_roi):
828
+ def _perform_frame_cleaning(self, endoscope_data_roi_nested, endoscope_image_roi):
858
829
  """Perform frame cleaning and anonymization."""
859
830
  # Instantiate frame cleaner
860
- frame_cleaner = FrameCleaner()
861
-
831
+ is_available, frame_cleaner = self._ensure_frame_cleaning_available()
832
+
833
+ if not is_available:
834
+ raise RuntimeError("Frame cleaning not available")
835
+
862
836
  # Prepare parameters for frame cleaning
863
837
  raw_video_path = self.processing_context.get('raw_video_path')
864
838
 
865
839
  if not raw_video_path or not Path(raw_video_path).exists():
866
840
  raise RuntimeError(f"Raw video path not found: {raw_video_path}")
867
-
868
- # Get processor name safely
869
- video = self._require_current_video()
870
- video_meta = getattr(video, "video_meta", None)
871
- processor = getattr(video_meta, "processor", None) if video_meta else None
872
- device_name = processor.name if processor else self.processing_context['processor_name']
841
+
873
842
 
874
843
  # Create temporary output path for cleaned video
875
844
  video_filename = self.processing_context.get('video_filename', Path(raw_video_path).name)
876
845
  cleaned_filename = f"cleaned_{video_filename}"
877
846
  cleaned_video_path = Path(raw_video_path).parent / cleaned_filename
878
847
 
879
- # Processor roi is used later to OCR preknown regions.
880
848
 
881
849
  # Clean video with ROI masking (heavy I/O operation)
882
850
  actual_cleaned_path, extracted_metadata = frame_cleaner.clean_video(
883
851
  video_path=Path(raw_video_path),
884
- video_file_obj=video,
885
852
  endoscope_image_roi=endoscope_image_roi,
886
853
  endoscope_data_roi_nested=endoscope_data_roi_nested,
887
854
  output_path=cleaned_video_path,
@@ -1024,7 +991,7 @@ def import_and_anonymize(
1024
991
  center_name: str,
1025
992
  processor_name: str,
1026
993
  save_video: bool = True,
1027
- delete_source: bool = False,
994
+ delete_source: bool = True,
1028
995
  ) -> VideoFile | None:
1029
996
  """Module-level helper that instantiates VideoImportService and runs import_and_anonymize.
1030
997
  Kept for backward compatibility with callers that import this function directly.
@@ -53,7 +53,7 @@ def apply_video_mask_task(self, video_id: int, mask_type: str = 'device_default'
53
53
  self.update_state(state='PROGRESS', meta={'progress': 10, 'message': 'Setting up FrameCleaner...'})
54
54
 
55
55
  # Initialize FrameCleaner
56
- cleaner = FrameCleaner(use_minicpm=True)
56
+ cleaner = FrameCleaner()
57
57
 
58
58
  # Determine mask configuration
59
59
  if mask_type == 'custom' and custom_mask:
@@ -110,14 +110,14 @@ def _setup_frame_removal(video_id: int, detection_engine: str):
110
110
  from lx_anonymizer.frame_cleaner import FrameCleaner
111
111
  from django.shortcuts import get_object_or_404
112
112
  video = get_object_or_404(VideoFile, pk=video_id)
113
- video_path = Path(video.file.path)
113
+ video_path = Path(video.raw_file.path)
114
114
  if not video_path.exists():
115
115
  raise FileNotFoundError(f"Video file not found: {video_path}")
116
116
  output_dir = video_path.parent / "processed"
117
117
  output_dir.mkdir(exist_ok=True)
118
118
  output_path = output_dir / f"{video_path.stem}_cleaned{video_path.suffix}"
119
119
  use_minicpm = detection_engine == 'minicpm'
120
- cleaner = FrameCleaner(use_minicpm=use_minicpm)
120
+ cleaner = FrameCleaner()
121
121
  return video, video_path, output_path, cleaner
122
122
 
123
123
  def _detect_sensitive_frames(self, cleaner, video_path, selection_method, manual_frames, total_frames):
@@ -257,7 +257,7 @@ def reprocess_video_task(self, video_id: int):
257
257
  self.update_state(state='PROGRESS', meta={'progress': 20, 'message': 'Initializing FrameCleaner...'})
258
258
 
259
259
  # Initialize FrameCleaner with optimal settings
260
- cleaner = FrameCleaner(use_minicpm=True)
260
+ cleaner = FrameCleaner()
261
261
 
262
262
  # Create output path
263
263
  output_dir = video_path.parent / "processed"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endoreg-db
3
- Version: 0.8.3.0
3
+ Version: 0.8.3.2
4
4
  Summary: EndoReg Db Django App
5
5
  Project-URL: Homepage, https://info.coloreg.de
6
6
  Project-URL: Repository, https://github.com/wg-lux/endoreg-db
@@ -33,7 +33,7 @@ Requires-Dist: huggingface-hub>=0.35.3
33
33
  Requires-Dist: icecream>=2.1.4
34
34
  Requires-Dist: librosa==0.11.0
35
35
  Requires-Dist: llvmlite>=0.44.0
36
- Requires-Dist: lx-anonymizer[llm,ocr]>=0.8.7
36
+ Requires-Dist: lx-anonymizer[llm,ocr]>=0.8.8
37
37
  Requires-Dist: moviepy==2.2.1
38
38
  Requires-Dist: mypy>=1.16.0
39
39
  Requires-Dist: numpy>=2.2.3
@@ -600,10 +600,10 @@ endoreg_db/services/pseudonym_service.py,sha256=CJhbtRa6K6SPbphgCZgEMi8AFQtB18CU
600
600
  endoreg_db/services/requirements_object.py,sha256=290zf8AEbVtCoHhW4Jr7_ud-RvrqYmb1Nz9UBHtTnc0,6164
601
601
  endoreg_db/services/segment_sync.py,sha256=YgHvIHkbW4mqCu0ACf3zjRSZnNfxWwt4gh5syUVXuE0,6400
602
602
  endoreg_db/services/storage_aware_video_processor.py,sha256=kKFK64vXLeBSVkp1YJonU3gFDTeXZ8C4qb9QZZB99SE,13420
603
- endoreg_db/services/video_import.py,sha256=PhcOgxU5M4uSEklBXEWHpIaNX-yIYv1rJy-T-fCU8cs,47830
603
+ endoreg_db/services/video_import.py,sha256=gDuVTW5WUYGSc0m5ly67cc10YpnTpBkxO7uOEcRa3Ok,45663
604
604
  endoreg_db/tasks/upload_tasks.py,sha256=OJq7DhNwcbWdXzHY8jz5c51BCVkPN5gSWOz-6Fx6W5M,7799
605
605
  endoreg_db/tasks/video_ingest.py,sha256=kxFuYkHijINV0VabQKCFVpJRv6eCAw07tviONurDgg8,5265
606
- endoreg_db/tasks/video_processing_tasks.py,sha256=KjcERRJ1TZzmavBpvr6OsvSTUViU0PR1ECWnEdzu2Js,14140
606
+ endoreg_db/tasks/video_processing_tasks.py,sha256=rZ7Kr49bAR4Q-vALO2SURebrhcJ5hSFGwjF4aULrOao,14089
607
607
  endoreg_db/templates/timeline.html,sha256=H9VXKOecCzqcWWkpNIZXFI29ztg-oxV5uvxMglgoClk,6167
608
608
  endoreg_db/templates/admin/patient_finding_intervention.html,sha256=F3JUKm3HhWIf_xoZZ-SET5d5ZDlm2jMM8g909w1dnYc,10164
609
609
  endoreg_db/templates/admin/start_examination.html,sha256=3K4wirul9KNyB5mN9cpfCSCAyAD6ro19GwxFOY5sZ3A,267
@@ -784,7 +784,7 @@ endoreg_db/views/video/video_meta.py,sha256=C1wBMTtQb_yzEUrhFGAy2UHEWMk_CbU75WXX
784
784
  endoreg_db/views/video/video_processing_history.py,sha256=mhFuS8RG5GV8E-lTtuD0qrq-bIpnUFp8vy9aERfC-J8,770
785
785
  endoreg_db/views/video/video_remove_frames.py,sha256=2FmvNrSPM0fUXiBxINN6vBUUDCqDlBkNcGR3WsLDgKo,1696
786
786
  endoreg_db/views/video/video_stream.py,sha256=kLyuf0ORTmsLeYUQkTQ6iRYqlIQozWhMMR3Lhfe_trk,12148
787
- endoreg_db-0.8.3.0.dist-info/METADATA,sha256=q7jvhqzrBQmwSOuzXARYftJxbQ5vBUL_zmJG9U338dA,14758
788
- endoreg_db-0.8.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
789
- endoreg_db-0.8.3.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
790
- endoreg_db-0.8.3.0.dist-info/RECORD,,
787
+ endoreg_db-0.8.3.2.dist-info/METADATA,sha256=Hdg0xL9WKegEgoyGOY0vgwAX1UVB87Ph86WNsYgcSms,14758
788
+ endoreg_db-0.8.3.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
789
+ endoreg_db-0.8.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
790
+ endoreg_db-0.8.3.2.dist-info/RECORD,,