Coverage for pygeodesy/unitsBase.py: 94%

1

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

3

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

5'''

6

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

8# from pygeodesy.constants import isneg0 # _MODS

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

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

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

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

13 _std_ # PYCHOK used!

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

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

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

17 from pygeodesy.streprs import Fmt, fstr

18

19 __all__ = _ALL_LAZY.unitsBase

20 __version__ = '26.02.22'

21

22

23 class _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, suffix=NN, # unused

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

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

61

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

63 '''

64 kwds, x = {}, cause

65 if x is not None: # caught exception

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

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

68 if txt is _invalid_:

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

70 kwds.update(cause=x)

71 if name_arg:

72 try:

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

74 except Exception: # ignore, same error?

75 kwds.update(name_arg)

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

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

78

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

80 def std_repr(self):

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

82 '''

83 return self._std_repr

84

85 @std_repr.setter # PYCHOK setter!

86 def std_repr(self, std):

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

88 '''

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

90

91 def _toRepr(self, value):

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

93 '''

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

95 Fmt.PAREN( self.classname, value)

96

97 @property_doc_(' units name.')

98 def units(self):

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

100 '''

101 if self._units is None:

102 self._units = self.classname

103 return self._units

104

105 @units.setter # PYCHOK setter!

106 def units(self, units):

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

108 '''

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

110

111

112 class Float(float, _NamedUnit):

113 '''Named C{float}.

114 '''

115 # _std_repr = True # set below

116

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

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

119

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

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

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

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

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

125

126 @returns: A named C{Float}.

127

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

129 '''

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

131

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

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

134

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

136

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

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

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

140 representation.

141 '''

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

143

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

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

146 '''

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

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

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

150 # mimicks this bhavior

151

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

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

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

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

156 return float.__str__(self) # always _std_str_

157

158 @property_RO

159 def isneg0(self):

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

161 '''

162 return _MODS.constants.isneg0(self)

163

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

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

166

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

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

169

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

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

172 '''

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

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

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

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

177

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

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

180

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

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

183 '''

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

185

186

187 class Int(int, _NamedUnit):

188 '''Named C{int}.

189 '''

190 # _std_repr = True # set below

191

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

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

194

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

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

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

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

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

200

201 @returns: A named C{Int}.

202

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

204 '''

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

206

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

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

209

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

211

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

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

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

215 get the named C{toRepr} representation.

216 '''

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

218

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

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

221 '''

222 return self.toStr()

223

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

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

226

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

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

229

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

231 '''

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

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

234

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

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

237

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

239 '''

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

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

242 return '%d' % (self,)

243

244

245 class Radius(Float):

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

247 '''

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

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

250 '''

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

252

253

254 class Str(str, _NamedUnit):

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

256 '''

257 # _std_repr = True # set below

258

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

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

261

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

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

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

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

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

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

268

269 @returns: A named L{Str}.

270

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

272

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

274 '''

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

276

277 def __repr__(self):

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

279

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

281

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

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

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

285 get the named C{toRepr} representation.

286 '''

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

288

289 def __str__(self):

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

291 '''

292 return self.toStr()

293

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

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

296

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

298

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

300 result re-callable.

301 '''

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

303

304 __call__ = join_

305

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

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

308

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

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

311

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

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

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

318

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

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

321 '''

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

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

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

325

326

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

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

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

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

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

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

333

334

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

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

337 '''

338 _xsubclassof(_NamedUnit, Base=Base)

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

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

341

342

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

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

345 '''

346 _xsubclassof(_NamedUnit, Base=Base)

347 if issubclassof(units, Base):

348 U = units

349 elif isstr(units):

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

351 else:

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

353 return U

354

355

356 __all__ += _ALL_DOCS(_NamedUnit)

357

358# **) MIT License

359#

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

361#

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

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

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

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

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

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

368#

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

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

371#

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

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

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

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

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

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

378# OTHER DEALINGS IN THE SOFTWARE.