Coverage for pygeodesy/unitsBase.py: 94%

122 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-26 09:04 -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 

8# from pygeodesy.constants import isneg0 # _MODS 

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

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

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

12 _radians_, _radians2_, _radius_, _UNDER_, _units_, \ 

13 _std_ # PYCHOK used! 

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

15from pygeodesy.named import modulename, _Named, property_doc_, property_RO 

16# from pygeodesy.props import property_doc_, property_RO # from .named 

17from pygeodesy.streprs import Fmt, fstr 

18 

19__all__ = _ALL_LAZY.unitsBase 

20__version__ = '26.02.22' 

21 

22 

23class _NamedUnit(_Named): 

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

25 ''' 

26 _std_repr = True # set below 

27 _units = None 

28 

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

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

31 ''' 

32 if name_arg: 

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

34 try: # assert typ in cls.__mro__ 

35 self = typ.__new__(cls, arg) 

36 if name: 

37 self.name = name 

38 except Exception as x: 

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

40 return self 

41 

42 @staticmethod 

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

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

45 ''' 

46 if name_arg: 

47 if len(name_arg) > 1: 

48 raise _UnexpectedError(**name_arg) 

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

50 break 

51 elif name: 

52 pass 

53 elif name__ is not None: 

54 name = typename(name__) 

55 return name, arg 

56 

57 @staticmethod # PYCHOK unused suffix 

58 def _Error(cls, arg, name, Error=UnitError, # PYLINT W0211 unused 

59 suffix=NN, # PYLINT W0613 

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

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

62 

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

64 ''' 

65 kwds, x = {}, cause 

66 if x is not None: # caught exception 

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

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

69 if txt is _invalid_: 

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

71 kwds.update(cause=x) 

72 if name_arg: 

73 try: 

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

75 except Exception: # PYLINT W0718 ignore, same error? 

76 kwds.update(name_arg) 

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

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

79 

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

81 def std_repr(self): 

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

83 ''' 

84 return self._std_repr 

85 

86 @std_repr.setter # PYCHOK setter! 

87 def std_repr(self, std): 

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

89 ''' 

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

91 

92 def _toRepr(self, value): 

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

94 ''' 

95 return (Fmt.PARENSPACED(self.name, value) if self.name else # PYLINT W0125 

96 Fmt.PAREN( self.classname, value)) 

97 

98 @property_doc_(' units name.') 

99 def units(self): 

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

101 ''' 

102 if self._units is None: 

103 self._units = self.classname 

104 return self._units 

105 

106 @units.setter # PYCHOK setter! 

107 def units(self, units): 

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

109 ''' 

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

111 

112 

113class Float(float, _NamedUnit): 

114 '''Named C{float}. 

115 ''' 

116 # _std_repr = True # set below 

117 

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

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

120 

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

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

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

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

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

126 

127 @returns: A named C{Float}. 

128 

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

130 ''' 

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

132 

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

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

135 

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

137 

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

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

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

141 representation. 

142 ''' 

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

144 

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

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

147 ''' 

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

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

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

151 # mimicks this bhavior 

152 

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

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

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

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

157 return float.__str__(self) # always _std_str_ 

158 

159 @property_RO 

160 def isneg0(self): 

161 '''Is this value L{NEG0<pygeodesy.NEG0>}, C{-0.0} or C{-0}? (C{bool}). 

162 ''' 

163 return _MODS.constants.isneg0(self) 

164 

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

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

167 

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

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

170 

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

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

173 ''' 

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

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

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

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

178 

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

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

181 

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

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

184 ''' 

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

186 

187 

188class Int(int, _NamedUnit): 

189 '''Named C{int}. 

190 ''' 

191 # _std_repr = True # set below 

192 

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

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

195 

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

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

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

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

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

201 

202 @returns: A named C{Int}. 

203 

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

205 ''' 

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

207 

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

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

210 

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

212 

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

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

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

216 get the named C{toRepr} representation. 

217 ''' 

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

219 

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

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

222 ''' 

223 return self.toStr() 

224 

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

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

227 

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

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

230 

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

232 ''' 

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

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

235 

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

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

238 

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

240 ''' 

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

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

243 return '%d' % (self,) 

244 

245 

246class Radius(Float): 

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

248 ''' 

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

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

251 ''' 

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

253 

254 

255class Str(str, _NamedUnit): 

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

257 ''' 

258 # _std_repr = True # set below 

259 

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

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

262 

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

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

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

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

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

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

269 

270 @returns: A named L{Str}. 

271 

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

273 

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

275 ''' 

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

277 

278 def __repr__(self): 

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

280 

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

282 

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

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

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

286 get the named C{toRepr} representation. 

287 ''' 

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

289 

290 def __str__(self): 

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

292 ''' 

293 return self.toStr() 

294 

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

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

297 

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

299 

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

301 result re-callable. 

302 ''' 

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

304 

305 __call__ = join_ 

306 

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

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

309 

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

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

312 

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

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 r = super(Str, self).__repr__() 

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

319 

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

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

322 ''' 

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

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

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

326 

327 

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

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

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

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

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

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

334 

335 

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

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

338 ''' 

339 _xsubclassof(_NamedUnit, Base=Base) 

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

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

342 

343 

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

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

346 ''' 

347 _xsubclassof(_NamedUnit, Base=Base) 

348 if issubclassof(units, Base): 

349 U = units 

350 elif isstr(units): 

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

352 else: 

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

354 return U 

355 

356 

357__all__ += _ALL_DOCS(_NamedUnit) 

358 

359# **) MIT License 

360# 

361# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved. 

362# 

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

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

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

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

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

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

369# 

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

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

372# 

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

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

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

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

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

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

379# OTHER DEALINGS IN THE SOFTWARE.