def event(encrypted_token, config, branch_id, date, time): return d(config=config, queryStringParameters=d( token=encrypted_token, branch_id=branch_id, date=date, time=time, ))
def user_data(first_name, last_name, email, phone, date_of_birth): return d({ 'dob': date_of_birth, 'email': email, 'first_name': first_name, 'last_name': last_name, 'phone': phone, })
def match_and_notify_user(config, user, input_data): user_filters = user.filters def _day_filter(d): day_name = calendar.day_name[arrow.get(d).weekday()].lower() return day_name in user_filters.days email_ctx = [] for branch_name in user_filters.branches: if branch_name not in input_data['branches']: continue branch = input_data['branches'][branch_name] available_dates = list(filter(_day_filter, branch['available_dates'])) if not available_dates: continue email_ctx.append( dict(branch, available_dates={ date: branch['available_dates'][date] for date in available_dates })) if not email_ctx: log.info('No available slots after filter') return encrypted_token = token.encrypt( config.encrypt_key, d(user=user, service=d(publicId=input_data.publicId, name=input_data.name, qpId=input_data.qpId))) template = templates.generate_email(config, user, email_ctx, encrypted_token) subject = f'{len(email_ctx)} Locations Have An Available ICBC Timeslot!' if len(email_ctx) == 1: subject = f'{email_ctx[0]["name"]} Has An Available ICBC Timeslot!' log.info( ses.send_email(config.sender_email, user.email, subject, template, bcc_recipients=[config.sender_email]))
def test_handler_existing_appointment( appointment_reserve_stub, appointment_check_multiple_fail_stub, appointment_confirm_stub, appointments_search_stub, appointment_cancel_stub, configuration_stub, appointment_confirm_json, event): output = book.handler(event, None) assert output == d(statusCode=200, body=json.dumps(appointment_confirm_json))
def config(user_data, email, encrypt_key, api_gateway): with patch('pycbc.config.s3') as s3_mock: s3_mock.get_object.return_value = {'Body': StringIO('{}')} yield pycbc_config.load({ 'config': { 'users': [ d(user_data, filters=d(services=['learners'], days=['saturday', 'sunday'], branches=['Burnaby'])) ], 'encrypt_key': encrypt_key, 'api_gateway': api_gateway, 'sender_email': email, } })
def available_timeslots(input_data: Input): input_data = _set_defaults(input_data) pycbc = client.WebBookingClient() services = filter( lambda s: (not input_data.service_ids or s.qpId in input_data.service_ids), pycbc.services_search()) results = [] for service in services: log.info(f'Searching for branches for {service}') branches_for_service = pycbc.branches_service_search(service.publicId) output = d(service, branches={}) branches_to_select = set(input_data.branches) before_date = arrow.now().shift(months=input_data.month_offset) for branch in filter(lambda b: b.name in branches_to_select, branches_for_service): log.info(f'Searching for dates for branch {branch}') branch_dates = pycbc.branches_dates_search(service.publicId, branch.id) available_dates = (data.date for data in branch_dates if arrow.get(data.date) <= before_date) available_datetimes = d() for _, date in zip(range(input_data.max_times), available_dates): available_datetimes[date] = [ t.time for t in pycbc.branches_times_search( service.publicId, branch.id, date) ] if not available_datetimes: continue output.branches[branch['name']] = d( branch, available_dates=available_datetimes) if output.branches: results.append(output) return results
def load(event): event_override = event.get('config', d()) env_prefix = event_override.get( 'env_prefix', os.getenv('ENV_PREFIX', 'PYCBC_')) s3_bucket = event_override.get( 's3_bucket', os.getenv(f'{env_prefix}S3_BUCKET', 'pycbc')) s3_filename = event_override.get( 's3_filename', os.getenv(f'{env_prefix}S3_FILENAME', 'pycbc-config.yaml') ) return json.loads(json.dumps(reduce( _merge, [ deepcopy(_DEFAULTS), _from_s3(s3_bucket, s3_filename), _from_env(env_prefix), event_override, {'s3_bucket': s3_bucket, 's3_filename': s3_filename} ]) ))
def handler(event, context): config = load(event) logging.config.dictConfig(config.logging) try: query_params = event['queryStringParameters'] query_token = query_params.pop('token') payload = token.decrypt(config.encrypt_key, query_token, ttl=3600) except InvalidToken: return { 'statusCode': 400, 'body': 'token expired', } except Exception as exc: log.exception(exc) return { 'statusCode': 401, 'body': 'UNAUTHORIZED', } log.info(f'Payload: {payload}') branch = d(branch_id=query_params['branch_id'], date=query_params['date'], time=query_params['time']) try: reservation = reserve(payload.service, branch, payload.user) return { 'statusCode': 200, 'body': json.dumps(reservation), } except Exception as exc: log.exception(exc) return { 'statusCode': 500, 'body': 'failed to book appointment', }
def _set_defaults(input_data): return d(service_ids=input_data.get('service_ids', []), month_offset=input_data.get('month_offset', 1), max_times=input_data.get('max_times', 3), branches=input_data.get('branches', []), debug=input_data.get('debug', False))
def _from_env(prefix): env_vars = (k for k in os.environ if k.startswith(prefix)) return d({ k[len(prefix):].lower(): os.environ[k] for k in env_vars })
def _yaml_load(data): yaml.add_constructor( _mapping_tag, lambda loader, node: d(loader.construct_pairs(node)), ) return yaml.load(data, Loader=yaml.FullLoader)
s3 = boto3.client('s3') _mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG _DEFAULTS = d({ 'users': [], 'encrypt_key': Fernet.generate_key().decode('utf-8'), 'api_gateway': None, 'sender_email': None, 'logging': d({ 'version': 1, 'formatters': d({ 'default': d({ 'format': '%(asctime)-15s - %(levelname)-7s - %(message)s', }), }), 'handlers': d({ 'console': d({ 'class': 'logging.StreamHandler', 'formatter': 'default', 'level': 'DEBUG', 'stream': 'ext://sys.stderr', }), }), 'loggers': d({ 'pycbc': d({ 'handlers': ['console'], 'level': 'INFO', }) }) }) })
def test_loads(): assert json.loads('{"test": {"test": 1}}') == d(test=d(test=1))
def test_handler(appointment_reserve_stub, appointment_check_multiple_stub, appointment_confirm_stub, configuration_stub, event, appointment_confirm_json): output = book.handler(event, None) assert output == d(statusCode=200, body=json.dumps(appointment_confirm_json))