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.errors import _IsnotError, _UnexpectedError, UnitError, _XError

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

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

11 _radians_, _radians2_, _radius_, _UNDER_, _units_, \

12 _std_ # PYCHOK used!

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

14 from pygeodesy.named import modulename, _Named, property_doc_

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

16 from pygeodesy.streprs import Fmt, fstr

17

18__all__ = _ALL_LAZY.unitsBase

19__version__ = '25.04.14'

20

21

22 class _NamedUnit(_Named):

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

24 '''

25 _std_repr = True # set below

26 _units = None

27

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

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

30 '''

31 if name_arg:

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

33 try: # assert typ in cls.__mro__

34 self = typ.__new__(cls, arg)

35 if name:

36 self.name = name

37 except Exception as x:

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

39 return self

40

41 @staticmethod

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

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

44 '''

45 if name_arg:

46 if len(name_arg) > 1:

47 raise _UnexpectedError(**name_arg)

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

49 break

50 elif name:

51 pass

52 elif name__ is not None:

53 name = typename(name__)

54 return name, arg

55

56 @staticmethod # PYCHOK unused suffix

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

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

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

60

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

62 '''

63 kwds, x = {}, cause

64 if x is not None: # caught exception

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

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

67 if txt is _invalid_:

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

69 kwds.update(cause=x)

70 if name_arg:

71 try:

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

73 except Exception: # ignore, same error?

74 kwds.update(name_arg)

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

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

77

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

79 def std_repr(self):

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

81 '''

82 return self._std_repr

83

84 @std_repr.setter # PYCHOK setter!

85 def std_repr(self, std):

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

87 '''

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

89

90 def _toRepr(self, value):

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

92 '''

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

94 Fmt.PAREN( self.classname, value)

95

96 @property_doc_(' units name.')

97 def units(self):

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

99 '''

100 if self._units is None:

101 self._units = self.classname

102 return self._units

103

104 @units.setter # PYCHOK setter!

105 def units(self, units):

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

107 '''

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

109

110

111 class Float(float, _NamedUnit):

112 '''Named C{float}.

113 '''

114 # _std_repr = True # set below

115

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

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

118

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

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

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

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

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

124

125 @returns: A named C{Float}.

126

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

128 '''

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

130

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

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

133

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

135

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

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

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

139 representation.

140 '''

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

142

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

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

145 '''

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

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

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

149 # mimicks this bhavior

150

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

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

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

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

155 return float.__str__(self) # always _std_str_

156

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

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

159

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

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

162

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

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

165 '''

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

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

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

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

170

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

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

173

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

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

176 '''

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

178

179

180 class Int(int, _NamedUnit):

181 '''Named C{int}.

182 '''

183 # _std_repr = True # set below

184

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

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

187

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

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

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

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

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

193

194 @returns: A named C{Int}.

195

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

197 '''

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

199

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

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

202

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

204

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

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

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

208 get the named C{toRepr} representation.

209 '''

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

211

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

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

214 '''

215 return self.toStr()

216

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

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

219

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

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

222

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

224 '''

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

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

227

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

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

230

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

232 '''

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

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

235 return '%d' % (self,)

236

237

238 class Radius(Float):

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

240 '''

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

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

243 '''

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

245

246

247 class Str(str, _NamedUnit):

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

249 '''

250 # _std_repr = True # set below

251

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

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

254

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

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

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

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

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

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

261

262 @returns: A named L{Str}.

263

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

265

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

267 '''

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

269

270 def __repr__(self):

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

272

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

274

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

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

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

278 get the named C{toRepr} representation.

279 '''

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

281

282 def __str__(self):

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

284 '''

285 return self.toStr()

286

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

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

289

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

291

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

293 result re-callable.

294 '''

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

296

297 __call__ = join_

298

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

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

301

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

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

304

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

306 '''

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

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

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

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

311

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

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

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 return super(Str, self).__str__()

318

319

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

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

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

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

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

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

326

327

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

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

330 '''

331 _xsubclassof(_NamedUnit, Base=Base)

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

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

334

335

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

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

338 '''

339 _xsubclassof(_NamedUnit, Base=Base)

340 if issubclassof(units, Base):

341 U = units

342 elif isstr(units):

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

344 else:

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

346 return U

347

348

349__all__ += _ALL_DOCS(_NamedUnit)

350

351# **) MIT License

352#

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

354#

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

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

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

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

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

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

361#

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

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

364#

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

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

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

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

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

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

371# OTHER DEALINGS IN THE SOFTWARE.