Skip to content

Commit e456259

Browse files
committed
switch to an api adapter
1 parent d8ed92f commit e456259

File tree

3 files changed

+96
-38
lines changed

3 files changed

+96
-38
lines changed

cargo_api.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import annotations
2+
import logging
3+
from datetime import date
4+
from typing import Protocol, Optional
5+
import requests
6+
import requests.exceptions
7+
8+
from domain import Shipment
9+
10+
11+
class CargoAPI(Protocol):
12+
13+
def get_latest_eta(self, reference: str) -> date:
14+
...
15+
16+
def sync(self, shipment: Shipment) -> None:
17+
...
18+
19+
20+
21+
class RealCargoAPI:
22+
API_URL = 'https://example.org'
23+
24+
25+
def get_latest_eta(self, reference: str) -> date:
26+
external_shipment_id = self._get_shipment_id(reference)
27+
if external_shipment_id is None:
28+
logging.warning(
29+
'tried to get updated eta for shipment %s not yet sent to partners',
30+
reference
31+
)
32+
return None
33+
34+
[journey] = requests.get(f"{self.API_URL}/shipments/{external_shipment_id}/journeys").json()['items']
35+
return date.fromisoformat(journey['eta'])
36+
37+
38+
39+
def sync(self, shipment: Shipment) -> None:
40+
external_shipment_id = self._get_shipment_id(shipment.reference)
41+
if external_shipment_id is None:
42+
requests.post(f'{self.API_URL}/shipments/', json={
43+
'client_reference': shipment.reference,
44+
'arrival_date': shipment.eta.isoformat()[:10] if shipment.eta else None,
45+
'products': [
46+
{'sku': ol.sku, 'quantity': ol.qty}
47+
for ol in shipment.lines
48+
]
49+
})
50+
51+
else:
52+
requests.put(f'{self.API_URL}/shipments/{external_shipment_id}/', json={
53+
'client_reference': shipment.reference,
54+
'arrival_date': shipment.eta.isoformat()[:10] if shipment.eta else None,
55+
'products': [
56+
{'sku': ol.sku, 'quantity': ol.qty}
57+
for ol in shipment.lines
58+
]
59+
})
60+
61+
62+
def _get_shipment_id(self, our_reference) -> Optional[str]:
63+
try:
64+
their_shipments = requests.get(f"{self.API_URL}/shipments/").json()['items']
65+
return next(
66+
(s['id'] for s in their_shipments if s['client_reference'] == our_reference),
67+
None
68+
)
69+
70+
except requests.exceptions.RequestException:
71+
logging.exception('Error retrieving shipment')
72+
raise

test_sync.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from use_cases import create_shipment, API_URL
44

55
def test_create_shipment_does_post_to_external_api():
6-
with mock.patch('use_cases.requests') as mock_requests:
6+
with mock.patch('cargo_api.requests') as mock_requests:
77
shipment = create_shipment({'sku1': 10}, incoterm='EXW')
88
expected_data = {
99
'client_reference': shipment.reference,
@@ -16,7 +16,7 @@ def test_create_shipment_does_post_to_external_api():
1616

1717

1818
def test_does_PUT_if_shipment_already_exists():
19-
with mock.patch('use_cases.uuid') as mock_uuid, mock.patch('use_cases.requests') as mock_requests:
19+
with mock.patch('use_cases.uuid') as mock_uuid, mock.patch('cargo_api.requests') as mock_requests:
2020
mock_uuid.uuid4.return_value.hex = 'our-id'
2121
mock_requests.get.return_value.json.return_value = {
2222
'items': [{'id': 'their-id', 'client_reference': 'our-id'}]
@@ -33,3 +33,22 @@ def test_does_PUT_if_shipment_already_exists():
3333
API_URL + '/shipments/their-id/', json=expected_data
3434
)
3535

36+
37+
def test_does_PUT_if_shipment_already_exists2():
38+
with mock.patch('use_cases.cargo_api._get_shipment_id') as mock_get_shipment_id, mock.patch('cargo_api.requests') as mock_requests:
39+
mock_get_shipment_id.return_value = 'their-id'
40+
mock_requests.get.return_value.json.return_value = {
41+
'items': [{'id': 'their-id', 'client_reference': 'our-id'}]
42+
}
43+
44+
shipment = create_shipment({'sku1': 10}, incoterm='EXW')
45+
assert mock_requests.post.called is False
46+
expected_data = {
47+
'client_reference': shipment.reference,
48+
'arrival_date': None,
49+
'products': [{'sku': 'sku1', 'quantity': 10}],
50+
}
51+
assert mock_requests.put.call_args == mock.call(
52+
API_URL + '/shipments/their-id/', json=expected_data
53+
)
54+

use_cases.py

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from domain import Shipment, OrderLine
1010
from notifications import notify_delay, notify_new_large_shipment
11+
from cargo_api import RealCargoAPI
12+
cargo_api = RealCargoAPI()
1113

1214
API_URL = 'https://example.org'
1315

@@ -18,7 +20,7 @@ def create_shipment(quantities: Dict[str, int], incoterm) -> Shipment:
1820
order_lines = [OrderLine(sku=sku, qty=qty) for sku, qty in quantities.items()]
1921
shipment = Shipment(reference=reference, lines=order_lines, eta=None, incoterm=incoterm)
2022
shipment.save()
21-
sync_to_api(shipment)
23+
cargo_api.sync(shipment)
2224
return shipment
2325

2426

@@ -48,38 +50,3 @@ def get_updated_eta(shipment):
4850
shipment.save()
4951

5052

51-
52-
def sync_to_api(shipment):
53-
external_shipment_id = get_shipment_id(shipment.reference)
54-
if external_shipment_id is None:
55-
requests.post(f'{API_URL}/shipments/', json={
56-
'client_reference': shipment.reference,
57-
'arrival_date': shipment.eta,
58-
'products': [
59-
{'sku': ol.sku, 'quantity': ol.qty}
60-
for ol in shipment.lines
61-
]
62-
})
63-
64-
else:
65-
requests.put(f'{API_URL}/shipments/{external_shipment_id}/', json={
66-
'client_reference': shipment.reference,
67-
'arrival_date': shipment.eta,
68-
'products': [
69-
{'sku': ol.sku, 'quantity': ol.qty}
70-
for ol in shipment.lines
71-
]
72-
})
73-
74-
75-
def get_shipment_id(our_reference) -> Optional[str]:
76-
try:
77-
their_shipments = requests.get(f"{API_URL}/shipments/").json()['items']
78-
return next(
79-
(s['id'] for s in their_shipments if s['client_reference'] == our_reference),
80-
None
81-
)
82-
83-
except requests.exceptions.RequestException:
84-
logging.exception('Error retrieving shipment')
85-
raise

0 commit comments

Comments
 (0)