pygeodesy 24.5.24__py2.py3-none-any.whl → 24.6.9__py2.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 (71) hide show
  1. {PyGeodesy-24.5.24.dist-info → PyGeodesy-24.6.9.dist-info}/METADATA +6 -5
  2. PyGeodesy-24.6.9.dist-info/RECORD +116 -0
  3. pygeodesy/__init__.py +4 -4
  4. pygeodesy/auxilats/__init__.py +1 -1
  5. pygeodesy/auxilats/__main__.py +2 -2
  6. pygeodesy/auxilats/auxAngle.py +4 -4
  7. pygeodesy/basics.py +39 -5
  8. pygeodesy/booleans.py +54 -67
  9. pygeodesy/cartesianBase.py +138 -147
  10. pygeodesy/constants.py +3 -3
  11. pygeodesy/deprecated/functions.py +9 -3
  12. pygeodesy/ecef.py +67 -72
  13. pygeodesy/ellipsoidalBase.py +18 -56
  14. pygeodesy/ellipsoidalGeodSolve.py +2 -2
  15. pygeodesy/ellipsoidalKarney.py +3 -3
  16. pygeodesy/ellipsoidalNvector.py +7 -7
  17. pygeodesy/ellipsoids.py +6 -5
  18. pygeodesy/errors.py +20 -10
  19. pygeodesy/etm.py +16 -21
  20. pygeodesy/fmath.py +9 -20
  21. pygeodesy/formy.py +60 -74
  22. pygeodesy/frechet.py +13 -14
  23. pygeodesy/fsums.py +60 -26
  24. pygeodesy/geodesicx/__init__.py +1 -1
  25. pygeodesy/geodesicx/__main__.py +2 -2
  26. pygeodesy/geodesicx/gx.py +3 -5
  27. pygeodesy/geodsolve.py +24 -26
  28. pygeodesy/geohash.py +27 -40
  29. pygeodesy/geoids.py +1 -1
  30. pygeodesy/hausdorff.py +17 -18
  31. pygeodesy/heights.py +17 -30
  32. pygeodesy/internals.py +15 -14
  33. pygeodesy/interns.py +3 -9
  34. pygeodesy/iters.py +2 -2
  35. pygeodesy/karney.py +8 -7
  36. pygeodesy/latlonBase.py +189 -176
  37. pygeodesy/lazily.py +92 -56
  38. pygeodesy/lcc.py +2 -2
  39. pygeodesy/ltp.py +93 -55
  40. pygeodesy/ltpTuples.py +304 -240
  41. pygeodesy/mgrs.py +51 -24
  42. pygeodesy/named.py +159 -136
  43. pygeodesy/namedTuples.py +43 -14
  44. pygeodesy/nvectorBase.py +20 -23
  45. pygeodesy/osgr.py +40 -48
  46. pygeodesy/points.py +11 -11
  47. pygeodesy/props.py +29 -16
  48. pygeodesy/rhumb/aux_.py +13 -15
  49. pygeodesy/rhumb/bases.py +12 -5
  50. pygeodesy/rhumb/ekx.py +24 -18
  51. pygeodesy/rhumb/solve.py +20 -70
  52. pygeodesy/simplify.py +16 -16
  53. pygeodesy/solveBase.py +35 -32
  54. pygeodesy/sphericalBase.py +33 -31
  55. pygeodesy/sphericalTrigonometry.py +17 -17
  56. pygeodesy/streprs.py +6 -4
  57. pygeodesy/trf.py +11 -9
  58. pygeodesy/triaxials.py +71 -50
  59. pygeodesy/units.py +40 -65
  60. pygeodesy/unitsBase.py +2 -2
  61. pygeodesy/ups.py +66 -70
  62. pygeodesy/utily.py +7 -6
  63. pygeodesy/utm.py +152 -156
  64. pygeodesy/utmups.py +38 -38
  65. pygeodesy/utmupsBase.py +102 -106
  66. pygeodesy/vector3d.py +34 -36
  67. pygeodesy/vector3dBase.py +12 -9
  68. pygeodesy/webmercator.py +43 -51
  69. PyGeodesy-24.5.24.dist-info/RECORD +0 -116
  70. {PyGeodesy-24.5.24.dist-info → PyGeodesy-24.6.9.dist-info}/WHEEL +0 -0
  71. {PyGeodesy-24.5.24.dist-info → PyGeodesy-24.6.9.dist-info}/top_level.txt +0 -0
pygeodesy/mgrs.py CHANGED
@@ -44,7 +44,7 @@ from pygeodesy.interns import NN, _0_, _A_, _AtoZnoIO_, _band_, _B_, \
44
44
  _COMMASPACE_, _datum_, _easting_, _invalid_, \
45
45
  _northing_, _SPACE_, _W_, _Y_, _Z_, _zone_
46
46
  from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _PYGEODESY_GEOCONVERT_
47
- from pygeodesy.named import _NamedBase, _NamedTuple, _Pass, _xnamed
47
+ from pygeodesy.named import _name2__, _NamedBase, _NamedTuple, _Pass
48
48
  from pygeodesy.namedTuples import EasNor2Tuple, UtmUps5Tuple
49
49
  from pygeodesy.props import deprecated_property_RO, property_RO, Property_RO
50
50
  from pygeodesy.streprs import enstr2, _enstr2m3, Fmt, _resolution10, _xzipairs
@@ -55,7 +55,7 @@ from pygeodesy.utm import toUtm8, _to3zBlat, Utm, _UTM_ZONE_MAX, _UTM_ZONE_MIN
55
55
  # from pygeodesy.utmupsBase import _UTM_ZONE_MAX, _UTM_ZONE_MIN # from .utm
56
56
 
57
57
  __all__ = _ALL_LAZY.mgrs
58
- __version__ = '24.05.24'
58
+ __version__ = '24.06.04'
59
59
 
60
60
  _AN_ = 'AN' # default south pole grid tile and band B
61
61
  _AtoPx_ = _AtoZnoIO_.tillP
@@ -87,7 +87,7 @@ class Mgrs(_NamedBase):
87
87
  _zone = 0 # longitudinal or polar zone (C{int}), 0..60
88
88
 
89
89
  def __init__(self, zone=0, EN=NN, easting=0, northing=0, band=NN,
90
- datum=_WGS84, resolution=0, name=NN):
90
+ datum=_WGS84, resolution=0, **name):
91
91
  '''New L{Mgrs} Military grid reference.
92
92
 
93
93
  @arg zone: The 6° I{longitudinal} zone (C{int}), 1..60 covering
@@ -104,7 +104,7 @@ class Mgrs(_NamedBase):
104
104
  @kwarg datum: This reference's datum (L{Datum}, L{Ellipsoid},
105
105
  L{Ellipsoid2} or L{a_f2Tuple}).
106
106
  @kwarg resolution: Optional resolution (C{meter}), C{0} for default.
107
- @kwarg name: Optional name (C{str}).
107
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
108
108
 
109
109
  @raise MGRSError: Invalid B{C{zone}}, B{C{EN}}, B{C{easting}},
110
110
  B{C{northing}}, B{C{band}} or B{C{resolution}}.
@@ -235,20 +235,18 @@ class Mgrs(_NamedBase):
235
235
  toUps8(a, 0, datum=self.datum, Ups=None)
236
236
  return int(u.northing / _100km) * _100km
237
237
 
238
- def parse(self, strMGRS, name=NN):
238
+ def parse(self, strMGRS, **name):
239
239
  '''Parse a string to a similar L{Mgrs} instance.
240
240
 
241
- @arg strMGRS: The MGRS reference (C{str}),
242
- see function L{parseMGRS}.
243
- @kwarg name: Optional instance name (C{str}),
244
- overriding this name.
241
+ @arg strMGRS: The MGRS reference (C{str}), see function L{parseMGRS}.
242
+ @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.
245
243
 
246
244
  @return: The similar instance (L{Mgrs}).
247
245
 
248
246
  @raise MGRSError: Invalid B{C{strMGRS}}.
249
247
  '''
250
248
  return parseMGRS(strMGRS, datum=self.datum, Mgrs=self.classof,
251
- name=name or self.name)
249
+ name=self._name__(name))
252
250
 
253
251
  @property
254
252
  def resolution(self):
@@ -536,7 +534,7 @@ class _RE(object):
536
534
  _RE = _RE() # PYCHOK singleton
537
535
 
538
536
 
539
- def parseMGRS(strMGRS, datum=_WGS84, Mgrs=Mgrs, name=NN):
537
+ def parseMGRS(strMGRS, datum=_WGS84, Mgrs=Mgrs, **name):
540
538
  '''Parse a string representing a MGRS grid reference,
541
539
  consisting of C{"[zone]Band, EN, easting, northing"}.
542
540
 
@@ -544,7 +542,7 @@ def parseMGRS(strMGRS, datum=_WGS84, Mgrs=Mgrs, name=NN):
544
542
  @kwarg datum: Optional datum to use (L{Datum}).
545
543
  @kwarg Mgrs: Optional class to return the MGRS grid
546
544
  reference (L{Mgrs}) or C{None}.
547
- @kwarg name: Optional B{C{Mgrs}} name (C{str}).
545
+ @kwarg name: Optional B{C{Mgrs}} C{B{name}=NN} (C{str}).
548
546
 
549
547
  @return: The MGRS grid reference as B{C{Mgrs}} or if
550
548
  C{B{Mgrs} is None} as an L{Mgrs4Tuple}C{(zone,
@@ -582,29 +580,29 @@ def parseMGRS(strMGRS, datum=_WGS84, Mgrs=Mgrs, name=NN):
582
580
  e, n, m = _enstr2m3(*m[2:])
583
581
 
584
582
  if Mgrs is None:
585
- r = Mgrs4Tuple(zB, EN, e, n, name=name)
583
+ r = Mgrs4Tuple(zB, EN, e, n, **name)
586
584
  _ = r.toMgrs(resolution=m) # validate
587
585
  else:
588
- r = Mgrs(zB, EN, e, n, datum=datum, resolution=m, name=name)
586
+ r = Mgrs(zB, EN, e, n, datum=datum, resolution=m, **name)
589
587
  return r
590
588
 
591
589
  return _parseX(_MGRS, strMGRS, datum, Mgrs, name,
592
590
  strMGRS=strMGRS, Error=MGRSError)
593
591
 
594
592
 
595
- def toMgrs(utmups, Mgrs=Mgrs, name=NN, **Mgrs_kwds):
593
+ def toMgrs(utmups, Mgrs=Mgrs, **name_Mgrs_kwds):
596
594
  '''Convert a UTM or UPS coordinate to an MGRS grid reference.
597
595
 
598
596
  @arg utmups: A UTM or UPS coordinate (L{Utm}, L{Etm} or L{Ups}).
599
597
  @kwarg Mgrs: Optional class to return the MGRS grid reference
600
598
  (L{Mgrs}) or C{None}.
601
- @kwarg name: Optional B{C{Mgrs}} name (C{str}).
602
- @kwarg Mgrs_kwds: Optional, additional B{C{Mgrs}} keyword
603
- arguments, ignored if C{B{Mgrs} is None}.
599
+ @kwarg name_Mgrs_kwds: Optional C{B{name}=NN} (C{str}) and
600
+ optional, additional B{C{Mgrs}} keyword arguments,
601
+ ignored if C{B{Mgrs} is None}.
604
602
 
605
- @return: The MGRS grid reference as B{C{Mgrs}} or if
606
- C{B{Mgrs} is None} as an L{Mgrs6Tuple}C{(zone,
607
- EN, easting, northing, band, datum)}.
603
+ @return: The MGRS grid reference as B{C{Mgrs}} or if C{B{Mgrs}
604
+ is None} as an L{Mgrs6Tuple}C{(zone, EN, easting,
605
+ northing, band, datum)}.
608
606
 
609
607
  @raise MGRSError: Invalid B{C{utmups}}.
610
608
 
@@ -634,12 +632,14 @@ def toMgrs(utmups, Mgrs=Mgrs, name=NN, **Mgrs_kwds):
634
632
  except (IndexError, TypeError, ValueError) as x:
635
633
  raise MGRSError(B=B, E=E, N=N, utmups=utmups, cause=x)
636
634
 
635
+ t, kwds = _name2__(name_Mgrs_kwds, _or_nameof=utmups)
637
636
  if Mgrs is None:
638
- r = Mgrs4Tuple(Fmt.zone(z), EN, e, n).to6Tuple(B, utmups.datum)
637
+ r = Mgrs4Tuple(Fmt.zone(z), EN, e, n, name=t) \
638
+ .to6Tuple(B, utmups.datum)
639
639
  else:
640
- kwds = _xkwds(Mgrs_kwds, band=B, datum=utmups.datum)
640
+ kwds = _xkwds(kwds, band=B, datum=utmups.datum, name=t)
641
641
  r = Mgrs(z, EN, e, n, **kwds)
642
- return _xnamed(r, name or utmups.name)
642
+ return r
643
643
 
644
644
 
645
645
  def _um100km2(m):
@@ -699,6 +699,33 @@ if __name__ == '__main__':
699
699
  p = e * 100.0 / n
700
700
  printf('%6s: %s errors (%.2f%%)', n, (e if e else 'no'), p)
701
701
 
702
+ # % python3 -m pygeodesy.mgrs
703
+ # using: /opt/local/bin/GeoConvert -m ...
704
+ # 0: lat -90 ... OK
705
+ # 361: lat -89 ... OK
706
+ # 722: lat -88 ... OK
707
+ # 1083: lat -87 ... OK
708
+ # 1444: lat -86 ... OK
709
+ # 1805: lat -85 ... OK
710
+ # 2166: lat -84 ... OK
711
+ # 2527: lat -83 ... OK
712
+ # 2888: lat -82 ... OK
713
+ # 3249: lat -81 ... OK
714
+ # 3610: lat -80 ... OK
715
+ # ...
716
+ # 61370: lat 80 ... OK
717
+ # 61731: lat 81 ... OK
718
+ # 62092: lat 82 ... OK
719
+ # 62453: lat 83 ... OK
720
+ # 62814: lat 84 ... OK
721
+ # 63175: lat 85 ... OK
722
+ # 63536: lat 86 ... OK
723
+ # 63897: lat 87 ... OK
724
+ # 64258: lat 88 ... OK
725
+ # 64619: lat 89 ... OK
726
+ # 64980: lat 90 ... OK
727
+ # 65341: no errors (0.00%)
728
+
702
729
  # **) MIT License
703
730
  #
704
731
  # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
pygeodesy/named.py CHANGED
@@ -13,27 +13,28 @@ standard Python C{namedtuple}s.
13
13
  @see: Module L{pygeodesy.namedTuples} for (most of) the C{Named-Tuples}.
14
14
  '''
15
15
 
16
- from pygeodesy.basics import isclass, isidentifier, iskeyword, isstr, issubclassof, \
17
- itemsorted, len2, _xcopy, _xdup, _zip
16
+ from pygeodesy.basics import isidentifier, iskeyword, isstr, itemsorted, len2, \
17
+ _xcopy, _xdup, _xinstanceof, _xsubclassof, _zip
18
18
  from pygeodesy.errors import _AssertionError, _AttributeError, _incompatible, \
19
- _IndexError, _IsnotError, _KeyError, LenError, \
20
- _NameError, _NotImplementedError, _TypeError, \
21
- _TypesError, UnitError, _ValueError, _xattr, _xkwds, \
22
- _xkwds_item2, _xkwds_pop2
19
+ _IndexError, _KeyError, LenError, _NameError, \
20
+ _NotImplementedError, _TypeError, _TypesError, \
21
+ _UnexpectedError, UnitError, _ValueError, \
22
+ _xattr, _xkwds, _xkwds_item2, _xkwds_pop2
23
23
  from pygeodesy.internals import _caller3, _dunder_nameof, _isPyPy, _sizeof, _under
24
24
  from pygeodesy.interns import MISSING, NN, _AT_, _COLON_, _COLONSPACE_, _COMMA_, \
25
25
  _COMMASPACE_, _doesn_t_exist_, _DOT_, _DUNDER_, \
26
26
  _dunder_name_, _EQUAL_, _exists_, _immutable_, _name_, \
27
27
  _NL_, _NN_, _no_, _other_, _s_, _SPACE_, _std_, \
28
- _UNDER_, _valid_, _vs_
28
+ _UNDER_, _vs_
29
29
  from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv
30
30
  from pygeodesy.props import _allPropertiesOf_n, deprecated_method, _hasProperty, \
31
31
  _update_all, property_doc_, Property_RO, property_RO, \
32
32
  _update_attrs
33
33
  from pygeodesy.streprs import attrs, Fmt, lrstrip, pairs, reprs, unstr
34
+ # from pygeodesy.units import _toUnit # _MODS
34
35
 
35
36
  __all__ = _ALL_LAZY.named
36
- __version__ = '24.05.21'
37
+ __version__ = '24.06.10'
37
38
 
38
39
  _COMMANL_ = _COMMA_ + _NL_
39
40
  _COMMASPACEDOT_ = _COMMASPACE_ + _DOT_
@@ -50,69 +51,6 @@ _Units_ = '_Units_'
50
51
  _UP = 2
51
52
 
52
53
 
53
- def _xjoined_(prefix, name=NN, enquote=True, **name__of_name):
54
- '''(INTERNAL) Join C{prefix} and non-empty C{name}.
55
- '''
56
- if name__of_name:
57
- name = _name__(name, **name__of_name)
58
- if name and prefix:
59
- if enquote:
60
- name = repr(name)
61
- t = _SPACE_(prefix, name)
62
- else:
63
- t = prefix or name
64
- return t
65
-
66
-
67
- def _xnamed(inst, name=NN, force=False, **name__of_name):
68
- '''(INTERNAL) Set the instance' C{.name = B{name}}.
69
-
70
- @arg inst: The instance (C{_Named}).
71
- @kwarg name: The name (C{str}).
72
- @kwarg force: If C{True}, force rename (C{bool}).
73
-
74
- @return: The B{C{inst}}, renamed if B{C{force}}d
75
- or if not named before.
76
- '''
77
- if name__of_name:
78
- name = _name__(name, **name__of_name)
79
- if name and isinstance(inst, _Named):
80
- if not inst.name:
81
- inst.name = name
82
- elif force:
83
- inst.rename(name)
84
- return inst
85
-
86
-
87
- def _xother3(inst, other, name=_other_, up=1, **name_other):
88
- '''(INTERNAL) Get C{name} and C{up} for a named C{other}.
89
- '''
90
- if name_other: # and other is None
91
- name, other = _xkwds_item2(name_other)
92
- elif other and len(other) == 1:
93
- name, other = _name__(name), other[0]
94
- else:
95
- raise _AssertionError(name, other, txt=classname(inst, prefixed=True))
96
- return other, name, up
97
-
98
-
99
- def _xotherError(inst, other, name=_other_, up=1):
100
- '''(INTERNAL) Return a C{_TypeError} for an incompatible, named C{other}.
101
- '''
102
- n = _callname(name, classname(inst, prefixed=True), inst.name, up=up + 1)
103
- return _TypeError(name, other, txt=_incompatible(n))
104
-
105
-
106
- def _xvalid(name, underOK=False):
107
- '''(INTERNAL) Check valid attribute name C{name}.
108
- '''
109
- return bool(name and isstr(name)
110
- and name != _name_
111
- and (underOK or not name.startswith(_UNDER_))
112
- and (not iskeyword(name))
113
- and isidentifier(name))
114
-
115
-
116
54
  class ADict(dict):
117
55
  '''A C{dict} with both key I{and} attribute access to
118
56
  the C{dict} items.
@@ -170,7 +108,6 @@ class ADict(dict):
170
108
  C{floats} formatted by function L{pygeodesy.fstr}.
171
109
  '''
172
110
  n = _xattr(self, name=NN) or self.__class__.__name__
173
- print(1, n)
174
111
  return Fmt.PAREN(n, self._toT(_EQUAL_, **prec_fmt))
175
112
 
176
113
  def toStr(self, **prec_fmt):
@@ -312,7 +249,7 @@ class _Named(object):
312
249
 
313
250
  fmt, props, kwds = _fmt_props_kwds(**fmt_props_kwds)
314
251
 
315
- t = () if name is None else (Fmt.EQUAL(name=repr(name or self.name)),)
252
+ t = () if name is None else (Fmt.EQUAL(name=repr(self._name__(name))),)
316
253
  if attrs:
317
254
  t += pairs(((a, getattr(self, a)) for a in attrs),
318
255
  prec=prec, ints=True, fmt=fmt)
@@ -373,6 +310,11 @@ class _Named(object):
373
310
  '''
374
311
  return _name__(name, _or_nameof=self) # nameof(self)
375
312
 
313
+ def _name1__(self, kwds):
314
+ '''(INTERNAL) Resolve and set the C{B{name}=NN}.
315
+ '''
316
+ return _name1__(kwds, _or_nameof=self.name) if self.name else kwds
317
+
376
318
  @Property_RO
377
319
  def named(self):
378
320
  '''Get the name I{or} class name or C{""} (C{str}).
@@ -486,11 +428,8 @@ class _Named(object):
486
428
 
487
429
  @raise TypeError: Not C{isinstance(B{inst}, _Named)}.
488
430
  '''
489
- if not isinstance(inst, _Named):
490
- raise _IsnotError(_valid_, inst=inst)
491
-
492
- _ = inst.rename(self.name)
493
- return inst
431
+ _xinstanceof(_Named, inst=inst) # assert
432
+ return inst.renamed(self.name)
494
433
 
495
434
  _Named_Property_ROs = _allPropertiesOf_n(5, _Named, Property_RO) # PYCHOK once
496
435
 
@@ -780,12 +719,14 @@ class _NamedEnum(_NamedDict):
780
719
  if not (n and isstr(n) and isidentifier(n)):
781
720
  raise ValueError()
782
721
  except (AttributeError, ValueError, TypeError) as x:
783
- raise _NameError(_DOT_(_item_, _name_), item, cause=x)
722
+ n = _DOT_(_item_, _name_)
723
+ raise _NameError(n, item, cause=x)
784
724
  if n in self:
785
725
  t = _SPACE_(_item_, self._DOT_(n), _exists_)
786
726
  raise _NameError(t, txt=repr(item))
787
- if not isinstance(item, self._item_Classes):
788
- raise _TypesError(self._DOT_(n), item, *self._item_Classes)
727
+ if not isinstance(item, self._item_Classes): # _xinstanceof
728
+ n = self._DOT_(n)
729
+ raise _TypesError(n, item, *self._item_Classes)
789
730
  self[n] = item
790
731
  return n
791
732
 
@@ -907,7 +848,7 @@ class _NamedEnumItem(_NamedBase):
907
848
  '''
908
849
  name = _name__(name) or _NN_
909
850
  if self._enum:
910
- raise _NameError(name, self, txt=_registered_) # XXX _TypeError
851
+ raise _NameError(name, self, txt=_registered_) # _TypeError
911
852
  if name:
912
853
  self._name = name
913
854
 
@@ -934,8 +875,8 @@ class _NamedEnumItem(_NamedBase):
934
875
  enum = self._enum
935
876
  if enum and self.name and self.name in enum:
936
877
  item = enum.unregister(self.name)
937
- if item is not self:
938
- t = _SPACE_(repr(item), _vs_, repr(self)) # PYCHOK no cover
878
+ if item is not self: # PYCHOK no cover
879
+ t = _SPACE_(repr(item), _vs_, repr(self))
939
880
  raise _AssertionError(t)
940
881
 
941
882
 
@@ -987,7 +928,11 @@ class _NamedTuple(tuple, _Named):
987
928
  raise LenError(self.__class__, args=n, _Names_=N)
988
929
 
989
930
  if iteration_name:
990
- self._kwdself(**iteration_name)
931
+ i, name = _xkwds_pop2(iteration_name, iteration=None)
932
+ if i is not None:
933
+ self._iteration = i
934
+ if name:
935
+ self.name = name
991
936
  return self
992
937
 
993
938
  def __delattr__(self, name):
@@ -996,7 +941,8 @@ class _NamedTuple(tuple, _Named):
996
941
  @note: Items can not be deleted.
997
942
  '''
998
943
  if name in self._Names_:
999
- raise _TypeError(_del_, _DOT_(self.classname, name), txt=_immutable_)
944
+ t = _SPACE_(_del_, self._DOT_(name))
945
+ raise _TypeError(t, txt=_immutable_)
1000
946
  elif name in (_name_, _name):
1001
947
  _Named.__setattr__(self, name, NN) # XXX _Named.name.fset(self, NN)
1002
948
  else:
@@ -1007,10 +953,10 @@ class _NamedTuple(tuple, _Named):
1007
953
  '''
1008
954
  try:
1009
955
  return tuple.__getitem__(self, self._Names_.index(name))
1010
- except IndexError:
1011
- raise _IndexError(_DOT_(self.classname, Fmt.ANGLE(_name_)), name)
956
+ except IndexError as x:
957
+ raise _IndexError(self._DOT_(name), cause=x)
1012
958
  except ValueError: # e.g. _iteration
1013
- return tuple.__getattribute__(self, name)
959
+ return tuple.__getattr__(self, name) # __getattribute__
1014
960
 
1015
961
  # def __getitem__(self, index): # index, slice, etc.
1016
962
  # '''Get the item(s) at an B{C{index}} or slice.
@@ -1029,7 +975,8 @@ class _NamedTuple(tuple, _Named):
1029
975
  '''Set attribute or item B{C{name}} to B{C{value}}.
1030
976
  '''
1031
977
  if name in self._Names_:
1032
- raise _TypeError(_DOT_(self.classname, name), value, txt=_immutable_)
978
+ t = Fmt.EQUALSPACED(self._DOT_(name), repr(value))
979
+ raise _TypeError(t, txt=_immutable_)
1033
980
  elif name in (_name_, _name):
1034
981
  _Named.__setattr__(self, name, value) # XXX _Named.name.fset(self, value)
1035
982
  else: # e.g. _iteration
@@ -1040,6 +987,11 @@ class _NamedTuple(tuple, _Named):
1040
987
  '''
1041
988
  return self.toStr()
1042
989
 
990
+ def _DOT_(self, *names):
991
+ '''(INTERNAL) Period-join C{self.classname} and C{names}.
992
+ '''
993
+ return _DOT_(self.classname, *names)
994
+
1043
995
  def dup(self, name=NN, **items):
1044
996
  '''Duplicate this tuple replacing one or more items.
1045
997
 
@@ -1050,15 +1002,18 @@ class _NamedTuple(tuple, _Named):
1050
1002
 
1051
1003
  @raise NameError: Some B{C{items}} invalid.
1052
1004
  '''
1053
- tl = list(self)
1005
+ t = list(self)
1006
+ U = self._Units_
1054
1007
  if items:
1055
- _ix = self._Names_.index
1008
+ _ix = self._Names_.index
1009
+ _2U = _MODS.units._toUnit
1056
1010
  try:
1057
1011
  for n, v in items.items():
1058
- tl[_ix(n)] = v
1012
+ i = _ix(n)
1013
+ t[i] = _2U(U[i], v, name=n)
1059
1014
  except ValueError: # bad item name
1060
- raise _NameError(_DOT_(self.classname, n), v, this=self)
1061
- return self.classof(*tl, name=name or self.name)
1015
+ raise _NameError(self._DOT_(n), v, this=self)
1016
+ return self.classof(*t).reUnit(*U, name=name)
1062
1017
 
1063
1018
  def items(self):
1064
1019
  '''Yield the items, each as a C{(name, value)} pair (C{2-tuple}).
@@ -1070,13 +1025,23 @@ class _NamedTuple(tuple, _Named):
1070
1025
 
1071
1026
  iteritems = items
1072
1027
 
1073
- def _kwdself(self, iteration=None, **name):
1074
- '''(INTERNAL) Set C{__new__} keyword arguments.
1028
+ def reUnit(self, *Units, **name):
1029
+ '''Replace some of this C{Named-Tuple}'s C{Units}.
1030
+
1031
+ @arg Units: One or more C{Unit} classes, all positional.
1032
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
1033
+
1034
+ @return: This instance with updated C{Units}.
1035
+
1036
+ @note: This C{Named-Tuple}'s values are I{not updated}.
1075
1037
  '''
1076
- if iteration is not None:
1077
- self._iteration = iteration
1078
- if name:
1079
- self.name = name
1038
+ U = self._Units_
1039
+ n = min(len(U), len(Units))
1040
+ if n:
1041
+ R = Units + U[n:]
1042
+ if R != U:
1043
+ self._Units_ = R
1044
+ return self.renamed(name) if name else self
1080
1045
 
1081
1046
  def toRepr(self, prec=6, sep=_COMMASPACE_, fmt=Fmt.F, **unused): # PYCHOK signature
1082
1047
  '''Return this C{Named-Tuple} items as C{name=value} string(s).
@@ -1107,37 +1072,34 @@ class _NamedTuple(tuple, _Named):
1107
1072
  '''
1108
1073
  return Fmt.PAREN(sep.join(reprs(self, prec=prec, fmt=fmt)))
1109
1074
 
1110
- def toUnits(self, Error=UnitError): # overloaded in .frechet, .hausdorff
1075
+ def toUnits(self, Error=UnitError, **name): # overloaded in .frechet, .hausdorff
1111
1076
  '''Return a copy of this C{Named-Tuple} with each item value wrapped
1112
1077
  as an instance of its L{units} class.
1113
1078
 
1114
1079
  @kwarg Error: Error to raise for L{units} issues (C{UnitError}).
1080
+ @kwarg name: Optional C{B{name}=NN} (C{str}).
1115
1081
 
1116
1082
  @return: A duplicate of this C{Named-Tuple} (C{C{Named-Tuple}}).
1117
1083
 
1118
1084
  @raise Error: Invalid C{Named-Tuple} item or L{units} class.
1119
1085
  '''
1120
- t = (v for _, v in self.units(Error=Error))
1121
- return self.classof(*tuple(t))
1086
+ t = tuple(v for _, v in self.units(Error=Error))
1087
+ return self.classof(*t).reUnit(*self._Units_, **name)
1122
1088
 
1123
- def units(self, Error=UnitError):
1124
- '''Yield the items, each as a C{(name, value}) pair (C{2-tuple}) with
1125
- the value wrapped as an instance of its L{units} class.
1089
+ def units(self, **Error):
1090
+ '''Yield the items, each as a C{2-tuple (name, value}) with the
1091
+ value wrapped as an instance of its L{units} class.
1126
1092
 
1127
- @kwarg Error: Error to raise for L{units} issues (C{UnitError}).
1093
+ @kwarg Error: Optional C{B{Error}=UnitError} to raise for
1094
+ L{units} issues.
1128
1095
 
1129
1096
  @raise Error: Invalid C{Named-Tuple} item or L{units} class.
1130
1097
 
1131
1098
  @see: Method C{.items}.
1132
1099
  '''
1100
+ _2U = _MODS.units._toUnit
1133
1101
  for n, v, U in _zip(self._Names_, self, self._Units_): # strict=True
1134
- if not (v is None or U is None
1135
- or (isclass(U) and
1136
- isinstance(v, U) and
1137
- hasattr(v, _name_) and
1138
- v.name == n)): # PYCHOK indent
1139
- v = U(v, name=n, Error=Error)
1140
- yield n, v
1102
+ yield n, _2U(U, v, name=n, **Error)
1141
1103
 
1142
1104
  iterunits = units
1143
1105
 
@@ -1147,35 +1109,35 @@ class _NamedTuple(tuple, _Named):
1147
1109
  '''
1148
1110
  ns = self._Names_
1149
1111
  if not (isinstance(ns, tuple) and len(ns) > 1): # XXX > 0
1150
- raise _TypeError(_DOT_(self.classname, _Names_), ns)
1112
+ raise _TypeError(self._DOT_(_Names_), ns)
1151
1113
  for i, n in enumerate(ns):
1152
1114
  if not _xvalid(n, underOK=underOK):
1153
1115
  t = Fmt.SQUARE(_Names_=i) # PYCHOK no cover
1154
- raise _ValueError(_DOT_(self.classname, t), n)
1116
+ raise _ValueError(self._DOT_(t), n)
1155
1117
 
1156
1118
  us = self._Units_
1157
1119
  if not isinstance(us, tuple):
1158
- raise _TypeError(_DOT_(self.classname, _Units_), us)
1120
+ raise _TypeError(self._DOT_(_Units_), us)
1159
1121
  if len(us) != len(ns):
1160
1122
  raise LenError(self.__class__, _Units_=len(us), _Names_=len(ns))
1161
1123
  for i, u in enumerate(us):
1162
1124
  if not (u is None or callable(u)):
1163
1125
  t = Fmt.SQUARE(_Units_=i) # PYCHOK no cover
1164
- raise _TypeError(_DOT_(self.classname, t), u)
1126
+ raise _TypeError(self._DOT_(t), u)
1165
1127
 
1166
1128
  self.__class__._validated = True
1167
1129
 
1168
1130
  def _xtend(self, xTuple, *items, **name):
1169
1131
  '''(INTERNAL) Extend this C{Named-Tuple} with C{items} to an other B{C{xTuple}}.
1170
1132
  '''
1171
- if (issubclassof(xTuple, _NamedTuple) and
1172
- (len(self._Names_) + len(items)) == len(xTuple._Names_) and
1173
- self._Names_ == xTuple._Names_[:len(self)]):
1174
- n = _name__(**name) or self.name
1175
- return xTuple(self + items, name=n) # *(self + items)
1176
- c = NN(self.classname, repr(self._Names_)) # PYCHOK no cover
1177
- x = NN(xTuple.__name__, repr(xTuple._Names_)) # PYCHOK no cover
1178
- raise TypeError(_SPACE_(c, _vs_, x))
1133
+ _xsubclassof(_NamedTuple, xTuple=xTuple)
1134
+ if len(xTuple._Names_) != (len(self._Names_) + len(items)) or \
1135
+ xTuple._Names_[:len(self)] != self._Names_: # PYCHOK no cover
1136
+ c = NN(self.classname, repr(self._Names_))
1137
+ x = NN(xTuple.__name__, repr(xTuple._Names_))
1138
+ raise TypeError(_SPACE_(c, _vs_, x))
1139
+ t = self + items
1140
+ return xTuple(t, name=self._name__(name)) # .reUnit(*self._Units_)
1179
1141
 
1180
1142
 
1181
1143
  def callername(up=1, dflt=NN, source=False, underOK=False):
@@ -1295,18 +1257,16 @@ def _name__(name=NN, **kwds):
1295
1257
  if name or kwds:
1296
1258
  name, kwds = _name2__(name, **kwds)
1297
1259
  if kwds: # "unexpected keyword arguments ..."
1298
- m = _MODS.errors
1299
- raise m._UnexpectedError(**kwds)
1260
+ raise _UnexpectedError(**kwds)
1300
1261
  return name if name or name is None else NN
1301
1262
 
1302
1263
 
1303
- def _name1__(kwds_name, _or_nameof=None):
1264
+ def _name1__(kwds_name, **name__or_nameof):
1304
1265
  '''(INTERNAL) Resolve and set the C{B{name}=NN}.
1305
1266
  '''
1306
- if kwds_name:
1307
- n, kwds_name = _name2__(kwds_name, _or_nameof=_or_nameof)
1308
- if n:
1309
- kwds_name.update(name=n)
1267
+ if kwds_name or name__or_nameof:
1268
+ n, kwds_name = _name2__(kwds_name, **name__or_nameof)
1269
+ kwds_name.update(name=n)
1310
1270
  return kwds_name
1311
1271
 
1312
1272
 
@@ -1320,11 +1280,11 @@ def _name2__(name=NN, name__=None, _or_nameof=None, **kwds):
1320
1280
  else:
1321
1281
  n = str(name)
1322
1282
  elif name__ is not None:
1323
- n = getattr(name__, _dunder_name_, NN) # _xattr(name__, __name__=NN)
1283
+ n = _dunder_nameof(name__, NN)
1324
1284
  else:
1325
1285
  n = name if name is None else NN
1326
1286
  if _or_nameof is not None and not n:
1327
- n = getattr(_or_nameof, _name_, NN) # _xattr(_or_nameof, name=NN)
1287
+ n = _xattr(_or_nameof, name=NN) # nameof
1328
1288
  return n, kwds # (str or None or {}), dict
1329
1289
 
1330
1290
 
@@ -1406,6 +1366,69 @@ def _Pass(arg, **unused): # PYCHOK no cover
1406
1366
  return arg
1407
1367
 
1408
1368
 
1369
+ def _xjoined_(prefix, name=NN, enquote=True, **name__or_nameof):
1370
+ '''(INTERNAL) Join C{prefix} and non-empty C{name}.
1371
+ '''
1372
+ if name__or_nameof:
1373
+ name = _name__(name, **name__or_nameof)
1374
+ if name and prefix:
1375
+ if enquote:
1376
+ name = repr(name)
1377
+ t = _SPACE_(prefix, name)
1378
+ else:
1379
+ t = prefix or name
1380
+ return t
1381
+
1382
+
1383
+ def _xnamed(inst, name=NN, force=False, **name__or_nameof):
1384
+ '''(INTERNAL) Set the instance' C{.name = B{name}}.
1385
+
1386
+ @arg inst: The instance (C{_Named}).
1387
+ @kwarg name: The name (C{str}).
1388
+ @kwarg force: If C{True}, force rename (C{bool}).
1389
+
1390
+ @return: The B{C{inst}}, renamed if B{C{force}}d
1391
+ or if not named before.
1392
+ '''
1393
+ if name__or_nameof:
1394
+ name = _name__(name, **name__or_nameof)
1395
+ if name and isinstance(inst, _Named):
1396
+ if not inst.name:
1397
+ inst.name = name
1398
+ elif force:
1399
+ inst.rename(name)
1400
+ return inst
1401
+
1402
+
1403
+ def _xother3(inst, other, name=_other_, up=1, **name_other):
1404
+ '''(INTERNAL) Get C{name} and C{up} for a named C{other}.
1405
+ '''
1406
+ if name_other: # and other is None
1407
+ name, other = _xkwds_item2(name_other)
1408
+ elif other and len(other) == 1:
1409
+ name, other = _name__(name), other[0]
1410
+ else:
1411
+ raise _AssertionError(name, other, txt=classname(inst, prefixed=True))
1412
+ return other, name, up
1413
+
1414
+
1415
+ def _xotherError(inst, other, name=_other_, up=1):
1416
+ '''(INTERNAL) Return a C{_TypeError} for an incompatible, named C{other}.
1417
+ '''
1418
+ n = _callname(name, classname(inst, prefixed=True), inst.name, up=up + 1)
1419
+ return _TypeError(name, other, txt=_incompatible(n))
1420
+
1421
+
1422
+ def _xvalid(name, underOK=False):
1423
+ '''(INTERNAL) Check valid attribute name C{name}.
1424
+ '''
1425
+ return bool(name and isstr(name)
1426
+ and name != _name_
1427
+ and (underOK or not name.startswith(_UNDER_))
1428
+ and (not iskeyword(name))
1429
+ and isidentifier(name))
1430
+
1431
+
1409
1432
  __all__ += _ALL_DOCS(_Named,
1410
1433
  _NamedBase, # _NamedDict,
1411
1434
  _NamedEnum, _NamedEnumItem,