Skip to content

Commit 2918e4e

Browse files
xqtjenkins-bot
authored andcommitted
[IMPR] Make Coordinate.__init__ parameters keyword-only
- Enforce keyword-only parameters after lat and lon in __init__ and lazy_load parameter in get_globe_item to improve clarity - Handle possible zero division in precision calculation explained in docs - Update docstrings - Minor code cleanup and consistency improvements Change-Id: I087c9db9a1736029fe625ad67a9242fd3488d67a
1 parent 60aba25 commit 2918e4e

File tree

1 file changed

+111
-62
lines changed

1 file changed

+111
-62
lines changed

pywikibot/_wbtypes.py

Lines changed: 111 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,19 @@
1212
import math
1313
import re
1414
from collections.abc import Mapping
15+
from contextlib import suppress
1516
from decimal import Decimal
1617
from typing import TYPE_CHECKING, Any
1718

1819
import pywikibot
1920
from pywikibot import exceptions
2021
from pywikibot.backports import Iterator
2122
from pywikibot.time import Timestamp
22-
from pywikibot.tools import issue_deprecation_warning, remove_last_args
23+
from pywikibot.tools import (
24+
deprecate_positionals,
25+
issue_deprecation_warning,
26+
remove_last_args,
27+
)
2328

2429

2530
if TYPE_CHECKING:
@@ -95,28 +100,40 @@ class Coordinate(WbRepresentation):
95100

96101
_items = ('lat', 'lon', 'entity')
97102

98-
def __init__(self, lat: float, lon: float, alt: float | None = None,
99-
precision: float | None = None,
100-
globe: str | None = None, typ: str = '',
101-
name: str = '', dim: int | None = None,
102-
site: DataSite | None = None,
103-
globe_item: ItemPageStrNoneType = None,
104-
primary: bool = False) -> None:
103+
@deprecate_positionals(since='10.4.0')
104+
def __init__(
105+
self,
106+
lat: float,
107+
lon: float,
108+
*,
109+
alt: float | None = None,
110+
precision: float | None = None,
111+
globe: str | None = None,
112+
typ: str = '',
113+
name: str = '',
114+
dim: int | None = None,
115+
site: DataSite | None = None,
116+
globe_item: ItemPageStrNoneType = None,
117+
primary: bool = False
118+
) -> None:
105119
"""Represent a geo coordinate.
106120
107-
:param lat: Latitude
108-
:param lon: Longitude
109-
:param alt: Altitude
110-
:param precision: precision
111-
:param globe: Which globe the point is on
112-
:param typ: The type of coordinate point
113-
:param name: The name
114-
:param dim: Dimension (in meters)
115-
:param site: The Wikibase site
116-
:param globe_item: The Wikibase item for the globe, or the
117-
entity URI of this Wikibase item. Takes precedence over
118-
'globe' if present.
119-
:param primary: True for a primary set of coordinates
121+
.. versionchanged:: 10.4
122+
The parameters after `lat` and `lon` are now keyword-only.
123+
124+
:param lat: Latitude coordinate
125+
:param lon: Longitude coordinate
126+
:param alt: Altitude in meters
127+
:param precision: Precision of the coordinate
128+
:param globe: The globe the coordinate is on (e.g. 'earth')
129+
:param typ: Type of coordinate point
130+
:param name: Name associated with the coordinate
131+
:param dim: Dimension in meters used for precision calculation
132+
:param site: The Wikibase site instance
133+
:param globe_item: Wikibase item or entity URI for the globe;
134+
takes precedence over *globe*
135+
:param primary: Indicates if this is a primary coordinate set
136+
(default: False)
120137
"""
121138
self.lat = lat
122139
self.lon = lon
@@ -137,11 +154,16 @@ def __init__(self, lat: float, lon: float, alt: float | None = None,
137154

138155
@property
139156
def entity(self) -> str:
140-
"""Return the entity uri of the globe."""
157+
"""Return the entity URI of the globe.
158+
159+
:raises CoordinateGlobeUnknownError: the globe is not supported
160+
by Wikibase
161+
"""
141162
if not self._entity:
142163
if self.globe not in self.site.globes():
143164
raise exceptions.CoordinateGlobeUnknownError(
144165
f'{self.globe} is not supported in Wikibase yet.')
166+
145167
return self.site.globes()[self.globe]
146168

147169
if isinstance(self._entity, pywikibot.ItemPage):
@@ -152,37 +174,41 @@ def entity(self) -> str:
152174
def toWikibase(self) -> dict[str, Any]:
153175
"""Export the data to a JSON object for the Wikibase API.
154176
155-
FIXME: Should this be in the DataSite object?
156-
157-
:return: Wikibase JSON
177+
:return: Wikibase JSON representation of the coordinate
158178
"""
159-
return {'latitude': self.lat,
160-
'longitude': self.lon,
161-
'altitude': self.alt,
162-
'globe': self.entity,
163-
'precision': self.precision,
164-
}
179+
return {
180+
'latitude': self.lat,
181+
'longitude': self.lon,
182+
'altitude': self.alt,
183+
'globe': self.entity,
184+
'precision': self.precision,
185+
}
165186

166187
@classmethod
167188
def fromWikibase(cls, data: dict[str, Any],
168189
site: DataSite | None = None) -> Coordinate:
169-
"""Constructor to create an object from Wikibase's JSON output.
190+
"""Create an object from Wikibase's JSON output.
170191
171-
:param data: Wikibase JSON
172-
:param site: The Wikibase site
192+
:param data: Wikibase JSON data
193+
:param site: The Wikibase site instance
194+
:return: Coordinate instance
173195
"""
174-
if site is None:
175-
site = pywikibot.Site().data_repository()
176-
196+
site = site or pywikibot.Site().data_repository()
177197
globe = None
178198

179-
if data['globe']:
199+
if data.get('globe'):
180200
globes = {entity: name for name, entity in site.globes().items()}
181201
globe = globes.get(data['globe'])
182202

183-
return cls(data['latitude'], data['longitude'],
184-
data['altitude'], data['precision'],
185-
globe, site=site, globe_item=data['globe'])
203+
return cls(
204+
data['latitude'],
205+
data['longitude'],
206+
alt=data.get('altitude'),
207+
precision=data.get('precision'),
208+
globe=globe,
209+
site=site,
210+
globe_item=data.get('globe')
211+
)
186212

187213
@property
188214
def precision(self) -> float | None:
@@ -214,17 +240,28 @@ def precision(self) -> float | None:
214240
215241
precision = math.degrees(
216242
self._dim / (radius * math.cos(math.radians(self.lat))))
243+
244+
:return: precision in degrees or None
217245
"""
218-
if self._dim is None and self._precision is None:
246+
if self._precision is not None:
247+
return self._precision
248+
249+
if self._dim is None:
219250
return None
220-
if self._precision is None and self._dim is not None:
221-
radius = 6378137 # TODO: Support other globes
251+
252+
radius = 6378137 # Earth radius in meters (TODO: support other globes)
253+
with suppress(ZeroDivisionError):
222254
self._precision = math.degrees(
223255
self._dim / (radius * math.cos(math.radians(self.lat))))
256+
224257
return self._precision
225258

226259
@precision.setter
227260
def precision(self, value: float) -> None:
261+
"""Set the precision value.
262+
263+
:param value: precision in degrees
264+
"""
228265
self._precision = value
229266

230267
def precisionToDim(self) -> int | None:
@@ -251,38 +288,50 @@ def precisionToDim(self) -> int | None:
251288
But this is not valid, since it returns a float value for dim which is
252289
an integer. We must round it off to the nearest integer.
253290
254-
Therefore::
291+
Therefore:
292+
293+
.. code-block:: python
294+
295+
dim = int(round(math.radians(
296+
precision)*radius*math.cos(math.radians(self.lat))))
255297
256-
dim = int(round(math.radians(
257-
precision)*radius*math.cos(math.radians(self.lat))))
298+
:return: dimension in meters
299+
:raises ValueError: if neither dim nor precision is set
258300
"""
259-
if self._dim is None and self._precision is None:
301+
if self._dim is not None:
302+
return self._dim
303+
304+
if self._precision is None:
260305
raise ValueError('No values set for dim or precision')
261-
if self._dim is None and self._precision is not None:
262-
radius = 6378137
263-
self._dim = int(
264-
round(
265-
math.radians(self._precision) * radius * math.cos(
266-
math.radians(self.lat))
267-
)
306+
307+
radius = 6378137
308+
self._dim = int(
309+
round(
310+
math.radians(self._precision) * radius * math.cos(
311+
math.radians(self.lat))
268312
)
313+
)
269314
return self._dim
270315

271-
def get_globe_item(self, repo: DataSite | None = None,
316+
@deprecate_positionals(since='10.4.0')
317+
def get_globe_item(self, repo: DataSite | None = None, *,
272318
lazy_load: bool = False) -> pywikibot.ItemPage:
273319
"""Return the ItemPage corresponding to the globe.
274320
275-
Note that the globe need not be in the same data repository as
276-
the Coordinate itself.
321+
.. note:: The globe need not be in the same data repository as
322+
the Coordinate itself.
277323
278324
A successful lookup is stored as an internal value to avoid the
279325
need for repeated lookups.
280326
327+
.. versionchanged:: 10.4
328+
The *lazy_load* parameter is now keyword-only.
329+
281330
:param repo: the Wikibase site for the globe, if different from
282-
that provided with the Coordinate.
283-
:param lazy_load: Do not raise NoPage if ItemPage does not
284-
exist.
285-
:return: pywikibot.ItemPage
331+
that provided with the Coordinate
332+
:param lazy_load: Do not raise :exc:`exceptions.NoPageError` if
333+
ItemPage does not exist
334+
:return: pywikibot.ItemPage of the globe
286335
"""
287336
if isinstance(self._entity, pywikibot.ItemPage):
288337
return self._entity

0 commit comments

Comments
 (0)