geolysis 0.4.3__py3-none-any.whl → 0.4.5__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.
geolysis/spt.py CHANGED
@@ -9,6 +9,7 @@ Enums
9
9
 
10
10
  HammerType
11
11
  SamplerType
12
+ OPCType
12
13
 
13
14
  Classes
14
15
  =======
@@ -24,13 +25,20 @@ Classes
24
25
  LiaoWhitmanOPC
25
26
  SkemptonOPC
26
27
  DilatancyCorrection
28
+
29
+ Functions
30
+ =========
31
+
32
+ .. autosummary::
33
+ :toctree: _autosummary
34
+
35
+ create_spt_correction
27
36
  """
28
37
  import enum
29
38
  from abc import abstractmethod
30
39
  from typing import Final, Sequence
31
40
 
32
- from geolysis.utils import isclose, log10, mean, round_, sqrt, validators, \
33
- enum_repr
41
+ from .utils import enum_repr, isclose, log10, mean, round_, sqrt, validators
34
42
 
35
43
  __all__ = ["SPTNDesign",
36
44
  "HammerType",
@@ -66,6 +74,7 @@ class SPTNDesign:
66
74
 
67
75
  @property
68
76
  def corrected_spt_n_values(self) -> Sequence[float]:
77
+ """Corrected SPT N-values within the foundation influence zone."""
69
78
  return self._corrected_spt_n_values
70
79
 
71
80
  @corrected_spt_n_values.setter
@@ -148,17 +157,15 @@ class EnergyCorrection:
148
157
  ``ENERGY``: 0.6, 0.55, etc
149
158
  """
150
159
 
151
- #: Hammer efficiency factors
152
- HAMMER_EFFICIENCY_FACTORS = {HammerType.AUTOMATIC: 0.70,
153
- HammerType.DONUT_1: 0.60,
154
- HammerType.DONUT_2: 0.50,
155
- HammerType.SAFETY: 0.55,
156
- HammerType.DROP: 0.45,
157
- HammerType.PIN: 0.45}
160
+ _HAMMER_EFFICIENCY_FACTORS = {HammerType.AUTOMATIC: 0.70,
161
+ HammerType.DONUT_1: 0.60,
162
+ HammerType.DONUT_2: 0.50,
163
+ HammerType.SAFETY: 0.55,
164
+ HammerType.DROP: 0.45,
165
+ HammerType.PIN: 0.45}
158
166
 
159
- #: Sampler correction factors
160
- SAMPLER_CORRECTION_FACTORS = {SamplerType.STANDARD: 1.00,
161
- SamplerType.NON_STANDARD: 1.20}
167
+ _SAMPLER_CORRECTION_FACTORS = {SamplerType.STANDARD: 1.00,
168
+ SamplerType.NON_STANDARD: 1.20}
162
169
 
163
170
  def __init__(self, recorded_spt_n_value: int, *,
164
171
  energy_percentage=0.6,
@@ -174,10 +181,10 @@ class EnergyCorrection:
174
181
  sampler, defaults to 0.6
175
182
  :type energy_percentage: float, optional
176
183
 
177
- :param borehole_diameter: Borehole diameter, defaults to 65.0. (mm)
184
+ :param borehole_diameter: Borehole diameter (mm), defaults to 65.0.
178
185
  :type borehole_diameter: float, optional
179
186
 
180
- :param rod_length: Rod length, defaults to 3.0. (m)
187
+ :param rod_length: Length of SPT rod, defaults to 3.0. (m)
181
188
  :type rod_length: float, optional
182
189
 
183
190
  :param hammer_type: Hammer type, defaults to :attr:`HammerType.DONUT_1`
@@ -195,6 +202,7 @@ class EnergyCorrection:
195
202
 
196
203
  @property
197
204
  def recorded_spt_n_value(self) -> int:
205
+ """Recorded SPT N-value from field."""
198
206
  return self._recorded_spt_value
199
207
 
200
208
  @recorded_spt_n_value.setter
@@ -205,6 +213,7 @@ class EnergyCorrection:
205
213
 
206
214
  @property
207
215
  def energy_percentage(self) -> float:
216
+ """Energy percentage reaching the tip of the sampler."""
208
217
  return self._energy_percentage
209
218
 
210
219
  @energy_percentage.setter
@@ -215,6 +224,7 @@ class EnergyCorrection:
215
224
 
216
225
  @property
217
226
  def borehole_diameter(self) -> float:
227
+ """Borehole diameter (mm)."""
218
228
  return self._borehole_diameter
219
229
 
220
230
  @borehole_diameter.setter
@@ -225,6 +235,7 @@ class EnergyCorrection:
225
235
 
226
236
  @property
227
237
  def rod_length(self) -> float:
238
+ """Length of SPT rod."""
228
239
  return self._rod_length
229
240
 
230
241
  @rod_length.setter
@@ -235,7 +246,7 @@ class EnergyCorrection:
235
246
  @property
236
247
  def hammer_efficiency(self) -> float:
237
248
  """Hammer efficiency correction factor."""
238
- return self.HAMMER_EFFICIENCY_FACTORS[self.hammer_type]
249
+ return self._HAMMER_EFFICIENCY_FACTORS[self.hammer_type]
239
250
 
240
251
  @property
241
252
  def borehole_diameter_correction(self) -> float:
@@ -251,7 +262,7 @@ class EnergyCorrection:
251
262
  @property
252
263
  def sampler_correction(self) -> float:
253
264
  """Sampler correction factor."""
254
- return self.SAMPLER_CORRECTION_FACTORS[self.sampler_type]
265
+ return self._SAMPLER_CORRECTION_FACTORS[self.sampler_type]
255
266
 
256
267
  @property
257
268
  def rod_length_correction(self) -> float:
@@ -275,8 +286,8 @@ class EnergyCorrection:
275
286
  return numerator / self.energy_percentage
276
287
 
277
288
  @round_(ndigits=1)
278
- def corrected_spt_n_value(self) -> float:
279
- """Corrected SPT N-value."""
289
+ def standardized_spt_n_value(self) -> float:
290
+ """Standardized SPT N-value."""
280
291
  return self.correction() * self.recorded_spt_n_value
281
292
 
282
293
 
@@ -296,6 +307,7 @@ class OPC:
296
307
 
297
308
  @property
298
309
  def std_spt_n_value(self) -> float:
310
+ """SPT N-value standardized for field procedures."""
299
311
  return self._std_spt_n_value
300
312
 
301
313
  @std_spt_n_value.setter
@@ -305,7 +317,18 @@ class OPC:
305
317
 
306
318
  @round_(ndigits=1)
307
319
  def corrected_spt_n_value(self) -> float:
308
- """Corrected SPT N-value."""
320
+ r"""Corrected SPT N-value.
321
+
322
+ :Equation:
323
+
324
+ .. math:: (N_1)_{60} = C_N \cdot N_{60}
325
+
326
+ .. note::
327
+
328
+ ``60`` is used in this case to represent ``60%`` hammer efficiency
329
+ and can be any percentage of hammer efficiency e.g :math:`N_{55}`
330
+ for ``55%`` hammer efficiency.
331
+ """
309
332
  corrected_spt = self.correction() * self.std_spt_n_value
310
333
  # Corrected SPT should not be more
311
334
  # than 2 times the Standardized SPT
@@ -317,20 +340,11 @@ class OPC:
317
340
 
318
341
 
319
342
  class GibbsHoltzOPC(OPC):
320
- r"""Overburden Pressure Correction according to ``Gibbs & Holtz (1957)``.
321
-
322
- :Equation:
323
-
324
- .. math:: C_N = \dfrac{350}{\sigma_o + 70} \, \sigma_o \le 280kN/m^2
325
-
326
- :math:`\frac{N_c}{N_{60}}` should lie between 0.45 and 2.0, if
327
- :math:`\frac{N_c}{N_{60}}` is greater than 2.0, :math:`N_c` should be
328
- divided by 2.0 to obtain the design value used in finding the bearing
329
- capacity of the soil.
330
- """
343
+ """Overburden Pressure Correction according to ``Gibbs & Holtz (1957)``."""
331
344
 
332
345
  @property
333
346
  def eop(self) -> float:
347
+ """Effective overburden pressure (:math:`kpa`)."""
334
348
  return self._eop
335
349
 
336
350
  @eop.setter
@@ -340,25 +354,24 @@ class GibbsHoltzOPC(OPC):
340
354
  self._eop = val
341
355
 
342
356
  def correction(self) -> float:
343
- """SPT Correction."""
357
+ r"""SPT Correction.
358
+
359
+ :Equation:
360
+
361
+ .. math:: C_N = \dfrac{350}{\sigma_o + 70} \, \sigma_o \le 280kN/m^2
362
+
363
+ :math:`\frac{N_c}{N_{60}}` should lie between 0.45 and 2.0, if
364
+ :math:`\frac{N_c}{N_{60}}` is greater than 2.0, :math:`N_c` should be
365
+ divided by 2.0 to obtain the design value used in finding the bearing
366
+ capacity of the soil.
367
+ """
344
368
  corr = 350.0 / (self.eop + 70.0)
345
369
  return corr / 2.0 if corr > 2.0 else corr
346
370
 
347
371
 
348
372
  class BazaraaPeckOPC(OPC):
349
- r"""Overburden Pressure Correction according to ``Bazaraa (1967)``, and
373
+ """Overburden Pressure Correction according to ``Bazaraa (1967)``, and
350
374
  also by ``Peck and Bazaraa (1969)``.
351
-
352
- :Equation:
353
-
354
- .. math::
355
-
356
- C_N &= \dfrac{4}{1 + 0.0418 \cdot \sigma_o}, \, \sigma_o \lt 71.8kN/m^2
357
-
358
- C_N &= \dfrac{4}{3.25 + 0.0104 \cdot \sigma_o},
359
- \, \sigma_o \gt 71.8kN/m^2
360
-
361
- C_N &= 1 \, , \, \sigma_o = 71.8kN/m^2
362
375
  """
363
376
 
364
377
  #: Maximum effective overburden pressure (:math:`kPa`).
@@ -366,15 +379,29 @@ class BazaraaPeckOPC(OPC):
366
379
 
367
380
  @property
368
381
  def eop(self) -> float:
382
+ """Effective overburden pressure (:math:`kPa`)."""
369
383
  return self._eop
370
384
 
371
385
  @eop.setter
372
386
  @validators.ge(0.0)
373
387
  def eop(self, val: float) -> None:
388
+ """Effective overburden pressure (:math:`kPa`)."""
374
389
  self._eop = val
375
390
 
376
391
  def correction(self) -> float:
377
- """SPT Correction."""
392
+ r"""SPT Correction.
393
+
394
+ :Equation:
395
+
396
+ .. math::
397
+
398
+ C_N &= \dfrac{4}{1 + 0.0418 \cdot \sigma_o}, \, \sigma_o \lt 71.8kN/m^2
399
+
400
+ C_N &= \dfrac{4}{3.25 + 0.0104 \cdot \sigma_o},
401
+ \, \sigma_o \gt 71.8kN/m^2
402
+
403
+ C_N &= 1 \, , \, \sigma_o = 71.8kN/m^2
404
+ """
378
405
  if isclose(self.eop, self.STD_PRESSURE, rel_tol=0.01):
379
406
  corr = 1.0
380
407
  elif self.eop < self.STD_PRESSURE:
@@ -385,15 +412,11 @@ class BazaraaPeckOPC(OPC):
385
412
 
386
413
 
387
414
  class PeckOPC(OPC):
388
- r"""Overburden Pressure Correction according to ``Peck et al. (1974)``.
389
-
390
- :Equation:
391
-
392
- .. math:: C_N = 0.77 \log \left(\dfrac{2000}{\sigma_o} \right)
393
- """
415
+ """Overburden Pressure Correction according to ``Peck et al. (1974)``."""
394
416
 
395
417
  @property
396
418
  def eop(self) -> float:
419
+ """Effective overburden pressure (:math:`kPa`)."""
397
420
  return self._eop
398
421
 
399
422
  @eop.setter
@@ -402,20 +425,22 @@ class PeckOPC(OPC):
402
425
  self._eop = val
403
426
 
404
427
  def correction(self) -> float:
405
- """SPT Correction."""
406
- return 0.77 * log10(2000.0 / self.eop)
428
+ r"""SPT Correction.
407
429
 
430
+ :Equation:
408
431
 
409
- class LiaoWhitmanOPC(OPC):
410
- r"""Overburden Pressure Correction according to ``Liao & Whitman (1986)``.
432
+ .. math:: C_N = 0.77 \log \left(\dfrac{2000}{\sigma_o} \right)
433
+ """
434
+ return 0.77 * log10(2000.0 / self.eop)
411
435
 
412
- :Equation:
413
436
 
414
- .. math:: C_N = \sqrt{\dfrac{100}{\sigma_o}}
437
+ class LiaoWhitmanOPC(OPC):
438
+ """Overburden Pressure Correction according to ``Liao & Whitman (1986)``.
415
439
  """
416
440
 
417
441
  @property
418
442
  def eop(self) -> float:
443
+ """Effective overburden pressure (:math:`kPa`)."""
419
444
  return self._eop
420
445
 
421
446
  @eop.setter
@@ -424,20 +449,21 @@ class LiaoWhitmanOPC(OPC):
424
449
  self._eop = val
425
450
 
426
451
  def correction(self) -> float:
427
- """SPT Correction."""
428
- return sqrt(100.0 / self.eop)
452
+ r"""SPT Correction.
429
453
 
454
+ :Equation:
430
455
 
431
- class SkemptonOPC(OPC):
432
- r"""Overburden Pressure Correction according to ``Skempton (1986)``.
456
+ .. math:: C_N = \sqrt{\dfrac{100}{\sigma_o}}
457
+ """
458
+ return sqrt(100.0 / self.eop)
433
459
 
434
- :Equation:
435
460
 
436
- .. math:: C_N = \dfrac{2}{1 + 0.01044 \cdot \sigma_o}
437
- """
461
+ class SkemptonOPC(OPC):
462
+ """Overburden Pressure Correction according to ``Skempton (1986)``."""
438
463
 
439
464
  @property
440
465
  def eop(self) -> float:
466
+ """Effective overburden pressure (:math:`kPa`)."""
441
467
  return self._eop
442
468
 
443
469
  @eop.setter
@@ -446,25 +472,21 @@ class SkemptonOPC(OPC):
446
472
  self._eop = val
447
473
 
448
474
  def correction(self) -> float:
449
- """SPT Correction."""
475
+ r"""SPT Correction.
476
+
477
+ :Equation:
478
+
479
+ .. math:: C_N = \dfrac{2}{1 + 0.01044 \cdot \sigma_o}
480
+ """
450
481
  return 2.0 / (1.0 + 0.01044 * self.eop)
451
482
 
452
483
 
453
484
  class DilatancyCorrection:
454
- r"""Dilatancy SPT Correction according to ``Terzaghi & Peck (1948)``.
485
+ """Dilatancy SPT Correction according to ``Terzaghi & Peck (1948)``.
455
486
 
456
487
  For coarse sand, this correction is not required. In applying this
457
488
  correction, overburden pressure correction is applied first and then
458
489
  dilatancy correction is applied.
459
-
460
- :Equation:
461
-
462
- .. math::
463
-
464
- (N_1)_{60} &= 15 + \dfrac{1}{2}((N_1)_{60} - 15) \, , \,
465
- (N_1)_{60} \gt 15
466
-
467
- (N_1)_{60} &= (N_1)_{60} \, , \, (N_1)_{60} \le 15
468
490
  """
469
491
 
470
492
  def __init__(self, corr_spt_n_value: float) -> None:
@@ -477,6 +499,9 @@ class DilatancyCorrection:
477
499
 
478
500
  @property
479
501
  def corr_spt_n_value(self) -> float:
502
+ """SPT N-value standardized for field procedures and/or corrected for
503
+ overburden pressure.
504
+ """
480
505
  return self._std_spt_n_value
481
506
 
482
507
  @corr_spt_n_value.setter
@@ -486,7 +511,103 @@ class DilatancyCorrection:
486
511
 
487
512
  @round_(ndigits=1)
488
513
  def corrected_spt_n_value(self) -> float:
489
- """Corrected SPT N-value."""
514
+ r"""Corrected SPT N-value.
515
+
516
+ :Equation:
517
+
518
+ .. math::
519
+
520
+ (N_1)_{60} &= 15 + \dfrac{1}{2}((N_1)_{60} - 15) \, , \,
521
+ (N_1)_{60} \gt 15
522
+
523
+ (N_1)_{60} &= (N_1)_{60} \, , \, (N_1)_{60} \le 15
524
+ """
490
525
  if self.corr_spt_n_value <= 15.0:
491
526
  return self.corr_spt_n_value
492
527
  return 15.0 + 0.5 * (self.corr_spt_n_value - 15.0)
528
+
529
+
530
+ @enum_repr
531
+ class OPCType(enum.StrEnum):
532
+ """Enumeration of overburden pressure correction types."""
533
+ GIBBS = enum.auto()
534
+ BAZARAA = enum.auto()
535
+ PECK = enum.auto()
536
+ LIAO = enum.auto()
537
+ SKEMPTON = enum.auto()
538
+
539
+
540
+ def create_spt_correction(recorded_spt_n_value: int,
541
+ eop: float,
542
+ energy_percentage=0.6,
543
+ borehole_diameter=65.0,
544
+ rod_length=3.0,
545
+ hammer_type=HammerType.DONUT_1,
546
+ sampler_type=SamplerType.STANDARD,
547
+ opc_type: OPCType | str = OPCType.GIBBS,
548
+ apply_dilatancy_correction: bool = False
549
+ ) -> OPC | DilatancyCorrection:
550
+ """A factory function that encapsulates the creation of spt correction.
551
+
552
+ :param recorded_spt_n_value: Recorded SPT N-value from field.
553
+ :type recorded_spt_n_value: int
554
+
555
+ :param eop: Effective overburden pressure (:math:`kPa`).
556
+ :type eop: float
557
+
558
+ :param energy_percentage: Energy percentage reaching the tip of the
559
+ sampler, defaults to 0.6
560
+ :type energy_percentage: float, optional
561
+
562
+ :param borehole_diameter: Borehole diameter, defaults to 65.0 (mm).
563
+ :type borehole_diameter: float, optional
564
+
565
+ :param rod_length: Rod length, defaults to 3.0 (m).
566
+ :type rod_length: float, optional
567
+
568
+ :param hammer_type: Hammer type, defaults to :attr:`HammerType.DONUT_1`
569
+ :type hammer_type: HammerType, optional
570
+
571
+ :param sampler_type: Sampler type, defaults to :attr:`SamplerType.STANDARD`
572
+ :type sampler_type: SamplerType, optional
573
+
574
+ :param opc_type: Overburden Pressure Correction type to apply,
575
+ defaults to :attr:`OPCType.GIBBS`
576
+ :type opc_type: OPCType, optional
577
+
578
+ :param apply_dilatancy_correction: Indicates whether to apply dilatancy
579
+ correction, defaults to False.
580
+ :type apply_dilatancy_correction: bool, optional
581
+ """
582
+
583
+ try:
584
+ opc_type = OPCType(str(opc_type).casefold())
585
+ except ValueError as e:
586
+ msg = (f"{opc_type=} is not supported, Supported "
587
+ f"types are: {list(OPCType)}")
588
+ raise ValueError(msg) from e
589
+
590
+ energy_correction = EnergyCorrection(
591
+ recorded_spt_n_value=recorded_spt_n_value,
592
+ energy_percentage=energy_percentage,
593
+ borehole_diameter=borehole_diameter,
594
+ rod_length=rod_length,
595
+ hammer_type=hammer_type,
596
+ sampler_type=sampler_type)
597
+
598
+ std_spt_n_value = energy_correction.standardized_spt_n_value()
599
+
600
+ opc_types = {OPCType.GIBBS: GibbsHoltzOPC,
601
+ OPCType.BAZARAA: BazaraaPeckOPC,
602
+ OPCType.PECK: PeckOPC,
603
+ OPCType.LIAO: LiaoWhitmanOPC,
604
+ OPCType.SKEMPTON: SkemptonOPC}
605
+
606
+ opc_class = opc_types[opc_type]
607
+ opc_corr = opc_class(std_spt_n_value=std_spt_n_value, eop=eop)
608
+
609
+ if apply_dilatancy_correction:
610
+ corr_spt_n_value = opc_corr.corrected_spt_n_value()
611
+ return DilatancyCorrection(corr_spt_n_value=corr_spt_n_value)
612
+
613
+ return opc_corr
@@ -0,0 +1,100 @@
1
+ import functools
2
+ import math
3
+ from math import exp, inf, isclose, log10, pi, sqrt
4
+ from statistics import fmean as mean
5
+ from typing import Callable, SupportsRound
6
+
7
+ from . import validators
8
+
9
+ __all__ = ["enum_repr",
10
+ "inf",
11
+ "pi",
12
+ "deg2rad",
13
+ "rad2deg",
14
+ "tan",
15
+ "cot",
16
+ "sin",
17
+ "cos",
18
+ "arctan",
19
+ "round_",
20
+ "mean",
21
+ "exp",
22
+ "isclose",
23
+ "log10",
24
+ "sqrt",
25
+ "validators"]
26
+
27
+
28
+ def deg2rad(x: float, /) -> float:
29
+ """Convert angle x from degrees to radians."""
30
+ return math.radians(x)
31
+
32
+
33
+ def rad2deg(x: float, /) -> float:
34
+ """Convert angle x from radians to degrees."""
35
+ return math.degrees(x)
36
+
37
+
38
+ def tan(x: float, /) -> float:
39
+ """Return the tangent of x (measured in degrees)."""
40
+ return math.tan(deg2rad(x))
41
+
42
+
43
+ def cot(x: float, /) -> float:
44
+ """Return the cotangent of x (measured in degrees)."""
45
+ return 1 / tan(x)
46
+
47
+
48
+ def sin(x: float, /) -> float:
49
+ """Return the sine of x (measured in degrees)."""
50
+ return math.sin(deg2rad(x))
51
+
52
+
53
+ def cos(x: float, /) -> float:
54
+ """Return the cosine of x (measured in degrees)."""
55
+ return math.cos(deg2rad(x))
56
+
57
+
58
+ def arctan(x: float, /) -> float:
59
+ """Return the arc tangent (measured in degrees) of x."""
60
+ return rad2deg(math.atan(x))
61
+
62
+
63
+ def enum_repr(cls):
64
+ cls.__repr__ = lambda self: f"{self.value}"
65
+ return cls
66
+
67
+
68
+ def round_(ndigits: int | Callable[..., SupportsRound]) -> Callable:
69
+ """A decorator that rounds the result of a callable to a specified number
70
+ of decimal places.
71
+
72
+ The returned value of the callable should support the ``__round__`` dunder
73
+ method and should be a numeric value. ``ndigits`` can either be an int
74
+ which will indicate the number of decimal places to round to or a
75
+ callable. If ``ndigits`` is callable the default decimal places is 2.
76
+
77
+ TypeError is raised when ``ndigits`` is neither an int nor a callable.
78
+ """
79
+
80
+ default_dp = 2
81
+
82
+ def dec(fn) -> Callable[..., float]:
83
+ @functools.wraps(fn)
84
+ def wrapper(*args, **kwargs) -> float:
85
+ dp = ndigits if not callable(ndigits) else default_dp
86
+ res = fn(*args, **kwargs)
87
+ return round(res, ndigits=dp)
88
+
89
+ return wrapper
90
+
91
+ # See if we're being called as @round_ or @round_().
92
+ # We're called with parens.
93
+ if isinstance(ndigits, int):
94
+ return dec
95
+
96
+ # We're called as @round_ without parens.
97
+ if callable(ndigits):
98
+ return dec(ndigits)
99
+
100
+ raise TypeError("ndigits must be an int or a callable")
@@ -0,0 +1,80 @@
1
+ """validators"""
2
+ import operator
3
+ from typing import Callable, TypeAlias
4
+
5
+ Number: TypeAlias = int | float
6
+
7
+
8
+ def _num_validator(bound: float, /, *,
9
+ compare_symbol: str,
10
+ compare_fn: Callable,
11
+ err_msg: str,
12
+ exc_type: Callable):
13
+ def dec(fn):
14
+ def wrapper(obj, val):
15
+ if not compare_fn(val, bound):
16
+ msg = f"{fn.__name__} must be {compare_symbol} {bound}"
17
+ raise exc_type(err_msg if err_msg else msg)
18
+ fn(obj, val)
19
+
20
+ return wrapper
21
+
22
+ return dec
23
+
24
+
25
+ def _len_validator(bound: float, /, *,
26
+ compare_symbol: str,
27
+ compare_fn: Callable,
28
+ err_msg: str,
29
+ exc_type: Callable):
30
+ def dec(fn):
31
+ def wrapper(obj, val):
32
+ _len = len(val)
33
+ if not compare_fn(_len, bound):
34
+ msg = f"Length of '{fn.__name__}' must be {compare_symbol} {bound}"
35
+ raise exc_type(err_msg if err_msg else msg)
36
+ return fn(obj, val)
37
+
38
+ return wrapper
39
+
40
+ return dec
41
+
42
+
43
+ def min_len(m_len: int, /, *, exc_type=ValueError, err_msg=None):
44
+ return _len_validator(m_len, compare_symbol=">=",
45
+ compare_fn=operator.ge,
46
+ err_msg=err_msg,
47
+ exc_type=exc_type)
48
+
49
+
50
+ # def lt(val: Number, /, *, exc_type=ValueError, err_msg=None):
51
+ # return _NumberValidator(val, "<", operator.lt, exc_type, err_msg)
52
+
53
+
54
+ def le(val: Number, /, *, exc_type=ValueError, err_msg=None):
55
+ return _num_validator(val, compare_symbol="<=",
56
+ compare_fn=operator.le,
57
+ err_msg=err_msg,
58
+ exc_type=exc_type)
59
+
60
+
61
+ # def eq(val: Number, /, *, exc_type=ValueError, err_msg=None):
62
+ # return _NumberValidator(val, "==", operator.eq, exc_type, err_msg)
63
+
64
+
65
+ # def ne(val: Number, /, *, exc_type=ValueError, err_msg=None):
66
+ # return _NumberValidator(val, "!=", operator.ne, exc_type, err_msg)
67
+
68
+
69
+ def ge(val: Number, /, *, exc_type=ValueError, err_msg=None):
70
+ return _num_validator(val, compare_symbol=">=",
71
+ compare_fn=operator.ge,
72
+ err_msg=err_msg,
73
+ exc_type=exc_type)
74
+
75
+
76
+ def gt(val: Number, /, *, exc_type=ValueError, err_msg=None):
77
+ return _num_validator(val, compare_symbol=">",
78
+ compare_fn=operator.gt,
79
+ err_msg=err_msg,
80
+ exc_type=exc_type)