Rename DistanceMatrix
into Path
- a `Path` is a better description for an instance of the model - the `Location`s en route are renamed into `.waypoints` - generic `assoc` is renamed into `path` in the test suite
This commit is contained in:
parent
2d08afa309
commit
2d324b77eb
3 changed files with 153 additions and 160 deletions
|
@ -1,7 +1,7 @@
|
|||
"""Provide the ORM models and a connection to the database."""
|
||||
|
||||
from urban_meal_delivery.db.addresses import Address
|
||||
from urban_meal_delivery.db.addresses_addresses import DistanceMatrix
|
||||
from urban_meal_delivery.db.addresses_addresses import Path
|
||||
from urban_meal_delivery.db.addresses_pixels import AddressPixelAssociation
|
||||
from urban_meal_delivery.db.cities import City
|
||||
from urban_meal_delivery.db.connection import connection
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Model for the relationship between two `Address` objects (= distance matrix)."""
|
||||
"""Model for the `Path` relationship between two `Address` objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
@ -20,14 +20,14 @@ from urban_meal_delivery.db import meta
|
|||
from urban_meal_delivery.db import utils
|
||||
|
||||
|
||||
class DistanceMatrix(meta.Base):
|
||||
"""Distance matrix between `Address` objects.
|
||||
class Path(meta.Base):
|
||||
"""Path between two `Address` objects.
|
||||
|
||||
Models the pairwise distances between two `Address` objects,
|
||||
including directions for a `Courier` to get from one `Address` to another.
|
||||
Models the path between two `Address` objects, including directions
|
||||
for a `Courier` to get from one `Address` to another.
|
||||
|
||||
As the couriers are on bicycles, we model the distance matrix
|
||||
as a symmetric graph (i.e., same distance in both directions).
|
||||
As the couriers are on bicycles, we model the paths as
|
||||
a symmetric graph (i.e., same distance in both directions).
|
||||
|
||||
Implements an association pattern between `Address` and `Address`.
|
||||
|
||||
|
@ -88,37 +88,36 @@ class DistanceMatrix(meta.Base):
|
|||
|
||||
# Relationships
|
||||
first_address = orm.relationship(
|
||||
'Address',
|
||||
foreign_keys='[DistanceMatrix.first_address_id, DistanceMatrix.city_id]',
|
||||
'Address', foreign_keys='[Path.first_address_id, Path.city_id]',
|
||||
)
|
||||
second_address = orm.relationship(
|
||||
'Address',
|
||||
foreign_keys='[DistanceMatrix.second_address_id, DistanceMatrix.city_id]',
|
||||
foreign_keys='[Path.second_address_id, Path.city_id]',
|
||||
overlaps='first_address',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_addresses(
|
||||
cls, *addresses: db.Address, google_maps: bool = False,
|
||||
) -> List[DistanceMatrix]:
|
||||
"""Calculate pair-wise distances for `Address` objects.
|
||||
) -> List[Path]:
|
||||
"""Calculate pair-wise paths for `Address` objects.
|
||||
|
||||
This is the main constructor method for the class.
|
||||
|
||||
It handles the "sorting" of the `Address` objects by `.id`, which is
|
||||
the logic that enforces the symmetric graph behind the distances.
|
||||
the logic that enforces the symmetric graph behind the paths.
|
||||
|
||||
Args:
|
||||
*addresses: to calculate the pair-wise distances for;
|
||||
*addresses: to calculate the pair-wise paths for;
|
||||
must contain at least two `Address` objects
|
||||
google_maps: if `.bicycle_distance` and `._directions` should be
|
||||
populated with a query to the Google Maps Directions API;
|
||||
by default, only the `.air_distance` is calculated with `geopy`
|
||||
|
||||
Returns:
|
||||
distances
|
||||
paths
|
||||
"""
|
||||
distances = []
|
||||
paths = []
|
||||
|
||||
# We consider all 2-tuples of `Address`es. The symmetric graph is ...
|
||||
for first, second in itertools.combinations(addresses, 2):
|
||||
|
@ -127,35 +126,35 @@ class DistanceMatrix(meta.Base):
|
|||
(first, second) if first.id < second.id else (second, first)
|
||||
)
|
||||
|
||||
# If there is no `DistanceMatrix` object in the database ...
|
||||
distance = (
|
||||
db.session.query(db.DistanceMatrix)
|
||||
.filter(db.DistanceMatrix.first_address == first)
|
||||
.filter(db.DistanceMatrix.second_address == second)
|
||||
# If there is no `Path` object in the database ...
|
||||
path = (
|
||||
db.session.query(db.Path)
|
||||
.filter(db.Path.first_address == first)
|
||||
.filter(db.Path.second_address == second)
|
||||
.first()
|
||||
)
|
||||
# ... create a new one.
|
||||
if distance is None:
|
||||
if path is None:
|
||||
air_distance = geo_distance.great_circle(
|
||||
first.location.lat_lng, second.location.lat_lng,
|
||||
)
|
||||
|
||||
distance = cls(
|
||||
path = cls(
|
||||
first_address=first,
|
||||
second_address=second,
|
||||
air_distance=round(air_distance.meters),
|
||||
)
|
||||
|
||||
db.session.add(distance)
|
||||
db.session.add(path)
|
||||
db.session.commit()
|
||||
|
||||
distances.append(distance)
|
||||
paths.append(path)
|
||||
|
||||
if google_maps:
|
||||
for distance in distances: # noqa:WPS440
|
||||
distance.sync_with_google_maps()
|
||||
for path in paths: # noqa:WPS440
|
||||
path.sync_with_google_maps()
|
||||
|
||||
return distances
|
||||
return paths
|
||||
|
||||
def sync_with_google_maps(self) -> None:
|
||||
"""Fill in `.bicycle_distance` and `._directions` with Google Maps.
|
||||
|
@ -210,18 +209,16 @@ class DistanceMatrix(meta.Base):
|
|||
db.session.commit()
|
||||
|
||||
@functools.cached_property
|
||||
def path(self) -> List[utils.Location]:
|
||||
"""The couriers' path from `.first_address` to `.second_address`.
|
||||
def waypoints(self) -> List[utils.Location]:
|
||||
"""The couriers' route from `.first_address` to `.second_address`.
|
||||
|
||||
The returned `Location`s all relates to `.first_address.city.southwest`.
|
||||
The returned `Location`s all relate to `.first_address.city.southwest`.
|
||||
|
||||
Implementation detail: This property is cached as none of the
|
||||
underlying attributes (i.e., `._directions`) are to be changed.
|
||||
"""
|
||||
inner_points = [
|
||||
utils.Location(*point) for point in json.loads(self._directions)
|
||||
]
|
||||
for point in inner_points:
|
||||
points = [utils.Location(*point) for point in json.loads(self._directions)]
|
||||
for point in points:
|
||||
point.relate_to(self.first_address.city.southwest)
|
||||
|
||||
return inner_points
|
||||
return points
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Test the ORM's `DistanceMatrix` model."""
|
||||
"""Test the ORM's `Path` model."""
|
||||
|
||||
import json
|
||||
|
||||
|
@ -19,8 +19,8 @@ def another_address(make_address):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def assoc(address, another_address, make_address):
|
||||
"""An association between `address` and `another_address`."""
|
||||
def path(address, another_address, make_address):
|
||||
"""A `Path` from `address` to `another_address`."""
|
||||
air_distance = distance.great_circle( # noqa:WPS317
|
||||
address.location.lat_lng, another_address.location.lat_lng,
|
||||
).meters
|
||||
|
@ -34,7 +34,7 @@ def assoc(address, another_address, make_address):
|
|||
],
|
||||
)
|
||||
|
||||
return db.DistanceMatrix(
|
||||
return db.Path(
|
||||
first_address=address,
|
||||
second_address=another_address,
|
||||
air_distance=round(air_distance),
|
||||
|
@ -45,34 +45,34 @@ def assoc(address, another_address, make_address):
|
|||
|
||||
|
||||
class TestSpecialMethods:
|
||||
"""Test special methods in `DistanceMatrix`."""
|
||||
"""Test special methods in `Path`."""
|
||||
|
||||
def test_create_an_address_address_association(self, assoc):
|
||||
"""Test instantiation of a new `DistanceMatrix` object."""
|
||||
assert assoc is not None
|
||||
def test_create_an_address_address_association(self, path):
|
||||
"""Test instantiation of a new `Path` object."""
|
||||
assert path is not None
|
||||
|
||||
|
||||
@pytest.mark.db
|
||||
@pytest.mark.no_cover
|
||||
class TestConstraints:
|
||||
"""Test the database constraints defined in `DistanceMatrix`."""
|
||||
"""Test the database constraints defined in `Path`."""
|
||||
|
||||
def test_insert_into_database(self, db_session, assoc):
|
||||
def test_insert_into_database(self, db_session, path):
|
||||
"""Insert an instance into the (empty) database."""
|
||||
assert db_session.query(db.DistanceMatrix).count() == 0
|
||||
assert db_session.query(db.Path).count() == 0
|
||||
|
||||
db_session.add(assoc)
|
||||
db_session.add(path)
|
||||
db_session.commit()
|
||||
|
||||
assert db_session.query(db.DistanceMatrix).count() == 1
|
||||
assert db_session.query(db.Path).count() == 1
|
||||
|
||||
def test_delete_a_referenced_first_address(self, db_session, assoc):
|
||||
def test_delete_a_referenced_first_address(self, db_session, path):
|
||||
"""Remove a record that is referenced with a FK."""
|
||||
db_session.add(assoc)
|
||||
db_session.add(path)
|
||||
db_session.commit()
|
||||
|
||||
# Must delete without ORM as otherwise an UPDATE statement is emitted.
|
||||
stmt = sqla.delete(db.Address).where(db.Address.id == assoc.first_address.id)
|
||||
stmt = sqla.delete(db.Address).where(db.Address.id == path.first_address.id)
|
||||
|
||||
with pytest.raises(
|
||||
sa_exc.IntegrityError,
|
||||
|
@ -80,13 +80,13 @@ class TestConstraints:
|
|||
):
|
||||
db_session.execute(stmt)
|
||||
|
||||
def test_delete_a_referenced_second_address(self, db_session, assoc):
|
||||
def test_delete_a_referenced_second_address(self, db_session, path):
|
||||
"""Remove a record that is referenced with a FK."""
|
||||
db_session.add(assoc)
|
||||
db_session.add(path)
|
||||
db_session.commit()
|
||||
|
||||
# Must delete without ORM as otherwise an UPDATE statement is emitted.
|
||||
stmt = sqla.delete(db.Address).where(db.Address.id == assoc.second_address.id)
|
||||
stmt = sqla.delete(db.Address).where(db.Address.id == path.second_address.id)
|
||||
|
||||
with pytest.raises(
|
||||
sa_exc.IntegrityError,
|
||||
|
@ -102,7 +102,7 @@ class TestConstraints:
|
|||
|
||||
# Must insert without ORM as otherwise SQLAlchemy figures out
|
||||
# that something is wrong before any query is sent to the database.
|
||||
stmt = sqla.insert(db.DistanceMatrix).values(
|
||||
stmt = sqla.insert(db.Path).values(
|
||||
first_address_id=address.id,
|
||||
second_address_id=another_address.id,
|
||||
city_id=999,
|
||||
|
@ -115,34 +115,34 @@ class TestConstraints:
|
|||
):
|
||||
db_session.execute(stmt)
|
||||
|
||||
def test_redundant_addresses(self, db_session, assoc):
|
||||
def test_redundant_addresses(self, db_session, path):
|
||||
"""Insert a record that violates a unique constraint."""
|
||||
db_session.add(assoc)
|
||||
db_session.add(path)
|
||||
db_session.commit()
|
||||
|
||||
# Must insert without ORM as otherwise SQLAlchemy figures out
|
||||
# that something is wrong before any query is sent to the database.
|
||||
stmt = sqla.insert(db.DistanceMatrix).values(
|
||||
first_address_id=assoc.first_address.id,
|
||||
second_address_id=assoc.second_address.id,
|
||||
city_id=assoc.city_id,
|
||||
air_distance=assoc.air_distance,
|
||||
stmt = sqla.insert(db.Path).values(
|
||||
first_address_id=path.first_address.id,
|
||||
second_address_id=path.second_address.id,
|
||||
city_id=path.city_id,
|
||||
air_distance=path.air_distance,
|
||||
)
|
||||
|
||||
with pytest.raises(sa_exc.IntegrityError, match='duplicate key value'):
|
||||
db_session.execute(stmt)
|
||||
|
||||
def test_symmetric_addresses(self, db_session, assoc):
|
||||
def test_symmetric_addresses(self, db_session, path):
|
||||
"""Insert a record that violates a check constraint."""
|
||||
db_session.add(assoc)
|
||||
db_session.add(path)
|
||||
db_session.commit()
|
||||
|
||||
another_assoc = db.DistanceMatrix(
|
||||
first_address=assoc.second_address,
|
||||
second_address=assoc.first_address,
|
||||
air_distance=assoc.air_distance,
|
||||
another_path = db.Path(
|
||||
first_address=path.second_address,
|
||||
second_address=path.first_address,
|
||||
air_distance=path.air_distance,
|
||||
)
|
||||
db_session.add(another_assoc)
|
||||
db_session.add(another_path)
|
||||
|
||||
with pytest.raises(
|
||||
sa_exc.IntegrityError,
|
||||
|
@ -150,44 +150,44 @@ class TestConstraints:
|
|||
):
|
||||
db_session.commit()
|
||||
|
||||
def test_negative_air_distance(self, db_session, assoc):
|
||||
def test_negative_air_distance(self, db_session, path):
|
||||
"""Insert an instance with invalid data."""
|
||||
assoc.air_distance = -1
|
||||
db_session.add(assoc)
|
||||
path.air_distance = -1
|
||||
db_session.add(path)
|
||||
|
||||
with pytest.raises(sa_exc.IntegrityError, match='realistic_air_distance'):
|
||||
db_session.commit()
|
||||
|
||||
def test_air_distance_too_large(self, db_session, assoc):
|
||||
def test_air_distance_too_large(self, db_session, path):
|
||||
"""Insert an instance with invalid data."""
|
||||
assoc.air_distance = 20_000
|
||||
assoc.bicycle_distance = 21_000
|
||||
db_session.add(assoc)
|
||||
path.air_distance = 20_000
|
||||
path.bicycle_distance = 21_000
|
||||
db_session.add(path)
|
||||
|
||||
with pytest.raises(sa_exc.IntegrityError, match='realistic_air_distance'):
|
||||
db_session.commit()
|
||||
|
||||
def test_bicycle_distance_too_large(self, db_session, assoc):
|
||||
def test_bicycle_distance_too_large(self, db_session, path):
|
||||
"""Insert an instance with invalid data."""
|
||||
assoc.bicycle_distance = 25_000
|
||||
db_session.add(assoc)
|
||||
path.bicycle_distance = 25_000
|
||||
db_session.add(path)
|
||||
|
||||
with pytest.raises(sa_exc.IntegrityError, match='realistic_bicycle_distance'):
|
||||
db_session.commit()
|
||||
|
||||
def test_air_distance_shorter_than_bicycle_distance(self, db_session, assoc):
|
||||
def test_air_distance_shorter_than_bicycle_distance(self, db_session, path):
|
||||
"""Insert an instance with invalid data."""
|
||||
assoc.bicycle_distance = round(0.75 * assoc.air_distance)
|
||||
db_session.add(assoc)
|
||||
path.bicycle_distance = round(0.75 * path.air_distance)
|
||||
db_session.add(path)
|
||||
|
||||
with pytest.raises(sa_exc.IntegrityError, match='air_distance_is_shortest'):
|
||||
db_session.commit()
|
||||
|
||||
@pytest.mark.parametrize('duration', [-1, 3601])
|
||||
def test_unrealistic_bicycle_travel_time(self, db_session, assoc, duration):
|
||||
def test_unrealistic_bicycle_travel_time(self, db_session, path, duration):
|
||||
"""Insert an instance with invalid data."""
|
||||
assoc.bicycle_duration = duration
|
||||
db_session.add(assoc)
|
||||
path.bicycle_duration = duration
|
||||
db_session.add(path)
|
||||
|
||||
with pytest.raises(
|
||||
sa_exc.IntegrityError, match='realistic_bicycle_travel_time',
|
||||
|
@ -197,47 +197,47 @@ class TestConstraints:
|
|||
|
||||
@pytest.mark.db
|
||||
class TestFromAddresses:
|
||||
"""Test the alternative constructor `DistanceMatrix.from_addresses()`."""
|
||||
"""Test the alternative constructor `Path.from_addresses()`."""
|
||||
|
||||
@pytest.fixture
|
||||
def _prepare_db(self, db_session, address):
|
||||
"""Put the `address` into the database.
|
||||
|
||||
`Address`es must be in the database as otherwise the `.city_id` column
|
||||
cannot be resolved in `DistanceMatrix.from_addresses()`.
|
||||
cannot be resolved in `Path.from_addresses()`.
|
||||
"""
|
||||
db_session.add(address)
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_make_distance_matrix_instance(
|
||||
def test_make_path_instance(
|
||||
self, db_session, address, another_address,
|
||||
):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
assert db_session.query(db.DistanceMatrix).count() == 0
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
assert db_session.query(db.Path).count() == 0
|
||||
|
||||
db.DistanceMatrix.from_addresses(address, another_address)
|
||||
db.Path.from_addresses(address, another_address)
|
||||
|
||||
assert db_session.query(db.DistanceMatrix).count() == 1
|
||||
assert db_session.query(db.Path).count() == 1
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_make_the_same_distance_matrix_instance_twice(
|
||||
def test_make_the_same_path_instance_twice(
|
||||
self, db_session, address, another_address,
|
||||
):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
assert db_session.query(db.DistanceMatrix).count() == 0
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
assert db_session.query(db.Path).count() == 0
|
||||
|
||||
db.DistanceMatrix.from_addresses(address, another_address)
|
||||
db.Path.from_addresses(address, another_address)
|
||||
|
||||
assert db_session.query(db.DistanceMatrix).count() == 1
|
||||
assert db_session.query(db.Path).count() == 1
|
||||
|
||||
db.DistanceMatrix.from_addresses(another_address, address)
|
||||
db.Path.from_addresses(another_address, address)
|
||||
|
||||
assert db_session.query(db.DistanceMatrix).count() == 1
|
||||
assert db_session.query(db.Path).count() == 1
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_structure_of_return_value(self, db_session, address, another_address):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
results = db.DistanceMatrix.from_addresses(address, another_address)
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
results = db.Path.from_addresses(address, another_address)
|
||||
|
||||
assert isinstance(results, list)
|
||||
|
||||
|
@ -245,10 +245,10 @@ class TestFromAddresses:
|
|||
def test_instances_must_have_air_distance(
|
||||
self, db_session, address, another_address,
|
||||
):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
distances = db.DistanceMatrix.from_addresses(address, another_address)
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
paths = db.Path.from_addresses(address, another_address)
|
||||
|
||||
result = distances[0]
|
||||
result = paths[0]
|
||||
|
||||
assert result.air_distance is not None
|
||||
|
||||
|
@ -256,10 +256,10 @@ class TestFromAddresses:
|
|||
def test_do_not_sync_instances_with_google_maps(
|
||||
self, db_session, address, another_address,
|
||||
):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
distances = db.DistanceMatrix.from_addresses(address, another_address)
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
paths = db.Path.from_addresses(address, another_address)
|
||||
|
||||
result = distances[0]
|
||||
result = paths[0]
|
||||
|
||||
assert result.bicycle_distance is None
|
||||
assert result.bicycle_duration is None
|
||||
|
@ -268,52 +268,46 @@ class TestFromAddresses:
|
|||
def test_sync_instances_with_google_maps(
|
||||
self, db_session, address, another_address, monkeypatch,
|
||||
):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
|
||||
def sync(self):
|
||||
self.bicycle_distance = 1.25 * self.air_distance
|
||||
self.bicycle_duration = 300
|
||||
|
||||
monkeypatch.setattr(db.DistanceMatrix, 'sync_with_google_maps', sync)
|
||||
monkeypatch.setattr(db.Path, 'sync_with_google_maps', sync)
|
||||
|
||||
distances = db.DistanceMatrix.from_addresses(
|
||||
address, another_address, google_maps=True,
|
||||
)
|
||||
paths = db.Path.from_addresses(address, another_address, google_maps=True)
|
||||
|
||||
result = distances[0]
|
||||
result = paths[0]
|
||||
|
||||
assert result.bicycle_distance is not None
|
||||
assert result.bicycle_duration is not None
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_one_distance_for_two_addresses(self, db_session, address, another_address):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
result = len(db.DistanceMatrix.from_addresses(address, another_address))
|
||||
def test_one_path_for_two_addresses(self, db_session, address, another_address):
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
result = len(db.Path.from_addresses(address, another_address))
|
||||
|
||||
assert result == 1
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_two_distances_for_three_addresses(self, db_session, make_address):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
result = len(
|
||||
db.DistanceMatrix.from_addresses(*[make_address() for _ in range(3)]),
|
||||
)
|
||||
def test_two_paths_for_three_addresses(self, db_session, make_address):
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
result = len(db.Path.from_addresses(*[make_address() for _ in range(3)]))
|
||||
|
||||
assert result == 3
|
||||
|
||||
@pytest.mark.usefixtures('_prepare_db')
|
||||
def test_six_distances_for_four_addresses(self, db_session, make_address):
|
||||
"""Test instantiation of a new `DistanceMatrix` instance."""
|
||||
result = len(
|
||||
db.DistanceMatrix.from_addresses(*[make_address() for _ in range(4)]),
|
||||
)
|
||||
def test_six_paths_for_four_addresses(self, db_session, make_address):
|
||||
"""Test instantiation of a new `Path` instance."""
|
||||
result = len(db.Path.from_addresses(*[make_address() for _ in range(4)]))
|
||||
|
||||
assert result == 6
|
||||
|
||||
|
||||
@pytest.mark.db
|
||||
class TestSyncWithGoogleMaps:
|
||||
"""Test the `DistanceMatrix.sync_with_google_maps()` method."""
|
||||
"""Test the `Path.sync_with_google_maps()` method."""
|
||||
|
||||
@pytest.fixture
|
||||
def api_response(self):
|
||||
|
@ -575,56 +569,58 @@ class TestSyncWithGoogleMaps:
|
|||
monkeypatch.setattr(googlemaps.Client, 'directions', directions)
|
||||
|
||||
@pytest.mark.usefixtures('_fake_google_api')
|
||||
def test_sync_instances_with_google_maps(self, db_session, assoc):
|
||||
"""Call the method for a `DistanceMatrix` object without Google data."""
|
||||
assoc.bicycle_distance = None
|
||||
assoc.bicycle_duration = None
|
||||
assoc._directions = None
|
||||
def test_sync_instances_with_google_maps(self, db_session, path):
|
||||
"""Call the method for a `Path` object without Google data."""
|
||||
path.bicycle_distance = None
|
||||
path.bicycle_duration = None
|
||||
path._directions = None
|
||||
|
||||
assoc.sync_with_google_maps()
|
||||
path.sync_with_google_maps()
|
||||
|
||||
assert assoc.bicycle_distance == 2_999
|
||||
assert assoc.bicycle_duration == 596
|
||||
assert assoc._directions is not None
|
||||
assert path.bicycle_distance == 2_999
|
||||
assert path.bicycle_duration == 596
|
||||
assert path._directions is not None
|
||||
|
||||
@pytest.mark.usefixtures('_fake_google_api')
|
||||
def test_repeated_sync_instances_with_google_maps(self, db_session, assoc):
|
||||
"""Call the method for a `DistanceMatrix` object with Google data.
|
||||
def test_repeated_sync_instances_with_google_maps(self, db_session, path):
|
||||
"""Call the method for a `Path` object with Google data.
|
||||
|
||||
That call should immediately return without changing any data.
|
||||
|
||||
We use the `assoc`'s "Google" data from above.
|
||||
We use the `path`'s "Google" data from above.
|
||||
"""
|
||||
old_distance = assoc.bicycle_distance
|
||||
old_duration = assoc.bicycle_duration
|
||||
old_directions = assoc._directions
|
||||
old_distance = path.bicycle_distance
|
||||
old_duration = path.bicycle_duration
|
||||
old_directions = path._directions
|
||||
|
||||
assoc.sync_with_google_maps()
|
||||
path.sync_with_google_maps()
|
||||
|
||||
assert assoc.bicycle_distance is old_distance
|
||||
assert assoc.bicycle_duration is old_duration
|
||||
assert assoc._directions is old_directions
|
||||
assert path.bicycle_distance is old_distance
|
||||
assert path.bicycle_duration is old_duration
|
||||
assert path._directions is old_directions
|
||||
|
||||
|
||||
class TestProperties:
|
||||
"""Test properties in `DistanceMatrix`."""
|
||||
"""Test properties in `Path`."""
|
||||
|
||||
def test_path_structure(self, assoc):
|
||||
"""Test `DistanceMatrix.path` property."""
|
||||
result = assoc.path
|
||||
def test_waypoints_structure(self, path):
|
||||
"""Test `Path.waypoints` property."""
|
||||
result = path.waypoints
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert isinstance(result[0], utils.Location)
|
||||
|
||||
def test_path_content(self, assoc):
|
||||
"""Test `DistanceMatrix.path` property."""
|
||||
result = assoc.path
|
||||
def test_waypoints_content(self, path):
|
||||
"""Test `Path.waypoints` property."""
|
||||
result = path.waypoints
|
||||
|
||||
assert len(result) == 5 # = 5 inner points, excluding start and end
|
||||
# There are 5 inner points, excluding start and end,
|
||||
# i.e., the `.first_address` and `second_address`.
|
||||
assert len(result) == 5
|
||||
|
||||
def test_path_is_cached(self, assoc):
|
||||
"""Test `DistanceMatrix.path` property."""
|
||||
result1 = assoc.path
|
||||
result2 = assoc.path
|
||||
def test_waypoints_is_cached(self, path):
|
||||
"""Test `Path.waypoints` property."""
|
||||
result1 = path.waypoints
|
||||
result2 = path.waypoints
|
||||
|
||||
assert result1 is result2
|
||||
|
|
Loading…
Reference in a new issue