diffract-core 0.2.1__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.
Files changed (193) hide show
  1. diffract/__init__.py +52 -0
  2. diffract/configs/fast_speed_without_disk.ini +48 -0
  3. diffract/configs/hybrid.ini +74 -0
  4. diffract/configs/sqlite.ini +62 -0
  5. diffract/containers.py +405 -0
  6. diffract/core/__init__.py +23 -0
  7. diffract/core/cache/__init__.py +24 -0
  8. diffract/core/cache/containers.py +93 -0
  9. diffract/core/cache/interface.py +117 -0
  10. diffract/core/cache/redis_manager.py +464 -0
  11. diffract/core/cache/simple_manager.py +478 -0
  12. diffract/core/compute/__init__.py +46 -0
  13. diffract/core/compute/config.py +48 -0
  14. diffract/core/compute/containers.py +81 -0
  15. diffract/core/compute/decorator.py +125 -0
  16. diffract/core/compute/exceptions.py +31 -0
  17. diffract/core/compute/execution/__init__.py +14 -0
  18. diffract/core/compute/execution/_types.py +70 -0
  19. diffract/core/compute/execution/aggregation.py +74 -0
  20. diffract/core/compute/execution/aggregation_runner.py +362 -0
  21. diffract/core/compute/execution/enums.py +38 -0
  22. diffract/core/compute/execution/executor.py +125 -0
  23. diffract/core/compute/execution/parameter_runner.py +125 -0
  24. diffract/core/compute/execution/restrictions.py +61 -0
  25. diffract/core/compute/execution/strategy.py +118 -0
  26. diffract/core/compute/execution/utils.py +112 -0
  27. diffract/core/compute/extensions/__init__.py +0 -0
  28. diffract/core/compute/extensions/power_law.py +1051 -0
  29. diffract/core/compute/extensions/rmt.py +150 -0
  30. diffract/core/compute/extensions/utils.py +36 -0
  31. diffract/core/compute/field_signature.py +183 -0
  32. diffract/core/compute/kernels/__init__.py +48 -0
  33. diffract/core/compute/kernels/alignment.py +106 -0
  34. diffract/core/compute/kernels/heavy_tailed.py +394 -0
  35. diffract/core/compute/kernels/marchenko_pastur.py +99 -0
  36. diffract/core/compute/kernels/mat_decomposition.py +101 -0
  37. diffract/core/compute/kernels/mat_properties.py +53 -0
  38. diffract/core/compute/kernels/model_quality.py +27 -0
  39. diffract/core/compute/kernels/norms.py +88 -0
  40. diffract/core/compute/kernels/ranks.py +35 -0
  41. diffract/core/compute/kernels/tracy_widom.py +36 -0
  42. diffract/core/compute/metadata.py +67 -0
  43. diffract/core/compute/registry.py +485 -0
  44. diffract/core/constants.py +147 -0
  45. diffract/core/data/__init__.py +32 -0
  46. diffract/core/data/interface.py +304 -0
  47. diffract/core/data/metadata/__init__.py +15 -0
  48. diffract/core/data/metadata/containers.py +60 -0
  49. diffract/core/data/metadata/interface.py +208 -0
  50. diffract/core/data/metadata/sqlite_index.py +604 -0
  51. diffract/core/data/nn/__init__.py +43 -0
  52. diffract/core/data/nn/aggregates/__init__.py +35 -0
  53. diffract/core/data/nn/aggregates/metadata.py +96 -0
  54. diffract/core/data/nn/aggregates/proxy.py +57 -0
  55. diffract/core/data/nn/aggregates/repository.py +87 -0
  56. diffract/core/data/nn/aggregates/view.py +307 -0
  57. diffract/core/data/nn/containers.py +86 -0
  58. diffract/core/data/nn/extractors/__init__.py +49 -0
  59. diffract/core/data/nn/extractors/base.py +300 -0
  60. diffract/core/data/nn/extractors/factory.py +234 -0
  61. diffract/core/data/nn/extractors/flax.py +112 -0
  62. diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
  63. diffract/core/data/nn/extractors/handlers/base.py +110 -0
  64. diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
  65. diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
  66. diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
  67. diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
  68. diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
  69. diffract/core/data/nn/extractors/interface.py +56 -0
  70. diffract/core/data/nn/extractors/numpy.py +94 -0
  71. diffract/core/data/nn/extractors/onnx.py +108 -0
  72. diffract/core/data/nn/extractors/tensorflow.py +99 -0
  73. diffract/core/data/nn/extractors/torch.py +204 -0
  74. diffract/core/data/nn/params/__init__.py +27 -0
  75. diffract/core/data/nn/params/interface.py +402 -0
  76. diffract/core/data/nn/params/metadata.py +110 -0
  77. diffract/core/data/nn/params/proxy.py +93 -0
  78. diffract/core/data/nn/params/repository.py +45 -0
  79. diffract/core/data/nn/params/schema.py +82 -0
  80. diffract/core/data/nn/params/view.py +393 -0
  81. diffract/core/data/proxy.py +246 -0
  82. diffract/core/data/repository.py +227 -0
  83. diffract/core/data/utils.py +33 -0
  84. diffract/core/data/view.py +389 -0
  85. diffract/core/export/__init__.py +27 -0
  86. diffract/core/export/containers.py +47 -0
  87. diffract/core/export/exporters.py +191 -0
  88. diffract/core/export/formatters/__init__.py +23 -0
  89. diffract/core/export/formatters/base.py +68 -0
  90. diffract/core/export/formatters/dict_formatter.py +38 -0
  91. diffract/core/export/formatters/json_formatter.py +58 -0
  92. diffract/core/export/formatters/list_formatter.py +41 -0
  93. diffract/core/export/formatters/pandas_formatter.py +86 -0
  94. diffract/core/export/formatters/polars_formatter.py +86 -0
  95. diffract/core/export/formatters/registry.py +84 -0
  96. diffract/core/export/interface.py +105 -0
  97. diffract/core/parallel/__init__.py +21 -0
  98. diffract/core/parallel/containers.py +57 -0
  99. diffract/core/parallel/execution.py +91 -0
  100. diffract/core/parallel/runtime.py +91 -0
  101. diffract/core/storage/__init__.py +35 -0
  102. diffract/core/storage/base_manager.py +330 -0
  103. diffract/core/storage/containers.py +122 -0
  104. diffract/core/storage/hdf5_manager.py +856 -0
  105. diffract/core/storage/hybrid_manager.py +218 -0
  106. diffract/core/storage/interface.py +222 -0
  107. diffract/core/storage/metadata.py +89 -0
  108. diffract/core/storage/ram_manager.py +159 -0
  109. diffract/core/storage/sqlite_manager.py +1062 -0
  110. diffract/core/storage/zarr_manager.py +992 -0
  111. diffract/core/utils/__init__.py +45 -0
  112. diffract/core/utils/build.py +46 -0
  113. diffract/core/utils/exceptions.py +11 -0
  114. diffract/core/utils/hashing.py +90 -0
  115. diffract/core/utils/imports.py +292 -0
  116. diffract/core/utils/math.py +15 -0
  117. diffract/py.typed +0 -0
  118. diffract/session/__init__.py +24 -0
  119. diffract/session/errors.py +17 -0
  120. diffract/session/field_cache.py +216 -0
  121. diffract/session/namespaces/__init__.py +15 -0
  122. diffract/session/namespaces/compute/__init__.py +219 -0
  123. diffract/session/namespaces/models/__init__.py +282 -0
  124. diffract/session/namespaces/models/parameters/__init__.py +133 -0
  125. diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
  126. diffract/session/namespaces/results/__init__.py +378 -0
  127. diffract/session/namespaces/results/eraser.py +129 -0
  128. diffract/session/namespaces/results/injester.py +249 -0
  129. diffract/session/namespaces/utils/__init__.py +141 -0
  130. diffract/session/namespaces/utils/merger.py +295 -0
  131. diffract/session/namespaces/viz/__init__.py +91 -0
  132. diffract/session/namespaces/viz/_utils.py +14 -0
  133. diffract/session/namespaces/viz/box.py +207 -0
  134. diffract/session/namespaces/viz/grid.py +160 -0
  135. diffract/session/namespaces/viz/heatmap.py +164 -0
  136. diffract/session/namespaces/viz/scatter.py +202 -0
  137. diffract/session/namespaces/viz/sparkline.py +270 -0
  138. diffract/session/namespaces/viz/violin.py +212 -0
  139. diffract/session/session.py +260 -0
  140. diffract/session/utils.py +81 -0
  141. diffract/viz/__init__.py +42 -0
  142. diffract/viz/data/__init__.py +34 -0
  143. diffract/viz/data/detection.py +57 -0
  144. diffract/viz/data/extraction.py +117 -0
  145. diffract/viz/data/filtering.py +57 -0
  146. diffract/viz/data/ordering.py +129 -0
  147. diffract/viz/data/provider.py +99 -0
  148. diffract/viz/data/types.py +98 -0
  149. diffract/viz/plots/__init__.py +37 -0
  150. diffract/viz/plots/base/__init__.py +20 -0
  151. diffract/viz/plots/base/axis.py +219 -0
  152. diffract/viz/plots/base/coloraxis.py +133 -0
  153. diffract/viz/plots/base/configurator.py +18 -0
  154. diffract/viz/plots/base/jitter.py +368 -0
  155. diffract/viz/plots/base/line.py +197 -0
  156. diffract/viz/plots/base/marker.py +278 -0
  157. diffract/viz/plots/base/overlay.py +14 -0
  158. diffract/viz/plots/base/plot.py +110 -0
  159. diffract/viz/plots/base/update.py +50 -0
  160. diffract/viz/plots/boxplot.py +283 -0
  161. diffract/viz/plots/cluster.py +374 -0
  162. diffract/viz/plots/heatmap.py +168 -0
  163. diffract/viz/plots/scatter.py +233 -0
  164. diffract/viz/plots/sparkline.py +405 -0
  165. diffract/viz/plots/subplots/__init__.py +32 -0
  166. diffract/viz/plots/subplots/coloraxis.py +339 -0
  167. diffract/viz/plots/subplots/factory.py +713 -0
  168. diffract/viz/plots/subplots/grid.py +214 -0
  169. diffract/viz/plots/subplots/layout.py +370 -0
  170. diffract/viz/plots/subplots/spec.py +39 -0
  171. diffract/viz/plots/violin.py +298 -0
  172. diffract/viz/renderer.py +513 -0
  173. diffract/viz/styling/__init__.py +78 -0
  174. diffract/viz/styling/palettes/__init__.py +20 -0
  175. diffract/viz/styling/palettes/bundle.py +16 -0
  176. diffract/viz/styling/palettes/color.py +83 -0
  177. diffract/viz/styling/palettes/dashes.py +27 -0
  178. diffract/viz/styling/palettes/symbols.py +42 -0
  179. diffract/viz/styling/resolvers/__init__.py +11 -0
  180. diffract/viz/styling/resolvers/categorical.py +68 -0
  181. diffract/viz/styling/resolvers/color.py +77 -0
  182. diffract/viz/styling/resolvers/numeric.py +108 -0
  183. diffract/viz/styling/sources.py +27 -0
  184. diffract/viz/styling/theme/__init__.py +29 -0
  185. diffract/viz/styling/theme/applier.py +114 -0
  186. diffract/viz/styling/theme/components.py +66 -0
  187. diffract/viz/styling/theme/presets.py +58 -0
  188. diffract/viz/styling/theme/theme.py +36 -0
  189. diffract_core-0.2.1.dist-info/METADATA +530 -0
  190. diffract_core-0.2.1.dist-info/RECORD +193 -0
  191. diffract_core-0.2.1.dist-info/WHEEL +4 -0
  192. diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
  193. diffract_core-0.2.1.dist-info/licenses/NOTICE +2 -0
diffract/__init__.py ADDED
@@ -0,0 +1,52 @@
1
+ """Diffract: neural network weight analysis and evolution tracking.
2
+
3
+ Quick start::
4
+
5
+ from diffract import Session
6
+
7
+ # In-memory session (no persistence)
8
+ session = Session(profile="ram")
9
+
10
+ # Persistent local session (SQLite in .diffract/)
11
+ session = Session(profile="local")
12
+
13
+ # Or with a custom config file
14
+ session = Session(config_path="my_config.ini")
15
+
16
+ Available profiles: "ram", "local", "hybrid".
17
+ Use ``diffract.list_profiles()`` to see all options.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import importlib
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ __version__ = "0.2.1"
26
+
27
+ from .containers import list_profiles
28
+ from .core.data.nn.params.schema import ParameterType
29
+ from .session import ParameterOverrides, Session
30
+
31
+ if TYPE_CHECKING:
32
+ from . import viz as viz
33
+
34
+ __all__ = [
35
+ "ParameterOverrides",
36
+ "ParameterType",
37
+ "Session",
38
+ "__version__",
39
+ "list_profiles",
40
+ "viz",
41
+ ]
42
+
43
+
44
+ def __getattr__(name: str) -> Any:
45
+ if name == "viz":
46
+ return importlib.import_module(".viz", __name__)
47
+ msg = f"module {__name__!r} has no attribute {name!r}"
48
+ raise AttributeError(msg)
49
+
50
+
51
+ def __dir__() -> list[str]:
52
+ return sorted(__all__)
@@ -0,0 +1,48 @@
1
+ ; Fast configuration: RAM storage, no cache
2
+
3
+ [logging]
4
+ version = 1
5
+ disable_existing_loggers = false
6
+
7
+ [logging.formatters.standard]
8
+ format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
9
+ datefmt = "%Y-%m-%d %H:%M:%S"
10
+
11
+ [logging.handlers.console]
12
+ class = logging.StreamHandler
13
+ level = WARNING
14
+ formatter = standard
15
+ stream = ext://sys.stdout
16
+
17
+ [logging.loggers.diffract]
18
+ level = INFO
19
+ handlers = ["console"]
20
+ propagate = false
21
+
22
+ [logging.root]
23
+ level = WARNING
24
+ handlers = ["console"]
25
+
26
+ [storage]
27
+ backend = "ram"
28
+
29
+ [metadata]
30
+ backend = "sqlite"
31
+
32
+ [metadata.sqlite]
33
+ path = ":memory:"
34
+
35
+ [cache]
36
+ backend = "none"
37
+
38
+ [parallel.thread_pool]
39
+ max_workers = 8
40
+
41
+ [parallel.process_pool]
42
+ max_workers = 8
43
+
44
+ [nn.extractor]
45
+ skip_not_implemented_types = true
46
+
47
+ [export]
48
+ default_export_format = "dict"
@@ -0,0 +1,74 @@
1
+ [logging]
2
+ version = 1
3
+ disable_existing_loggers = false
4
+
5
+ [logging.formatters.standard]
6
+ format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
7
+ datefmt = "%Y-%m-%d %H:%M:%S"
8
+
9
+ [logging.handlers.console]
10
+ class = logging.StreamHandler
11
+ level = INFO
12
+ formatter = standard
13
+ stream = ext://sys.stdout
14
+
15
+ [logging.handlers.file]
16
+ class = logging.handlers.RotatingFileHandler
17
+ level = INFO
18
+ formatter = standard
19
+ filename = ".diffract/hybrid.log"
20
+ maxBytes = 1048576
21
+ backupCount = 1
22
+
23
+ [logging.loggers.diffract]
24
+ level = INFO
25
+ handlers = ["console", "file"]
26
+ propagate = false
27
+
28
+ [logging.root]
29
+ level = INFO
30
+ handlers = ["console", "file"]
31
+
32
+ [storage]
33
+ backend = "hybrid"
34
+
35
+ [storage.hybrid]
36
+ light = "sqlite"
37
+ heavy = "hdf5"
38
+ array_threshold = 1048576
39
+
40
+ [storage.sqlite]
41
+ path = ".diffract/hybrid/sqlite_storage.db"
42
+ blob_write_workers = 8
43
+ array_threshold = 1048576
44
+ read_mmap_size = 4294967296
45
+ write_mmap_size = 4294967296
46
+ batch_size_limit_bytes = 4294967296
47
+
48
+ [storage.hdf5]
49
+ path = ".diffract/hybrid/hdf5_storage.h5"
50
+ batch_size_limit_bytes = 4294967296
51
+
52
+ [metadata]
53
+ backend = "sqlite"
54
+
55
+ [metadata.sqlite]
56
+ path = ".diffract/hybrid/metadata_index.db"
57
+
58
+ [cache]
59
+ backend = "simple"
60
+
61
+ [cache.simple]
62
+ max_memory_mb = 128_000
63
+
64
+ [parallel.thread_pool]
65
+ max_workers = 8
66
+
67
+ [parallel.process_pool]
68
+ max_workers = 8
69
+
70
+ [nn.extractor]
71
+ skip_not_implemented_types = true
72
+
73
+ [export]
74
+ default_export_format = "pandas"
@@ -0,0 +1,62 @@
1
+ ; SQLite-backed storage configuration
2
+
3
+ [logging]
4
+ version = 1
5
+ disable_existing_loggers = false
6
+
7
+ [logging.formatters.standard]
8
+ format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
9
+ datefmt = "%Y-%m-%d %H:%M:%S"
10
+
11
+ [logging.handlers.console]
12
+ class = logging.StreamHandler
13
+ level = WARNING
14
+ formatter = standard
15
+ stream = ext://sys.stdout
16
+
17
+ [logging.handlers.file]
18
+ class = logging.handlers.RotatingFileHandler
19
+ level = INFO
20
+ formatter = standard
21
+ filename = ".diffract/sqlite.log"
22
+ maxBytes = 1048576
23
+ backupCount = 1
24
+
25
+ [logging.loggers.diffract]
26
+ level = DEBUG
27
+ handlers = ["console", "file"]
28
+ propagate = false
29
+
30
+ [logging.root]
31
+ level = DEBUG
32
+ handlers = ["console", "file"]
33
+
34
+ [storage]
35
+ backend = "sqlite"
36
+
37
+ [storage.sqlite]
38
+ path = ".diffract/sqlite/sqlite_storage.db"
39
+
40
+ [metadata]
41
+ backend = "sqlite"
42
+
43
+ [metadata.sqlite]
44
+ path = ".diffract/sqlite/metadata_index.db"
45
+
46
+ [cache]
47
+ backend = "simple"
48
+
49
+ [cache.simple]
50
+ max_memory_mb = 4096
51
+
52
+ [parallel.thread_pool]
53
+ max_workers = 8
54
+
55
+ [parallel.process_pool]
56
+ max_workers = 8
57
+
58
+ [nn.extractor]
59
+ skip_not_implemented_types = true
60
+
61
+ [export]
62
+ default_export_format = "pandas"
diffract/containers.py ADDED
@@ -0,0 +1,405 @@
1
+ """Main application dependency injection container.
2
+
3
+ This module provides the MainContainer class that orchestrates all subsystem
4
+ containers for the diffract application, including storage, cache, compute,
5
+ model parameters, and export functionality.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import configparser
11
+ import contextlib
12
+ import json
13
+ import logging
14
+ import os
15
+ import re
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from dependency_injector import containers, providers
20
+
21
+ from .core.cache.containers import CacheContainer
22
+ from .core.compute.containers import (
23
+ ComputeContainer,
24
+ ComputeSingletonContainer,
25
+ ComputeSingletonContainerWiringConfig,
26
+ )
27
+ from .core.data.metadata.containers import MetadataContainer
28
+ from .core.data.nn.containers import ModelParametersContainer
29
+ from .core.export.containers import ExportContainer
30
+ from .core.parallel import ParallelSingletonContainer
31
+ from .core.storage.containers import StorageContainer
32
+ from .core.utils.exceptions import format_exception_message
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Built-in configuration profiles for common use cases.
37
+ #
38
+ # Values are relative paths to INI configs shipped with the repo/package.
39
+ PROFILES: dict[str, str] = {
40
+ "ram": "configs/fast_speed_without_disk.ini",
41
+ "local": "configs/sqlite.ini",
42
+ "hybrid": "configs/hybrid.ini",
43
+ }
44
+
45
+
46
+ def list_profiles() -> list[str]:
47
+ """Return available profile names."""
48
+ return list(PROFILES.keys())
49
+
50
+
51
+ def _resolve_packaged_or_repo_path(relative_path: str) -> Path:
52
+ """Resolve a config path within package or repository layout."""
53
+ candidates = [
54
+ # Packaged layout: <site-packages>/diffract/<relative_path>
55
+ Path(__file__).resolve().parent / relative_path,
56
+ # Repo layout: <root>/<relative_path>
57
+ Path(__file__).resolve().parents[2] / relative_path,
58
+ ]
59
+ for candidate in candidates:
60
+ if candidate.exists():
61
+ return candidate
62
+ msg = (
63
+ f"Config file not found for relative path {relative_path!r}. "
64
+ f"Tried: {', '.join(str(c) for c in candidates)}"
65
+ )
66
+ raise FileNotFoundError(msg)
67
+
68
+
69
+ def get_profile_config(profile: str) -> dict[str, Any]:
70
+ """Get configuration dictionary for a profile."""
71
+ if profile not in PROFILES:
72
+ available = ", ".join(PROFILES.keys())
73
+ msg = f"Unknown profile '{profile}'. Available: {available}"
74
+ raise ValueError(msg)
75
+ config_path = _resolve_packaged_or_repo_path(PROFILES[profile])
76
+ return _parse_ini_config(config_path)
77
+
78
+
79
+ _FALLBACK_LOGGING_CONFIG: dict[str, Any] = {
80
+ "version": 1,
81
+ "disable_existing_loggers": False,
82
+ "formatters": {
83
+ "standard": {"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"}
84
+ },
85
+ "handlers": {
86
+ "console": {
87
+ "class": "logging.StreamHandler",
88
+ "level": "INFO",
89
+ "formatter": "standard",
90
+ "stream": "ext://sys.stdout",
91
+ }
92
+ },
93
+ "root": {"level": "INFO", "handlers": ["console"]},
94
+ }
95
+
96
+
97
+ def _expand_env_vars(value: str) -> str:
98
+ """Expand ${VAR} and $VAR patterns with environment variables."""
99
+
100
+ def replacer(match: re.Match[str]) -> str:
101
+ var_name = match.group(1) or match.group(2)
102
+ return os.environ.get(var_name, match.group(0))
103
+
104
+ return re.sub(r"\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)", replacer, value)
105
+
106
+
107
+ def _coerce_ini_value(value: str) -> Any:
108
+ """Convert an INI string value into a reasonable Python type.
109
+
110
+ INI files store values as strings; for service initialization configs we
111
+ accept basic scalar types and JSON literals for lists/dicts.
112
+ Supports ${VAR} syntax for environment variable expansion.
113
+ """
114
+ raw = _expand_env_vars(value.strip())
115
+ lowered = raw.lower()
116
+
117
+ if lowered in {"none", "null"}:
118
+ return None
119
+ if lowered in {"true", "false"}:
120
+ return lowered == "true"
121
+
122
+ # Try JSON first for explicit literals like lists/dicts/quoted strings.
123
+ if raw.startswith(("{", "[", '"')) or raw in {"0", "1"}:
124
+ with contextlib.suppress(json.JSONDecodeError):
125
+ return json.loads(raw)
126
+
127
+ # Numeric scalars (allow underscores).
128
+ int_candidate = raw.replace("_", "")
129
+ with contextlib.suppress(ValueError):
130
+ return int(int_candidate)
131
+
132
+ try:
133
+ return float(int_candidate)
134
+ except ValueError:
135
+ return raw
136
+
137
+
138
+ def _parse_ini_config(path: Path) -> dict[str, Any]:
139
+ """Parse an INI file into a nested dictionary.
140
+
141
+ Section names can use dot-notation to represent nesting, e.g.:
142
+ [storage.hdf5]
143
+ path=/tmp/main.h5
144
+ """
145
+ # Disable interpolation because logging formatters use "%(name)s" syntax.
146
+ parser = configparser.ConfigParser(interpolation=None)
147
+ # Preserve case for keys like "maxBytes" used by logging.config.dictConfig.
148
+ parser.optionxform = str
149
+ parser.read(path)
150
+
151
+ data: dict[str, Any] = {}
152
+ for section in parser.sections():
153
+ cursor: dict[str, Any] = data
154
+ for part in section.split("."):
155
+ cursor = cursor.setdefault(part, {})
156
+ for key, val in parser.items(section):
157
+ cursor[key] = _coerce_ini_value(val)
158
+ return data
159
+
160
+
161
+ def _default_config_path() -> Path | None:
162
+ """Return the default launch config path if present."""
163
+ with contextlib.suppress(FileNotFoundError):
164
+ return _resolve_packaged_or_repo_path(PROFILES["ram"])
165
+ return None
166
+
167
+
168
+ class MainContainer(containers.DeclarativeContainer):
169
+ """Main application container that orchestrates all subsystem containers.
170
+
171
+ This container manages the dependency injection for the entire application,
172
+ providing centralized configuration and wiring of all subsystem components
173
+ including storage, caching, compute kernels, model parameters, and export.
174
+ """
175
+
176
+ # Configuration
177
+ config = providers.Configuration()
178
+
179
+ # Logging configuration
180
+ logging_config = providers.Resource(
181
+ "logging.config.dictConfig",
182
+ config=config.logging,
183
+ )
184
+
185
+ # Subsystem containers
186
+ storage = providers.Container(
187
+ StorageContainer,
188
+ config=config.storage,
189
+ )
190
+
191
+ cache = providers.Container(
192
+ CacheContainer,
193
+ config=config.cache,
194
+ )
195
+
196
+ metadata = providers.Container(
197
+ MetadataContainer,
198
+ config=config.metadata,
199
+ )
200
+
201
+ compute_singleton = providers.Container(ComputeSingletonContainer)
202
+
203
+ parallel_singleton = providers.Container(
204
+ ParallelSingletonContainer,
205
+ config=config.parallel,
206
+ )
207
+
208
+ nn = providers.Container(
209
+ ModelParametersContainer,
210
+ storage_manager=storage.storage_manager_resource,
211
+ cache_manager=cache.cache_manager_resource,
212
+ metadata_index=metadata.metadata_index_resource,
213
+ parallel=parallel_singleton.thread_pool_context,
214
+ config=config.nn,
215
+ )
216
+
217
+ compute = providers.Container(
218
+ ComputeContainer,
219
+ config=config.compute,
220
+ compute_singleton=compute_singleton,
221
+ parallel=parallel_singleton.thread_pool_context,
222
+ process_pool=parallel_singleton.process_pool_context,
223
+ aggregate_repository=nn.aggregate_repository,
224
+ )
225
+
226
+ export = providers.Container(
227
+ ExportContainer,
228
+ config=config.export,
229
+ )
230
+
231
+
232
+ class WiringConfiguration:
233
+ """Complete application wiring configuration.
234
+
235
+ Manages the dependency injection wiring for all containers in the application,
236
+ ensuring proper module and package registration for dependency resolution.
237
+ """
238
+
239
+ @classmethod
240
+ def get_wiring_configs(cls) -> list[dict[str, Any]]:
241
+ """Get all wiring configurations for the application.
242
+
243
+ Returns:
244
+ List of dictionaries containing container, modules, and packages
245
+ configuration for each subsystem that requires wiring.
246
+ """
247
+ return [
248
+ {
249
+ "container": ComputeSingletonContainer,
250
+ "modules": ComputeSingletonContainerWiringConfig.modules,
251
+ "packages": ComputeSingletonContainerWiringConfig.packages,
252
+ },
253
+ ]
254
+
255
+ @classmethod
256
+ def wire(cls, container: MainContainer) -> None:
257
+ """Wire the entire application with all subsystem containers.
258
+
259
+ Args:
260
+ container: The main application container to wire.
261
+
262
+ Raises:
263
+ Exception: If wiring fails for any container or module.
264
+ """
265
+ try:
266
+ # Wire main container
267
+ container.wire(
268
+ modules=[
269
+ __name__,
270
+ "diffract.session",
271
+ "diffract.session.namespaces",
272
+ "diffract.session.namespaces.models",
273
+ ],
274
+ )
275
+
276
+ # Wire subsystem containers
277
+ for config in cls.get_wiring_configs():
278
+ container_cls = config["container"]
279
+ parts = re.findall(r"[A-Z][^A-Z]*", container_cls.__name__)
280
+ parts = [p.lower() for p in parts]
281
+ parts.remove("container")
282
+ container_name = "_".join(parts)
283
+ container_instance = getattr(container, container_name)
284
+
285
+ if config.get("modules"):
286
+ container_instance.wire(modules=config["modules"])
287
+
288
+ if config.get("packages"):
289
+ container_instance.wire(packages=config["packages"])
290
+
291
+ logger.debug("Successfully wired all application containers")
292
+
293
+ except Exception:
294
+ logger.exception("Failed to wire application containers")
295
+ raise
296
+
297
+
298
+ compute_singleton_container = ComputeSingletonContainer()
299
+
300
+
301
+ def create_main_container(
302
+ config_path: str | Path | None = None,
303
+ profile: str | None = None,
304
+ ) -> MainContainer:
305
+ """Create and configure the main application container.
306
+
307
+ Args:
308
+ config_path: Path to configuration file (YAML/JSON/INI). Overrides
309
+ profile settings if both are provided.
310
+ profile: Built-in profile name ("ram", "local", "hybrid").
311
+
312
+ Returns:
313
+ Configured MainContainer instance with all subsystems wired
314
+ and default kernels registered.
315
+
316
+ Raises:
317
+ ValueError: If profile name is not recognized.
318
+ Exception: If container creation or wiring fails.
319
+ """
320
+ container = MainContainer(
321
+ compute_singleton=compute_singleton_container,
322
+ )
323
+
324
+ repo_root = Path(__file__).resolve().parents[2]
325
+ cwd = Path.cwd()
326
+
327
+ config_loaded = False
328
+
329
+ if config_path is not None:
330
+ config_path = Path(config_path)
331
+ if config_path.exists():
332
+ suffix = config_path.suffix.lower()
333
+ match suffix:
334
+ case ".yaml" | ".yml":
335
+ container.config.from_yaml(str(config_path))
336
+ case ".json":
337
+ container.config.from_json(str(config_path))
338
+ case ".ini":
339
+ container.config.from_dict(_parse_ini_config(config_path))
340
+ case _:
341
+ msg = f"Unsupported config file extension: '{suffix}'"
342
+ raise ValueError(msg)
343
+ logger.info("Using config file: %s", config_path)
344
+ config_loaded = True
345
+ else:
346
+ logger.warning("Configuration file not found: %s", config_path)
347
+
348
+ if not config_loaded and profile is not None:
349
+ profile_config = get_profile_config(profile)
350
+ container.config.from_dict(profile_config)
351
+ logger.info("Using profile: %s", profile)
352
+ config_loaded = True
353
+
354
+ if not config_loaded:
355
+ default_path = _default_config_path()
356
+ if default_path is not None:
357
+ container.config.from_dict(_parse_ini_config(default_path))
358
+ logger.debug("Loaded default configuration from %s", default_path)
359
+
360
+ # Resolve relative file paths to absolute paths.
361
+ base_path = cwd if profile is not None else repo_root
362
+ resolved = container.config()
363
+ for key in ("path", "filename"):
364
+ stack: list[dict[str, Any]] = [resolved]
365
+ while stack:
366
+ cur = stack.pop()
367
+ for k, v in list(cur.items()):
368
+ if isinstance(v, dict):
369
+ stack.append(v)
370
+ continue
371
+ if k != key or not isinstance(v, str):
372
+ continue
373
+ value = v.strip().strip('"')
374
+ if value.startswith(("ext://", "/", "~")):
375
+ continue
376
+ cur[k] = str(base_path / value)
377
+ container.config.from_dict(resolved)
378
+
379
+ # Configs without a [logging] section get the fallback silently.
380
+ if not resolved.get("logging"):
381
+ container.config.logging.override(_FALLBACK_LOGGING_CONFIG)
382
+
383
+ # logging.config.dictConfig cannot create missing log directories itself.
384
+ for handler in resolved.get("logging", {}).get("handlers", {}).values():
385
+ filename = handler.get("filename") if isinstance(handler, dict) else None
386
+ if isinstance(filename, str):
387
+ Path(filename.strip().strip('"')).parent.mkdir(parents=True, exist_ok=True)
388
+
389
+ try:
390
+ container.logging_config()
391
+ except Exception as e: # noqa: BLE001
392
+ logger.warning(
393
+ "Failed to configure logging; using fallback config: %s",
394
+ format_exception_message(e),
395
+ )
396
+ logger.debug("Logging configuration failure details", exc_info=True)
397
+ container.config.logging.override(_FALLBACK_LOGGING_CONFIG)
398
+ container.logging_config()
399
+
400
+ # Wire all containers
401
+ WiringConfiguration.wire(container)
402
+
403
+ container.compute_singleton.register_default_kernels()
404
+
405
+ return container
@@ -0,0 +1,23 @@
1
+ """Core module for diffract."""
2
+
3
+ from diffract.core.constants import (
4
+ CONTEXT_SEPARATOR,
5
+ HDF5_INDEX_DATASET,
6
+ HDF5_INDEX_GROUP,
7
+ HDF5_INDEX_TOMBSTONE,
8
+ MODELS_CONTEXT_PREFIX,
9
+ PARAMS_CONTEXT_PREFIX,
10
+ REGEX_PREFIX,
11
+ WEIGHTS_FIELD,
12
+ )
13
+
14
+ __all__ = [
15
+ "CONTEXT_SEPARATOR",
16
+ "HDF5_INDEX_DATASET",
17
+ "HDF5_INDEX_GROUP",
18
+ "HDF5_INDEX_TOMBSTONE",
19
+ "MODELS_CONTEXT_PREFIX",
20
+ "PARAMS_CONTEXT_PREFIX",
21
+ "REGEX_PREFIX",
22
+ "WEIGHTS_FIELD",
23
+ ]
@@ -0,0 +1,24 @@
1
+ """Cache management module providing flexible caching backends.
2
+
3
+ This module offers a unified interface for caching with support for multiple
4
+ backends including Redis and in-memory implementations. It uses dependency
5
+ injection for flexible configuration and backend selection.
6
+
7
+ Example:
8
+ >>> from diffract.core.cache import CacheContainer
9
+ >>> container = CacheContainer()
10
+ >>> cache_manager = container.cache_manager()
11
+ """
12
+
13
+ from .containers import CacheContainer
14
+ from .interface import UID, ICacheManager
15
+ from .redis_manager import RedisLRUCacheManager
16
+ from .simple_manager import SimpleLRUCacheManager
17
+
18
+ __all__ = [
19
+ "UID",
20
+ "CacheContainer",
21
+ "ICacheManager",
22
+ "RedisLRUCacheManager",
23
+ "SimpleLRUCacheManager",
24
+ ]