Source code for pyticketswitch.availability

import datetime
from pyticketswitch.mixins import JSONMixin
from pyticketswitch.currency import CurrencyMeta
from pyticketswitch.misc import MONTH_NUMBERS


[docs]class AvailabilityMeta(CurrencyMeta): """Meta data about an availability response Attributes: contiguous_seat_selection_only (bool): indicates that the backend system will only allow seats to be selected that are in a contiguous line. valid_quantities (list): list of valid number of tickets available for selection. max_bundle_size (int): maximum bundle size for this event. If it is `None` then there is no maximum size. currencies (dict): dictionary of :class:`Currency <pyticketswitch.currency.Currency>`) objects indexed on currency code. default_currency_code (str): unless other wise specified all prices in the related response will be in this currency. desired_currency_code (str): the currency that the user account is expecting. Useful for conversions. backend_is_broken (bool): indicates that the backend system is presently marked as broken. backend_is_down (bool): indicates that we are not currently able to contact the backend system. backend_throttle_failed (bool): indicates that your call was throttled and failed to get a slot inside a viable timeframe. When true this indicates that the backend system is under heavy load. source_code (str): the code for the backend system must_select_whole_seat_block (bool): indicates that the backend system will only allow seats to be selected if you select all the seats in the seat_block. """ def __init__(self, contiguous_seat_selection_only=True, currency=None, valid_quantities=None, max_bundle_size=None, backend_is_down=False, backend_is_broken=False, backend_throttle_failed=False, source_code=None, must_select_whole_seat_block=False, *args, **kwargs): self.contiguous_seat_selection_only = contiguous_seat_selection_only self.currency = currency self.max_bundle_size = max_bundle_size self.valid_quantities = valid_quantities self.backend_is_broken = backend_is_broken self.backend_is_down = backend_is_down self.backend_throttle_failed = backend_throttle_failed self.source_code = source_code self.must_select_whole_seat_block = must_select_whole_seat_block super(AvailabilityMeta, self).__init__(*args, **kwargs)
[docs] @classmethod def from_api_data(cls, data): """Creates a new AvailabilityMeta object from API data from ticketswitch. Args: data (dict): the whole response from the availability call. Returns: :class:`AvailabilityMeta <pyticketswitch.availability.AvailabilityMeta>`: a new :class:`AvailabilityMeta <pyticketswitch.availability.AvailabilityMeta>` object populated with the data from the api. """ inst = super(AvailabilityMeta, cls).from_api_data(data) inst.valid_quantities = data.get('valid_quantities') inst.max_bundle_size = data.get('max_bundle_size') inst.contiguous_seat_selection_only = data.get( 'contiguous_seat_selection_only', True) inst.backend_is_broken = data.get('backend_is_broken') inst.backend_is_down = data.get('backend_is_down') inst.backend_throttle_failed = data.get('backend_throttle_failed') inst.source_code = data.get('source_code') inst.must_select_whole_seat_block = data.get('must_select_whole_seat_block') return inst
class AvailabilityDetails(JSONMixin, object): """Details about the available tickets of a performance. Attributes: ticket_type_code (str): identifier of the ticket type. ticket_type_description (str): human readable description of the ticket type. price_band_code (str): identifier of the price band. price_band_description (str): human readable description of the price band. seatprice (float): price of an individual seat. surcharge (float): additional charges per seat. full_seatprice (float): the non-offer price of an individual seat. full_surcharge (float): the non-offer additional charges per seat. currency (str): the currency of the prices. first_date (datetime.datetime): the first date and time this combination of ticket type and price band is available from. last_date (datetime.datetime): the latest date and time this combination of ticket type and price band is available from. valid_quantities (list): list of valid number of tickets available for selection. cached_number_available (int): the maximum number of consecutive tickets available. discount_code (str): the identifier for the discount. discount_desc (str): a human readable description for the discount. discount_semantic_type (str): a mapping of the code to a meaning e.g. standard (adult) or child """ def __init__(self, ticket_type=None, price_band=None, ticket_type_description=None, price_band_description=None, seatprice=None, surcharge=None, full_seatprice=None, full_surcharge=None, currency=None, first_date=None, percentage_saving=None, absolute_saving=None, last_date=None, calendar_masks=None, weekday_mask=None, valid_quantities=None, cached_number_available=None, discount_code=None, discount_desc=None, discount_semantic_type=None, suffixed_price_band_code=None): self.ticket_type = ticket_type self.ticket_type_description = ticket_type_description self.price_band = price_band self.price_band_description = price_band_description self.seatprice = seatprice self.surcharge = surcharge self.full_seatprice = full_seatprice self.full_surcharge = full_surcharge self.percentage_saving = percentage_saving self.absolute_saving = absolute_saving self.currency = currency self.first_date = first_date self.last_date = last_date self._calendar_masks = calendar_masks self._weekday_mask = weekday_mask self.valid_quantities = valid_quantities self.cached_number_available = cached_number_available self.discount_code = discount_code self.discount_desc = discount_desc self.discount_semantic_type = discount_semantic_type self.suffixed_price_band_code = suffixed_price_band_code @classmethod def from_api_data(cls, data): """Creates AvailabilityDetails from API data from ticketswitch. Args: data (dict): the part of the response from a ticketswitch API call that concerns availability details. Returns: list: a list of :class:`AvailabilityDetails <pyticketswitch.availability.AvailabilityDetails>` objects populated with the data from the api. """ details = [] for ticket_type in data.get('ticket_type', []): kwargs = { 'ticket_type': ticket_type.get('ticket_type_code'), 'ticket_type_description': ticket_type.get('ticket_type_desc'), } for price_band in ticket_type.get('price_band', []): kwargs.update( price_band=price_band.get('price_band_code'), price_band_description=price_band.get('price_band_desc'), ) raw_details_list = price_band.get('avail_detail', [{}]) for raw_details in raw_details_list: kwargs['seatprice'] = raw_details.get('seatprice', 0.0) kwargs['full_seatprice'] = raw_details.get('full_seatprice', 0.0) kwargs['surcharge'] = raw_details.get('surcharge', 0.0) kwargs['full_surcharge'] = raw_details.get('full_surcharge', 0.0) kwargs['percentage_saving'] = raw_details.get( 'percentage_saving', 0.0) kwargs['absolute_saving'] = raw_details.get('absolute_saving', 0.0) kwargs['currency'] = raw_details.get('avail_currency_code') kwargs['discount_code'] = raw_details.get('discount_code') kwargs['discount_desc'] = raw_details.get('discount_desc') kwargs['discount_semantic_type'] = raw_details.get( 'discount_semantic_type') kwargs['suffixed_price_band_code'] = raw_details.get( 'suffixed_price_band_code') available_dates = raw_details.get('available_dates', {}) if 'first_yyyymmdd' in available_dates: try: first = datetime.datetime.strptime( available_dates['first_yyyymmdd'], '%Y%m%d' ) kwargs['first_date'] = first.date() except ValueError: pass if 'last_yyyymmdd' in available_dates: try: last = datetime.datetime.strptime( available_dates['last_yyyymmdd'], '%Y%m%d' ) kwargs['last_date'] = last.date() except ValueError: pass kwargs['calendar_masks'] = { int(year[5:]): { MONTH_NUMBERS[month[:3]]: mask for month, mask in month_masks.items() } for year, month_masks in available_dates.items() if year.startswith('year_') } if 'available_weekdays_bitmask' in raw_details: kwargs['weekday_mask'] = raw_details['available_weekdays_bitmask'] if 'valid_quantities' in raw_details: kwargs['valid_quantities'] = raw_details['valid_quantities'] kwargs['cached_number_available'] = ( raw_details.get('cached_number_available')) avail_details = AvailabilityDetails(**kwargs) if avail_details._weekday_mask: weekday_list = [] for day in range(0, 7): weekday_list.append(avail_details.on_weekday(day)) avail_details.weekday_list = weekday_list details.append(avail_details) return sorted(details, key=lambda x: (x.ticket_type_description, x.combined_price())) def is_available(self, year, month=None, day=None): """Check availablity on a given year/month/day. Args: year (int): the year to check. For example, 2005, 2016, 2222, etc month (int): (optional) the month to check. 1 == jan, 12 == dec, etc day (int): (optional) the day of the month, zero indexed. Returns: bool: :obj:`True` when availble inside the given parameters, other wise :obj:`False`. Raises: ValueError: when a day is specified but not the month. """ if day and not month: raise ValueError('a month must be specified to specify a day') if year not in self._calendar_masks: return False year_data = self._calendar_masks[year] if month and month not in year_data: return False month_mask = year_data.get(month) # the month mask is a 32 bit int where the right most bit is the first # day of the month. To find if we have availability on a specfic day, # we bit shift the mask by the zero indexed day and AND it by 1. If the # result is a 1 then this combo of ticket type and price band is # available on that day, otherwise it is not. if day and (month_mask >> (day - 1) & 1) == 0: return False return True def on_weekday(self, day): """ Check if this combination of ticket_type and price band is available on a given day of the week, where 0 is monday and sunday is 6. .. note:: the api uses sunday as day 0 where as python uses monday. I've made a concious decision here to use the python numbers to keep it consistant with anything written against this. (also sunday as day 0 is silly) Args: day: the day of the week zero indexed from monday. Returns: bool: :obj:`True` when available on the given day of the week, otherwise :obj:`False`. """ adjusted_day = day + 1 if day < 6 else 0 return bool(self._weekday_mask >> adjusted_day & 1) def combined_price(self): return self.seatprice + self.surcharge def combined_full_price(self): return self.full_seatprice + self.full_surcharge