LoopStructural 1.6.15__py3-none-any.whl → 1.6.17__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 LoopStructural might be problematic. Click here for more details.

@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from contextlib import contextmanager
5
+ from typing import Any, Generic, Protocol, TypeAlias, TypeVar, runtime_checkable
6
+ import threading
7
+ import weakref
8
+
9
+ __all__ = ["Observer", "Observable", "Disposable"]
10
+
11
+
12
+ @runtime_checkable
13
+ class Observer(Protocol):
14
+ """Objects implementing an *update* method can subscribe."""
15
+
16
+ def update(self, observable: "Observable", event: str, *args: Any, **kwargs: Any) -> None:
17
+ """Receive a notification."""
18
+
19
+
20
+ Callback: TypeAlias = Callable[["Observable", str, Any], None]
21
+ T = TypeVar("T", bound="Observable")
22
+
23
+
24
+ class Disposable:
25
+ """A small helper that detaches an observer when disposed."""
26
+
27
+ __slots__ = ("_detach",)
28
+
29
+ def __init__(self, detach: Callable[[], None]):
30
+ self._detach = detach
31
+
32
+ def dispose(self) -> None:
33
+ """Detach the associated observer immediately."""
34
+
35
+ self._detach()
36
+
37
+ # Allow use as a context‑manager for temporary subscriptions
38
+ def __enter__(self) -> "Disposable":
39
+ return self
40
+
41
+ def __exit__(self, exc_type, exc, tb):
42
+ self.dispose()
43
+ return False # do not swallow exceptions
44
+
45
+
46
+ class Observable(Generic[T]):
47
+ """Base‑class that provides Observer pattern plumbing."""
48
+
49
+ #: Internal storage: mapping *event* → WeakSet[Callback]
50
+ _observers: dict[str, weakref.WeakSet[Callback]]
51
+ _any_observers: weakref.WeakSet[Callback]
52
+
53
+ def __init__(self) -> None:
54
+ self._lock = threading.RLock()
55
+ self._observers = {}
56
+ self._any_observers = weakref.WeakSet()
57
+ self._frozen = 0
58
+ self._pending: list[tuple[str, tuple[Any, ...], dict[str, Any]]] = []
59
+
60
+ # ‑‑‑ subscription api --------------------------------------------------
61
+ def attach(self, listener: Observer | Callback, event: str | None = None) -> Disposable:
62
+ """Register *listener* for *event* (all events if *event* is None).
63
+
64
+ Returns a :class:`Disposable` so the caller can easily detach again.
65
+ """
66
+ callback: Callback = (
67
+ listener.update # type: ignore[attr‑defined]
68
+ if isinstance(listener, Observer) # type: ignore[misc]
69
+ else listener # already a callable
70
+ )
71
+
72
+ with self._lock:
73
+ if event is None:
74
+ self._any_observers.add(callback)
75
+ else:
76
+ self._observers.setdefault(event, weakref.WeakSet()).add(callback)
77
+
78
+ return Disposable(lambda: self.detach(listener, event))
79
+
80
+ def detach(self, listener: Observer | Callback, event: str | None = None) -> None:
81
+ """Unregister a previously attached *listener*."""
82
+
83
+ callback: Callback = (
84
+ listener.update # type: ignore[attr‑defined]
85
+ if isinstance(listener, Observer) # type: ignore[misc]
86
+ else listener
87
+ )
88
+
89
+ with self._lock:
90
+ if event is None:
91
+ self._any_observers.discard(callback)
92
+ for s in self._observers.values():
93
+ s.discard(callback)
94
+ else:
95
+ self._observers.get(event, weakref.WeakSet()).discard(callback)
96
+ def __getstate__(self):
97
+ state = self.__dict__.copy()
98
+ state.pop('_lock', None) # RLock cannot be pickled
99
+ state.pop('_observers', None) # WeakSet cannot be pickled
100
+ state.pop('_any_observers', None)
101
+ return state
102
+ def __setstate__(self, state):
103
+ self.__dict__.update(state)
104
+ self._lock = threading.RLock()
105
+ self._observers = {}
106
+ self._any_observers = weakref.WeakSet()
107
+ self._frozen = 0
108
+ # ‑‑‑ notification api --------------------------------------------------
109
+ def notify(self: T, event: str, *args: Any, **kwargs: Any) -> None:
110
+ """Notify observers that *event* happened."""
111
+
112
+ with self._lock:
113
+ if self._frozen:
114
+ # defer until freeze_notifications() exits
115
+ self._pending.append((event, args, kwargs))
116
+ return
117
+
118
+ observers = list(self._any_observers)
119
+ observers.extend(self._observers.get(event, ()))
120
+
121
+ # Call outside lock — prevent deadlocks if observers trigger other
122
+ # notifications.
123
+ for cb in observers:
124
+ try:
125
+ cb(self, event, *args, **kwargs)
126
+ except Exception: # pragma: no cover
127
+ # Optionally log; never allow an observer error to break flow.
128
+ import logging
129
+
130
+ logging.getLogger(__name__).exception(
131
+ "Unhandled error in observer %s for event %s", cb, event
132
+ )
133
+
134
+ # ‑‑‑ batching ----------------------------------------------------------
135
+ @contextmanager
136
+ def freeze_notifications(self):
137
+ """Context manager that batches notifications until exit."""
138
+
139
+ with self._lock:
140
+ self._frozen += 1
141
+ try:
142
+ yield self
143
+ finally:
144
+ with self._lock:
145
+ self._frozen -= 1
146
+ if self._frozen == 0 and self._pending:
147
+ pending = self._pending[:]
148
+ self._pending.clear()
149
+ for event, args, kw in pending: # type: ignore[has‑type]
150
+ self.notify(event, *args, **kw)
LoopStructural/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.6.15"
1
+ __version__ = "1.6.17"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: LoopStructural
3
- Version: 1.6.15
3
+ Version: 1.6.17
4
4
  Summary: 3D geological modelling
5
5
  Author-email: Lachlan Grose <lachlan.grose@monash.edu>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
- LoopStructural/__init__.py,sha256=fg_Vm1aMDYIf_CffTFopLsTx21u6deLaI7JMVpRYdOI,1378
2
- LoopStructural/version.py,sha256=Pie9fpkEEQtWduVE5VudTkNuOKHw2-cSLQQzkG-8ENk,23
1
+ LoopStructural/__init__.py,sha256=ZS5J2TI2OuhRE0o3EXmPbbjrvh1-Vy9RFwL7Hc1YBow,2079
2
+ LoopStructural/version.py,sha256=RFX1x9AQ2Z9aP2gCXAUVtaLq0UOmist-rpJN8M8jz9I,23
3
3
  LoopStructural/datasets/__init__.py,sha256=ylb7fzJU_DyQ73LlwQos7VamqkDSGITbbnoKg7KAOmE,677
4
4
  LoopStructural/datasets/_base.py,sha256=FB_D5ybBYHoaNbycdkpZcRffzjrrL1xp9X0k-pyob9Y,7618
5
5
  LoopStructural/datasets/_example_models.py,sha256=Zg33IeUyh4C-lC0DRMLqCDP2IrX8L-gNV1WxJwBGjzM,113
@@ -45,7 +45,7 @@ LoopStructural/interpolators/_constant_norm.py,sha256=gGaDGDoEzfnL4b6386YwInCxIA
45
45
  LoopStructural/interpolators/_discrete_fold_interpolator.py,sha256=eDe0R1lcQ0AuMcv7zlpu5c-soCv7AybIqQAuN2vFE3M,6542
46
46
  LoopStructural/interpolators/_discrete_interpolator.py,sha256=bPGJ1CrvLmz3m86JkXAiw7WbfbGEeGXR5cklDX54PQU,26083
47
47
  LoopStructural/interpolators/_finite_difference_interpolator.py,sha256=qc7zpqJka16I7yv-GigjQxF0hWRRHyWpHm8dHersy_8,18712
48
- LoopStructural/interpolators/_geological_interpolator.py,sha256=BXUJD1OrSbgZbJwe884FDkew0m1XxiG1mtY5Og_CFJE,11196
48
+ LoopStructural/interpolators/_geological_interpolator.py,sha256=74tQUImZ9axkXUrWygl6vqspY3bsoHf_d3twVB30rho,11430
49
49
  LoopStructural/interpolators/_interpolator_builder.py,sha256=Z8bhmco5aSQX19A8It2SB_rG61wnlyshWfp3ivm8rU0,4586
50
50
  LoopStructural/interpolators/_interpolator_factory.py,sha256=fbjebXSe5IgTol1tnBlnsw9gD426v-TGkX3gquIg7LI,2782
51
51
  LoopStructural/interpolators/_interpolatortype.py,sha256=q8U9JGyFpO2FBA9XsMI5ojv3TV1LYqyvYHzLAbHcj9A,593
@@ -71,7 +71,9 @@ LoopStructural/interpolators/supports/_face_table.py,sha256=Hyj4Io63NkPRN8ab9uDH
71
71
  LoopStructural/interpolators/supports/_support_factory.py,sha256=XNAxnr-JS3KEhdsoZeJ-VaLTJwlvxgBuRMCqYrCDW18,1485
72
72
  LoopStructural/modelling/__init__.py,sha256=a-bq2gDhyUlcky5l9kl_IP3ExMdohkgYjQz2V8madQE,902
73
73
  LoopStructural/modelling/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
- LoopStructural/modelling/core/geological_model.py,sha256=2NIVrPHu_8rx3Cgp9oPgFazBWHUI0dLs7vWFngfN3g0,65272
74
+ LoopStructural/modelling/core/fault_topology.py,sha256=bChp5dnfc-4GJRENWxB14mEW_13uBMh5ZYRKbLdjweE,11195
75
+ LoopStructural/modelling/core/geological_model.py,sha256=23ZQF2Xvs-PeRdrxt-IqrkVaMDWud_JjW00ok2tKs2k,65850
76
+ LoopStructural/modelling/core/stratigraphic_column.py,sha256=GB93W607LY3d_NE75lzwBWrV34YhHN_troCXWuUMBk8,18070
75
77
  LoopStructural/modelling/features/__init__.py,sha256=Vf-qd5EDBtJ1DpuXXyCcw2-wf6LWPRW5wzxDEO3vOc8,939
76
78
  LoopStructural/modelling/features/_analytical_feature.py,sha256=U_g86LgQhYY2359rdsDqpvziYwqrWkc5EdvhJARiUWo,3597
77
79
  LoopStructural/modelling/features/_base_geological_feature.py,sha256=kGyrbb8nNzfi-M8WSrVMEQYKtxThdcBxaji5HKXtAqw,13483
@@ -84,14 +86,14 @@ LoopStructural/modelling/features/_structural_frame.py,sha256=e3QmNHLwuZc5PX3rLa
84
86
  LoopStructural/modelling/features/_unconformity_feature.py,sha256=2Bx0BI38YLdcNvDWuP9E1pKFN4orEUq9aC8b5xG1UVk,2362
85
87
  LoopStructural/modelling/features/builders/__init__.py,sha256=Gqld1C-PcaXfJ8vpkWMDCmehmd3hZNYQk1knPtl59Bk,266
86
88
  LoopStructural/modelling/features/builders/_base_builder.py,sha256=N3txGC98V08A8-k2TLdoIWgWLfblZ91kaTvciPq_QVM,3750
87
- LoopStructural/modelling/features/builders/_fault_builder.py,sha256=CeQnvgDrgMIbyPV6nB0qnpY5PJG1OYTJIukRXv4df1E,25324
89
+ LoopStructural/modelling/features/builders/_fault_builder.py,sha256=_DZ0Hy_-jjm2fFU-5lY60zGisixdUWbAjsOQzMFKigY,25359
88
90
  LoopStructural/modelling/features/builders/_folded_feature_builder.py,sha256=1_0BVTzcvmFl6K3_lX-jF0tiMFPmS8j6vPeSLn9MbrE,6607
89
91
  LoopStructural/modelling/features/builders/_geological_feature_builder.py,sha256=tQJJol1U5wH6V0Rw3OgigCFPssv8uOPQ5jdwdLFg3cc,22015
90
92
  LoopStructural/modelling/features/builders/_structural_frame_builder.py,sha256=ms3-fuFpDEarjzYU5W499TquOIlTwHPUibVxIypfmWY,8019
91
93
  LoopStructural/modelling/features/fault/__init__.py,sha256=4u0KfYzmoO-ddFGo9qd9ov0gBoLqBiPAUsaw5zhEOAQ,189
92
94
  LoopStructural/modelling/features/fault/_fault_function.py,sha256=QEPh2jIvgD68hEJc5SM5xuMzZw-93V1me1ZbK9G2TB0,12655
93
95
  LoopStructural/modelling/features/fault/_fault_function_feature.py,sha256=4m0jVNx7ewrVI0pECI1wNciv8Cy8FzhZrYDjKJ_e2GU,2558
94
- LoopStructural/modelling/features/fault/_fault_segment.py,sha256=dNTCY0ZyC8krrL1suSnhywSE_i5V_VZ4DJ2BieirkhI,18305
96
+ LoopStructural/modelling/features/fault/_fault_segment.py,sha256=BEIVAY_-iQYYuoyIj1doq_cDLgmMpY0PDYBiuBXOjN8,18309
95
97
  LoopStructural/modelling/features/fold/__init__.py,sha256=pOv20yQvshZozvmO_YFw2E7Prp9DExlm855N-0SnxbQ,175
96
98
  LoopStructural/modelling/features/fold/_fold.py,sha256=bPnnLUSiF4uoMRg8aHoOSTPRgaM0JyLoRQPu5_A-J3w,5448
97
99
  LoopStructural/modelling/features/fold/_fold_rotation_angle_feature.py,sha256=CXLbFRQ3CrTMAcHmfdbKcmSvvLs9_6TLe0Wqi1pK2tg,892
@@ -114,8 +116,8 @@ LoopStructural/modelling/intrusions/intrusion_builder.py,sha256=PtNLDreUZTGodMwt
114
116
  LoopStructural/modelling/intrusions/intrusion_feature.py,sha256=ESjtikHFJQzUnowbYiY7UZ_kYdV2QHobQoRJ2far9Vc,15489
115
117
  LoopStructural/modelling/intrusions/intrusion_frame_builder.py,sha256=YEJv2GURAL8bW6J1KscM69ZVx9yoKVQKF_gXbjB513I,40150
116
118
  LoopStructural/modelling/intrusions/intrusion_support_functions.py,sha256=wodakheMD62WJyoKnyX8UO-C1pje0I-5kHQEoDqShzo,13951
117
- LoopStructural/utils/__init__.py,sha256=t-vJQ0cF2DrjSRtAfuPEL4hc73XJyQno7PucBnd-fu8,950
118
- LoopStructural/utils/_surface.py,sha256=Eg7x1GGfELl7bPe21_wU96Dn4JWJNReEFxwq-aIV4A4,6165
119
+ LoopStructural/utils/__init__.py,sha256=n-3PGnV-KEy4LOJkHZ6nlnoDyKDlfG6rJhXM0OgFYm0,1004
120
+ LoopStructural/utils/_surface.py,sha256=M0ZYujrqu-pWz-Y8iAhZsgoDxvI5YZG4GyhtBHdbCfk,6389
119
121
  LoopStructural/utils/_transformation.py,sha256=peuLPH3BJ5DxnPbOuNKcqK4eXhAXdbT540L1OIsO3v0,5404
120
122
  LoopStructural/utils/colours.py,sha256=-KRf1MXKx4L8TXnwyiunmKAX4tfy0qG68fRadyfn_bM,1163
121
123
  LoopStructural/utils/config.py,sha256=ITGOtZTo2_QBwXkG_0AFANfE90J9siCXLzxypVmg9QA,414
@@ -127,12 +129,13 @@ LoopStructural/utils/json_encoder.py,sha256=5YNouf1TlhjEqOYgthd07MRXc0JLgxern-ny
127
129
  LoopStructural/utils/linalg.py,sha256=tBXyu6NXcG2AcPuzUMnkVI4ncZWtE_MPHGj2PLXRwfY,123
128
130
  LoopStructural/utils/logging.py,sha256=dIUWEsS2lT4G1dsf4ZYXknTR7eQkrgvGA4b_E0vMIRU,2402
129
131
  LoopStructural/utils/maths.py,sha256=KaLj9RHsxdaSkEHm4t0JEzykhiuETAV14KpjL6lknWY,10374
132
+ LoopStructural/utils/observer.py,sha256=BjoL2-DydNWF1QDfFNd7TQIlRbKus_JNBfqX2ZFDAek,5327
130
133
  LoopStructural/utils/regions.py,sha256=SjCC40GI7_n03G4mlcmvyrBgJFbxnvB3leBzXWco37o,3891
131
134
  LoopStructural/utils/typing.py,sha256=29uVSTZdzXXH-jdlaYyBWZ1gQ2-nlZ2-XoVgG_PXNFY,157
132
135
  LoopStructural/utils/utils.py,sha256=2Z4zVE6G752-SPmM29zebk82bROJxEwi_YiiJjcVED4,2438
133
136
  LoopStructural/visualisation/__init__.py,sha256=5BDgKor8-ae6DrS7IZybJ3Wq_pTnCchxuY4EgzA7v1M,318
134
- loopstructural-1.6.15.dist-info/licenses/LICENSE,sha256=ZqGeNFOgmYevj7Ld7Q-kR4lAxWXuBRUdUmPC6XM_py8,1071
135
- loopstructural-1.6.15.dist-info/METADATA,sha256=2aOi3Osgig1KIQcpmKAyTDOJbo1DksD-mA3SICfAbI0,6453
136
- loopstructural-1.6.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
- loopstructural-1.6.15.dist-info/top_level.txt,sha256=QtQErKzYHfg6ddxTQ1NyaTxXBVM6qAqrM_vxEPyXZLg,15
138
- loopstructural-1.6.15.dist-info/RECORD,,
137
+ loopstructural-1.6.17.dist-info/licenses/LICENSE,sha256=ZqGeNFOgmYevj7Ld7Q-kR4lAxWXuBRUdUmPC6XM_py8,1071
138
+ loopstructural-1.6.17.dist-info/METADATA,sha256=Io5NikqqA3SupAKqkEtWRytgor6JPn9c5nV8L0FzQLw,6453
139
+ loopstructural-1.6.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
140
+ loopstructural-1.6.17.dist-info/top_level.txt,sha256=QtQErKzYHfg6ddxTQ1NyaTxXBVM6qAqrM_vxEPyXZLg,15
141
+ loopstructural-1.6.17.dist-info/RECORD,,