Coverage for pygeodesy/unitsBase.py: 95%

119 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-29 12:40 -0400

1 

2# -*- coding: utf-8 -*- 

3 

4u'''Basic C{Float}, C{Int} and C{Str}ing units classes. 

5''' 

6 

7from pygeodesy.basics import _isin, isstr, issubclassof, _xsubclassof, typename 

8from pygeodesy.errors import _IsnotError, _UnexpectedError, UnitError, _XError 

9# from pygeodesy.internals import typename # from .basics 

10from pygeodesy.interns import NN, _degrees_, _degrees2_, _invalid_, _meter_, \ 

11 _radians_, _radians2_, _radius_, _UNDER_, _units_, \ 

12 _std_ # PYCHOK used! 

13from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS 

14from pygeodesy.named import modulename, _Named, property_doc_ 

15# from pygeodesy.props import property_doc_ # from .named 

16from pygeodesy.streprs import Fmt, fstr 

17 

18__all__ = _ALL_LAZY.unitsBase 

19__version__ = '25.04.14' 

20 

21 

22class _NamedUnit(_Named): 

23 '''(INTERNAL) Base class for C{units}. 

24 ''' 

25 _std_repr = True # set below 

26 _units = None 

27 

28 def __new__(cls, typ, arg, name, Error=UnitError, **name_arg): 

29 '''(INTERNAL) Return a named C{typ.__new__(cls, arg)} instance. 

30 ''' 

31 if name_arg: 

32 name, arg = _NamedUnit._arg_name_arg2(arg, name, **name_arg) 

33 try: # assert typ in cls.__mro__ 

34 self = typ.__new__(cls, arg) 

35 if name: 

36 self.name = name 

37 except Exception as x: 

38 raise _NamedUnit._Error(cls, arg, name, Error, cause=x) 

39 return self 

40 

41 @staticmethod 

42 def _arg_name_arg2(arg, name=NN, name__=None, **name_arg): # in .units 

43 '''(INTERNAL) Get the 2-tuple C{(name, arg)}. 

44 ''' 

45 if name_arg: 

46 if len(name_arg) > 1: 

47 raise _UnexpectedError(**name_arg) 

48 for name, arg in name_arg.items(): # next(iter(.items())) 

49 break 

50 elif name: 

51 pass 

52 elif name__ is not None: 

53 name = typename(name__) 

54 return name, arg 

55 

56 @staticmethod # PYCHOK unused suffix 

57 def _Error(cls, arg, name, Error=UnitError, suffix=NN, # unused 

58 txt=_invalid_, cause=None, **name_arg): 

59 '''(INTERNAL) Return a C{_NamedUnit} error with explanation. 

60 

61 @returns: An B{C{Error}} instance. 

62 ''' 

63 kwds, x = {}, cause 

64 if x is not None: # caught exception 

65 if Error is UnitError: # and isError(x) 

66 Error = type(x) # i.e. not overridden 

67 if txt is _invalid_: 

68 txt = str(x) # i.e. not overridden 

69 kwds.update(cause=x) 

70 if name_arg: 

71 try: 

72 name, arg = _NamedUnit._arg_name_arg2(arg, name, **name_arg) 

73 except Exception: # ignore, same error? 

74 kwds.update(name_arg) 

75 n = name if name else modulename(cls).lstrip(_UNDER_) 

76 return _XError(Error, n, arg, txt=txt, **kwds) 

77 

78 @property_doc_(' standard C{repr} or named C{toRepr} representation.') 

79 def std_repr(self): 

80 '''Get the representation (C{bool}, C{True} means standard). 

81 ''' 

82 return self._std_repr 

83 

84 @std_repr.setter # PYCHOK setter! 

85 def std_repr(self, std): 

86 '''Set the representation (C{True} or C{"std"} for standard). 

87 ''' 

88 self._std_repr = _isin(std, True, _std_) 

89 

90 def _toRepr(self, value): 

91 '''(INTERNAL) Representation "<name> (<value>)" or "<classname>(<value>)". 

92 ''' 

93 return Fmt.PARENSPACED(self.name, value) if self.name else \ 

94 Fmt.PAREN( self.classname, value) 

95 

96 @property_doc_(' units name.') 

97 def units(self): 

98 '''Get the units name (C{str}). 

99 ''' 

100 if self._units is None: 

101 self._units = self.classname 

102 return self._units 

103 

104 @units.setter # PYCHOK setter! 

105 def units(self, units): 

106 '''Set the units name for this instance (C{str} or C{None} for default). 

107 ''' 

108 self._units = None if units is None else str(units) 

109 

110 

111class Float(float, _NamedUnit): 

112 '''Named C{float}. 

113 ''' 

114 # _std_repr = True # set below 

115 

116 def __new__(cls, arg=None, name=NN, **Error_name_arg): 

117 '''New, named C{Ffloat}. 

118 

119 @kwarg arg: The value (any C{type} acceptable to C{float}). 

120 @kwarg name: Optional name (C{str}). 

121 @kwarg Error_name_arg: Optional C{B{Error}=UnitError} to raise 

122 and optional C{name=arg} keyword argument, inlieu 

123 of separate B{C{arg}} and B{C{name}} ones. 

124 

125 @returns: A named C{Float}. 

126 

127 @raise Error: Invalid B{C{arg}}. 

128 ''' 

129 return _NamedUnit.__new__(cls, float, arg, name, **Error_name_arg) 

130 

131 def __repr__(self): # to avoid MRO(float) 

132 '''Return a representation of this C{Float}. 

133 

134 @see: Method C{Float.toRepr} and property C{Float.std_repr}. 

135 

136 @note: Use C{env} variable C{PYGEODESY_FLOAT_STD_REPR=std} prior 

137 to C{import pygeodesy} to get the standard C{repr} or set 

138 property C{std_repr=False} to always get the named C{toRepr} 

139 representation. 

140 ''' 

141 return self.toRepr(std=self._std_repr) 

142 

143 def __str__(self): # to avoid MRO(float) 

144 '''Return this C{Float} as standard C{str}. 

145 ''' 

146 # must use super(Float, self)... since super()... only works 

147 # for Python 3+ and float.__str__(self) invokes .__repr__(self); 

148 # calling self.toRepr(std=True) super(Float, self).__repr__() 

149 # mimicks this bhavior 

150 

151 # XXX the default number of decimals is 10-12 when using 

152 # float.__str__(self) with both python 3.8+ and 2.7-, but 

153 # float.__repr__(self) shows DIG decimals in python2.7! 

154 # return super(Float, self).__repr__() # see .testCss.py 

155 return float.__str__(self) # always _std_str_ 

156 

157 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ... 

158 '''Return a representation of this C{Float}. 

159 

160 @kwarg std: If C{True}, return the standard C{repr}, 

161 otherwise the named representation (C{bool}). 

162 

163 @see: Function L{fstr<pygeodesy.streprs.fstr>} and methods 

164 L{Float.__repr__}, L{Float.toStr} for further details. 

165 ''' 

166 # must use super(Float, self)... since super()... only works for 

167 # Python 3+; return super(Float, self).__repr__() if std else \ 

168 return float.__repr__(self) if std else \ 

169 self._toRepr(self.toStr(**prec_fmt_ints)) 

170 

171 def toStr(self, prec=12, fmt=Fmt.g, ints=False): # PYCHOK prec=8, ... 

172 '''Format this C{Float} as C{str}. 

173 

174 @see: Function L{fstr<pygeodesy.streprs.fstr>} and method 

175 L{Float.__repr__} and for further information. 

176 ''' 

177 return fstr(self, prec=prec, fmt=fmt, ints=ints) 

178 

179 

180class Int(int, _NamedUnit): 

181 '''Named C{int}. 

182 ''' 

183 # _std_repr = True # set below 

184 

185 def __new__(cls, arg=None, name=NN, **Error_name_arg): 

186 '''New, named C{Int}. 

187 

188 @kwarg arg: The value (any C{type} acceptable to C{int}). 

189 @kwarg name: Optional name (C{str}). 

190 @kwarg Error_name_arg: Optional C{B{Error}=UnitError} to raise 

191 and optional C{name=arg} keyword argument, inlieu 

192 of separate B{C{arg}} and B{C{name}} ones. 

193 

194 @returns: A named C{Int}. 

195 

196 @raise Error: Invalid B{C{arg}}. 

197 ''' 

198 return _NamedUnit.__new__(cls, int, arg, name, **Error_name_arg) 

199 

200 def __repr__(self): # to avoid MRO(int) 

201 '''Return a representation of this named C{int}. 

202 

203 @see: Method C{Int.toRepr} and property C{Int.std_repr}. 

204 

205 @note: Use C{env} variable C{PYGEODESY_INT_STD_REPR=std} 

206 prior to C{import pygeodesy} to get the standard 

207 C{repr} or set property C{std_repr=False} to always 

208 get the named C{toRepr} representation. 

209 ''' 

210 return self.toRepr(std=self._std_repr) 

211 

212 def __str__(self): # to avoid MRO(int) 

213 '''Return this C{Int} as standard C{str}. 

214 ''' 

215 return self.toStr() 

216 

217 def toRepr(self, std=False, **unused): # PYCHOK **unused 

218 '''Return a representation of this C{Int}. 

219 

220 @kwarg std: If C{True}, return the standard C{repr}, 

221 otherwise the named representation (C{bool}). 

222 

223 @see: Method L{Int.__repr__} for more documentation. 

224 ''' 

225 r = int.__repr__(self) # self.toStr() 

226 return r if std else self._toRepr(r) 

227 

228 def toStr(self, **unused): # PYCHOK **unused 

229 '''Return this C{Int} as standard C{str}. 

230 

231 @see: Method L{Int.__repr__} for more documentation. 

232 ''' 

233 # XXX must use '%d' % (self,) since 

234 # int.__str__(self) fails with 3.8+ 

235 return '%d' % (self,) 

236 

237 

238class Radius(Float): 

239 '''Named C{float} representing a radius, conventionally in C{meter}. 

240 ''' 

241 def __new__(cls, arg=None, name=_radius_, **Error_name_arg): 

242 '''New L{Radius} instance, see L{Float}. 

243 ''' 

244 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg) 

245 

246 

247class Str(str, _NamedUnit): 

248 '''Named, callable C{str}. 

249 ''' 

250 # _std_repr = True # set below 

251 

252 def __new__(cls, arg=None, name=NN, **Error_name_arg): 

253 '''New, named and callable C{Str}. 

254 

255 @kwarg cls: This class (C{Str} or sub-class). 

256 @kwarg arg: The value (any C{type} acceptable to C{str}). 

257 @kwarg name: Optional name (C{str}). 

258 @kwarg Error_name_arg: Optional C{B{Error}=UnitError} to raise 

259 and optional C{name=arg} keyword argument, inlieu 

260 of separate B{C{arg}} and B{C{name}} ones. 

261 

262 @returns: A named L{Str}. 

263 

264 @raise Error: Invalid B{C{arg}}. 

265 

266 @see: Callable, not nameable class L{Str_<pygeodesy.interns.Str_>}. 

267 ''' 

268 return _NamedUnit.__new__(cls, str, arg, name, **Error_name_arg) 

269 

270 def __repr__(self): 

271 '''Return a representation of this C{Str}. 

272 

273 @see: Method C{Str.toRepr} and property C{Str.std_repr}. 

274 

275 @note: Use C{env} variable C{PYGEODESY_STR_STD_REPR=std} 

276 prior to C{import pygeodesy} to get the standard 

277 C{repr} or set property C{std_repr=False} to always 

278 get the named C{toRepr} representation. 

279 ''' 

280 return self.toRepr(std=self._std_repr) # see .test/testGars.py 

281 

282 def __str__(self): 

283 '''Return this C{Str} as standard C{str}. 

284 ''' 

285 return self.toStr() 

286 

287 def join_(self, *args, **name_Error): 

288 '''Join all positional B{C{args}} like C{self.join(B{args})}. 

289 

290 @return: All B{C{args}} joined by this instance (L{Str_}). 

291 

292 @note: An other L{Str} instance is returned to make the 

293 result re-callable. 

294 ''' 

295 return Str(str.join(self, map(str, args)), **name_Error) # re-callable 

296 

297 __call__ = join_ 

298 

299 def toRepr(self, std=False, **unused): # PYCHOK **unused 

300 '''Return a representation of this C{Str}. 

301 

302 @kwarg std: If C{True}, return the standard C{repr}, 

303 otherwise the named representation (C{bool}). 

304 

305 @see: Method L{Str.__repr__} for more documentation. 

306 ''' 

307 # must use super(Str, self)... since super()... only works 

308 # for Python 3+ and str.__repr__(self) fails in Python 3.8+ 

309 r = super(Str, self).__repr__() 

310 return r if std else self._toRepr(r) 

311 

312 def toStr(self, **unused): # PYCHOK **unused 

313 '''Return this C{Str} as standard C{str}. 

314 ''' 

315 # must use super(Str, self)... since super()... only works 

316 # for Python 3+ and str.__repr__(self) fails in Python 3.8+ 

317 return super(Str, self).__str__() 

318 

319 

320_Str_degrees = Str(_degrees_) # PYCHOK in .frechet, .hausdorff 

321_Str_degrees2 = Str(_degrees2_) # PYCHOK in .frechet, .hausdorff 

322_Str_meter = Str(_meter_) # PYCHOK in .frechet, .hausdorff 

323_Str_NN = Str(NN) # PYCHOK in .frechet, .hausdorff 

324_Str_radians = Str(_radians_) # PYCHOK in .frechet, .hausdorff 

325_Str_radians2 = Str(_radians2_) # PYCHOK in .frechet, .hausdorff 

326 

327 

328def _xUnit(units, Base): # PYCHOK in .frechet, .hausdorff 

329 '''(INTERNAL) Get C{Unit} from C{units} or C{name}, ortherwise C{Base}. 

330 ''' 

331 _xsubclassof(_NamedUnit, Base=Base) 

332 U = getattr(_MODS, units.capitalize(), Base) if isstr(units) else units 

333 return U if issubclassof(U, Base) else Base 

334 

335 

336def _xUnits(units, Base=_NamedUnit): # in .frechet, .hausdorff 

337 '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}. 

338 ''' 

339 _xsubclassof(_NamedUnit, Base=Base) 

340 if issubclassof(units, Base): 

341 U = units 

342 elif isstr(units): 

343 U = Str(units, name=_units_) # XXX Str to _Pass and for backward compatibility 

344 else: 

345 raise _IsnotError(Base, Str, str, units=units) 

346 return U 

347 

348 

349__all__ += _ALL_DOCS(_NamedUnit) 

350 

351# **) MIT License 

352# 

353# Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved. 

354# 

355# Permission is hereby granted, free of charge, to any person obtaining a 

356# copy of this software and associated documentation files (the "Software"), 

357# to deal in the Software without restriction, including without limitation 

358# the rights to use, copy, modify, merge, publish, distribute, sublicense, 

359# and/or sell copies of the Software, and to permit persons to whom the 

360# Software is furnished to do so, subject to the following conditions: 

361# 

362# The above copyright notice and this permission notice shall be included 

363# in all copies or substantial portions of the Software. 

364# 

365# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 

366# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

367# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 

368# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 

369# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 

370# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 

371# OTHER DEALINGS IN THE SOFTWARE.