From 3bd53ce819826479f82800b2d9286dbe231f3ea3 Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Tue, 11 Feb 2020 22:39:17 +0300 Subject: [PATCH 1/9] Update dependencies --- requirements.txt | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/requirements.txt b/requirements.txt index 25fda34..49e0d05 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,36 @@ appdirs==1.4.3 -atomicwrites==1.3.0 attrs==19.3.0 -black==19.3b0 -certifi==2019.9.11 +black==19.10b0 +certifi==2019.11.28 chardet==3.0.4 Click==7.0 -coverage==4.5.4 +coverage==5.0.3 entrypoints==0.3 -flake8==3.7.8 -flake8-debugger==3.2.0 -flake8-isort==2.7.0 -flake8-print==3.1.1 -flake8-quotes==2.1.0 +flake8==3.7.9 +flake8-debugger==3.2.1 +flake8-isort==2.8.0 +flake8-print==3.1.4 +flake8-quotes==2.1.1 idna==2.8 -importlib-metadata==0.23 isort==4.3.21 mccabe==0.6.1 -more-itertools==7.2.0 -packaging==19.2 -pluggy==0.13.0 -py==1.8.0 +more-itertools==8.2.0 +packaging==20.1 +pathspec==0.7.0 +pluggy==0.13.1 +py==1.8.1 pycodestyle==2.5.0 pyflakes==2.1.1 -pyparsing==2.4.2 -pytest==5.2.2 +pyparsing==2.4.6 +pytest==5.3.5 pytest-cov==2.8.1 -pytest-mock==1.11.2 +pytest-mock==2.0.0 +regex==2020.1.8 requests==2.22.0 requests-mock==1.7.0 -six==1.12.0 -testfixtures==6.10.0 +six==1.14.0 +testfixtures==6.12.0 toml==0.10.0 -urllib3==1.25.6 -wcwidth==0.1.7 -zipp==0.6.0 +typed-ast==1.4.1 +urllib3==1.25.8 +wcwidth==0.1.8 From a4e13a5a70e260be4d639b971a8ff9e2eb6e5c1b Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Tue, 11 Feb 2020 22:42:23 +0300 Subject: [PATCH 2/9] Run CI tests in python 3.8 too --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2399809..4ac77c0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: [3.6, 3.7] + python-version: [3.6, 3.7, 3.8] steps: - uses: actions/checkout@v1 From cf920f05b1e714714260f40a4eb9c597e7c18789 Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Tue, 11 Feb 2020 22:54:46 +0300 Subject: [PATCH 3/9] Update checkout action --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4ac77c0..2c59e51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: python-version: [3.6, 3.7, 3.8] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v1 From fc38ce49f91e32f50cb5588dbce12ea83d620e8b Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Tue, 11 Feb 2020 23:13:52 +0300 Subject: [PATCH 4/9] Improve linting and autoformatting rules --- .editorconfig | 13 +++++++++++++ .flake8 | 3 ++- .isort.cfg | 5 +++-- .travis.yml | 29 ----------------------------- pyproject.toml | 3 --- setup.py | 5 ++--- tests/conftest.py | 23 +++++++++++++++++++++++ tests/test_coordinates.py | 18 +----------------- tests/test_requests.py | 13 +------------ yandex_geocoder/client.py | 12 +++--------- 10 files changed, 48 insertions(+), 76 deletions(-) create mode 100644 .editorconfig delete mode 100644 .travis.yml delete mode 100644 pyproject.toml create mode 100644 tests/conftest.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a51f019 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +end_of_line = lf +charset = utf-8 + +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +[*.json] +indent_style = space +indent_size = 4 diff --git a/.flake8 b/.flake8 index ac7b48a..b6e5688 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,4 @@ [flake8] +exclude = .env inline-quotes = " - +max-line-length = 88 diff --git a/.isort.cfg b/.isort.cfg index 22a03c4..cc8fddb 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,5 +1,6 @@ [settings] -line-length=79 -multi_line_output=3 combine_as_imports=true include_trailing_comma=true +line-length=88 +multi_line_output=3 +use_parentheses = true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4381a1b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -dist: xenial -sudo: false - -language: python -cache: pip - -python: - - "3.6" - - "3.7" - -install: - - python setup.py develop - - pip install coveralls -r requirements.txt - -script: - - flake8 - - black --check ./ - - coverage run --source yandex_geocoder -m pytest - -after_success: - - coveralls - -deploy: - provider: pypi - user: $PyPiLogin - password: $PyPiPassword - on: - tags: true - python: "3.7" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index f3497b3..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[tool.black] -line-length = 79 - diff --git a/setup.py b/setup.py index d83b22a..12a1670 100644 --- a/setup.py +++ b/setup.py @@ -11,9 +11,7 @@ def read(fname): setup( author="Nikita Sivakov", author_email="sivakov512@gmail.com", - description=( - "Simple library for getting address coordinates via Yandex geocoder" - ), + description="Simple library for getting address coordinates via Yandex geocoder", install_requires=["requests~=2.22"], keywords="yandex geocoder geo coordinates maps api", license="MIT", @@ -28,5 +26,6 @@ def read(fname): "Programming Language :: Python", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", ], ) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f07bf52 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +import json + +import pytest +import requests_mock + + +@pytest.fixture +def mock_response(): + with requests_mock.mock() as _m: + yield lambda **kwargs: _m.get( + "https://geocode-maps.yandex.ru/1.x/?geocode=b&format=json", **kwargs + ) + + +@pytest.fixture +def mock_client_response(mocker): + def _mock(fixture_name): + with open("./tests/fixtures/{}.json".format(fixture_name)) as fixture: + return mocker.patch( + "yandex_geocoder.Client.request", return_value=json.load(fixture), + ) + + return _mock diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index f418eca..f133fc4 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,23 +1,9 @@ -import json - import pytest from yandex_geocoder import Client from yandex_geocoder.exceptions import YandexGeocoderAddressNotFound -@pytest.fixture -def mock_client_response(mocker): - def _mock(fixture_name): - with open("./tests/fixtures/{}.json".format(fixture_name)) as fixture: - return mocker.patch( - "yandex_geocoder.Client.request", - return_value=json.load(fixture), - ) - - return _mock - - def test_returns_found_coordinates(mock_client_response): mock_client_response("coords_found") @@ -27,7 +13,5 @@ def test_returns_found_coordinates(mock_client_response): def test_raises_if_coordinates_not_found(mock_client_response): mock_client_response("coords_not_found") - with pytest.raises( - YandexGeocoderAddressNotFound, match='"some address" not found' - ): + with pytest.raises(YandexGeocoderAddressNotFound, match='"some address" not found'): Client.coordinates("some address") diff --git a/tests/test_requests.py b/tests/test_requests.py index 149960d..5d4a735 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -1,19 +1,9 @@ import pytest -import requests_mock from yandex_geocoder import Client from yandex_geocoder.exceptions import YandexGeocoderHttpException -@pytest.fixture -def mock_response(): - with requests_mock.mock() as _m: - yield lambda **kwargs: _m.get( - "https://geocode-maps.yandex.ru/1.x/?geocode=b&format=json", - **kwargs - ) - - def test_request_ok(mock_response): mock_response(json={"response": {"ok": True}}) @@ -24,7 +14,6 @@ def test_request_fails(mock_response): mock_response(status_code=400) with pytest.raises( - YandexGeocoderHttpException, - match="Non-200 response from yandex geocoder", + YandexGeocoderHttpException, match="Non-200 response from yandex geocoder", ): Client.request("b") diff --git a/yandex_geocoder/client.py b/yandex_geocoder/client.py index 066d785..846709c 100644 --- a/yandex_geocoder/client.py +++ b/yandex_geocoder/client.py @@ -29,14 +29,10 @@ def request(cls, address: str) -> dict: different from `200`. """ - response = requests.get( - cls.API_URL, params=dict(geocode=address, **cls.PARAMS) - ) + response = requests.get(cls.API_URL, params=dict(geocode=address, **cls.PARAMS)) if response.status_code != 200: - raise YandexGeocoderHttpException( - "Non-200 response from yandex geocoder" - ) + raise YandexGeocoderHttpException("Non-200 response from yandex geocoder") return response.json()["response"] @@ -51,9 +47,7 @@ def coordinates(cls, address: str) -> typing.Tuple[str, str]: data = cls.request(address)["GeoObjectCollection"]["featureMember"] if not data: - raise YandexGeocoderAddressNotFound( - '"{}" not found'.format(address) - ) + raise YandexGeocoderAddressNotFound('"{}" not found'.format(address)) coordinates = data[0]["GeoObject"]["Point"]["pos"] # type: str return tuple(coordinates.split(" ")) From 2c46a624144c06e2302cc25b112591af3af15dfc Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Wed, 12 Feb 2020 11:17:05 +0300 Subject: [PATCH 5/9] Add support for now required api key --- tests/conftest.py | 19 +++- tests/fixtures/coords_found.json | 152 ++++++++++++++------------- tests/fixtures/coords_not_found.json | 20 ++-- tests/test_coordinates.py | 47 +++++++-- tests/test_requests.py | 19 ---- yandex_geocoder/__init__.py | 14 ++- yandex_geocoder/client.py | 50 ++++----- yandex_geocoder/exceptions.py | 8 +- 8 files changed, 186 insertions(+), 143 deletions(-) delete mode 100644 tests/test_requests.py diff --git a/tests/conftest.py b/tests/conftest.py index f07bf52..87cc459 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,30 @@ import json +from urllib.parse import urlencode import pytest import requests_mock @pytest.fixture -def mock_response(): +def mock_api(): + def _encode(geocode: str, api_key: str = "123456") -> str: + params = {"format": "json", "apikey": api_key, "geocode": geocode} + query = urlencode(params) + return f"https://geocode-maps.yandex.ru/1.x/?{query}" + with requests_mock.mock() as _m: - yield lambda **kwargs: _m.get( - "https://geocode-maps.yandex.ru/1.x/?geocode=b&format=json", **kwargs + yield lambda resp, status, **encode_kw: _m.get( + _encode(**encode_kw), + json=load_fixture(resp) if isinstance(resp, str) else resp, + status_code=status, ) +def load_fixture(fixture_name: str) -> dict: + with open(f"./tests/fixtures/{fixture_name}.json") as fixture: + return json.load(fixture) + + @pytest.fixture def mock_client_response(mocker): def _mock(fixture_name): diff --git a/tests/fixtures/coords_found.json b/tests/fixtures/coords_found.json index 574eba8..6f22859 100644 --- a/tests/fixtures/coords_found.json +++ b/tests/fixtures/coords_found.json @@ -1,66 +1,68 @@ { - "GeoObjectCollection": { - "metaDataProperty": { - "GeocoderResponseMetaData": { - "request": "Москва, улица Новый Арбат, дом 24", - "found": "1", - "results": "10" - } - }, - "featureMember": [ - { - "GeoObject": { - "metaDataProperty": { - "GeocoderMetaData": { - "kind": "house", - "text": "Россия, Москва, улица Новый Арбат, 24", - "precision": "exact", - "Address": { - "country_code": "RU", - "postal_code": "119019", - "formatted": "Москва, улица Новый Арбат, 24", - "Components": [ - { - "kind": "country", - "name": "Россия" - }, - { - "kind": "province", - "name": "Центральный федеральный округ" - }, - { - "kind": "province", - "name": "Москва" - }, - { - "kind": "locality", - "name": "Москва" - }, - { - "kind": "street", - "name": "улица Новый Арбат" - }, - { - "kind": "house", - "name": "24" - } - ] - }, - "AddressDetails": { - "Country": { - "AddressLine": "Москва, улица Новый Арбат, 24", - "CountryNameCode": "RU", - "CountryName": "Россия", - "AdministrativeArea": { - "AdministrativeAreaName": "Москва", - "Locality": { - "LocalityName": "Москва", - "Thoroughfare": { - "ThoroughfareName": "улица Новый Арбат", - "Premise": { - "PremiseNumber": "24", - "PostalCode": { - "PostalCodeNumber": "119019" + "response": { + "GeoObjectCollection": { + "metaDataProperty": { + "GeocoderResponseMetaData": { + "request": "Москва, улица Новый Арбат, дом 24", + "found": "1", + "results": "10" + } + }, + "featureMember": [ + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "kind": "house", + "text": "Россия, Москва, улица Новый Арбат, 24", + "precision": "exact", + "Address": { + "country_code": "RU", + "postal_code": "119019", + "formatted": "Москва, улица Новый Арбат, 24", + "Components": [ + { + "kind": "country", + "name": "Россия" + }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "street", + "name": "улица Новый Арбат" + }, + { + "kind": "house", + "name": "24" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Москва, улица Новый Арбат, 24", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "Thoroughfare": { + "ThoroughfareName": "улица Новый Арбат", + "Premise": { + "PremiseNumber": "24", + "PostalCode": { + "PostalCodeNumber": "119019" + } } } } @@ -68,21 +70,21 @@ } } } + }, + "description": "Москва, Россия", + "name": "улица Новый Арбат, 24", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.583508 55.750768", + "upperCorner": "37.591719 55.755398" + } + }, + "Point": { + "pos": "37.587614 55.753083" } - }, - "description": "Москва, Россия", - "name": "улица Новый Арбат, 24", - "boundedBy": { - "Envelope": { - "lowerCorner": "37.583508 55.750768", - "upperCorner": "37.591719 55.755398" - } - }, - "Point": { - "pos": "37.587614 55.753083" } } - } - ] + ] + } } } diff --git a/tests/fixtures/coords_not_found.json b/tests/fixtures/coords_not_found.json index aa1aab2..b27e694 100644 --- a/tests/fixtures/coords_not_found.json +++ b/tests/fixtures/coords_not_found.json @@ -1,12 +1,14 @@ { - "GeoObjectCollection": { - "metaDataProperty": { - "GeocoderResponseMetaData": { - "request": "OhjisuTho5ee", - "found": "0", - "results": "10" - } - }, - "featureMember": [] + "response": { + "GeoObjectCollection": { + "metaDataProperty": { + "GeocoderResponseMetaData": { + "request": "OhjisuTho5ee", + "found": "0", + "results": "10" + } + }, + "featureMember": [] + } } } diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index f133fc4..16d5bc1 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,17 +1,46 @@ import pytest -from yandex_geocoder import Client -from yandex_geocoder.exceptions import YandexGeocoderAddressNotFound +from yandex_geocoder import ( + Client, + InvalidKey, + NothingFound, + UnexpectedResponse, +) -def test_returns_found_coordinates(mock_client_response): - mock_client_response("coords_found") +def test_returns_found_coordinates(mock_api): + mock_api("coords_found", 200, geocode="some address") + client = Client("123456") - assert Client.coordinates("some address") == ("37.587614", "55.753083") + assert client.coordinates("some address") == ("37.587614", "55.753083") -def test_raises_if_coordinates_not_found(mock_client_response): - mock_client_response("coords_not_found") +def test_raises_if_coordinates_not_found(mock_api): + mock_api("coords_not_found", 200, geocode="unknown address") + client = Client("123456") - with pytest.raises(YandexGeocoderAddressNotFound, match='"some address" not found'): - Client.coordinates("some address") + with pytest.raises(NothingFound, match='Nothing found for "unknown address"'): + client.coordinates("unknown address") + + +def test_raises_for_invalid_api_key(mock_api): + mock_api( + {"statusCode": 403, "error": "Forbidden", "message": "Invalid key"}, + 403, + geocode="some address", + api_key="unkown-api-key", + ) + client = Client("unkown-api-key") + + with pytest.raises(InvalidKey): + client.coordinates("some address") + + +def test_raises_for_unknown_response(mock_api): + mock_api({}, 500, geocode="some address") + client = Client("123456") + + with pytest.raises(UnexpectedResponse) as exc_info: + client.coordinates("some address") + + assert "status_code=500, body=b'{}'" in exc_info.value.args diff --git a/tests/test_requests.py b/tests/test_requests.py deleted file mode 100644 index 5d4a735..0000000 --- a/tests/test_requests.py +++ /dev/null @@ -1,19 +0,0 @@ -import pytest - -from yandex_geocoder import Client -from yandex_geocoder.exceptions import YandexGeocoderHttpException - - -def test_request_ok(mock_response): - mock_response(json={"response": {"ok": True}}) - - assert Client.request("b") == {"ok": True} - - -def test_request_fails(mock_response): - mock_response(status_code=400) - - with pytest.raises( - YandexGeocoderHttpException, match="Non-200 response from yandex geocoder", - ): - Client.request("b") diff --git a/yandex_geocoder/__init__.py b/yandex_geocoder/__init__.py index 6c07126..c9933c6 100644 --- a/yandex_geocoder/__init__.py +++ b/yandex_geocoder/__init__.py @@ -1,3 +1,15 @@ from yandex_geocoder.client import Client +from yandex_geocoder.exceptions import ( + InvalidKey, + NothingFound, + UnexpectedResponse, + YandexGeocoderException, +) -__all__ = ["Client"] +__all__ = [ + "Client", + "InvalidKey", + "NothingFound", + "UnexpectedResponse", + "YandexGeocoderException", +] diff --git a/yandex_geocoder/client.py b/yandex_geocoder/client.py index 846709c..71d6ea8 100644 --- a/yandex_geocoder/client.py +++ b/yandex_geocoder/client.py @@ -2,10 +2,7 @@ import requests -from yandex_geocoder.exceptions import ( - YandexGeocoderAddressNotFound, - YandexGeocoderHttpException, -) +from .exceptions import InvalidKey, NothingFound, UnexpectedResponse class Client: @@ -13,41 +10,44 @@ class Client: :Example: >>> from yandex_geocoder import Client - >>> Client.coordinates('Хабаровск 60 октября 150') + >>> client = Client("api-key") + >>> client.coordinates('Хабаровск 60 октября 150') ('135.114326', '48.47839') """ - API_URL = "https://geocode-maps.yandex.ru/1.x/" - PARAMS = {"format": "json"} + __slots__ = ("api_key",) - @classmethod - def request(cls, address: str) -> dict: - """Requests passed address and returns content of `response` key. + api_key: str - Raises `YandexGeocoderHttpException` if response's status code is - different from `200`. + def __init__(self, api_key: str): + self.api_key = api_key - """ - response = requests.get(cls.API_URL, params=dict(geocode=address, **cls.PARAMS)) - - if response.status_code != 200: - raise YandexGeocoderHttpException("Non-200 response from yandex geocoder") + def _request(self, address: str) -> dict: + response = requests.get( + "https://geocode-maps.yandex.ru/1.x/", + params=dict(format="json", apikey=self.api_key, geocode=address), + ) - return response.json()["response"] + if response.status_code == 200: + return response.json()["response"] + elif response.status_code == 403: + raise InvalidKey() + else: + raise UnexpectedResponse( + f"status_code={response.status_code}, body={response.content}" + ) - @classmethod - def coordinates(cls, address: str) -> typing.Tuple[str, str]: - """Returns a tuple of ccordinates (longtitude, latitude) for - passed address. + def coordinates(self, address: str) -> typing.Tuple[str, str]: + """Returns a tuple of ccordinates (longtitude, latitude) for passed address. - Raises `YandexGeocoderAddressNotFound` if nothing found. + Raises `NothingFound` if nothing found. """ - data = cls.request(address)["GeoObjectCollection"]["featureMember"] + data = self._request(address)["GeoObjectCollection"]["featureMember"] if not data: - raise YandexGeocoderAddressNotFound('"{}" not found'.format(address)) + raise NothingFound(f'Nothing found for "{address}" not found') coordinates = data[0]["GeoObject"]["Point"]["pos"] # type: str return tuple(coordinates.split(" ")) diff --git a/yandex_geocoder/exceptions.py b/yandex_geocoder/exceptions.py index 45e130e..8a50ff2 100644 --- a/yandex_geocoder/exceptions.py +++ b/yandex_geocoder/exceptions.py @@ -2,9 +2,13 @@ class YandexGeocoderException(Exception): pass -class YandexGeocoderHttpException(YandexGeocoderException): +class UnexpectedResponse(YandexGeocoderException): pass -class YandexGeocoderAddressNotFound(YandexGeocoderException): +class NothingFound(YandexGeocoderException): + pass + + +class InvalidKey(YandexGeocoderException): pass From a3c3dfccfd5012b6381b66734ce10e953a5d9f52 Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Thu, 13 Feb 2020 10:06:23 +0300 Subject: [PATCH 6/9] Update test data and mocks --- tests/conftest.py | 2 +- tests/fixtures/coords_found.json | 44 ++++++++++++---------------- tests/fixtures/coords_not_found.json | 6 ++-- tests/test_coordinates.py | 24 +++++++-------- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 87cc459..466b3d6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ @pytest.fixture def mock_api(): - def _encode(geocode: str, api_key: str = "123456") -> str: + def _encode(geocode: str, api_key: str = "well-known-key") -> str: params = {"format": "json", "apikey": api_key, "geocode": geocode} query = urlencode(params) return f"https://geocode-maps.yandex.ru/1.x/?{query}" diff --git a/tests/fixtures/coords_found.json b/tests/fixtures/coords_found.json index 6f22859..0bbe0f2 100644 --- a/tests/fixtures/coords_found.json +++ b/tests/fixtures/coords_found.json @@ -3,9 +3,9 @@ "GeoObjectCollection": { "metaDataProperty": { "GeocoderResponseMetaData": { - "request": "Москва, улица Новый Арбат, дом 24", - "found": "1", - "results": "10" + "request": "Москва Льва Толстого 16", + "results": "10", + "found": "1" } }, "featureMember": [ @@ -13,18 +13,15 @@ "GeoObject": { "metaDataProperty": { "GeocoderMetaData": { - "kind": "house", - "text": "Россия, Москва, улица Новый Арбат, 24", "precision": "exact", + "text": "Россия, Москва, улица Льва Толстого, 16", + "kind": "house", "Address": { "country_code": "RU", - "postal_code": "119019", - "formatted": "Москва, улица Новый Арбат, 24", + "formatted": "Россия, Москва, улица Льва Толстого, 16", + "postal_code": "119021", "Components": [ - { - "kind": "country", - "name": "Россия" - }, + { "kind": "country", "name": "Россия" }, { "kind": "province", "name": "Центральный федеральный округ" @@ -39,17 +36,14 @@ }, { "kind": "street", - "name": "улица Новый Арбат" + "name": "улица Льва Толстого" }, - { - "kind": "house", - "name": "24" - } + { "kind": "house", "name": "16" } ] }, "AddressDetails": { "Country": { - "AddressLine": "Москва, улица Новый Арбат, 24", + "AddressLine": "Россия, Москва, улица Льва Толстого, 16", "CountryNameCode": "RU", "CountryName": "Россия", "AdministrativeArea": { @@ -57,11 +51,11 @@ "Locality": { "LocalityName": "Москва", "Thoroughfare": { - "ThoroughfareName": "улица Новый Арбат", + "ThoroughfareName": "улица Льва Толстого", "Premise": { - "PremiseNumber": "24", + "PremiseNumber": "16", "PostalCode": { - "PostalCodeNumber": "119019" + "PostalCodeNumber": "119021" } } } @@ -71,17 +65,15 @@ } } }, + "name": "улица Льва Толстого, 16", "description": "Москва, Россия", - "name": "улица Новый Арбат, 24", "boundedBy": { "Envelope": { - "lowerCorner": "37.583508 55.750768", - "upperCorner": "37.591719 55.755398" + "lowerCorner": "37.582987 55.731653", + "upperCorner": "37.591198 55.736285" } }, - "Point": { - "pos": "37.587614 55.753083" - } + "Point": { "pos": "37.587093 55.733969" } } } ] diff --git a/tests/fixtures/coords_not_found.json b/tests/fixtures/coords_not_found.json index b27e694..53a8a45 100644 --- a/tests/fixtures/coords_not_found.json +++ b/tests/fixtures/coords_not_found.json @@ -3,9 +3,9 @@ "GeoObjectCollection": { "metaDataProperty": { "GeocoderResponseMetaData": { - "request": "OhjisuTho5ee", - "found": "0", - "results": "10" + "request": "абырвалг", + "results": "10", + "found": "0" } }, "featureMember": [] diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index 16d5bc1..e76ee7e 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -9,38 +9,38 @@ def test_returns_found_coordinates(mock_api): - mock_api("coords_found", 200, geocode="some address") - client = Client("123456") + mock_api("coords_found", 200, geocode="Москва Льва Толстого 16") + client = Client("well-known-key") - assert client.coordinates("some address") == ("37.587614", "55.753083") + assert client.coordinates("Москва Льва Толстого 16") == ("37.587093", "55.733969") def test_raises_if_coordinates_not_found(mock_api): - mock_api("coords_not_found", 200, geocode="unknown address") - client = Client("123456") + mock_api("coords_not_found", 200, geocode="абырвалг") + client = Client("well-known-key") - with pytest.raises(NothingFound, match='Nothing found for "unknown address"'): - client.coordinates("unknown address") + with pytest.raises(NothingFound, match='Nothing found for "абырвалг"'): + client.coordinates("абырвалг") def test_raises_for_invalid_api_key(mock_api): mock_api( {"statusCode": 403, "error": "Forbidden", "message": "Invalid key"}, 403, - geocode="some address", + geocode="Москва Льва Толстого 16", api_key="unkown-api-key", ) client = Client("unkown-api-key") with pytest.raises(InvalidKey): - client.coordinates("some address") + client.coordinates("Москва Льва Толстого 16") def test_raises_for_unknown_response(mock_api): - mock_api({}, 500, geocode="some address") - client = Client("123456") + mock_api({}, 500, geocode="Москва Льва Толстого 16") + client = Client("well-known-key") with pytest.raises(UnexpectedResponse) as exc_info: - client.coordinates("some address") + client.coordinates("Москва Льва Толстого 16") assert "status_code=500, body=b'{}'" in exc_info.value.args From a5cd971ed6fe5efebaeedd80583a919fc7c69ddb Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Thu, 13 Feb 2020 11:34:50 +0300 Subject: [PATCH 7/9] Return coordinates as decimal objects instead of strings --- tests/test_coordinates.py | 7 ++++++- yandex_geocoder/client.py | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/test_coordinates.py b/tests/test_coordinates.py index e76ee7e..ee314bd 100644 --- a/tests/test_coordinates.py +++ b/tests/test_coordinates.py @@ -1,3 +1,5 @@ +from decimal import Decimal + import pytest from yandex_geocoder import ( @@ -12,7 +14,10 @@ def test_returns_found_coordinates(mock_api): mock_api("coords_found", 200, geocode="Москва Льва Толстого 16") client = Client("well-known-key") - assert client.coordinates("Москва Льва Толстого 16") == ("37.587093", "55.733969") + assert client.coordinates("Москва Льва Толстого 16") == ( + Decimal("37.587093"), + Decimal("55.733969"), + ) def test_raises_if_coordinates_not_found(mock_api): diff --git a/yandex_geocoder/client.py b/yandex_geocoder/client.py index 71d6ea8..d7f4f0d 100644 --- a/yandex_geocoder/client.py +++ b/yandex_geocoder/client.py @@ -1,4 +1,5 @@ -import typing +from decimal import Decimal +from typing import Tuple import requests @@ -38,8 +39,8 @@ def _request(self, address: str) -> dict: f"status_code={response.status_code}, body={response.content}" ) - def coordinates(self, address: str) -> typing.Tuple[str, str]: - """Returns a tuple of ccordinates (longtitude, latitude) for passed address. + def coordinates(self, address: str) -> Tuple[Decimal]: + """Returns a tuple of coordinates (longtitude, latitude) for passed address. Raises `NothingFound` if nothing found. @@ -50,4 +51,6 @@ def coordinates(self, address: str) -> typing.Tuple[str, str]: raise NothingFound(f'Nothing found for "{address}" not found') coordinates = data[0]["GeoObject"]["Point"]["pos"] # type: str - return tuple(coordinates.split(" ")) + longitude, latitude = tuple(coordinates.split(" ")) + + return Decimal(longitude), Decimal(latitude) From e81d0965fdd79259ee48a4c474208c1cc40dc84f Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Fri, 21 Feb 2020 08:55:28 +0300 Subject: [PATCH 8/9] Add feature to fetch address by coordinates --- tests/fixtures/address_found.json | 502 ++++++++++++++++++++++++++ tests/fixtures/address_not_found.json | 15 + tests/test_address.py | 51 +++ yandex_geocoder/client.py | 14 + 4 files changed, 582 insertions(+) create mode 100644 tests/fixtures/address_found.json create mode 100644 tests/fixtures/address_not_found.json create mode 100644 tests/test_address.py diff --git a/tests/fixtures/address_found.json b/tests/fixtures/address_found.json new file mode 100644 index 0000000..480e6ac --- /dev/null +++ b/tests/fixtures/address_found.json @@ -0,0 +1,502 @@ +{ + "response": { + "GeoObjectCollection": { + "metaDataProperty": { + "GeocoderResponseMetaData": { + "Point": { "pos": "37.587093 55.733969" }, + "request": "37.587093,55.733969", + "results": "10", + "found": "9" + } + }, + "featureMember": [ + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "exact", + "text": "Россия, Москва, улица Льва Толстого, 16", + "kind": "house", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва, улица Льва Толстого, 16", + "postal_code": "119021", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "street", + "name": "улица Льва Толстого" + }, + { "kind": "house", "name": "16" } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва, улица Льва Толстого, 16", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "Thoroughfare": { + "ThoroughfareName": "улица Льва Толстого", + "Premise": { + "PremiseNumber": "16", + "PostalCode": { + "PostalCodeNumber": "119021" + } + } + } + } + } + } + } + } + }, + "name": "улица Льва Толстого, 16", + "description": "Москва, Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.582987 55.731653", + "upperCorner": "37.591198 55.736285" + } + }, + "Point": { "pos": "37.587093 55.733969" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "street", + "text": "Россия, Москва, улица Льва Толстого", + "kind": "street", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва, улица Льва Толстого", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "street", + "name": "улица Льва Толстого" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва, улица Льва Толстого", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "Thoroughfare": { + "ThoroughfareName": "улица Льва Толстого" + } + } + } + } + } + } + }, + "name": "улица Льва Толстого", + "description": "Москва, Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.582475 55.731556", + "upperCorner": "37.59118 55.736868" + } + }, + "Point": { "pos": "37.58667 55.733984" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Москва, Центральный административный округ, район Хамовники, квартал Красная Роза", + "kind": "district", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва, Центральный административный округ, район Хамовники, квартал Красная Роза", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "district", + "name": "Центральный административный округ" + }, + { + "kind": "district", + "name": "район Хамовники" + }, + { + "kind": "district", + "name": "квартал Красная Роза" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва, Центральный административный округ, район Хамовники, квартал Красная Роза", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "DependentLocality": { + "DependentLocalityName": "Центральный административный округ", + "DependentLocality": { + "DependentLocalityName": "район Хамовники", + "DependentLocality": { + "DependentLocalityName": "квартал Красная Роза" + } + } + } + } + } + } + } + } + }, + "name": "квартал Красная Роза", + "description": "район Хамовники, Центральный административный округ, Москва, Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.5834 55.731536", + "upperCorner": "37.59233 55.737025" + } + }, + "Point": { "pos": "37.587721 55.734233" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Москва, Центральный административный округ, район Хамовники", + "kind": "district", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва, Центральный административный округ, район Хамовники", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "district", + "name": "Центральный административный округ" + }, + { + "kind": "district", + "name": "район Хамовники" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва, Центральный административный округ, район Хамовники", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "DependentLocality": { + "DependentLocalityName": "Центральный административный округ", + "DependentLocality": { + "DependentLocalityName": "район Хамовники" + } + } + } + } + } + } + } + }, + "name": "район Хамовники", + "description": "Центральный административный округ, Москва, Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.540928 55.710276", + "upperCorner": "37.612236 55.750383" + } + }, + "Point": { "pos": "37.574525 55.729199" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Москва, Центральный административный округ", + "kind": "district", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва, Центральный административный округ", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { + "kind": "locality", + "name": "Москва" + }, + { + "kind": "district", + "name": "Центральный административный округ" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва, Центральный административный округ", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва", + "DependentLocality": { + "DependentLocalityName": "Центральный административный округ" + } + } + } + } + } + } + }, + "name": "Центральный административный округ", + "description": "Москва, Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.51441 55.710276", + "upperCorner": "37.713593 55.797108" + } + }, + "Point": { "pos": "37.614069 55.753995" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Москва", + "kind": "locality", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { + "kind": "province", + "name": "Москва" + }, + { "kind": "locality", "name": "Москва" } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва", + "Locality": { + "LocalityName": "Москва" + } + } + } + } + } + }, + "name": "Москва", + "description": "Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "37.326051 55.49133", + "upperCorner": "37.96779 55.957565" + } + }, + "Point": { "pos": "37.617635 55.755814" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Москва", + "kind": "province", + "Address": { + "country_code": "RU", + "formatted": "Россия, Москва", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + }, + { "kind": "province", "name": "Москва" } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Москва", + "CountryNameCode": "RU", + "CountryName": "Россия", + "AdministrativeArea": { + "AdministrativeAreaName": "Москва" + } + } + } + } + }, + "name": "Москва", + "description": "Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "36.803259 55.142221", + "upperCorner": "37.96779 56.021281" + } + }, + "Point": { "pos": "37.622504 55.753215" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия, Центральный федеральный округ", + "kind": "province", + "Address": { + "country_code": "RU", + "formatted": "Россия, Центральный федеральный округ", + "Components": [ + { "kind": "country", "name": "Россия" }, + { + "kind": "province", + "name": "Центральный федеральный округ" + } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия, Центральный федеральный округ", + "CountryNameCode": "RU", + "CountryName": "Россия" + } + } + } + }, + "name": "Центральный федеральный округ", + "description": "Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "30.750266 49.556986", + "upperCorner": "47.641729 59.625172" + } + }, + "Point": { "pos": "38.064718 54.873745" } + } + }, + { + "GeoObject": { + "metaDataProperty": { + "GeocoderMetaData": { + "precision": "other", + "text": "Россия", + "kind": "country", + "Address": { + "country_code": "RU", + "formatted": "Россия", + "Components": [ + { "kind": "country", "name": "Россия" } + ] + }, + "AddressDetails": { + "Country": { + "AddressLine": "Россия", + "CountryNameCode": "RU", + "CountryName": "Россия" + } + } + } + }, + "name": "Россия", + "boundedBy": { + "Envelope": { + "lowerCorner": "19.484764 41.18599", + "upperCorner": "191.128003 81.886117" + } + }, + "Point": { "pos": "99.505405 61.698653" } + } + } + ] + } + } +} diff --git a/tests/fixtures/address_not_found.json b/tests/fixtures/address_not_found.json new file mode 100644 index 0000000..743f6b8 --- /dev/null +++ b/tests/fixtures/address_not_found.json @@ -0,0 +1,15 @@ +{ + "response": { + "GeoObjectCollection": { + "metaDataProperty": { + "GeocoderResponseMetaData": { + "Point": { "pos": "-22.412907 55.733969" }, + "request": "337.587093,55.733969", + "results": "10", + "found": "0" + } + }, + "featureMember": [] + } + } +} diff --git a/tests/test_address.py b/tests/test_address.py new file mode 100644 index 0000000..a278f16 --- /dev/null +++ b/tests/test_address.py @@ -0,0 +1,51 @@ +from decimal import Decimal + +import pytest + +from yandex_geocoder import ( + Client, + InvalidKey, + NothingFound, + UnexpectedResponse, +) + + +def test_returns_found_address(mock_api): + mock_api("address_found", 200, geocode="37.587093,55.733969") + client = Client("well-known-key") + + assert ( + client.address(Decimal("37.587093"), Decimal("55.733969")) + == "Россия, Москва, улица Льва Толстого, 16" + ) + + +def test_raises_if_address_not_found(mock_api): + mock_api("address_not_found", 200, geocode="337.587093,55.733969") + client = Client("well-known-key") + + with pytest.raises(NothingFound, match='Nothing found for "337.587093 55.733969"'): + client.address(Decimal("337.587093"), Decimal("55.733969")) + + +def test_raises_for_invalid_api_key(mock_api): + mock_api( + {"statusCode": 403, "error": "Forbidden", "message": "Invalid key"}, + 403, + geocode="37.587093,55.733969", + api_key="unkown-api-key", + ) + client = Client("unkown-api-key") + + with pytest.raises(InvalidKey): + client.address(Decimal("37.587093"), Decimal("55.733969")) + + +def test_raises_for_unknown_response(mock_api): + mock_api({}, 500, geocode="37.587093,55.733969") + client = Client("well-known-key") + + with pytest.raises(UnexpectedResponse) as exc_info: + client.address(Decimal("37.587093"), Decimal("55.733969")) + + assert "status_code=500, body=b'{}'" in exc_info.value.args diff --git a/yandex_geocoder/client.py b/yandex_geocoder/client.py index d7f4f0d..bee7249 100644 --- a/yandex_geocoder/client.py +++ b/yandex_geocoder/client.py @@ -54,3 +54,17 @@ def coordinates(self, address: str) -> Tuple[Decimal]: longitude, latitude = tuple(coordinates.split(" ")) return Decimal(longitude), Decimal(latitude) + + def address(self, longitude: Decimal, latitude: Decimal) -> str: + """Returns addres for passed coordinates. + + Raises `NothingFound` if nothing found. + + """ + got = self._request(f"{longitude},{latitude}") + data = got["GeoObjectCollection"]["featureMember"] + + if not data: + raise NothingFound(f'Nothing found for "{longitude} {latitude}"') + + return data[0]["GeoObject"]["metaDataProperty"]["GeocoderMetaData"]["text"] From db20fae99463a1ab0983a2ca797b91ab75c32820 Mon Sep 17 00:00:00 2001 From: Nikita Sivakov Date: Fri, 21 Feb 2020 10:32:38 +0300 Subject: [PATCH 9/9] Update documentation and docs (a bit) --- README.md | 19 +++++++++++++------ setup.py | 6 +++--- yandex_geocoder/client.py | 22 +++++++++------------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index a146eff..9ae4633 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Yandex Geocoder === Get address coordinates via Yandex geocoder -[![Build Status](https://github.com/sivakov512/yandex-geocoder/workflows/test/badge.svg)](https://github.com/sivakov512/yandex-geocoder) +[![Build Status](https://github.com/sivakov512/yandex-geocoder/workflows/test/badge.svg)](https://github.com/sivakov512/yandex-geocoder/actions?query=workflow%3Atest) [![Coverage Status](https://coveralls.io/repos/github/sivakov512/yandex-geocoder/badge.svg?branch=master)](https://coveralls.io/github/sivakov512/yandex-geocoder?branch=master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![Python versions](https://img.shields.io/pypi/pyversions/yandex-geocoder.svg)](https://pypi.python.org/pypi/yandex-geocoder) @@ -18,10 +18,21 @@ pip install yandex-geocoder Usage example --- +Yandex Geocoder requires an API developer key, you can get it [here](https://developer.tech.yandex.ru/services/) to use this library. ``` python +from decimal import Decimal + from yandex_geocoder import Client -Client.coordinates('Хабаровск 60 октября 150') # ('135.114326', '48.47839') + + +client = Client("your-api-key") + +coordinates = client.coordinates("Москва Льва Толстого 16") +assert coordinates == (Decimal("37.587093"), Decimal("55.733969")) + +address = client.address(Decimal("37.587093"), Decimal("55.733969")) +assert address == "Россия, Москва, улица Льва Толстого, 16" ``` Development and contribution @@ -49,7 +60,3 @@ black --check ./ ``` * feel free to contribute! - -Credits ---- -- [f213](https://github.com/f213) diff --git a/setup.py b/setup.py index 12a1670..76a3f0d 100644 --- a/setup.py +++ b/setup.py @@ -11,9 +11,9 @@ def read(fname): setup( author="Nikita Sivakov", author_email="sivakov512@gmail.com", - description="Simple library for getting address coordinates via Yandex geocoder", + description="Simple library for getting address or coordinates via Yandex geocoder", install_requires=["requests~=2.22"], - keywords="yandex geocoder geo coordinates maps api", + keywords="yandex geocoder geo coordinates address maps api", license="MIT", long_description=read("README.md"), long_description_content_type="text/markdown", @@ -21,7 +21,7 @@ def read(fname): packages=["yandex_geocoder"], python_requires=">=3.6", url="https://github.com/sivakov512/yandex-geocoder", - version="1.0.1", + version="2.0.0", classifiers=[ "Programming Language :: Python", "Programming Language :: Python :: 3.6", diff --git a/yandex_geocoder/client.py b/yandex_geocoder/client.py index bee7249..6f50334 100644 --- a/yandex_geocoder/client.py +++ b/yandex_geocoder/client.py @@ -11,9 +11,13 @@ class Client: :Example: >>> from yandex_geocoder import Client - >>> client = Client("api-key") - >>> client.coordinates('Хабаровск 60 октября 150') - ('135.114326', '48.47839') + >>> client = Client("your-api-key") + + >>> coordinates = client.coordinates("Москва Льва Толстого 16") + >>> assert coordinates == (Decimal("37.587093"), Decimal("55.733969")) + + >>> address = client.address(Decimal("37.587093"), Decimal("55.733969")) + >>> assert address == "Россия, Москва, улица Льва Толстого, 16" """ @@ -40,11 +44,7 @@ def _request(self, address: str) -> dict: ) def coordinates(self, address: str) -> Tuple[Decimal]: - """Returns a tuple of coordinates (longtitude, latitude) for passed address. - - Raises `NothingFound` if nothing found. - - """ + """Fetch coordinates (longitude, latitude) for passed address.""" data = self._request(address)["GeoObjectCollection"]["featureMember"] if not data: @@ -56,11 +56,7 @@ def coordinates(self, address: str) -> Tuple[Decimal]: return Decimal(longitude), Decimal(latitude) def address(self, longitude: Decimal, latitude: Decimal) -> str: - """Returns addres for passed coordinates. - - Raises `NothingFound` if nothing found. - - """ + """Fetch address for passed coordinates.""" got = self._request(f"{longitude},{latitude}") data = got["GeoObjectCollection"]["featureMember"]