Skip to content

Commit

Permalink
Merge branch 'release/2.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
cohaolain committed May 3, 2023
2 parents a621d84 + 36eb01a commit 9894e19
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 27 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

# [v2.3.0] - 2023.05.03
### Added
- Optional `destination_airport` keyword argument to the cheap flights endpoints,
in case you only care about the cheapest flight to a single airport.
- Improved type hints on the main API methods.
- In regard to the availability API:
- Ability to grab a session cookie for use by the availability API.
- Exceptions raised when the availbility API declines to provide results,
for reasons such as rate limiting or the lack of a session cookie.
- Thanks to [@dolohow](https://www.github.com/dolohow).

### Changed
- When the library hits an exception parsing an API response, it will now also log the original query params.
- Thanks to [@dolohow](https://www.github.com/dolohow).
- Small refactors, additional type hints

### Fixed
- `currency` is now only added as a parameter on calls to `get_cheapest_return_flights` whenit is configured, identical
to `get_cheapest_flights`.

# [v2.2.0] - 2023.04.18
### Added
- Ability to constrain the max price of retrieved flights/trips from the API when
Expand Down
96 changes: 70 additions & 26 deletions ryanair/ryanair.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,36 @@
logger.addHandler(console_handler)


class RyanairException(Exception):
def __init__(self, message):
super().__init__(f"Ryanair API: {message}")


class AvailabilityException(RyanairException):
def __init__(self):
super().__init__("Availability API declined to provide a result")


# noinspection PyBroadException
class Ryanair:
BASE_SERVICES_API_URL = "https://services-api.ryanair.com/farfnd/v4/"
BASE_AVAILABILITY_API_URL = "https://www.ryanair.com/api/booking/v4/"
BASE_SITE_FOR_SESSION_URL = "https://www.ryanair.com/ie/en"

def __init__(self, currency: Optional[str] = None):
self.currency = currency

self._num_queries = 0
self.session = requests.Session()
self._update_session_cookie()

@deprecated(version="2.0.0", reason="deprecated in favour of get_cheapest_flights", action="once")
def get_flights(self, airport, date_from, date_to, destination_country=None):
return self.get_cheapest_flights(airport, date_from, date_to, destination_country)

@deprecated(version="2.0.0", reason="deprecated in favour of get_cheapest_return_flights", action="once")
def get_return_flights(self, source_airport, date_from, date_to,
return_date_from, return_date_to,
destination_country=None):
return self.get_cheapest_return_flights(source_airport, date_from, date_to,
return_date_from, return_date_to, destination_country)

def get_cheapest_flights(self, airport, date_from, date_to, destination_country=None,
custom_params=None,
def get_cheapest_flights(self, airport: str, date_from: Union[datetime, date, str],
date_to: Union[datetime, date, str], destination_country: Optional[str] = None,
custom_params: Optional[dict] = None,
departure_time_from: Union[str, time] = "00:00",
departure_time_to: Union[str, time] = "23:59",
max_price: int = None
max_price: Optional[int] = None,
destination_airport: Optional[str] = None
):
query_url = ''.join((Ryanair.BASE_SERVICES_API_URL,
"oneWayFares"))
Expand All @@ -68,6 +72,8 @@ def get_cheapest_flights(self, airport, date_from, date_to, destination_country=
params['arrivalCountryCode'] = destination_country
if max_price:
params['priceValueTo'] = max_price
if destination_airport:
params['arrivalAirportIataCode'] = destination_airport
if custom_params:
params.update(custom_params)

Expand All @@ -82,15 +88,19 @@ def get_cheapest_flights(self, airport, date_from, date_to, destination_country=

return []

def get_cheapest_return_flights(self, source_airport, date_from, date_to,
return_date_from, return_date_to,
destination_country=None,
custom_params=None,
def get_cheapest_return_flights(self, source_airport: str,
date_from: Union[datetime, date, str],
date_to: Union[datetime, date, str],
return_date_from: Union[datetime, date, str],
return_date_to: Union[datetime, date, str],
destination_country: Optional[str] = None,
custom_params: Optional[dict] = None,
outbound_departure_time_from: Union[str, time] = "00:00",
outbound_departure_time_to: Union[str, time] = "23:59",
inbound_departure_time_from: Union[str, time] = "00:00",
inbound_departure_time_to: Union[str, time] = "23:59",
max_price: int = None
max_price: Optional[int] = None,
destination_airport: Optional[str] = None
):
query_url = ''.join((Ryanair.BASE_SERVICES_API_URL,
"roundTripFares"))
Expand All @@ -101,16 +111,19 @@ def get_cheapest_return_flights(self, source_airport, date_from, date_to,
"outboundDepartureDateTo": self._format_date_for_api(date_to),
"inboundDepartureDateFrom": self._format_date_for_api(return_date_from),
"inboundDepartureDateTo": self._format_date_for_api(return_date_to),
"currency": self.currency,
"outboundDepartureTimeFrom": self._format_time_for_api(outbound_departure_time_from),
"outboundDepartureTimeTo": self._format_time_for_api(outbound_departure_time_to),
"inboundDepartureTimeFrom": self._format_time_for_api(inbound_departure_time_from),
"inboundDepartureTimeTo": self._format_time_for_api(inbound_departure_time_to)
}
if self.currency:
params['currency'] = self.currency
if destination_country:
params['arrivalCountryCode'] = destination_country
if max_price:
params['priceValueTo'] = max_price
if destination_airport:
params['arrivalAirportIataCode'] = destination_airport
if custom_params:
params.update(custom_params)

Expand All @@ -126,9 +139,9 @@ def get_cheapest_return_flights(self, source_airport, date_from, date_to,
else:
return []

def get_all_flights(self, origin_airport, date_out, destination,
locale="en-ie", origin_is_mac=False, destination_is_mac=False,
custom_params=None):
def get_all_flights(self, origin_airport: str, date_out: Union[datetime, date, str], destination: str,
locale: str = "en-ie", origin_is_mac: bool = False, destination_is_mac: bool = False,
custom_params: Optional[dict] = None):
query_url = ''.join((Ryanair.BASE_AVAILABILITY_API_URL, f"{locale}/availability"))

params = {
Expand Down Expand Up @@ -164,7 +177,16 @@ def get_all_flights(self, origin_airport, date_out, destination,
params.update(custom_params)

try:
# Try once to get a new session cookie, just in case the old one has expired.
# If that fails too, we should raise the exception.
response = self._retryable_query(query_url, params)
if self.check_if_availability_response_is_declined(response):
logger.warning("Availability API declined to respond, attempting again with a new session cookie")
self._update_session_cookie()
response = self._retryable_query(query_url, params)
if self.check_if_availability_response_is_declined(response):
raise AvailabilityException

currency = response["currency"]
trip = response["trips"][0]
flights = trip['dates'][0]['flights']
Expand All @@ -178,10 +200,17 @@ def get_all_flights(self, origin_airport, date_out, destination,
trip['destinationName'],
currency)
for flight in flights]
except RyanairException:
logger.exception(f"Failed to parse response when querying {query_url} with parameters {params}")
return []
except Exception:
logger.exception(f"Failed to parse response when querying {query_url}")
logger.exception(f"Failed to parse response when querying {query_url} with parameters {params}")
return []

@staticmethod
def check_if_availability_response_is_declined(response: dict) -> bool:
return 'message' in response and response['message'] == 'Availability declined'

@staticmethod
def _on_query_error(e):
logger.exception(f"Gave up retrying query, last exception was {e}")
Expand All @@ -191,7 +220,11 @@ def _on_query_error(e):
def _retryable_query(self, url, params):
self._num_queries += 1

return requests.get(url, params=params).json()
return self.session.get(url, params=params).json()

def _update_session_cookie(self):
# Visit main website to get session cookies
self.session.get(Ryanair.BASE_SITE_FOR_SESSION_URL)

def _parse_cheapest_flight(self, flight):
currency = flight['price']['currencyCode']
Expand Down Expand Up @@ -243,7 +276,7 @@ def _format_date_for_api(d: Union[datetime, date, str]):
return d.isoformat()

@staticmethod
def _format_time_for_api(t):
def _format_time_for_api(t: Union[time, str]):
if isinstance(t, str):
return t

Expand All @@ -253,3 +286,14 @@ def _format_time_for_api(t):
@property
def num_queries(self):
return self._num_queries

@deprecated(version="2.0.0", reason="deprecated in favour of get_cheapest_flights", action="once")
def get_flights(self, airport, date_from, date_to, destination_country=None):
return self.get_cheapest_flights(airport, date_from, date_to, destination_country)

@deprecated(version="2.0.0", reason="deprecated in favour of get_cheapest_return_flights", action="once")
def get_return_flights(self, source_airport, date_from, date_to,
return_date_from, return_date_to,
destination_country=None):
return self.get_cheapest_return_flights(source_airport, date_from, date_to,
return_date_from, return_date_to, destination_country)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
long_description = f.read()

setup(name='ryanair-py',
version='2.2.0',
version='2.3.0',
description='A module which allows you to retrieve data about the cheapest one-way and return flights '
'in a date range, or all available flights on a given day for a given route.',
long_description=long_description,
Expand Down

0 comments on commit 9894e19

Please sign in to comment.