Coverage for pygeodesy/unitsBase.py: 99%

101 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2024-05-15 16:36 -0400

1 

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

3 

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

5''' 

6 

7from pygeodesy.errors import UnitError, _XError, _xkwds_item2 

8from pygeodesy.interns import NN, _degrees_, _degrees2_, _invalid_, \ 

9 _meter_, _radians_, _radians2_, \ 

10 _radius_, _UNDER_, _std_ # PYCHOK used! 

11from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY 

12from pygeodesy.named import modulename, _Named, property_doc_ 

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

14from pygeodesy.streprs import Fmt, fstr 

15 

16__all__ = _ALL_LAZY.unitsBase 

17__version__ = '24.02.20' 

18 

19 

20class _NamedUnit(_Named): 

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

22 ''' 

23 _std_repr = True # set below 

24 _units = None 

25 

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

27 def std_repr(self): 

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

29 ''' 

30 return self._std_repr 

31 

32 @std_repr.setter # PYCHOK setter! 

33 def std_repr(self, std): 

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

35 ''' 

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

37 

38 def _toRepr(self, value): 

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

40 ''' 

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

42 Fmt.PAREN( self.classname, value) 

43 

44 @property_doc_(' units name.') 

45 def units(self): 

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

47 ''' 

48 if self._units is None: 

49 self._units = self.classname 

50 return self._units 

51 

52 @units.setter # PYCHOK setter! 

53 def units(self, units): 

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

55 ''' 

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

57 

58 

59class Float(float, _NamedUnit): 

60 '''Named C{float}. 

61 ''' 

62 # _std_repr = True # set below 

63 

64 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg): 

65 '''New C{Ffloat} instance. 

66 

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

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

69 @kwarg Error: Optional error to raise, overriding the default 

70 L{UnitError}. 

71 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu 

72 of B{C{name}} and B{C{arg}}. 

73 

74 @returns: A C{Float} instance. 

75 

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

77 ''' 

78 if name_arg: 

79 name, arg = _xkwds_item2(name_arg) 

80 try: 

81 self = float.__new__(cls, arg) 

82 if name: 

83 _NamedUnit.name.fset(self, name) # see _Named.name 

84 except Exception as x: # XXX not ... as x: 

85 raise _Error(cls, arg, name, Error, x=x) 

86 return self 

87 

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

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

90 

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

92 

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

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

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

96 representation. 

97 ''' 

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

99 

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

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

102 ''' 

103 # XXX must use super(Float, self)... since super()... 

104 # only works for Python 3+ and float.__str__(self) 

105 # invokes .__repr__(self); calling self.toRepr(std=True) 

106 # super(Float, self).__repr__() mimicks this behavior 

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

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

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

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

111 return float.__str__(self) # always _std_str_ 

112 

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

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

115 

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

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

118 

119 @see: Methods L{Float.__repr__}, L{Float.toStr} and function 

120 L{pygeodesy.fstr} for more documentation. 

121 ''' 

122 # XXX must use super(Float, self)... since 

123 # super()... only works for Python 3+ 

124 # return super(Float, self).__repr__() if std else \ 

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

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

127 

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

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

130 

131 @see: Method L{Float.__repr__} and function L{pygeodesy.fstr} 

132 for more documentation. 

133 ''' 

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

135 

136 

137class Int(int, _NamedUnit): 

138 '''Named C{int}. 

139 ''' 

140 # _std_repr = True # set below 

141 

142 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg): 

143 '''New C{Int} instance. 

144 

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

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

147 @kwarg Error: Optional error to raise, overriding the 

148 default L{UnitError}. 

149 @kwarg name_arg: Optional C{name=arg} keyword argument, 

150 inlieu of B{C{name}} and B{C{arg}}. 

151 

152 @returns: An C{Int} instance. 

153 

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

155 ''' 

156 if name_arg: 

157 name, arg = _xkwds_item2(name_arg) 

158 try: 

159 self = int.__new__(cls, arg) 

160 if name: 

161 _NamedUnit.name.fset(self, name) # see _Named.name 

162 except Exception as x: # XXX not ... as x: 

163 raise _Error(cls, arg, name, Error, x=x) 

164 return self 

165 

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

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

168 

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

170 

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

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

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

174 get the named C{toRepr} representation. 

175 ''' 

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

177 

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

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

180 ''' 

181 return self.toStr() 

182 

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

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

185 

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

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

188 

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

190 ''' 

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

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

193 

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

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

196 

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

198 ''' 

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

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

201 return '%d' % (self,) 

202 

203 

204class Radius(Float): 

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

206 ''' 

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

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

209 ''' 

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

211 

212 

213class Str(str, _NamedUnit): 

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

215 ''' 

216 # _std_repr = True # set below 

217 

218 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg): 

219 '''New C{Str} instance. 

220 

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

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

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

224 @kwarg Error: Optional error to raise, overriding the 

225 default (C{ValueError}). 

226 @kwarg name_arg: Optional C{name=arg} keyword argument, 

227 inlieu of B{C{name}} and B{C{arg}}. 

228 

229 @returns: A L{Str} instance. 

230 

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

232 

233 @see: Callable, not-nameable class L{pygeodesy.Str_}. 

234 ''' 

235 if name_arg: 

236 name, arg = _xkwds_item2(name_arg) 

237 try: 

238 self = str.__new__(cls, arg) 

239 if name: 

240 _NamedUnit.name.fset(self, name) # see _Named.name 

241 except Exception as x: # XXX not ... as x: 

242 raise _Error(cls, arg, name, Error, x=x) 

243 return self 

244 

245 def __repr__(self): 

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

247 

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

249 

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

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

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

253 get the named C{toRepr} representation. 

254 ''' 

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

256 

257 def __str__(self): 

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

259 ''' 

260 return self.toStr() 

261 

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

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

264 

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

266 

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

268 result re-callable. 

269 ''' 

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

271 

272 __call__ = join_ 

273 

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

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

276 

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

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

279 

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

281 ''' 

282 # must use super(Str, self).. since 

283 # super()... only works for Python 3+ and 

284 # str.__repr__(self) fails with Python 3.8+ 

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

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

287 

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

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

290 ''' 

291 # must use super(Str, self)... since 

292 # super()... only works for Python 3+ and 

293 # str.__str__(self) fails with Python 3.8+ 

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

295 

296 

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

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

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

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

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

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

303 

304 

305def _Error(clas, arg, name, Error, txt=_invalid_, x=None): 

306 '''(INTERNAL) Return an error with explanation. 

307 

308 @arg clas: The C{units} class or sub-class. 

309 @arg arg: The original C{unit} value. 

310 @arg name: The instance name (C{str}). 

311 @arg Error: The Error class to use (C{Excetion}). 

312 @kwarg txt: An explanation of the error )C{str}). 

313 @kwarg x: Caught exception, used for exception 

314 chaining (iff enabled in Python3+). 

315 

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

317 ''' 

318 if x is not None: # caught exception, cause 

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

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

321 if txt is _invalid_: 

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

323 n = name if name else modulename(clas).lstrip(_UNDER_) 

324 return _XError(Error, n, arg, txt=txt, cause=x) 

325 

326 

327__all__ += _ALL_DOCS(_NamedUnit) 

328 

329# **) MIT License 

330# 

331# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved. 

332# 

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

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

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

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

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

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

339# 

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

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

342# 

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

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

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

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

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

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

349# OTHER DEALINGS IN THE SOFTWARE.