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 isstr, issubclassof, _xsubclassof

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

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

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

11 _std_ # PYCHOK used!

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

13 from pygeodesy.named import modulename, _Named, property_doc_

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

15 from pygeodesy.streprs import Fmt, fstr

16

17__all__ = _ALL_LAZY.unitsBase

18__version__ = '24.08.13'

19

20

21 class _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

110 class 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

179 class 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

237 class 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

246 class 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

327 def _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

335 def _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.