Coverage for pygeodesy/unitsBase.py: 95%

119 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-09 11:05 -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 isstr, issubclassof, _xsubclassof 

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

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

10 _radians_, _radians2_, _radius_, _UNDER_, _units_, \ 

11 _std_ # PYCHOK used! 

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

13from pygeodesy.named import modulename, _Named, property_doc_ 

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

15from pygeodesy.streprs import Fmt, fstr 

16 

17__all__ = _ALL_LAZY.unitsBase 

18__version__ = '24.08.13' 

19 

20 

21class _NamedUnit(_Named): 

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

23 ''' 

24 _std_repr = True # set below 

25 _units = None 

26 

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

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

29 ''' 

30 if name_arg: 

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

32 try: # assert typ in cls.__mro__ 

33 self = typ.__new__(cls, arg) 

34 if name: 

35 self.name = name 

36 except Exception as x: 

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

38 return self 

39 

40 @staticmethod 

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

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

43 ''' 

44 if name_arg: 

45 if len(name_arg) > 1: 

46 raise _UnexpectedError(**name_arg) 

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

48 break 

49 elif name: 

50 pass 

51 elif name__ is not None: 

52 name = name__.__name__ 

53 return name, arg 

54 

55 @staticmethod # PYCHOK unused suffix 

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

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

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

59 

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

61 ''' 

62 kwds, x = {}, cause 

63 if x is not None: # caught exception 

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

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

66 if txt is _invalid_: 

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

68 kwds.update(cause=x) 

69 if name_arg: 

70 try: 

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

72 except Exception: # ignore, same error? 

73 kwds.update(name_arg) 

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

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

76 

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

78 def std_repr(self): 

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

80 ''' 

81 return self._std_repr 

82 

83 @std_repr.setter # PYCHOK setter! 

84 def std_repr(self, std): 

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

86 ''' 

87 self._std_repr = std in (True, _std_) 

88 

89 def _toRepr(self, value): 

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

91 ''' 

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

93 Fmt.PAREN( self.classname, value) 

94 

95 @property_doc_(' units name.') 

96 def units(self): 

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

98 ''' 

99 if self._units is None: 

100 self._units = self.classname 

101 return self._units 

102 

103 @units.setter # PYCHOK setter! 

104 def units(self, units): 

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

106 ''' 

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

108 

109 

110class Float(float, _NamedUnit): 

111 '''Named C{float}. 

112 ''' 

113 # _std_repr = True # set below 

114 

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

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

117 

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

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

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

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

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

123 

124 @returns: A named C{Float}. 

125 

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

127 ''' 

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

129 

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

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

132 

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

134 

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

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

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

138 representation. 

139 ''' 

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

141 

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

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

144 ''' 

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

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

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

148 # mimicks this bhavior 

149 

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

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

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

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

154 return float.__str__(self) # always _std_str_ 

155 

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

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

158 

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

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

161 

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

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

164 ''' 

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

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

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

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

169 

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

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

172 

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

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

175 ''' 

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

177 

178 

179class Int(int, _NamedUnit): 

180 '''Named C{int}. 

181 ''' 

182 # _std_repr = True # set below 

183 

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

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

186 

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

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

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

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

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

192 

193 @returns: A named C{Int}. 

194 

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

196 ''' 

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

198 

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

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

201 

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

203 

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

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

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

207 get the named C{toRepr} representation. 

208 ''' 

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

210 

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

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

213 ''' 

214 return self.toStr() 

215 

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

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

218 

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

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

221 

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

223 ''' 

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

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

226 

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

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

229 

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

231 ''' 

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

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

234 return '%d' % (self,) 

235 

236 

237class Radius(Float): 

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

239 ''' 

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

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

242 ''' 

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

244 

245 

246class Str(str, _NamedUnit): 

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

248 ''' 

249 # _std_repr = True # set below 

250 

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

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

253 

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

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

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

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

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

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

260 

261 @returns: A named L{Str}. 

262 

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

264 

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

266 ''' 

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

268 

269 def __repr__(self): 

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

271 

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

273 

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

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

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

277 get the named C{toRepr} representation. 

278 ''' 

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

280 

281 def __str__(self): 

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

283 ''' 

284 return self.toStr() 

285 

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

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

288 

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

290 

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

292 result re-callable. 

293 ''' 

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

295 

296 __call__ = join_ 

297 

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

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

300 

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

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

303 

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

305 ''' 

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

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

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

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

310 

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

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

313 ''' 

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

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

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

317 

318 

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

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

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

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

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

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

325 

326 

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

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

329 ''' 

330 _xsubclassof(_NamedUnit, Base=Base) 

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

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

333 

334 

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

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

337 ''' 

338 _xsubclassof(_NamedUnit, Base=Base) 

339 if issubclassof(units, Base): 

340 U = units 

341 elif isstr(units): 

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

343 else: 

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

345 return U 

346 

347 

348__all__ += _ALL_DOCS(_NamedUnit) 

349 

350# **) MIT License 

351# 

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

353# 

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

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

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

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

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

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

360# 

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

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

363# 

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

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

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

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

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

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

370# OTHER DEALINGS IN THE SOFTWARE.