From c22ccb5634819fd59395b39e587350d43590e2e1 Mon Sep 17 00:00:00 2001 From: Sam-Safari Date: Thu, 22 Jan 2026 20:08:40 +0300 Subject: [PATCH] feat: add Freebie model, migration, seed, debug and db session --- lib/__pycache__/db.cpython-38.pyc | Bin 0 -> 380 bytes lib/__pycache__/debug.cpython-38.pyc | Bin 0 -> 571 bytes lib/__pycache__/models.cpython-38.pyc | Bin 0 -> 4177 bytes lib/db.py | 9 +++ lib/debug.py | 7 +- .../versions/8a1b2c3d4e5f_create_freebies.py | 31 ++++++++ lib/models.py | 75 ++++++++++++++++++ lib/seed.py | 50 +++++++++++- 8 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 lib/__pycache__/db.cpython-38.pyc create mode 100644 lib/__pycache__/debug.cpython-38.pyc create mode 100644 lib/__pycache__/models.cpython-38.pyc create mode 100644 lib/db.py create mode 100644 lib/migrations/versions/8a1b2c3d4e5f_create_freebies.py diff --git a/lib/__pycache__/db.cpython-38.pyc b/lib/__pycache__/db.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b61cfbfd06c2a0babd4aa354912c52c5c8b0b289 GIT binary patch literal 380 zcmYk1u};G<5Qgm}O`|Af;~_ev9#{}U2qDCT7^pJTOXbu@W7T$wovJEpXC8!C^2)@< z%*2IOq9^%$clzz`ziKqf3C6?Bsc*bL`{p!aY;N)ROH7hTs)?bT(!P##WMUp;9rFYz zBzwvS5W_$w@06#I?g<~t!IX^CFMLhLG>jGwLs`pMO)EDVZYa`xk1S{rCs9Uo`U^t(YUBU_ literal 0 HcmV?d00001 diff --git a/lib/__pycache__/debug.cpython-38.pyc b/lib/__pycache__/debug.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2103b1eae674d0f21f7dd95ce3c2616ef198b824 GIT binary patch literal 571 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 literal 0 HcmV?d00001 diff --git a/lib/db.py b/lib/db.py new file mode 100644 index 000000000..7e022c6b1 --- /dev/null +++ b/lib/db.py @@ -0,0 +1,9 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +# Default DB URL for the freebie tracker +DB_URL = 'sqlite:///freebies.db' + +engine = create_engine(DB_URL) +Session = sessionmaker(bind=engine) +session = Session() diff --git a/lib/debug.py b/lib/debug.py index 4f922eb69..784317a19 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -2,8 +2,11 @@ from sqlalchemy import create_engine -from models import Company, Dev +from models import Company, Dev, Freebie, Base +from db import engine, session if __name__ == '__main__': - engine = create_engine('sqlite:///freebies.db') + # ensure tables exist + Base.metadata.create_all(engine) + import ipdb; ipdb.set_trace() diff --git a/lib/migrations/versions/8a1b2c3d4e5f_create_freebies.py b/lib/migrations/versions/8a1b2c3d4e5f_create_freebies.py new file mode 100644 index 000000000..0f4381799 --- /dev/null +++ b/lib/migrations/versions/8a1b2c3d4e5f_create_freebies.py @@ -0,0 +1,31 @@ +"""create freebies table + +Revision ID: 8a1b2c3d4e5f +Revises: 5f72c58bf48c +Create Date: 2026-01-22 00:00:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '8a1b2c3d4e5f' +down_revision = '5f72c58bf48c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + 'freebies', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('item_name', sa.String(), nullable=True), + sa.Column('value', sa.Integer(), nullable=True), + sa.Column('dev_id', sa.Integer(), sa.ForeignKey('devs.id')), + sa.Column('company_id', sa.Integer(), sa.ForeignKey('companies.id')), + ) + + +def downgrade() -> None: + op.drop_table('freebies') diff --git a/lib/models.py b/lib/models.py index 2681bee5a..a4a808a5e 100644 --- a/lib/models.py +++ b/lib/models.py @@ -19,6 +19,36 @@ class Company(Base): def __repr__(self): return f'' + def give_freebie(self, dev, item_name, value): + """Create and return a Freebie associated with this company and the given dev. + + This will add the Freebie to the current session if one is available as + the global `session` variable (as created in `debug.py`). If no session + is present, the Freebie instance will be returned but not persisted. + """ + freebie = Freebie(item_name=item_name, value=value, dev=dev, company=self) + try: + # If a shared session exists in db.py, persist and commit + from db import session + session.add(freebie) + session.commit() + except Exception: + # No session available; return uncommitted instance + pass + return freebie + + @classmethod + def oldest_company(cls): + """Return the Company with the earliest founding_year. + + Requires a global `session` variable (created in `debug.py`) to query. + """ + try: + from db import session + return session.query(cls).order_by(cls.founding_year).first() + except Exception: + return None + class Dev(Base): __tablename__ = 'devs' @@ -27,3 +57,48 @@ class Dev(Base): def __repr__(self): return f'' + + def received_one(self, item_name): + """Return True if this Dev has received a freebie with item_name.""" + return any(f.item_name == item_name for f in getattr(self, 'freebies', [])) + + def give_away(self, dev, freebie): + """Give away a freebie to another dev. + + Only performs the transfer if the freebie currently belongs to self. + If a session exists, the change will be committed. + Returns the updated Freebie or None if transfer not allowed. + """ + if freebie in getattr(self, 'freebies', []): + freebie.dev = dev + try: + from db import session + session.add(freebie) + session.commit() + except Exception: + pass + return freebie + return None + + +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')) + + # relationships + dev = relationship('Dev', backref=backref('freebies', cascade='all, delete-orphan')) + company = relationship('Company', backref=backref('freebies', cascade='all, delete-orphan')) + + def print_details(self): + """Return a string: {dev name} owns a {freebie item_name} from {company name}.""" + dev_name = self.dev.name if self.dev else 'Unknown Dev' + comp_name = self.company.name if self.company else 'Unknown Company' + return f"{dev_name} owns a {self.item_name} from {comp_name}." + +# establish many-to-many view: Dev.companies via freebies +Dev.companies = relationship('Company', secondary='freebies', backref='devs', viewonly=True) diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..122fff179 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,51 @@ #!/usr/bin/env python3 -# Script goes here! +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +from models import Company, Dev, Freebie, Base + + +def seed(db_url: str = 'sqlite:///freebies.db'): + engine = create_engine(db_url) + Session = sessionmaker(bind=engine) + session = Session() + + Base.metadata.create_all(engine) + + # Clear existing data for a fresh seed + session.query(Freebie).delete() + session.query(Dev).delete() + session.query(Company).delete() + session.commit() + + # Create some companies and devs + c1 = Company(name='GitHub', founding_year=2008) + c2 = Company(name='Google', founding_year=1998) + c3 = Company(name='ACME Corp', founding_year=1970) + + d1 = Dev(name='Ada') + d2 = Dev(name='Grace') + + session.add_all([c1, c2, c3, d1, d2]) + session.commit() + + # Create freebies + f1 = Freebie(item_name='Sticker Pack', value=1, dev=d1, company=c1) + f2 = Freebie(item_name='T-Shirt', value=25, dev=d1, company=c2) + f3 = Freebie(item_name='Coffee Mug', value=10, dev=d2, company=c3) + + session.add_all([f1, f2, f3]) + session.commit() + + return { + 'companies': session.query(Company).count(), + 'devs': session.query(Dev).count(), + 'freebies': session.query(Freebie).count(), + } + + +if __name__ == '__main__': + print('Seeding freebie tracker...') + res = seed() + print('Seeded:', res)