Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,24 @@ build-backend = "uv_build"
[tool.ruff]
exclude = [
"examples",
".devcontainer",
]

[tool.ruff.lint]
# Full list of rules here: https://docs.astral.sh/ruff/rules/
select = [
# Core rules
# "E", # pycodestyle errors
"E", # pycodestyle errors
"F", # Pyflakes
# "UP", # pyupgrade
"UP", # pyupgrade
"I", # isort

# Quality and style
# "B", # flake8-bugbear
"B", # flake8-bugbear
"Q", # flake8-quotes
# "SIM", # flake8-simplify
"SIM", # flake8-simplify
"FLY", # flynt
# "PERF", # Perflint
"PERF", # Perflint

# Disabled rules (uncomment to enable):
# "AIR", # Airflow
Expand Down Expand Up @@ -130,7 +131,10 @@ select = [
# =============================================================================

[tool.pyright]
include = ["src"]
include = [
"src",
"tests",
]


# =============================================================================
Expand Down
5 changes: 2 additions & 3 deletions src/streetview/api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from io import BytesIO
from typing import Dict, Union

import requests
from PIL import Image
Expand Down Expand Up @@ -64,8 +63,8 @@ def get_streetview(
"""

url = "https://maps.googleapis.com/maps/api/streetview"
params: Dict[str, Union[str, int]] = {
"size": "%dx%d" % (width, height),
params: dict[str, str | int] = {
"size": f"{width:.0f}x{height:.0f}",
"fov": fov,
"pitch": pitch,
"heading": heading,
Expand Down
13 changes: 6 additions & 7 deletions src/streetview/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import concurrent.futures
import itertools
import time
from collections.abc import AsyncGenerator, Generator
from dataclasses import dataclass
from io import BytesIO
from typing import AsyncGenerator, Generator, Tuple

import httpx
import requests
Expand All @@ -30,7 +30,7 @@ class Tile:
image: Image.Image


def get_width_and_height_from_zoom(zoom: int) -> Tuple[int, int]:
def get_width_and_height_from_zoom(zoom: int) -> tuple[int, int]:
"""
Returns the width and height of a panorama at a given zoom level, depends on the
zoom level.
Expand Down Expand Up @@ -58,7 +58,7 @@ def fetch_panorama_tile(
try:
response = requests.get(tile_info.fileurl, stream=True)
return Image.open(BytesIO(response.content))
except requests.ConnectionError:
except requests.ConnectionError: # noqa: PERF203
print("Connection error. Trying again in 2 seconds.")
time.sleep(2)
raise requests.ConnectionError("Max retries exceeded.")
Expand All @@ -75,7 +75,7 @@ async def fetch_panorama_tile_async(
response = await async_client.get(tile_info.fileurl)
return Image.open(BytesIO(response.content))

except httpx.RequestError as e:
except httpx.RequestError as e: # noqa: PERF203
print(f"Request error {e}. Trying again in 2 seconds.")
await asyncio.sleep(2)

Expand Down Expand Up @@ -117,9 +117,8 @@ def iter_tiles(
try:
image = future.result()
except Exception as exc:
raise Exception(
f"Failed to download tile {info.fileurl} due to Exception: {exc}"
)
msg = f"Failed to download tile {info.fileurl} due to Exception: {exc}"
raise Exception(msg) from exc
else:
yield Tile(x=info.x, y=info.y, image=image)

Expand Down
18 changes: 7 additions & 11 deletions src/streetview/search.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json
import re
from typing import List, Optional

import requests
from pydantic import BaseModel
Expand All @@ -12,10 +11,10 @@ class Panorama(BaseModel):
lat: float
lon: float
heading: float
pitch: Optional[float]
roll: Optional[float]
date: Optional[str]
elevation: Optional[float]
pitch: float | None
roll: float | None
date: str | None
elevation: float | None


def make_search_url(lat: float, lon: float) -> str:
Expand Down Expand Up @@ -43,7 +42,7 @@ def search_request(lat: float, lon: float) -> Response:
return requests.get(url)


def extract_panoramas(text: str) -> List[Panorama]:
def extract_panoramas(text: str) -> list[Panorama]:
"""
Given a valid response from the panoids endpoint, return a list of all the
panoids.
Expand All @@ -61,10 +60,7 @@ def extract_panoramas(text: str) -> List[Panorama]:

raw_panos = subset[3][0]

if len(subset) < 9 or subset[8] is None:
raw_dates = []
else:
raw_dates = subset[8]
raw_dates = [] if (len(subset) < 9 or subset[8] is None) else subset[8]

# For some reason, dates do not include a date for each panorama.
# the n dates match the last n panos. Here we flip the arrays
Expand All @@ -89,7 +85,7 @@ def extract_panoramas(text: str) -> List[Panorama]:
]


def search_panoramas(lat: float, lon: float) -> List[Panorama]:
def search_panoramas(lat: float, lon: float) -> list[Panorama]:
"""
Gets the closest panoramas (ids) to the GPS coordinates.
"""
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

from streetview import get_panorama_meta, get_streetview

GOOGLE_MAPS_API_KEY = os.environ.get("GOOGLE_MAPS_API_KEY", None)
GOOGLE_MAPS_API_KEY = os.environ.get("GOOGLE_MAPS_API_KEY", "NOKEY")


@pytest.mark.vcr(filter_query_parameters=["key"])
def test_readme_metadata_example():

result = get_panorama_meta(
pano_id="_R1mwpMkiqa2p0zp48EBJg",
api_key=GOOGLE_MAPS_API_KEY,
Expand Down
40 changes: 18 additions & 22 deletions tests/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@
)
from streetview.search import Panorama

GOOGLE_MAPS_API_KEY = os.environ.get("GOOGLE_MAPS_API_KEY", None)
GOOGLE_MAPS_API_KEY = os.environ.get("GOOGLE_MAPS_API_KEY", "NOKEY")


SYDNEY = {
"lat": -33.8796052,
"lon": 151.1655341,
"result_count": 20,
}

BELGRAVIA = {
"lat": 51.4986562,
"lon": -0.1570917,
"result_count": 40,
}

MIDDLE_OF_OCEAN = {
Expand All @@ -34,13 +36,25 @@
}


class GenericGetPanoidsTest:
def setup_method(self):
raise NotImplementedError()
@pytest.mark.vcr
@pytest.mark.parametrize("location", [SYDNEY, BELGRAVIA])
class TestPanodsOnLocations:
"""Test panoramas in specific locations."""

@pytest.fixture(scope="function", autouse=True)
def setup_method(self, location: dict):
lat = location["lat"]
lon = location["lon"]

self.result_count = location["result_count"]
self.result = search_panoramas(lat, lon)

def test_that_there_is_at_least_one_item(self):
assert len(self.result) >= 1

def test_that_there_are_the_expected_number_of_results(self):
assert len(self.result) == self.result_count

def test_that_panoids_are_unique(self):
panoids = [p.pano_id for p in self.result]
uniques = list(dict.fromkeys(panoids))
Expand All @@ -61,24 +75,6 @@ def test_that_dates_are_correct(self):
assert dates == meta_dates


@pytest.mark.vcr
class TestPanoidsOnSydney(GenericGetPanoidsTest):
def setup_method(self):
self.result = search_panoramas(**SYDNEY)

def test_that_there_are_the_expected_number_of_results(self):
assert len(self.result) == 20


@pytest.mark.vcr
class TestPanoidsOnBelgravia(GenericGetPanoidsTest):
def setup_method(self):
self.result = search_panoramas(**BELGRAVIA)

def test_that_there_are_the_expected_number_of_results(self):
assert len(self.result) == 43


@pytest.mark.vcr
def test_readme_search_example():
result = search_panoramas(lat=41.8982208, lon=12.4764804)[0]
Expand Down
Loading