class AbstractModelBase(db.Model): """Abstract base class for SQLAlchemy models. Defines basic fields and generic create, update, delete, and aggregate table operations for application models. Defines the following fields: id (int): Auto-incrementing primary key id. date_created (datetime.datetime): Timestamp of model creation. date_modified (datetime.datetime): Timestamp of last model update. """ __abstract__ = True id = db.Column(db.Integer, primary_key=True, autoincrement=True) date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) def create(self): """Add this model instance to the database.""" db.session.add(self) db.session.commit() def update_instance(self, new_fields: dict): """Update this model instance in the database. Args: new_fields (dict): Dict containing new values for this `User`. """ db.session.query( self.__class__).filter_by(id=self.id).update(new_fields) db.session.commit() def delete_instance(self): """Delete this model instance from the database.""" db.session.query(self.__class__).filter_by(id=self.id).delete() db.session.commit() @classmethod def get_all(cls) -> List[T]: """Iterate through all model instances in the database. NOTE: This method can be very memory-intensive and should not be used in production. Yields: Next instance of this model in the database. """ for model in db.session.query(cls): yield model @classmethod def delete_all(cls): """Delete all instances of this model in the database. NOTE: This method is incredibly desctructive and should not be used in production. """ db.session.query(cls).delete() db.session.commit()
class TimeRange(AbstractModelBase): """Data access object providing a static interface to a time range table.""" __tablename__ = models.tables.TIME_RANGE description = db.Column(db.String(255)) start_time = db.Column(db.Integer) end_time = db.Column(db.Integer) @staticmethod def find_by_id(time_range_id: int) -> TimeRangeType: """Look up a `TimeRange` by id. Args: time_range_id (int): id to match. Returns: TimeRange with the given id if found, None if not found. """ return db.session.query(TimeRange).get(time_range_id) @staticmethod def find_by_start_time(start_time: int) -> TimeRangeType: """Look up a `TimeRange` by start time. Args: start_time (int): start time to match. Returns: TimeRange with the given start time if found, None if not found. """ return db.session.query(TimeRange).filter( TimeRange.start_time == start_time).first() @staticmethod def find_by_end_time(end_time: int) -> TimeRangeType: """Look up a `Time Range` by end time. Args: end_time (int): end time to match. Returns: TimeRange with the given end time if found. """ return db.session.query(TimeRange).filter( TimeRange.end_time == end_time).first() def __repr__(self) -> str: """Return a string representation of this `TimeRange`.""" return f'TODO'
class Location(AbstractModelBase): """Data access object providing a static interface to a location table.""" __tablename__ = models.tables.LOCATION name = db.Column(db.String(128)) db.UniqueConstraint('name') @staticmethod def find_by_id(location_id: int) -> LocationType: """Look up a `Location` by id. Args: location_id (int): id to match. Returns: `Location` with the given id if found, None if not found. """ return db.session.query(Location).get(location_id) @staticmethod def find_by_name(name: str) -> LocationType: """Look up a `Location` by name. Args: name (str): name to match. Returns: `Location` with the given name if found, None if not found. """ return db.session.query(Location).filter(Location.name == name).first() def __repr__(self) -> str: """Return a string representation of this `Location`.""" return f"Location({self.id}, '{self.name}')" def __str__(self) -> str: """Return this `Location` as a friendly string.""" return f"{self.id}. {self.name}"
class Status(AbstractModelBase): """Data access object providing a static interface to a status table.""" __tablename__ = models.tables.STATUS description = db.Column(db.String(64)) @staticmethod def find_by_id(status_id: int) -> StatusType: """Look up a `Status` by id. Args: status_id (int): id to match. Returns: `Status` with the given id if found, None if not found. """ return db.session.query(Status).get(status_id) @staticmethod def find_by_description(description: str) -> StatusType: """Look up a `Status` by description. Args: description: (str): description to match. Returns: `Status` with the given description if found, None if not found. """ return db.session.query(Status).filter(Status.description == description).first() def __repr__(self) -> str: """Return a string representation of this `Status`.""" return f"Status({self.id}, '{self.description}')" def __str__(self) -> str: """Return this `Status` as a friendly string.""" return f"{self.id}. {self.description}"
class User(AbstractModelBase): """Data access object providing a static interface to a user table.""" __tablename__ = models.tables.USER first_name = db.Column(db.String(_MAX_LENGTH)) last_name = db.Column(db.String(_MAX_LENGTH)) email = db.Column(db.String(_MAX_LENGTH)) password = db.Column(db.String(_MAX_LENGTH)) @db.validates('first_name') def validate_first_name(self, key: str, first_name: str): """Check that a first name is valid. Args: key (str): Dict key corresponding to the field being validated. first_name (str): Value provided to field. Raises: 'InvalidFirstNameError': If the length of given first name is longer than 64 or contains an invalid character. """ if len(first_name) > _MAX_LENGTH: raise InvalidFirstNameError(first_name) if re.compile(_INVALID_CHARS).search(first_name): raise InvalidFirstNameError(first_name) return first_name @db.validates('last_name') def validate_last_name(self, key: str, last_name: str): """Check that a first name is valid. Args: key (str): Dict key corresponding to the field being validated. last_name (str): Value provided to field. Raises: 'InvalidLastNameError': If the length of given first name is longer than 64 or contains an invalid character. """ if len(last_name) > _MAX_LENGTH: raise InvalidLastNameError(last_name) if re.compile(_INVALID_CHARS).search(last_name): raise InvalidLastNameError(last_name) return last_name @db.validates('email') def validate_email(self, key: str, email: str): """Check that an email is unique seems valid. Args: key (str): Dict key corresponding to the field being validated. email (str): Value provided to field. Return: str: Email if valid. Raises: `InvalidEmailError`: If the given email does not have exactly one '@' and a '.' after the '@'. `DuplicateEmailError`: If a user with the given email already exists. """ # REGEX notes: # # ^@ = any char except @ # \ = inhibit the specialness of character (aka escape character) # https://developers.google.com/edu/python/regular-expressions if not re.compile(r'[^@]+@[^@]+\.[^@]+').match(email): raise InvalidEmailError(email) if self.find_by_email(email): raise DuplicateEmailError(email) return email @staticmethod def find_by_id(user_id: int) -> UserType: """Look up a `User` by id. Args: id (int): id to match. Returns: User with the given id if found, None if not found. See: https://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.get """ return db.session.query(User).get(user_id) @staticmethod def find_by_email(email: str) -> UserType: """Look up a `User` by email. Args: email (str): email to match. Returns: `User` with the given email if found, None if not found. """ return db.session.query(User).filter(User.email == email).first() def __repr__(self) -> str: """Return a string representation of this `User`.""" return f'TODO'
class Passenger(AbstractModelBase): """Data access object providing a static interface to a Passenger table.""" __tablename__ = models.tables.PASSENGER # Column Attributes user_id = db.Column( db.Integer, db.ForeignKey(models.tables.USER + '.id', ondelete='CASCADE')) ride_id = db.Column( db.Integer, db.ForeignKey(models.tables.RIDE + '.id', ondelete='CASCADE')) status_id = db.Column(db.Integer, db.ForeignKey(models.tables.STATUS + '.id')) # Relationship Attributes db.UniqueConstraint('user_id', 'ride_id', 'status_id') db.relationship('User', uselist=False, backref=db.backref('passenger', passive_deletes=True), lazy='dynamic', passive_deletes=True) db.relationship('Ride', uselist=False, backref=db.backref('passenger', passive_deletes=True), lazy='dynamic', passive_deletes=True) db.relationship('Status', uselist=False, lazy='dynamic') def update(self, new_status: int): """Update a passenger status by creating a new row. Args: new_status (int): id of new status. """ # This should create a new row. pass @staticmethod def find_by_id(id: int) -> PassengerType: # pylint: disable=C0103 """Look up a `Passenger` by id. Args: id (int): id to match. Returns: `Passenger`s associated with the given id if found. """ return db.session.query(Passenger).filter(Passenger.id == id).first() @staticmethod def find_by_ride_id(ride_id: int) -> List[PassengerType]: """Look up a `Passenger` by ride_id. Args: ride_id (int): id to match. Returns: `Passenger`s associated with the given ride_id if found. """ return db.session.query(Passenger).filter(Passenger.ride_id == ride_id) @staticmethod def find_by_user_id(user_id: int) -> List[PassengerType]: """Look up a `Passenger` by user_id. Args: ride_id (int): id to match. Returns: `Passenger`s associated with the given user_id if found. """ return db.session.query(Passenger).filter(Passenger.user_id == user_id) @staticmethod def find_by_status_id(status_id: int) -> List[PassengerType]: """Look up a `Passenger` by status_id. Args: status_id (int): id to match. Returns: `Passenger`s associated with the given status_id if found. """ return db.session.query(Passenger).filter( Passenger.status_id == status_id)
class Ride(AbstractModelBase): """Data access object providing a static interface to a Ride table.""" __tablename__ = models.tables.RIDE # Column Attributes #actual_departure_time and departure_date were originally db.DateTime #but changed to db.String for the purpose of just getting this f*****g thing to work # actual_departure_time = db.Column(db.DateTime) departure_date = db.Column(db.DateTime) capacity = db.Column(db.Integer) time_range_id = db.Column(db.Integer, db.ForeignKey(models.tables.TIME_RANGE + '.id'), nullable=False) driver_id = db.Column(db.Integer, db.ForeignKey(models.tables.USER + '.id'), nullable=False) start_location_id = db.Column(db.Integer, db.ForeignKey(models.tables.LOCATION + '.id'), nullable=False) destination_id = db.Column(db.Integer, db.ForeignKey(models.tables.LOCATION + '.id'), nullable=False) # Relationship Attributes time_range = db.relationship(TimeRange) driver = db.relationship(User, backref='drives', lazy=True) passengers = db.relationship(Passenger, cascade="all, delete-orphan") start_location = db.relationship(Location, foreign_keys=[start_location_id]) destination = db.relationship(Location, foreign_keys=[destination_id]) @db.validates('capacity') def validate_capacity(self, key: str, capacity: str): """Check that capacity is valid. Args: capacity (str): Value provided to field. Return: int: capacity if valid. Raises: `InvalidCapacityError`: If the given capacity is not an int or greater than max. """ if int(capacity) > _MAX_CAPACITY: raise InvalidCapacityError(capacity) if not str(capacity).isdigit(): raise InvalidCapacityError(capacity) return int(capacity) # @db.validates('time_range') # TODO # @db.validates('driver') # TODO # I actually don't know if this is supposed to be validating 'start_location' or 'start_location_id'. @db.validates('start_location') def validate_start_location(self, key: str, start_location: str): """Check that a start location is valid. Args: start_location (str): Value provided to field. Return: int: start location id if valid. """ # TODO return start_location # See start_location # @db.validates('destination') # TODO @staticmethod def find_by_id(ride_id: int) -> RideType: """Look up a `Ride` by id. Args: id (int): id to match. Returns: Ride with the given id if found. """ return db.session.query(Ride).filter(Ride.id == ride_id).first() @staticmethod def find_by_departure_date(departure_date: datetime) -> List[RideType]: """Look up a `Ride` by departure date. Args: departure_date (datetime): id to match. Returns: `Ride`s associated with the given departure date if found. """ return db.session.query(Ride).filter( Ride.departure_date == departure_date) @staticmethod def find_by_actual_departure_time( actual_departure_time: datetime) -> List[RideType]: """Look up a `Ride` by actual departure time. Args: actual_departure_time (datetime): id to match. Returns: `Ride`s associated with the given actual departure time if found. """ return db.session.query(Ride).filter( Ride.actual_departure_time == actual_departure_time) @staticmethod def find_by_time_range_id(time_range_id: int) -> List['Ride']: """Look up a `Ride` by time_range_id. Args: time_range_id (int): id to match. Returns: `Ride`s associated with the given time_range_id if found. """ return db.session.query(Ride).filter( Ride.time_range_id == time_range_id) @staticmethod def find_by_driver_id(driver_id: int) -> List['Ride']: """Look up a `Ride` by driver_id. Args: driver_id (int): id to match. Returns: `Ride`s associated with the given driver_id if found. """ return db.session.query(Ride).filter(Ride.driver_id == driver_id) @staticmethod def find_by_start_location_id(start_location_id: int) -> List[RideType]: """Look up a `Ride` by start_location_id. Args: start_location_id (int): id to match. Returns: `Ride`s associated with the given start_location_ idif found. """ return db.session.query(Ride).filter( Ride.start_location_id == start_location_id) @staticmethod def find_by_destination_id(destination_id: int) -> List[RideType]: """Look up a `Ride` by destination_id. Args: destination (int): id to match. Returns: `Ride`s associated with the given destination_id if found. """ return db.session.query(Ride).filter( Ride.destination_id == destination_id) def __repr__(self) -> str: """Return a string representation of this `Ride`.""" return f'TODO'