class Config(object): logger.debug("Start of the Config() class.") # Apply the environment variables when running locally # When running in GCP, these are loaded from the env_variables.yaml file when the app loads if local: from env_tools import apply_env apply_env() logger.info("Local .env variables applied.") DEBUG = True else: DEBUG = False # Load AWS credentials AWS_ACCOUNT_ID = environ.get('AWS_ACCOUNT_ID') AWS_ACCESS_KEY = environ.get('AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = environ.get('AWS_SECRET_ACCESS_KEY') AWS_USER = environ.get('AWS_USER') AWS_REGION = environ.get('AWS_REGION') # Load app-related credentials BOUND_PORT = 5000 DOMAIN_URL = environ.get('DOMAIN_URL') WHITELISTED_ORIGIN = environ.get('WHITELISTED_ORIGIN') WHITELISTED_ORIGINS = environ.get('WHITELISTED_ORIGINS') SECRET_KEY = environ.get( 'SECRET_KEY') or '0y4TJIyEjH8ZVkXPMGBiFEcHk8tdfe57kE1IJhvR1yb1cmWY' if SECRET_KEY != environ.get('SECRET_KEY'): logger.warning( "Error loading SECRET_KEY! Temporarily using a hard-coded key.") logger.debug("End of the Config() class.")
def test_logging(self): string_test = "Basic string" with LogCapture() as log: log.install() logger.debug("Debug-level test") logger.error("Error-level test") logger.warning("Warning-level test") logger.info("Info-level test") logger.info(f"f-string test for {string_test}") log.check_present(("backend.global_logger", "DEBUG", "Debug-level test")) log.check_present(("backend.global_logger", "ERROR", "Error-level test")) log.check_present(("backend.global_logger", "WARNING", "Warning-level test")) log.check_present(("backend.global_logger", "INFO", "Info-level test")) log.check_present(("backend.global_logger", "INFO", "f-string test for Basic string"))
def __init__(self, **kwargs): super().__init__(**kwargs) # Type check: last_modified # Accept `str` or an epoch (`float`, `int`) for last_modified # logger.debug(f"Type check for last_modified of picklist: {self.list_name}") # logger.debug(f"{type(self.last_modified)}, {self.last_modified}") # if !isinstance(self.last_modified, datetime): if isinstance(self.last_modified, (float, int)): self.last_modified = datetime.utcfromtimestamp( kwargs['last_modified'] / 1000) else: # Assume a string was provided and parse a datetime object from that try: self.last_modified = datetime.fromisoformat( str(self.last_modified)) except (TypeError, ValueError) as e: logger.debug( f"Provided value for last_modified: {self.last_modified}, " f"{type(self.last_modified)} cannot be converted to datetime." ) raise ValueError( f"Value for last_modified must be an epoch (float/int) or " f"an iso-formatted string. {e}")
def get(self) -> json: """Return the list of picklist values for all fields.""" logger.debug(f"Request: {request}") try: # Read from the database logger.debug("Retrieving all picklist values...") all_picklists = Picklist.scan() # Convert each record to a dictionary, compile into a list output = [] for picklist in all_picklists: output.append(picklist.to_dict()) # picklist.save() # output = picklist.to_dict() # pass logger.debug(f"End of PicklistApi.GET") return {'message': 'Success', 'data': output}, 200 except PynamoDBException as e: error_msg = f"Error attempting to retrieve picklists from the database." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500
def get(self, beverage_id, location) -> json: """Return the specified beverage.""" logger.debug( f"Request: {request}, for id: {beverage_id}, loc: {location}.") # Retrieve specified beverage from the database try: beverage = Beverage.get(beverage_id, location) logger.debug(f"Retrieved beverage: {beverage}") return { 'message': 'Success', 'data': beverage.to_dict(dates_as_epoch=True) }, 200 except Beverage.DoesNotExist: logger.debug(f"Beverage {beverage_id} not found.") return { 'message': 'Not Found', 'data': f'Beverage {beverage_id} not found.' }, 404 except PynamoDBException as e: error_msg = f"Error attempting to retrieve beverage {beverage_id}." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500
def get(self) -> json: """Return all beverages in the database.""" logger.debug(f"Request: {request}") try: # Read from the database beverages = Beverage.scan() # Convert each record to a dictionary, compile into a list output = [] count = 0 for bev in beverages: output.append(bev.to_dict(dates_as_epoch=True)) count += 1 logger.debug(f"End of CellarCollectionApi.GET") return {'message': 'Success', 'data': output}, 200 except PynamoDBException as e: error_msg = f"Error attempting to retrieve beverages from the database." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500
skipped = 0 count = 0 success = 0 errors = [] beerlist = [] # Create table if necessary if not Beverage.exists(): logger.info(f"Creating a new table: {Beverage.Meta.table_name}.") Beverage.create_table(wait=True, read_capacity_units=5, write_capacity_units=5) logger.info(f"Table created.") for item in example_data: logger.debug(f"Initializing a new Beverage for: {item}") beer = Beverage(**item) beerlist.append(beer) # logger.debug("Beverage initialized.") logger.info(f"Starting batch save for {len(beerlist)} beers.") print(f"Starting batch save for {len(beerlist)} beers.") for beer in beerlist: logger.debug(f"Saving beer #{count+1}: {beer}.") print(f"Saving beer #{count+1}: {beer.to_dict()}") beer.save() logger.debug("Success!") count += 1 if not local and count % 20 == 0:
def put(self) -> json: """Add/update the list of picklist values.""" logger.debug(f"Request: {request}") # Ensure there's a body to accompany this request if not request.data: return {'message': 'Error', 'data': 'PUT request must contain a body.'}, 400 # Load & decode the provided JSON try: data = json.loads(request.data.decode()) logger.debug(f"Data submitted: {data}") except json.JSONDecodeError as e: error_msg = f"Error attempting to decode the provided JSON." logger.debug(f"{error_msg},\n{request.data.__str__()},\n{e}") return {'message': 'Error', 'data': error_msg + f"\n{request.data.__str__()}"}, 400 except BaseException as e: error_msg = f"Unknown error attempting to parse the provided JSON.\n{e}" logger.debug(error_msg) return {'message': 'Error', 'data': error_msg}, 400 # Create a Picklist instance from the provided data try: picklist = Picklist(**data) picklist.last_modified = datetime.utcnow() logger.debug(f"Picklist instance created: {picklist}") except PynamoDBException as e: error_msg = f"Error parsing data into the Picklist model." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': f'{error_msg}\n{e}'}, 500 # Save to the database try: logger.debug(f"Saving {picklist} to the db...") picklist.save() logger.info(f"Picklist updated: {picklist.list_name})") logger.debug(f"End of PicklistApi.PUT") return {'message': 'Success', 'data': picklist.to_dict()}, 200 except Picklist.DoesNotExist: logger.debug(f"Picklist {picklist} not found.") return {'message': 'Not Found', 'data': f'Picklist {picklist} not found.'}, 404 except PynamoDBException as e: error_msg = f"Error attempting to save picklist {picklist}." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500
def __init__(self, **kwargs): super().__init__(**kwargs) # logger.debug(f"Initializing a new instance of the Beverage model for {kwargs}.") # Replace empty strings with None # Construct the concatenated beverage_id when not provided: # producer, beverage name, year, size, {bottle date or batch}. Bottle date preferred. if 'beverage_id' not in kwargs.keys(): # Need to create a beverage_id for this beverage self.beverage_id = f"{kwargs['producer']}_{kwargs['name']}_{kwargs['year']}_{kwargs['size']}" if 'batch' in kwargs.keys() and 'bottle_date' in kwargs.keys(): # If both bottle_date and batch are provided, prefer bottle_date self.beverage_id += f"_{kwargs['bottle_date']}" elif 'batch' not in kwargs.keys() or kwargs['batch'] == '': # Batch is not provided self.batch = None if 'bottle_date' not in kwargs.keys( ) or kwargs['bottle_date'] == '': # When no batch or bottle_date is provided, append "_None" self.beverage_id += "_None" self.bottle_date = None else: # Bottle_date is provided self.beverage_id += f"_{kwargs['bottle_date']}" else: # Use batch when bottle_date isn't provided self.beverage_id += f"_{kwargs['batch']}" logger.debug( f"Created a beverage_id for this new Beverage: {self.beverage_id}." ) # Must provide a location if 'location' not in kwargs.keys() or kwargs['location'] is None: logger.debug(f"No value for location provided, raising KeyError.") raise KeyError("Location is required.") # Type check: Year try: self.year = int(kwargs['year']) except ValueError as e: logger.debug(f"Year must be an integer.\n{e}") raise ValueError(f"Year must be an integer.\n{e}") # Type check: Batch if 'batch' in kwargs.keys(): try: if self.batch and self.batch != "": self.batch = int(kwargs['batch']) if self.batch == "": self.batch = None except ValueError as e: logger.debug(f"Batch number must be an integer.\n{e}") raise ValueError(f"Batch number must be an integer.\n{e}") # Type check: qty if 'qty' in kwargs.keys(): try: self.qty = int(kwargs['qty']) except ValueError as e: logger.debug(f"Qty must be an integer.\n{e}") raise ValueError(f"Qty must be an integer.\n{e}") # Type check: qty_cold if 'qty_cold' in kwargs.keys(): try: self.qty_cold = int(kwargs['qty_cold']) except ValueError as e: logger.debug(f"Qty_cold must be an integer.\n{e}") raise ValueError(f"Qty_cold must be an integer.\n{e}") # Adjust last_modified due to JS working in milliseconds if 'last_modified' in kwargs.keys(): # Accept an epoch (`float` or `int`) for date_added if isinstance(self.last_modified, (float, int)): self.last_modified = datetime.utcfromtimestamp( kwargs['last_modified'] / 1000) else: # Assume a string was provided and parse a datetime object from that try: self.last_modified = datetime.fromisoformat( str(self.last_modified)) except (TypeError, ValueError) as e: logger.debug( f"Provided value for last_modified: {self.last_modified}, " f"{type(self.last_modified)} cannot be converted to datetime." ) raise ValueError( f"Value for last_modified must be an epoch (float/int) or " f"an ISO-formatted string. {e}") else: self.last_modified = datetime.utcnow() # Type & value checks for date_added if 'date_added' in kwargs.keys(): # Accept an epoch (`float` or `int`) for date_added if isinstance(self.date_added, (float, int)): self.date_added = datetime.utcfromtimestamp( kwargs['date_added'] / 1000) else: # Assume a string was provided and parse a datetime object from that try: self.date_added = datetime.fromisoformat( str(self.date_added)) except (TypeError, ValueError) as e: logger.debug( f"Provided value for date_added: {self.date_added}, " f"{type(self.date_added)} cannot be converted to datetime." ) raise ValueError( f"Value for date_added must be an epoch (float/int) or " f"an iso-formatted string. {e}") # Ensure date_added is always <= last_modified if self.date_added > self.last_modified: self.last_modified = self.date_added
def post(self) -> json: """Add a new beverage to the database based on the provided JSON.""" logger.debug(f"Request: {request}") # Ensure there's a body to accompany this request if not request.data: return { 'message': 'Error', 'data': 'POST request must contain a body.' }, 400 # Load the provided JSON try: data = json.loads(request.data.decode()) logger.debug(f"Data submitted: {type(data)}, {data}") # Replace empty strings with None for key in data.keys(): if data[key] == "": data[key] = None logger.debug(f"Data post-cleaning: {type(data)}, {data}") except json.JSONDecodeError as e: error_msg = f"Error attempting to decode the provided JSON." logger.debug(f"{error_msg},\n{request.data.__str__()},\n{e}") return { 'message': 'Error', 'data': error_msg + f"\n{request.data.__str__()}" }, 400 except BaseException as e: error_msg = f"Unknown error attempting to parse the provided JSON.\n{e}" logger.debug(error_msg) return {'message': 'Error', 'data': error_msg}, 400 # Create a new Beverage from the provided data try: new_beverage = Beverage(**data) logger.debug(f"New Beverage created: {new_beverage}") except PynamoDBException as e: error_msg = f"Error attempting to create new Beverage from: {data}." logger.debug(f"{error_msg}\nError: {e}.") return {'message': 'Error', 'data': error_msg}, 500 except BaseException as e: error_msg = f"Unknown error creating a new Beverage from: {data}." logger.debug(f"{error_msg}\n{e}.") return {'message': 'Error', 'data': error_msg}, 500 # Write this Beverage to the database try: logger.debug( f"Attempting to save Beverage {new_beverage} to the database.") new_beverage.save() logger.info(f"Successfully saved {new_beverage}.") logger.debug(f"End of CellarCollectionApi.POST") return { 'message': 'Created', 'data': new_beverage.to_dict(dates_as_epoch=True) }, 201 except PynamoDBException as e: error_msg = f"Error attempting to save new beverage." logger.debug(f"{error_msg}\n{new_beverage}: {e}.") return {'message': 'Error', 'data': error_msg}, 500
def delete(self, beverage_id, location) -> json: """Delete the specified beverage.""" logger.debug( f"Request: {request}, for id: {beverage_id}, loc: {location}.") # Retrieve this beverage from the database try: beverage = Beverage.get(beverage_id, location) logger.debug(f"Retrieved beverage: {beverage}") except Beverage.DoesNotExist: logger.debug(f"Beverage {beverage_id} not found.") return { 'message': 'Not Found', 'data': f'Beverage {beverage_id} not found.' }, 404 except PynamoDBException as e: error_msg = f"Error attempting to retrieve beverage {beverage_id}." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500 # Delete the specified beverage try: footprint = f"{beverage}" beverage.delete() logger.info(f"Beverage {footprint} deleted successfully.") logger.debug("End of BeverageApi.DELETE") return { 'message': 'Success', 'data': f'{footprint} deleted successfully.' }, 200 except PynamoDBException as e: error_msg = f"Error attempting to delete beverage: {beverage}." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500
def put(self, beverage_id, location) -> json: """Update the specified beverage.""" logger.debug( f"Request: {request}, for id: {beverage_id}, loc: {location}.") # Ensure there's a body to accompany this request if not request.data: return { 'message': 'Error', 'data': 'PUT request must contain a body.' }, 400 # Load & decode the provided JSON try: data = json.loads(request.data.decode()) logger.debug(f"Data submitted: {data}") except json.JSONDecodeError as e: error_msg = f"Error attempting to decode the provided JSON." logger.debug(f"{error_msg},\n{request.data.__str__()},\n{e}") return { 'message': 'Error', 'data': error_msg + f"\n{request.data.__str__()}" }, 400 except BaseException as e: error_msg = f"Unknown error attempting to parse the provided JSON.\n{e}" logger.debug(error_msg) return {'message': 'Error', 'data': error_msg}, 400 # Ensure the variables provided to the endpoint match the body details. if str(beverage_id) != str(data['beverage_id']): error_msg = f"/beverage_id provided to the endpoint ({beverage_id}) " \ f"doesn't match the beverage_id from the body ({data['beverage_id']})." logger.debug(f"{error_msg}") return {'message': 'Error', 'data': error_msg}, 400 elif str(location) != str(data['location']): error_msg = f"/location provided to the endpoint ({location}) " \ f"doesn't match the location from the body ({data['location']})." logger.debug(f"{error_msg}") return {'message': 'Error', 'data': error_msg}, 400 # Create a Beverage instance from the provided data try: beverage = Beverage(**data) beverage.last_modified = datetime.utcnow() logger.debug(f"Beverage instance created: {beverage}") except PynamoDBException as e: error_msg = f"Error parsing data into the Beverage model." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': f'{error_msg}\n{e}'}, 500 # Save to the database try: logger.debug(f"Saving {beverage} to the db...") beverage.save() logger.info(f"Beverage updated: {beverage})") logger.debug(f"End of BeverageApi.PUT") return { 'message': 'Success', 'data': beverage.to_dict(dates_as_epoch=True) }, 200 except Beverage.DoesNotExist: logger.debug(f"Beverage {beverage_id} not found.") return { 'message': 'Not Found', 'data': f'Beverage {beverage_id} not found.' }, 404 except PynamoDBException as e: error_msg = f"Error attempting to save beverage {beverage_id}." logger.debug(f"{error_msg}\n{e}") return {'message': 'Error', 'data': error_msg}, 500