1

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

3

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

5'''

6

7 from pygeodesy.errors import _UnexpectedError, UnitError, _XError

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

9 _meter_, MISSING, _radians_, _radians2_, \

10 _radius_, _UNDER_, _std_ # PYCHOK used!

11 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY

12 from pygeodesy.named import modulename, _Named, property_doc_

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

14 from pygeodesy.streprs import Fmt, fstr

15

16__all__ = _ALL_LAZY.unitsBase

17__version__ = '24.05.20'

18

19

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

59 class 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 separate B{C{arg}} and B{C{name}} ones.

73

74 @returns: A C{Float} instance.

75

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

77 '''

78 if name_arg:

79 name, arg = _arg_name_arg2(arg, **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

137 class 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, inlieu

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

151

152 @returns: An C{Int} instance.

153

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

155 '''

156 if name_arg:

157 name, arg = _arg_name_arg2(arg, **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

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

213 class 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, inlieu

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

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 = _arg_name_arg2(arg, **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

305 def _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 def _arg_name_arg2(arg, name__=None, **name_arg): # in .units

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

329 '''

330 if name__ is None:

331 t = name_arg.popitem() if len(name_arg) == 1 else \

332 (MISSING, arg)

333 else:

334 t = name__.__name__, arg

335 if name_arg:

336 raise _UnexpectedError(**name_arg)

337 return t

338

339

340__all__ += _ALL_DOCS(_NamedUnit)

341

342# **) MIT License

343#

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

345#

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

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

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

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

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

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

352#

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

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

355#

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

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

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

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

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

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

362# OTHER DEALINGS IN THE SOFTWARE.