diff --git a/lib/alembic.ini b/alembic.ini similarity index 79% rename from lib/alembic.ini rename to alembic.ini index 953863ddd..436a1f240 100644 --- a/lib/alembic.ini +++ b/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = migrations +script_location = migration # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s # Uncomment the line below if you want the files to be prepended with date and time @@ -16,9 +16,9 @@ prepend_sys_path = . # timezone to use when rendering the date within the migration file # as well as the filename. -# If specified, requires the python-dateutil library that can be -# installed by adding `alembic[tz]` to the pip requirements -# string value is passed to dateutil.tz.gettz() +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() # leave blank for localtime # timezone = @@ -36,10 +36,10 @@ prepend_sys_path = . # sourceless = false # version location specification; This defaults -# to migrations/versions. When using multiple version +# to migration/versions. When using multiple version # directories, initial revisions must be specified with --version-path. # The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions +# version_locations = %(here)s/bar:%(here)s/bat:migration/versions # version path separator; As mentioned above, this is the character used to split # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. @@ -51,11 +51,17 @@ prepend_sys_path = . # version_path_separator = space version_path_separator = os # Use os.pathsep. Default configuration used for new projects. +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + # the output encoding used when revision files # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = sqlite:///freebies.db +sqlalchemy.url = sqlite:///freebie_tracker.db + [post_write_hooks] @@ -69,6 +75,12 @@ sqlalchemy.url = sqlite:///freebies.db # black.entrypoint = black # black.options = -l 79 REVISION_SCRIPT_FILENAME +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + # Logging configuration [loggers] keys = root,sqlalchemy,alembic diff --git a/lib/freebies.db b/freebie_tracker.db similarity index 79% rename from lib/freebies.db rename to freebie_tracker.db index 12beb1c96..fe3f5dfbb 100644 Binary files a/lib/freebies.db and b/freebie_tracker.db differ diff --git a/lib/__pycache__/models.cpython-312.pyc b/lib/__pycache__/models.cpython-312.pyc new file mode 100644 index 000000000..0b207d06b Binary files /dev/null and b/lib/__pycache__/models.cpython-312.pyc differ diff --git a/lib/debug.py b/lib/debug.py index 4f922eb69..36867b876 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -1,9 +1,17 @@ #!/usr/bin/env python3 from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from models import Company, Dev, Freebie -from models import Company, Dev +# Set up database connection +engine = create_engine('sqlite:///freebie_tracker.db') +Session = sessionmaker(bind=engine) +session = Session() if __name__ == '__main__': - engine = create_engine('sqlite:///freebies.db') - import ipdb; ipdb.set_trace() + # Example query before debugging + dev = session.query(Dev).first() + print(dev.companies) # Check relationship data + + import ipdb; ipdb.set_trace() # Start debugger diff --git a/lib/migrations/versions/5f72c58bf48c_create_companies_devs.py b/lib/migrations/versions/5f72c58bf48c_create_companies_devs.py deleted file mode 100644 index c191bb2f9..000000000 --- a/lib/migrations/versions/5f72c58bf48c_create_companies_devs.py +++ /dev/null @@ -1,39 +0,0 @@ -"""create companies, devs - -Revision ID: 5f72c58bf48c -Revises: 7a71dbf71c64 -Create Date: 2023-03-15 15:06:20.944586 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '5f72c58bf48c' -down_revision = '7a71dbf71c64' -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('companies', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('founding_year', sa.Integer(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('devs', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('devs') - op.drop_table('companies') - # ### end Alembic commands ### diff --git a/lib/migrations/versions/7a71dbf71c64_create_db.py b/lib/migrations/versions/7a71dbf71c64_create_db.py deleted file mode 100644 index 23e0a655b..000000000 --- a/lib/migrations/versions/7a71dbf71c64_create_db.py +++ /dev/null @@ -1,24 +0,0 @@ -"""create db - -Revision ID: 7a71dbf71c64 -Revises: -Create Date: 2023-03-15 15:05:55.516631 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7a71dbf71c64' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - pass - - -def downgrade() -> None: - pass diff --git a/lib/models.py b/lib/models.py index 2681bee5a..246e7cef7 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,29 +1,66 @@ -from sqlalchemy import ForeignKey, Column, Integer, String, MetaData -from sqlalchemy.orm import relationship, backref +from sqlalchemy import Column, Integer, String, ForeignKey, create_engine +from sqlalchemy.orm import relationship, backref, sessionmaker from sqlalchemy.ext.declarative import declarative_base -convention = { - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", -} -metadata = MetaData(naming_convention=convention) +Base = declarative_base() -Base = declarative_base(metadata=metadata) +class Dev(Base): + __tablename__ = 'devs' + + id = Column(Integer, primary_key=True) + name = Column(String) + + freebies = relationship('Freebie', backref='dev') + + @property + def companies(self): + return list(set([freebie.company for freebie in self.freebies])) + + def received_one(self, item_name): + return any(freebie.item_name == item_name for freebie in self.freebies) + + def give_away(self, dev, freebie): + if freebie in self.freebies: + freebie.dev = dev class Company(Base): __tablename__ = 'companies' - id = Column(Integer(), primary_key=True) - name = Column(String()) - founding_year = Column(Integer()) + id = Column(Integer, primary_key=True) + name = Column(String) + founding_year = Column(Integer) - def __repr__(self): - return f'' + freebies = relationship('Freebie', backref='company') -class Dev(Base): - __tablename__ = 'devs' + @property + def devs(self): + return list(set([freebie.dev for freebie in self.freebies])) + + def give_freebie(self, dev, item_name, value): + freebie = Freebie(item_name=item_name, value=value, dev=dev, company=self) + return freebie # Ensure session.add(freebie) and session.commit() happen outside + + @classmethod + def oldest_company(cls, session): + return session.query(cls).order_by(cls.founding_year.asc()).first() + +class Freebie(Base): + __tablename__ = 'freebies' + + id = Column(Integer, primary_key=True) + item_name = Column(String) + value = Column(Integer) + dev_id = Column(Integer, ForeignKey('devs.id')) + company_id = Column(Integer, ForeignKey('companies.id')) + + def print_details(self): + return f"{self.dev.name} owns a {self.item_name} from {self.company.name}" - id = Column(Integer(), primary_key=True) - name= Column(String()) +# Database connection setup +DATABASE_URL = "sqlite:///freebie_tracker.db" +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) +session = Session() - def __repr__(self): - return f'' +# Create tables if they don’t exist +Base.metadata.create_all(engine) diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..fdad70d37 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,72 @@ #!/usr/bin/env python3 -# Script goes here! +# Import necessary modules +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from models import Company, Dev, Freebie, Base + +# Set up database connection +DATABASE_URL = "sqlite:///freebie_tracker.db" +engine = create_engine(DATABASE_URL) +Session = sessionmaker(bind=engine) +session = Session() + +# Function to seed database +def seed_data(): + try: + # Clear existing data + session.query(Company).delete() + session.query(Dev).delete() + session.query(Freebie).delete() + session.commit() + + # Add multiple companies + companies = [ + Company(name="Tech Corp", founding_year=2000), + Company(name="InnovateX", founding_year=2010), + Company(name="CodeMasters", founding_year=2015), + Company(name="CyberNet", founding_year=1999), + Company(name="NeuralSoft", founding_year=2022) + ] + session.add_all(companies) + session.commit() + + # Add multiple developers + devs = [ + Dev(name="Elvis"), + Dev(name="Jacky"), + Dev(name="Sophia"), + Dev(name="Michael"), + Dev(name="Aisha") + ] + session.add_all(devs) + session.commit() + + # Add multiple freebies + freebies = [ + Freebie(item_name="Laptop Bag", value=50, dev=devs[0], company=companies[0]), + Freebie(item_name="Mouse Pad", value=20, dev=devs[1], company=companies[1]), + Freebie(item_name="Wireless Keyboard", value=100, dev=devs[2], company=companies[2]), + Freebie(item_name="Noise-Canceling Headphones", value=200, dev=devs[3], company=companies[3]), + Freebie(item_name="USB-C Hub", value=75, dev=devs[4], company=companies[4]), + Freebie(item_name="Smartwatch", value=300, dev=devs[0], company=companies[2]), + Freebie(item_name="Tablet Stand", value=40, dev=devs[1], company=companies[3]), + Freebie(item_name="Portable SSD", value=150, dev=devs[2], company=companies[0]), + Freebie(item_name="VR Headset", value=600, dev=devs[3], company=companies[1]), + Freebie(item_name="Gaming Mouse", value=80, dev=devs[4], company=companies[4]) + ] + session.add_all(freebies) + session.commit() + + print("✅ Database seeded successfully!") + + except Exception as e: + print(f"❌ Error seeding database: {e}") + + finally: + session.close() + +# Run seeding function +if __name__ == "__main__": + seed_data() + diff --git a/lib/migrations/README b/migration/README similarity index 100% rename from lib/migrations/README rename to migration/README diff --git a/migration/__pycache__/env.cpython-312.pyc b/migration/__pycache__/env.cpython-312.pyc new file mode 100644 index 000000000..c12251abd Binary files /dev/null and b/migration/__pycache__/env.cpython-312.pyc differ diff --git a/lib/migrations/env.py b/migration/env.py similarity index 88% rename from lib/migrations/env.py rename to migration/env.py index c7aab9656..75a2c91c3 100644 --- a/lib/migrations/env.py +++ b/migration/env.py @@ -4,6 +4,10 @@ from sqlalchemy import pool from alembic import context +import sys +sys.path.append("/home/elvis/Desktop/phase3/python-p3-freebie-tracker/lib") # Add lib to Python path +from models import Base + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -18,7 +22,6 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -from models import Base target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, @@ -39,7 +42,7 @@ def run_migrations_offline() -> None: script output. """ - url = config.get_main_option("sqlalchemy.url") + url = config.get_main_option("sqlalchemy.url","sqlite:///freebie_tracker.db") context.configure( url=url, target_metadata=target_metadata, @@ -59,14 +62,14 @@ def run_migrations_online() -> None: """ connectable = engine_from_config( - config.get_section(config.config_ini_section), + config.get_section(config.config_ini_section, {}), prefix="sqlalchemy.", poolclass=pool.NullPool, ) with connectable.connect() as connection: context.configure( - connection=connection, target_metadata=target_metadata, render_as_batch=True, + connection=connection, target_metadata=target_metadata ) with context.begin_transaction(): diff --git a/lib/migrations/script.py.mako b/migration/script.py.mako similarity index 57% rename from lib/migrations/script.py.mako rename to migration/script.py.mako index 55df2863d..fbc4b07dc 100644 --- a/lib/migrations/script.py.mako +++ b/migration/script.py.mako @@ -5,15 +5,17 @@ Revises: ${down_revision | comma,n} Create Date: ${create_date} """ +from typing import Sequence, Union + from alembic import op import sqlalchemy as sa ${imports if imports else ""} # revision identifiers, used by Alembic. -revision = ${repr(up_revision)} -down_revision = ${repr(down_revision)} -branch_labels = ${repr(branch_labels)} -depends_on = ${repr(depends_on)} +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} def upgrade() -> None: diff --git a/migration/versions/__pycache__/ac27f2ac319b_create_model.cpython-312.pyc b/migration/versions/__pycache__/ac27f2ac319b_create_model.cpython-312.pyc new file mode 100644 index 000000000..5d93348a7 Binary files /dev/null and b/migration/versions/__pycache__/ac27f2ac319b_create_model.cpython-312.pyc differ diff --git a/migration/versions/__pycache__/b9830800fb36_create_freebies_model.cpython-312.pyc b/migration/versions/__pycache__/b9830800fb36_create_freebies_model.cpython-312.pyc new file mode 100644 index 000000000..dbebeb2c8 Binary files /dev/null and b/migration/versions/__pycache__/b9830800fb36_create_freebies_model.cpython-312.pyc differ diff --git a/migration/versions/ac27f2ac319b_create_model.py b/migration/versions/ac27f2ac319b_create_model.py new file mode 100644 index 000000000..2602524c9 --- /dev/null +++ b/migration/versions/ac27f2ac319b_create_model.py @@ -0,0 +1,26 @@ +"""create model + +Revision ID: ac27f2ac319b +Revises: b9830800fb36 +Create Date: 2025-05-26 00:10:10.186863 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'ac27f2ac319b' +down_revision: Union[str, None] = 'b9830800fb36' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/migration/versions/b9830800fb36_create_freebies_model.py b/migration/versions/b9830800fb36_create_freebies_model.py new file mode 100644 index 000000000..602d5a724 --- /dev/null +++ b/migration/versions/b9830800fb36_create_freebies_model.py @@ -0,0 +1,26 @@ +"""create freebies model + +Revision ID: b9830800fb36 +Revises: +Create Date: 2025-05-25 23:40:29.272980 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'b9830800fb36' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass