diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5939ad6..93a41ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,22 @@ repos: -- repo: https://github.com/ambv/black - rev: stable + # Using this mirror lets us use mypyc-compiled black, which is about 2x faster + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 24.8.0 hooks: - - id: black - language_version: python3.7 -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + - id: black + # It is recommended to specify the latest version of Python + # supported by your project here, or alternatively use + # pre-commit's default_language_version, see + # https://pre-commit.com/#top_level-default_language_version + language_version: python3.9 + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.6.3 hooks: - - id: flake8 + # Run the linter. + - id: ruff + args: [--fix] + - repo: https://github.com/PyCQA/bandit + rev: 1.7.0 + hooks: + - id: bandit diff --git a/app.py b/app.py index f3b9374..6fb7b04 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,6 @@ import logging import sentry_sdk -from flask import Flask, render_template, redirect, url_for +from flask import Flask, render_template from flask_apscheduler import APScheduler from flask_graphql import GraphQLView from graphene import Schema @@ -16,6 +16,8 @@ from src.scrapers.activities_scraper import fetch_activity from src.utils.utils import create_gym_table from src.models.openhours import OpenHours +from flasgger import Swagger + sentry_sdk.init( dsn="https://2a96f65cca45d8a7c3ffc3b878d4346b@o4507365244010496.ingest.us.sentry.io/4507850536386560", @@ -31,6 +33,7 @@ app = Flask(__name__) app.debug = True schema = Schema(query=Query, mutation=Mutation) +swagger = Swagger(app) # Scheduler scheduler = APScheduler() @@ -81,15 +84,8 @@ def scrape_capacities(): def scrape_classes(): logging.info("Scraping classes from group-fitness-classes...") - fetch_classes(3) - - -# Scrape classes every hour -@scheduler.task("interval", id="scrape_classes", seconds=3600) -def scrape_classes(): - logging.info("Scraping classes from group-fitness-classes...") - fetch_classes(3) + fetch_classes(10) # Create database and fill it with data @@ -108,4 +104,4 @@ def scrape_classes(): schema_file.close() if __name__ == "__main__": - app.run(host="0.0.0.0", port=5000) + app.run(host="127.0.0.1", port=5000) diff --git a/manager.py b/manager.py index 4c58245..c38d51d 100644 --- a/manager.py +++ b/manager.py @@ -1,10 +1,9 @@ -import os from flask_script import Manager -from flask_migrate import Migrate, MigrateCommand -from app import app#, db +from flask_migrate import MigrateCommand +from app import app # , db # Build manager -#migrate = Migrate(app, db) +# migrate = Migrate(app, db) manager = Manager(app) manager.add_command("db", MigrateCommand) diff --git a/requirements.txt b/requirements.txt index a389a06..87f0460 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,8 @@ Flask-GraphQL==2.0.0 Flask-Migrate==2.4.0 Flask-Script==2.0.5 Flask-SQLAlchemy==2.3.1 +Flask-RESTful==0.3.10 +flasgger==0.9.7.1 google-auth==1.12.0 graphene==2.1.3 graphene-sqlalchemy==2.3.0 @@ -38,7 +40,7 @@ itsdangerous==2.0 jedi==0.18.2 Jinja2==3.0 lxml==4.9.2 -Mako==1.0.9 +Mako==1.1.0 MarkupSafe==2.1.1 marshmallow==3.0.0rc4 marshmallow-sqlalchemy==0.16.2 @@ -77,4 +79,4 @@ wasmer-compiler-cranelift==1.1.0 wcwidth==0.2.6 Werkzeug==2.2.2 zipp==3.15.0 -sentry-sdk==2.13.0 +sentry-sdk==2.13.0 \ No newline at end of file diff --git a/schema.graphql b/schema.graphql index 07c4a26..e44663b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -69,14 +69,24 @@ type CreateGiveaway { giveaway: Giveaway } +type CreateReport { + report: Report +} + type CreateUser { user: User } scalar DateTime -type EnterGiveaway { - giveawayInstance: GiveawayInstance +enum DayOfWeekEnum { + MONDAY + TUESDAY + WEDNESDAY + THURSDAY + FRIDAY + SATURDAY + SUNDAY } type Equipment { @@ -141,12 +151,15 @@ type Gym { facilities: [Facility] hours: [OpenHours] classes: [ClassInstance] + reports: [Report] } type Mutation { - createGiveaway(name: String!): CreateGiveaway - createUser(instagram: String, netId: String!): CreateUser - enterGiveaway(giveawayId: Int!, userNetId: String!): EnterGiveaway + createGiveaway(name: String!): Giveaway + createUser(email: String!, name: String!, netId: String!): User + enterGiveaway(giveawayId: Int!, userNetId: String!): GiveawayInstance + setWorkoutGoals(userId: Int!, workoutGoal: [String]!): User + logWorkout(userId: Int!, workoutTime: DateTime!): Workout } type OpenHours { @@ -178,12 +191,42 @@ enum PriceType { type Query { getAllGyms: [Gym] getUsersByGiveawayId(id: Int): [User] + getWeeklyWorkoutDays(id: Int): [String] + getWorkoutsById(id: Int): [Workout] activities: [Activity] } +type Report { + id: ID! + createdAt: DateTime! + description: String! + gymId: Int! + issue: ReportType! + userId: Int! + gym: Gym + user: User +} + +enum ReportType { + INACCURATE_EQUIPMENT + INCORRECT_HOURS + INACCURATE_DESCRIPTION + WAIT_TIMES_NOT_UPDATED + OTHER +} + type User { id: ID! - instagram: String + email: String! netId: String! + name: String! + workoutGoal: [DayOfWeekEnum] giveaways: [Giveaway] + reports: [Report] +} + +type Workout { + id: ID! + workoutTime: DateTime! + userId: Int! } diff --git a/src/database.py b/src/database.py index 25cb416..7b89f42 100644 --- a/src/database.py +++ b/src/database.py @@ -1,4 +1,5 @@ -import logging, os +import logging +import os from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker diff --git a/src/models/classes.py b/src/models/classes.py index 4a563b1..e6502ee 100644 --- a/src/models/classes.py +++ b/src/models/classes.py @@ -1,16 +1,5 @@ -import datetime from src.database import Base -from sqlalchemy import ( - Table, - Column, - DateTime, - ForeignKey, - Integer, - Float, - String, - Boolean, - func, -) +from sqlalchemy import Table, Column, DateTime, ForeignKey, Integer, String, Boolean from sqlalchemy.orm import backref, relationship classes_to_gyms = Table( @@ -31,8 +20,8 @@ class Class(Base): __tablename__ = "class" id = Column(Integer, primary_key=True) - name = Column(String(), nullable=False) - description = Column(String(), nullable=False) + name = Column(String, nullable=False) + description = Column(String, nullable=False) class_instances = relationship("ClassInstance", back_populates="class_") def __init__(self, **kwargs): @@ -46,7 +35,7 @@ def serialize(self): "name": self.name, "description": self.description, } - + class ClassInstance(Base): __tablename__ = "class_instance" @@ -86,5 +75,3 @@ def serialize(self): "start_time": self.start_time, "end_time": self.end_time, } - - diff --git a/src/models/equipment.py b/src/models/equipment.py index e56712d..2c82b13 100644 --- a/src/models/equipment.py +++ b/src/models/equipment.py @@ -1,40 +1,39 @@ import enum from sqlalchemy import Column, String, Enum, Integer, ForeignKey -from sqlalchemy.orm import relationship from src.database import Base + class EquipmentType(enum.Enum): - cardio = 0 - racks_and_benches = 1 - selectorized = 2 - multi_cable = 3 - free_weights = 4 - miscellaneous = 5 - plate_loaded = 6 + cardio = 0 + racks_and_benches = 1 + selectorized = 2 + multi_cable = 3 + free_weights = 4 + miscellaneous = 5 + plate_loaded = 6 class AccessibilityType(enum.Enum): - - wheelchair = 0 - -class Equipment(Base): - __tablename__ = "equipment" + wheelchair = 0 - id = Column(Integer, primary_key=True) - name = Column(String, nullable=False) - equipment_type = Column(Enum(EquipmentType), nullable=False) - facility_id = Column(Integer, ForeignKey("facility.id"), nullable=False) - quantity = Column(Integer, nullable=True) - accessibility = Column(Enum(AccessibilityType), nullable=True) - - def __init__(self, **kwargs): - self.id = kwargs.get("id") - self.name = kwargs.get("name") - self.equipment_type = kwargs.get("equipment_type") - self.facility_id = kwargs.get("facility_id") - self.quantity = kwargs.get("quantity") - self.accessibility = kwargs.get("accessibility") +class Equipment(Base): + __tablename__ = "equipment" + + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) + equipment_type = Column(Enum(EquipmentType), nullable=False) + facility_id = Column(Integer, ForeignKey("facility.id"), nullable=False) + quantity = Column(Integer, nullable=True) + accessibility = Column(Enum(AccessibilityType), nullable=True) + + def __init__(self, **kwargs): + self.id = kwargs.get("id") + self.name = kwargs.get("name") + self.equipment_type = kwargs.get("equipment_type") + self.facility_id = kwargs.get("facility_id") + self.quantity = kwargs.get("quantity") + self.accessibility = kwargs.get("accessibility") diff --git a/src/models/facility.py b/src/models/facility.py index a112d61..deff02e 100644 --- a/src/models/facility.py +++ b/src/models/facility.py @@ -2,9 +2,6 @@ from sqlalchemy import Column, String, Enum, Integer, ForeignKey from sqlalchemy.orm import relationship from src.database import Base -from src.models.openhours import OpenHours -from src.models.equipment import Equipment -from src.models.capacity import Capacity class FacilityType(enum.Enum): diff --git a/src/models/giveaway.py b/src/models/giveaway.py index ae5ef23..973f63e 100644 --- a/src/models/giveaway.py +++ b/src/models/giveaway.py @@ -1,7 +1,6 @@ -from sqlalchemy import Column, Integer, Float, ForeignKey, String +from sqlalchemy import Column, Integer, ForeignKey, String from sqlalchemy.orm import relationship from src.database import Base -from src.models.user import User class Giveaway(Base): diff --git a/src/models/gym.py b/src/models/gym.py index 4ff56e8..a83aa41 100644 --- a/src/models/gym.py +++ b/src/models/gym.py @@ -1,8 +1,6 @@ from sqlalchemy import Column, Float, String, Integer from sqlalchemy.orm import relationship from src.database import Base -from src.models.openhours import OpenHours -from src.models.classes import ClassInstance, Class class Gym(Base): @@ -31,6 +29,7 @@ class Gym(Base): facilities = relationship("Facility") hours = relationship("OpenHours") classes = relationship("ClassInstance", back_populates="gym") + reports = relationship("Report", back_populates="gym") image_url = Column(String, nullable=True) latitude = Column(Float, nullable=False) longitude = Column(Float, nullable=False) diff --git a/src/models/openhours.py b/src/models/openhours.py index aac97e7..79022d5 100644 --- a/src/models/openhours.py +++ b/src/models/openhours.py @@ -1,5 +1,5 @@ import enum -from sqlalchemy import Boolean, Column, Enum, Integer, Float, ForeignKey +from sqlalchemy import Boolean, Column, Enum, Integer, ForeignKey from src.database import Base diff --git a/src/models/report.py b/src/models/report.py new file mode 100644 index 0000000..f7a7dfa --- /dev/null +++ b/src/models/report.py @@ -0,0 +1,36 @@ +from sqlalchemy import Column, Integer, ForeignKey, String, DateTime, Enum +from sqlalchemy.orm import relationship +from src.database import Base +import enum + +class ReportType(enum.Enum): + INACCURATE_EQUIPMENT = 0 + INCORRECT_HOURS = 1 + INACCURATE_DESCRIPTION = 2 + WAIT_TIMES_NOT_UPDATED = 3 + OTHER = 4 + +class Report(Base): + """ + A report object. + + Attributes: + - `id` The ID of the report. + - `user_id` The ID of the user who created the report. + - `issue` The issue reported (discrete options). + - `description` The description of the report. + - `created_at` The date and time the report was created. + - `gym_id` The ID of the gym associated with the report. + """ + + __tablename__ = "report" + + id = Column(Integer, primary_key=True) + created_at = Column(DateTime, nullable=False) # Timestamp for user submission + description = Column(String, nullable=False) # Text input + gym_id = Column(Integer, ForeignKey("gym.id"), nullable=False) # One to many relationship with gym + issue = Column(Enum(ReportType), nullable=False) # Discrete options (enumerate) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + # Make relationship with gym and user + gym = relationship("Gym", back_populates="reports") + user = relationship("User", back_populates="reports") diff --git a/src/models/user.py b/src/models/user.py index 7545215..f0f6bbc 100644 --- a/src/models/user.py +++ b/src/models/user.py @@ -1,6 +1,18 @@ -from sqlalchemy import Column, Integer, Float, ForeignKey, String +from sqlalchemy import Column, Integer, String, ARRAY +from sqlalchemy import Enum as SQLAEnum from sqlalchemy.orm import backref, relationship from src.database import Base +from enum import Enum + + +class DayOfWeekEnum(Enum): + MONDAY = "Monday" + TUESDAY = "Tuesday" + WEDNESDAY = "Wednesday" + THURSDAY = "Thursday" + FRIDAY = "Friday" + SATURDAY = "Saturday" + SUNDAY = "Sunday" class User(Base): @@ -8,15 +20,20 @@ class User(Base): An uplift user. Attributes: - - `id` The ID of user. - - `giveaways` (nullable) The list of giveaways a user is entered into. - - `instagram` (nullable) The username handle of this user's Instagram. - - `net_id` The user's Net ID. + - `id` The ID of user. + - `email` The user's email address. + - `giveaways` (nullable) The list of giveaways a user is entered into. + - `net_id` The user's Net ID. + - `name` The user's name. + - `workout_goal` The days of the week the user has set as their personal goal. """ __tablename__ = "users" id = Column(Integer, primary_key=True) + email = Column(String, nullable=False) giveaways = relationship("Giveaway", secondary="giveaway_instance", back_populates="users") - instagram = Column(String, nullable=True) + reports = relationship("Report", back_populates="user") net_id = Column(String, nullable=False) + name = Column(String, nullable=False) + workout_goal = Column(ARRAY(SQLAEnum(DayOfWeekEnum)), nullable=True) diff --git a/src/models/workout.py b/src/models/workout.py new file mode 100644 index 0000000..43f82a9 --- /dev/null +++ b/src/models/workout.py @@ -0,0 +1,20 @@ +from sqlalchemy import Column, Integer, Float, ForeignKey, String, DateTime +from sqlalchemy.orm import backref, relationship +from src.database import Base + + +class Workout(Base): + """ + A workout logged by a user. + + Attributes: + - `id` The ID of user. + - `workout_time` The date and time of the workout. + - `user_id` The ID of the user who completed the workout. + """ + + __tablename__ = "workout" + + id = Column(Integer, primary_key=True) + workout_time = Column(DateTime(), nullable=False) # should this be nullable? + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) diff --git a/src/schema.py b/src/schema.py index 8cf9eae..f68cb1c 100644 --- a/src/schema.py +++ b/src/schema.py @@ -1,9 +1,9 @@ import graphene -from graphene import Enum +from datetime import datetime, timedelta from graphene_sqlalchemy import SQLAlchemyObjectType from graphql import GraphQLError from src.models.capacity import Capacity as CapacityModel -from src.models.facility import Facility as FacilityModel, FacilityType +from src.models.facility import Facility as FacilityModel from src.models.gym import Gym as GymModel from src.models.openhours import OpenHours as OpenHoursModel from src.models.amenity import Amenity as AmenityModel @@ -12,8 +12,11 @@ from src.models.classes import Class as ClassModel from src.models.classes import ClassInstance as ClassInstanceModel from src.models.user import User as UserModel +from src.models.user import DayOfWeekEnum from src.models.giveaway import Giveaway as GiveawayModel from src.models.giveaway import GiveawayInstance as GiveawayInstanceModel +from src.models.workout import Workout as WorkoutModel +from src.models.report import Report as ReportModel from src.database import db_session @@ -119,6 +122,7 @@ class Price(SQLAlchemyObjectType): class Meta: model = PriceModel + # MARK: - Class @@ -131,7 +135,6 @@ class Meta: def resolve_class_instances(self, info): query = ClassInstance.get_query(info=info).filter(ClassInstanceModel.class_id == self.id) return query - # MARK: - Class Instance @@ -146,8 +149,8 @@ class Meta: def resolve_gym(self, info): query = Gym.get_query(info=info).filter(GymModel.id == self.gym_id).first() - return query - + return query + def resolve_class_(self, info): query = Class.get_query(info=info).filter(ClassModel.id == self.class_id).first() return query @@ -188,7 +191,7 @@ class Meta: model = GiveawayModel -# MARK: - Giveaway +# MARK: - Giveaway Instance class GiveawayInstance(SQLAlchemyObjectType): @@ -196,12 +199,24 @@ class Meta: model = GiveawayInstanceModel +# MARK: - Workout + + +class Workout(SQLAlchemyObjectType): + class Meta: + model = WorkoutModel + + # MARK: - Query class Query(graphene.ObjectType): get_all_gyms = graphene.List(Gym, description="Get all gyms.") get_users_by_giveaway_id = graphene.List(User, id=graphene.Int(), description="Get all users given a giveaway ID.") + get_weekly_workout_days = graphene.List( + graphene.String, id=graphene.Int(), description="Get the days a user worked out for the current week." + ) + get_workouts_by_id = graphene.List(Workout, id=graphene.Int(), description="Get all of a user's workouts by ID.") activities = graphene.List(Activity) def resolve_get_all_gyms(self, info): @@ -217,28 +232,76 @@ def resolve_get_users_by_giveaway_id(self, info, id): users = [User.get_query(info).filter(UserModel.id == entry.user_id).first() for entry in entries] return users + def resolve_get_workouts_by_id(self, info, id): + user = User.get_query(info).filter(UserModel.id == id).first() + if not user: + raise GraphQLError("User with the given ID does not exist.") + workouts = Workout.get_query(info).filter(WorkoutModel.user_id == user.id).all() + return workouts + + def resolve_get_weekly_workout_days(self, info, id): + user = User.get_query(info).filter(UserModel.id == id).first() + if not user: + raise GraphQLError("User with the given ID does not exist.") + + # Get the date 7 days ago + one_week_ago = datetime.utcnow() - timedelta(days=7) + + # Query distinct workout dates for the user in the past week. Workouts must never be logged for a future date. + workout_days = ( + Workout.get_query(info) + .filter( + WorkoutModel.user_id == user.id, WorkoutModel.workout_time >= one_week_ago # Use 'workout_time' here + ) + .all() + ) + + # Extract days of the week from the workout times (use a set to avoid duplicates) + workout_days_set = {workout.workout_time.strftime("%A") for workout in workout_days} + + return list(workout_days_set) + + +# MARK: - Report + +class Report(SQLAlchemyObjectType): + class Meta: + model = ReportModel + + gym = graphene.Field(lambda: Gym) + user = graphene.Field(lambda: User) + + def resolve_gym(self, info): + query = Gym.get_query(info).filter(GymModel.id == self.gym_id).first() + return query + + def resolve_user(self, info): + query = User.get_query(info).filter(UserModel.id == self.user_id).first() + return query + # MARK: - Mutation class CreateUser(graphene.Mutation): class Arguments: - instagram = graphene.String() + name = graphene.String(required=True) net_id = graphene.String(required=True) + email = graphene.String(required=True) - user = graphene.Field(User) + Output = User - def mutate(root, info, net_id, instagram=None): - # Check to see if NetID already exists - existing_user = User.get_query(info).filter(UserModel.net_id == net_id).first() + def mutate(self, info, name, net_id, email): + # Check if a user with the given NetID already exists + existing_user = db_session.query(UserModel).filter(UserModel.net_id == net_id).first() if existing_user: raise GraphQLError("NetID already exists.") - # NetID does not exist - new_user = UserModel(instagram=instagram, net_id=net_id) + new_user = UserModel(name=name, net_id=net_id, email=email) db_session.add(new_user) db_session.commit() - return CreateUser(user=new_user) + + return new_user class EnterGiveaway(graphene.Mutation): @@ -246,7 +309,7 @@ class Arguments: user_net_id = graphene.String(required=True) giveaway_id = graphene.Int(required=True) - giveaway_instance = graphene.Field(GiveawayInstance) + Output = GiveawayInstance def mutate(self, info, user_net_id, giveaway_id): # Check if NetID and Giveaway ID exists @@ -274,26 +337,108 @@ def mutate(self, info, user_net_id, giveaway_id): giveaway_instance.num_entries += 1 db_session.commit() - return EnterGiveaway(giveaway_instance=giveaway_instance) + return giveaway_instance class CreateGiveaway(graphene.Mutation): class Arguments: name = graphene.String(required=True) - giveaway = graphene.Field(Giveaway) + Output = Giveaway def mutate(self, info, name): giveaway = GiveawayModel(name=name) db_session.add(giveaway) db_session.commit() - return CreateGiveaway(giveaway=giveaway) + return giveaway + + +class SetWorkoutGoals(graphene.Mutation): + class Arguments: + user_id = graphene.Int(required=True, description="The ID of the user.") + workout_goal = graphene.List( + graphene.String, + required=True, + description="The new workout goal for the user in terms of days of the week.", + ) + + Output = User + + def mutate(self, info, user_id, workout_goal): + user = User.get_query(info).filter(UserModel.id == user_id).first() + if not user: + raise GraphQLError("User with given ID does not exist.") + + # Validate that all workout days are valid + validated_workout_goal = [] + for day in workout_goal: + try: + # Convert string to enum + validated_workout_goal.append(DayOfWeekEnum[day.upper()]) + except KeyError: + raise GraphQLError(f"Invalid day of the week: {day}") + + user.workout_goal = validated_workout_goal + + db_session.commit() + + return user + + +class logWorkout(graphene.Mutation): + class Arguments: + workout_time = graphene.DateTime(required=True) + user_id = graphene.Int(required=True) + + Output = Workout + + def mutate(self, info, workout_time, user_id): + user = User.get_query(info).filter(UserModel.id == user_id).first() + if not user: + raise GraphQLError("User with given ID does not exist.") + + workout = WorkoutModel(workout_time=workout_time, user_id=user.id) + + db_session.add(workout) + db_session.commit() + return workout + +class CreateReport(graphene.Mutation): + class Arguments: + user_id = graphene.Int(required=True) + issue = graphene.String(required=True) + description = graphene.String(required=True) + created_at = graphene.DateTime(required=True) + gym_id = graphene.Int(required=True) + + report = graphene.Field(Report) + + def mutate(self, info, description, user_id, issue, created_at, gym_id): + # Check if user exists + user = User.get_query(info).filter(UserModel.id == user_id).first() + if not user: + raise GraphQLError("User with given ID does not exist.") + # Check if gym exists + gym = Gym.get_query(info).filter(GymModel.id == gym_id).first() + if not gym: + raise GraphQLError("Gym with given ID does not exist.") + # Check if issue is a valid enumeration + if issue not in ["INACCURATE_EQUIPMENT", "INCORRECT_HOURS", "INACCURATE_DESCRIPTION", "WAIT_TIMES_NOT_UPDATED", "OTHER"]: + raise GraphQLError("Issue is not a valid enumeration.") + report = ReportModel(description=description, user_id=user_id, issue=issue, + created_at=created_at, gym_id=gym_id) + db_session.add(report) + db_session.commit() + return CreateReport(report=report) class Mutation(graphene.ObjectType): create_giveaway = CreateGiveaway.Field(description="Creates a new giveaway.") create_user = CreateUser.Field(description="Creates a new user.") enter_giveaway = EnterGiveaway.Field(description="Enters a user into a giveaway.") + set_workout_goals = SetWorkoutGoals.Field(description="Set a user's workout goals.") + log_workout = logWorkout.Field(description="Log a user's workout.") + create_report = CreateReport.Field(description="Creates a new report.") schema = graphene.Schema(query=Query, mutation=Mutation) diff --git a/src/scrapers/class_scraper.py b/src/scrapers/class_scraper.py index 6406bb7..18ccdd7 100644 --- a/src/scrapers/class_scraper.py +++ b/src/scrapers/class_scraper.py @@ -1,17 +1,11 @@ from datetime import datetime from src.database import db_session -import time as t -import datetime as dt -import random from bs4 import BeautifulSoup -import re import requests from src.utils.utils import get_gym_id from src.utils.constants import GYMS, BASE_URL, CLASSES_PATH from src.models.classes import Class, ClassInstance -from src.models.openhours import OpenHours -from src.models.facility import Facility """ Create a group class from a class page @@ -20,6 +14,8 @@ Returns: Class Object created """ + + def create_group_class(class_href): page = requests.get(BASE_URL + class_href).text soup = BeautifulSoup(page, "lxml") @@ -49,6 +45,8 @@ def create_group_class(class_href): Returns: dict of ClassInstance objects """ + + def fetch_classes(num_pages): classes = {} db_session.query(ClassInstance).delete() @@ -64,12 +62,17 @@ def fetch_classes(num_pages): row_elems = row.find_all("td") class_instance = ClassInstance() class_name = row_elems[0].a.text - class_href = row_elems[0].a["href"] + class_href = row_elems[0].a["href"].replace("/recreation/", "", 1) try: gym_class = db_session.query(Class).filter(Class.name == class_name).first() - assert gym_class is not None - except AssertionError: + if gym_class is None: + raise Exception("Gym class is none, creating new gym class") + except Exception: gym_class = create_group_class(class_href) + + if gym_class is None or not gym_class.id: + raise Exception(f"Failed to create or retrieve gym class from {BASE_URL + class_href}") + class_instance.class_id = gym_class.id date_string = row_elems[1].text.strip() if "Today" in date_string: @@ -78,7 +81,7 @@ def fetch_classes(num_pages): # special handling for time (cancelled) time_str = row_elems[3].string.replace("\n", "").strip() - if time_str != "" and time_str != 'Canceled': + if time_str != "" and time_str != "Canceled": class_instance.is_canceled = False time_strs = time_str.split(" - ") start_time_string = time_strs[0].strip() @@ -93,7 +96,7 @@ def fetch_classes(num_pages): try: class_instance.instructor = row_elems[4].a.string - except: + except Exception: class_instance.instructor = "" try: location = row_elems[5].a.string @@ -106,9 +109,9 @@ def fetch_classes(num_pages): gym_id = get_gym_id(gym) class_instance.gym_id = gym_id break - except: + except Exception: gym_class.location = "" db_session.add(class_instance) db_session.commit() classes[class_instance.id] = class_instance - return classes \ No newline at end of file + return classes diff --git a/src/utils/utils.py b/src/utils/utils.py index aeaa92b..5acb36c 100644 --- a/src/utils/utils.py +++ b/src/utils/utils.py @@ -1,4 +1,7 @@ -import hashlib, json, pytz, time +import hashlib +import json +import pytz +import time from datetime import datetime, timedelta from src.database import db_session from src.models.gym import Gym