Repository: amadeus4dev/amadeus-python

Issue:
New Transfer APIs to be added

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index c46b410..a1a3468 100644
--- a/README.rst
+++ b/README.rst
@@ -350,6 +350,15 @@ List of supported endpoints
     # Airline Routes
     amadeus.airline.destinations.get(airlineCode='BA')
 
+    # Transfer Offers Search
+    amadeus.shopping.transfer_offers_search.post(body)
+
+    # Transfer Create Order
+    amadeus.ordering.transfer_orders.post(body, offerId='1000000000')
+
+    # Transfer Order Management
+    amadeus.ordering.transfer_order('ABC').transfers.cancellation.post(body, confirmNbr=123)
+
 Development & Contributing
 --------------------------
 
diff --git a/amadeus/namespaces/_ordering.py b/amadeus/namespaces/_ordering.py
new file mode 100644
index 0000000..e26d623
--- /dev/null
+++ b/amadeus/namespaces/_ordering.py
@@ -0,0 +1,15 @@
+from amadeus.client.decorator import Decorator
+from amadeus.ordering._transfer_orders import TransferOrders
+from amadeus.ordering._transfer_order import TransferOrder
+
+
+class Ordering(Decorator, object):
+    def __init__(self, client):
+        Decorator.__init__(self, client)
+        self.transfer_orders = TransferOrders(client)
+
+    def transfer_order(self, order_id):
+        return TransferOrder(self.client, order_id)
+
+
+__all__ = ['TransferOrders', 'TransferOrder']
diff --git a/amadeus/namespaces/_shopping.py b/amadeus/namespaces/_shopping.py
index b6bfc85..2546373 100644
--- a/amadeus/namespaces/_shopping.py
+++ b/amadeus/namespaces/_shopping.py
@@ -9,6 +9,7 @@ from amadeus.shopping._activity import Activity
 from amadeus.shopping._availability import Availability
 from amadeus.shopping._hotel_offer_search import HotelOfferSearch
 from amadeus.shopping._hotel_offers_search import HotelOffersSearch
+from amadeus.shopping._transfer_offers_search import TransferOffersSearch
 
 
 class Shopping(Decorator, object):
@@ -22,6 +23,7 @@ class Shopping(Decorator, object):
         self.activities = Activities(client)
         self.availability = Availability(client)
         self.hotel_offers_search = HotelOffersSearch(client)
+        self.transfer_offers_search = TransferOffersSearch(client)
 
     def hotel_offer_search(self, offer_id):
         return HotelOfferSearch(self.client, offer_id)
diff --git a/amadeus/namespaces/core.py b/amadeus/namespaces/core.py
index 44bae59..bbb563d 100644
--- a/amadeus/namespaces/core.py
+++ b/amadeus/namespaces/core.py
@@ -10,6 +10,7 @@ from amadeus.namespaces._analytics import Analytics
 from amadeus.namespaces._location import Location
 from amadeus.namespaces._duty_of_care import DutyOfCare
 from amadeus.namespaces._airline import Airline
+from amadeus.namespaces._ordering import Ordering
 
 
 class Core(object):
@@ -26,3 +27,4 @@ class Core(object):
         self.location = Location(self)
         self.duty_of_care = DutyOfCare(self)
         self.airline = Airline(self)
+        self.ordering = Ordering(self)
diff --git a/amadeus/ordering/__init__.py b/amadeus/ordering/__init__.py
new file mode 100644
index 0000000..69f59ee
--- /dev/null
+++ b/amadeus/ordering/__init__.py
@@ -0,0 +1,4 @@
+from ._transfer_orders import TransferOrders
+from ._transfer_order import TransferOrder
+
+__all__ = ['TransferOrders', 'TransferOrder']
diff --git a/amadeus/ordering/_transfer_order.py b/amadeus/ordering/_transfer_order.py
new file mode 100644
index 0000000..0e01fc3
--- /dev/null
+++ b/amadeus/ordering/_transfer_order.py
@@ -0,0 +1,8 @@
+from amadeus.client.decorator import Decorator
+from amadeus.ordering.transfer_orders import Transfers
+
+
+class TransferOrder(Decorator, object):
+    def __init__(self, client, order_id):
+        Decorator.__init__(self, client)
+        self.transfers = Transfers(client, order_id)
diff --git a/amadeus/ordering/_transfer_orders.py b/amadeus/ordering/_transfer_orders.py
new file mode 100644
index 0000000..c8b49d6
--- /dev/null
+++ b/amadeus/ordering/_transfer_orders.py
@@ -0,0 +1,19 @@
+from amadeus.client.decorator import Decorator
+
+from urllib.parse import urlencode
+
+
+class TransferOrders(Decorator, object):
+    def post(self, body, **params):
+        '''
+        Performs the final booking for a chosen transfer
+
+        .. code-block:: python
+
+            amadeus.ordering.transfer_orders.post(body, offerId=offer_id)
+
+        :rtype: amadeus.Response
+        :raises amadeus.ResponseError: if the request could not be completed
+        '''
+        url = '/v1/ordering/transfer-orders?'
+        return self.client.post(url + urlencode(params), body)
diff --git a/amadeus/ordering/transfer_orders/__init__.py b/amadeus/ordering/transfer_orders/__init__.py
new file mode 100644
index 0000000..7406bb4
--- /dev/null
+++ b/amadeus/ordering/transfer_orders/__init__.py
@@ -0,0 +1,3 @@
+from ._transfers import Transfers
+
+__all__ = ['Transfers']
diff --git a/amadeus/ordering/transfer_orders/_transfers.py b/amadeus/ordering/transfer_orders/_transfers.py
new file mode 100644
index 0000000..3721dd2
--- /dev/null
+++ b/amadeus/ordering/transfer_orders/_transfers.py
@@ -0,0 +1,8 @@
+from amadeus.client.decorator import Decorator
+from amadeus.ordering.transfer_orders.transfers import Cancellation
+
+
+class Transfers(Decorator, object):
+    def __init__(self, client, order_id):
+        Decorator.__init__(self, client)
+        self.cancellation = Cancellation(client, order_id)
diff --git a/amadeus/ordering/transfer_orders/transfers/__init__.py b/amadeus/ordering/transfer_orders/transfers/__init__.py
new file mode 100644
index 0000000..a39b649
--- /dev/null
+++ b/amadeus/ordering/transfer_orders/transfers/__init__.py
@@ -0,0 +1,3 @@
+from ._cancellation import Cancellation
+
+__all__ = ['Cancellation']
diff --git a/amadeus/ordering/transfer_orders/transfers/_cancellation.py b/amadeus/ordering/transfer_orders/transfers/_cancellation.py
new file mode 100644
index 0000000..63f429f
--- /dev/null
+++ b/amadeus/ordering/transfer_orders/transfers/_cancellation.py
@@ -0,0 +1,26 @@
+from amadeus.client.decorator import Decorator
+
+from urllib.parse import urlencode
+
+
+class Cancellation(Decorator, object):
+    def __init__(self, client, order_id):
+        Decorator.__init__(self, client)
+        self.order_id = order_id
+
+    def post(self, body, **params):
+        '''
+        Cancels a transfer reservation
+
+        .. code-block:: python
+
+            amadeus.ordering.transfer_order(order_id).transfers.cancellation.post(body,
+            confirmNbr=confirm_nbr)
+
+        :rtype: amadeus.Response
+        :raises amadeus.ResponseError: if the request could not be completed
+        '''
+        url = '/v1/ordering/transfer-orders/{0}/transfers/cancellation?'.format(
+            self.order_id
+        )
+        return self.client.post(url + urlencode(params), body)
diff --git a/amadeus/shopping/__init__.py b/amadeus/shopping/__init__.py
index 3574597..0312392 100644
--- a/amadeus/shopping/__init__.py
+++ b/amadeus/shopping/__init__.py
@@ -4,7 +4,9 @@ from ._flight_offers_search import FlightOffersSearch
 from ._hotel_offer_search import HotelOfferSearch
 from ._hotel_offers_search import HotelOffersSearch
 from ._activities import Activities
+from ._transfer_offers_search import TransferOffersSearch
 
 __all__ = ['FlightDates', 'FlightDestinations',
            'HotelOffersSearch', 'HotelOfferSearch',
-           'FlightOffersSearch', 'Activities']
+           'FlightOffersSearch', 'Activities',
+           'TransferOffersSearch']
diff --git a/amadeus/shopping/_transfer_offers_search.py b/amadeus/shopping/_transfer_offers_search.py
new file mode 100644
index 0000000..7ae7422
--- /dev/null
+++ b/amadeus/shopping/_transfer_offers_search.py
@@ -0,0 +1,18 @@
+from amadeus.client.decorator import Decorator
+
+
+class TransferOffersSearch(Decorator, object):
+    def post(self, body):
+        '''
+        Get transfer offers
+
+        .. code-block:: python
+
+            amadeus.shopping.transfer_offers_search.post(body)
+
+        :param body: the parameters to send to the API
+
+        :rtype: amadeus.Response
+        :raises amadeus.ResponseError: if the request could not be completed
+        '''
+        return self.client.post('/v1/shopping/transfer-offers', body)
diff --git a/docs/index.rst b/docs/index.rst
index 12bd82c..6921cbf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -89,6 +89,12 @@ Shopping/Availability
 .. autoclass:: amadeus.shopping.availability.FlightAvailabilities
   :members: post
 
+Shopping/Transfers
+================
+
+.. autoclass:: amadeus.shopping.TransferOffersSearch
+  :members: post
+
 Travel/Analytics
 ================
 
@@ -253,3 +259,12 @@ Airline/Destinations
 
 .. autoclass:: amadeus.airline.Destinations
   :members: get
+
+Ordering/Transfers
+================
+
+.. autoclass:: amadeus.ordering.TransferOrders
+  :members: post
+
+.. autoclass:: amadeus.ordering.transfer_orders.transfers.Cancellation
+  :members: post


Test patch:
diff --git a/specs/namespaces/test_namespaces.py b/specs/namespaces/test_namespaces.py
index 1a4a78d..df0a2d1 100644
--- a/specs/namespaces/test_namespaces.py
+++ b/specs/namespaces/test_namespaces.py
@@ -67,6 +67,9 @@ def test_expected_paths(client):
     assert client.duty_of_care is not None
     assert client.duty_of_care.diseases.covid19_report is not None
     assert client.airline.destinations is not None
+    assert client.shopping.transfer_offers_search is not None
+    assert client.ordering.transfer_orders is not None
+    assert client.ordering.transfer_order is not None
 
 
 def test_expected_get_methods(client):
@@ -543,3 +546,27 @@ def test_airline_destinations_get(client_setup):
     client_setup.get.assert_called_with(
         '/v1/airline/destinations', a='b'
     )
+
+
+def test_shopping_transfer_offers_search_post(client_setup):
+    client_setup.shopping.transfer_offers_search.post({'foo': 'bar'})
+    client_setup.post.assert_called_with(
+        '/v1/shopping/transfer-offers', {'foo': 'bar'}
+    )
+
+
+def test_ordering_transfer_orders_post(client_setup):
+    client_setup.ordering.transfer_orders.post(
+        {'foo': 'bar'}, offerId='1')
+    client_setup.post.assert_called_with(
+        '/v1/ordering/transfer-orders?'+'offerId=1', {'foo': 'bar'}
+    )
+
+
+def test_ordering_transfer_order_transfers_cancellation_post(client_setup):
+    client_setup.ordering.transfer_order('XXX').transfers.cancellation.post(
+        {'foo': 'bar'}, confirmNbr=123)
+    client_setup.post.assert_called_with(
+        '/v1/ordering/transfer-orders/XXX/transfers/cancellation?confirmNbr=123',
+        {'foo': 'bar'}
+    )