From deff602b101b2c183e62eaad6195198de4750c26 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 16:06:56 +0300 Subject: [PATCH 01/24] Initialized project with ORM via an Entity Relationship Diagram. --- assets/freebie-ERD.png | Bin 0 -> 35459 bytes lib/models.py | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100755 assets/freebie-ERD.png diff --git a/assets/freebie-ERD.png b/assets/freebie-ERD.png new file mode 100755 index 0000000000000000000000000000000000000000..93c666ed72c74d46c9a7e466bf8313a33abbe014 GIT binary patch literal 35459 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' @@ -23,7 +23,20 @@ class Dev(Base): __tablename__ = 'devs' id = Column(Integer(), primary_key=True) - name= Column(String()) + name= Column(String(), nullable=False) def __repr__(self): return f'' + +class Freebie(Base): + __tablename__ = 'freebies' + + id = Column(Integer(), primart_key=True) + item_name = Column(String(), nullable=False) + value = Column(Integer(), nullable=False) + company_id = Column(Integer(), ForeignKey('companies.id'), nullable=False) + dev_id = Column(Integer(), ForeignKey('devs.id')) + + def __repr__(self): + return f' Date: Sat, 24 May 2025 16:15:06 +0300 Subject: [PATCH 02/24] Built Freebies model & defined relationship with foreignkeys. --- lib/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/models.py b/lib/models.py index 15c55b19b..3e732a1b8 100644 --- a/lib/models.py +++ b/lib/models.py @@ -15,6 +15,8 @@ class Company(Base): id = Column(Integer(), primary_key=True) name = Column(String(), nullable=False) founding_year = Column(Integer(), nullable=False) + + freebies = relationship('Freebie', backref=backref()) def __repr__(self): return f'' @@ -39,4 +41,6 @@ class Freebie(Base): def __repr__(self): return f' Date: Sat, 24 May 2025 16:27:01 +0300 Subject: [PATCH 03/24] Migration of Freebie model to database. --- .../51b9f3a7c085_add_freebie_model.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 lib/migrations/versions/51b9f3a7c085_add_freebie_model.py diff --git a/lib/migrations/versions/51b9f3a7c085_add_freebie_model.py b/lib/migrations/versions/51b9f3a7c085_add_freebie_model.py new file mode 100644 index 000000000..66d151898 --- /dev/null +++ b/lib/migrations/versions/51b9f3a7c085_add_freebie_model.py @@ -0,0 +1,63 @@ +"""Add Freebie model + +Revision ID: 51b9f3a7c085 +Revises: 5f72c58bf48c +Create Date: 2025-05-24 16:24:02.461152 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '51b9f3a7c085' +down_revision = '5f72c58bf48c' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('freebies', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('item_name', sa.String(), nullable=False), + sa.Column('value', sa.Integer(), nullable=False), + sa.Column('company_id', sa.Integer(), nullable=False), + sa.Column('dev_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['company_id'], ['companies.id'], name=op.f('fk_freebies_company_id_companies')), + sa.ForeignKeyConstraint(['dev_id'], ['devs.id'], name=op.f('fk_freebies_dev_id_devs')), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('companies', schema=None) as batch_op: + batch_op.alter_column('name', + existing_type=sa.VARCHAR(), + nullable=False) + batch_op.alter_column('founding_year', + existing_type=sa.INTEGER(), + nullable=False) + + with op.batch_alter_table('devs', schema=None) as batch_op: + batch_op.alter_column('name', + existing_type=sa.VARCHAR(), + nullable=False) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('devs', schema=None) as batch_op: + batch_op.alter_column('name', + existing_type=sa.VARCHAR(), + nullable=True) + + with op.batch_alter_table('companies', schema=None) as batch_op: + batch_op.alter_column('founding_year', + existing_type=sa.INTEGER(), + nullable=True) + batch_op.alter_column('name', + existing_type=sa.VARCHAR(), + nullable=True) + + op.drop_table('freebies') + # ### end Alembic commands ### From 4c6440a0b58d023e808f767e3f13938b1d044bfa Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 17:06:37 +0300 Subject: [PATCH 04/24] Seeded database with companies, devs, & freebies. --- lib/seed.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..2da5b5874 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,52 @@ #!/usr/bin/env python3 +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from models import Company, Dev, Freebie + +from faker import Faker +import random + +# defining database connection +engine = create_engine('sqlite:///freebies.db') +Session = sessionmaker(bind=engine) +session = Session() + +# initializing faker +fake = Faker() + +# creating instances +companies = [] +for _ in range(7): + company = Company( + name=fake.company(), + founding_year=random.randint(2000,2023) + ) + companies.append(company) +session.add_all(companies) +session.commit() + +devs = [] +for _ in range(7): + dev = Dev( + name=fake.name() + ) + devs.append(dev) +session.add_all(devs) +session.commit() + +freebies = [] +item_names = ['Pens', 'Tshirts', 'Uber-discounts', 'Notebook', 'stickers'] +for _ in range(10): + freebie = Freebie( + item_name=random.choice(item_names), + value=random.randint(1,100), + company=random.choice(companies), + dev=random.choice(devs) + ) + freebies.append(freebie) +session.add_all(freebies) +session.commit() + +print("successfuly seeded database!") + -# Script goes here! From 0f39cefe24df1fa35e47a8b88a3477462305ea41 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 17:46:52 +0300 Subject: [PATCH 05/24] Refactored debug.py & tested models' one-to-many relationships. --- lib/debug.py | 6 +++++- lib/models.py | 15 ++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/debug.py b/lib/debug.py index 4f922eb69..5c6c5a462 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -1,9 +1,13 @@ #!/usr/bin/env python3 from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker -from models import Company, Dev +from models import Company, Dev, Freebie if __name__ == '__main__': engine = create_engine('sqlite:///freebies.db') + Session = sessionmaker(bind=engine) + session = Session() + import ipdb; ipdb.set_trace() diff --git a/lib/models.py b/lib/models.py index 3e732a1b8..b5c574bd3 100644 --- a/lib/models.py +++ b/lib/models.py @@ -16,8 +16,8 @@ class Company(Base): name = Column(String(), nullable=False) founding_year = Column(Integer(), nullable=False) - freebies = relationship('Freebie', backref=backref()) - + freebies = relationship('Freebie', backref=backref('company')) + def __repr__(self): return f'' @@ -26,6 +26,8 @@ class Dev(Base): id = Column(Integer(), primary_key=True) name= Column(String(), nullable=False) + + freebies = relationship('Freebie', backref=backref('dev')) def __repr__(self): return f'' @@ -33,14 +35,13 @@ def __repr__(self): class Freebie(Base): __tablename__ = 'freebies' - id = Column(Integer(), primart_key=True) + id = Column(Integer(), primary_key=True) item_name = Column(String(), nullable=False) value = Column(Integer(), nullable=False) company_id = Column(Integer(), ForeignKey('companies.id'), nullable=False) dev_id = Column(Integer(), ForeignKey('devs.id')) def __repr__(self): - return f'" \ No newline at end of file From 804ccb398ec57dfa36d422129a6f39d1201cb92a Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 18:13:38 +0300 Subject: [PATCH 06/24] Created association table, setting many-many relationship. --- lib/models.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/models.py b/lib/models.py index b5c574bd3..91dc70f23 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,4 +1,4 @@ -from sqlalchemy import ForeignKey, Column, Integer, String, MetaData +from sqlalchemy import ForeignKey, Table, Column, Integer, String, MetaData from sqlalchemy.orm import relationship, backref from sqlalchemy.ext.declarative import declarative_base @@ -9,6 +9,14 @@ Base = declarative_base(metadata=metadata) +# Associations table => many-to-many relationship +company_dev = Table( + 'company_dev', + Base.metadata, + Column('company_id', Integer, ForeignKey('companies.id'), primary_key=True), + Column('dev_id', Integer, ForeignKey('devs.id'), primary_key=True) +) + class Company(Base): __tablename__ = 'companies' @@ -17,6 +25,7 @@ class Company(Base): founding_year = Column(Integer(), nullable=False) freebies = relationship('Freebie', backref=backref('company')) + devs = relationship('Dev', secondary='company_dev', back_populates='companies') def __repr__(self): return f'' @@ -28,6 +37,7 @@ class Dev(Base): name= Column(String(), nullable=False) freebies = relationship('Freebie', backref=backref('dev')) + companies = relationship('Company', secondary='company_dev', back_populates='devs') def __repr__(self): return f'' From e2d149eccf76cc5a3c9b2491c47c958e546f209f Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 18:38:58 +0300 Subject: [PATCH 07/24] recreated seeding script to establish many-to-many relationship. --- lib/seed.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/seed.py b/lib/seed.py index 2da5b5874..49904aef2 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -15,6 +15,7 @@ fake = Faker() # creating instances + companies = [] for _ in range(7): company = Company( @@ -34,6 +35,14 @@ session.add_all(devs) session.commit() + # linking companies and devs many-to-many relationships +for dev in devs: + associated_companies = random.sample(companies, random.randint(1,6)) + for company in associated_companies: + dev.companies.append(company) +session.commit() + +# freebies has-manys freebies = [] item_names = ['Pens', 'Tshirts', 'Uber-discounts', 'Notebook', 'stickers'] for _ in range(10): From 70568316cb7ab9faa6ffedf92df9dd273a8e93e8 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 18:46:03 +0300 Subject: [PATCH 08/24] Migration to update database with Add company_dev Association Table. --- lib/freebies.db | Bin 20480 -> 32768 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/freebies.db b/lib/freebies.db index 12beb1c963e832db481e7a7493e3029e691ac4dc..ec10c9b90dcab0185b01833ff0be3fa4abd79e13 100644 GIT binary patch literal 32768 zcmeI*&ub%P7zgloCX<;aYxAayYd|S|x24$7R@^PNDoD-7*>2Z1ZIkTQMKDa7H=Ci! zOq`k2tp{O?d+_K%@FF6f);~ZHZ{9q3kOff`yeW7Pk6r|yNt$jZwu-+l-S5zlcix}R zeBMdmZDx|v)|wqiQFpzT6^I3vW{Sd|7lJW1N{@9^Qr@%!tJ;)1 zZmVrM`<0s98%W0UMKf16MLBnA%@hL#VkVjB@7gu7S}2<FM2%A#;>ZEa2@6Vy}X zgrd#j>UyqtL%e9-5Ht6?nG?M>*|2$jqfjarbJVk_?^gPi!V3MgT{}$L(ifLEistG{ zA?$LX$E+xtm(8MC$eX3Z@WaY8cI_|5=mipLM7ur=aIo8Mov_Db_V$EsWF{t*gV6y~ z8n72JC5(vY$EB-M!07+JM!4b?$6<|#XaVJQFuBGAIcw3k$$xQ z+xc-cKrgz#dvc`SVK4mKcb=ybdS-P>VYX9~xBOR|#9y&Gf!p0bI`A;Q3w>eQ8R5V7 z7M_k_WX_#aZZCHypGy4jp+zQrGR*o%=i~4c(d!|r7@65wNzb=k zXJ}WAgfUbYo`Q$XDo&@>e?81BT+Bw^bOzovhI**9cXvc2#DV_kwfmf^XSQZfq8;w+ z!cazxX12Sw4;T3g|Ao;XHV8lf0uX=z1Rwwb2tWV=5P$##PKiKDQKuIcpRCFyS-ZHj z^mLMr3+w+=GPyVm2tWV=5P$##AOHafKmY;|fWV;uk4+~A*Z<+?{|3K9pa1jk`4{|C z{tK@y4`F_kE_&Z z-3o#RWnH1HloDf8>z21KN^QCg#}C*4bkjdJ2tWV=5P$##AOHafKmY;|fB*zeu|T-~ zPx5CO|CxWsKj$Cw5Bb~t4PNIZ%ESf%2tWV=5P$##AOHafKmY;|c;E#xNu6aCMY&d$ z-h9obFU{;Yfj=3GjU{w8P6d8o@9ff72ad$`M@G7J6VbZa_?Qu`x)e9ky3QV>)@8q8 zdx8HLppSkzVYRGU1NE5yCEfAh(9MbUAHN_mA2_ReA{aI#*gRYVUgBD}!FVEHD2L1{OXk27X@toqY9tQkw+@mhtK& zv#^VciZV88mLw+Sq^6{n6@xIVbC9cJh^s<~qmz%T0!TzbgNt*rKCgIqUSe*lLYQNa zvxj4ljsh2FK#-@eV^E}mw`-(=MrMj8mu4e3GrPFFJY!=j*v#bo+=9fs%+z8yg9XKO zi16f#JaV$=W~b$s=A~rjrN>vMCKgTh;g#9U&Hqw>$285{DB08^Db2(pd6U8yepCSm L7J Date: Sat, 24 May 2025 23:10:56 +0300 Subject: [PATCH 09/24] Wrapped seeding logic in a main guard. --- lib/models.py | 6 ++-- lib/seed.py | 99 ++++++++++++++++++++++++++------------------------- 2 files changed, 53 insertions(+), 52 deletions(-) diff --git a/lib/models.py b/lib/models.py index 91dc70f23..3e049060d 100644 --- a/lib/models.py +++ b/lib/models.py @@ -25,7 +25,7 @@ class Company(Base): founding_year = Column(Integer(), nullable=False) freebies = relationship('Freebie', backref=backref('company')) - devs = relationship('Dev', secondary='company_dev', back_populates='companies') + devs = relationship('Dev', secondary=company_dev, back_populates='companies') def __repr__(self): return f'' @@ -37,7 +37,7 @@ class Dev(Base): name= Column(String(), nullable=False) freebies = relationship('Freebie', backref=backref('dev')) - companies = relationship('Company', secondary='company_dev', back_populates='devs') + companies = relationship('Company', secondary=company_dev, back_populates='devs') def __repr__(self): return f'' @@ -49,7 +49,7 @@ class Freebie(Base): item_name = Column(String(), nullable=False) value = Column(Integer(), nullable=False) company_id = Column(Integer(), ForeignKey('companies.id'), nullable=False) - dev_id = Column(Integer(), ForeignKey('devs.id')) + dev_id = Column(Integer(), ForeignKey('devs.id'), nullable=False) def __repr__(self): dev_name = self.dev.name if self.dev else "unknown Dev" diff --git a/lib/seed.py b/lib/seed.py index 49904aef2..ae78e1ab8 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -6,56 +6,57 @@ from faker import Faker import random -# defining database connection -engine = create_engine('sqlite:///freebies.db') -Session = sessionmaker(bind=engine) -session = Session() - -# initializing faker -fake = Faker() - -# creating instances - -companies = [] -for _ in range(7): - company = Company( - name=fake.company(), - founding_year=random.randint(2000,2023) - ) - companies.append(company) -session.add_all(companies) -session.commit() - -devs = [] -for _ in range(7): - dev = Dev( - name=fake.name() - ) - devs.append(dev) -session.add_all(devs) -session.commit() +# defining database connection +if __name__ == '__main__': + engine = create_engine('sqlite:///freebies.db') + Session = sessionmaker(bind=engine) + session = Session() + + # initializing faker + fake = Faker() + + # creating instances + + companies = [] + for _ in range(7): + company = Company( + name=fake.company(), + founding_year=random.randint(2000,2023) + ) + companies.append(company) + session.add_all(companies) + session.commit() + + devs = [] + for _ in range(7): + dev = Dev( + name=fake.name() + ) + devs.append(dev) + session.add_all(devs) + session.commit() # linking companies and devs many-to-many relationships -for dev in devs: - associated_companies = random.sample(companies, random.randint(1,6)) - for company in associated_companies: - dev.companies.append(company) -session.commit() - -# freebies has-manys -freebies = [] -item_names = ['Pens', 'Tshirts', 'Uber-discounts', 'Notebook', 'stickers'] -for _ in range(10): - freebie = Freebie( - item_name=random.choice(item_names), - value=random.randint(1,100), - company=random.choice(companies), - dev=random.choice(devs) - ) - freebies.append(freebie) -session.add_all(freebies) -session.commit() - -print("successfuly seeded database!") + for dev in devs: + associated_companies = random.sample(companies, random.randint(1,6)) + for company in associated_companies: + dev.companies.append(company) + session.commit() + + # freebies has-manys + freebies = [] + item_names = ['Pens', 'Tshirts', 'Uber-discounts', 'Notebook', 'stickers'] + for _ in range(10): + freebie = Freebie( + item_name=random.choice(item_names), + value=random.randint(1,100), + company=random.choice(companies), + dev=random.choice(devs) + ) + freebies.append(freebie) + session.add_all(freebies) + session.commit() + + print("successfuly seeded database!") From ef39bec4544277ca1c5d8b28477f142d7fb0005e Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 23:31:27 +0300 Subject: [PATCH 10/24] Added reset_db to automate dropping and seeding. --- lib/reset_db.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/reset_db.py diff --git a/lib/reset_db.py b/lib/reset_db.py new file mode 100644 index 000000000..3b1e28348 --- /dev/null +++ b/lib/reset_db.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +DB_PATH = 'freebies.db' + +def delete_db(): + if os.path.exists(DB_PATH): + os.remove(DB_PATH) + print(f"Deleted old database: {DB_PATH}") + else: + print(f"No available database at {DB_PATH}") + +def run_migration(): + print("Running alembic migrations ...") + subprocess.run(["alembic", "upgrade", "head"], check=True) + print("database schema migrated") + +def seed_db(): + print("seeding the database...") + subprocess.run(["python3", "seed.py"], check=True) + print("Seeded database.") + +if __name__ == '__main__': + delete_db() + run_migration() + seed_db() + print("Reset complet! begin quering") \ No newline at end of file From 01fcb8a616f97fffb8c879584f837cd692a30762 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 23:43:17 +0300 Subject: [PATCH 11/24] Created models folder to hold classes as separate files. --- lib/models/__init__.py | 0 lib/models/base.py | 0 lib/models/company.py | 0 lib/models/dev.py | 0 lib/models/freebie.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/models/__init__.py create mode 100644 lib/models/base.py create mode 100644 lib/models/company.py create mode 100644 lib/models/dev.py create mode 100644 lib/models/freebie.py diff --git a/lib/models/__init__.py b/lib/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/models/base.py b/lib/models/base.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/models/company.py b/lib/models/company.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/models/dev.py b/lib/models/dev.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/models/freebie.py b/lib/models/freebie.py new file mode 100644 index 000000000..e69de29bb From 2669ef63605ec0d858674181cdb755d84d88b58e Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 23:47:56 +0300 Subject: [PATCH 12/24] Moved Base, metadata, and naming conventions to separate file. --- lib/models/base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/models/base.py b/lib/models/base.py index e69de29bb..5cba992b4 100644 --- a/lib/models/base.py +++ b/lib/models/base.py @@ -0,0 +1,9 @@ +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import MetaData + +convention = { + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", +} +metadata = MetaData(naming_convention=convention) + +Base = declarative_base(metadata=metadata) \ No newline at end of file From a1f24842c38a02681fc6bbf8cd8dd9d62237cd30 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 23:54:04 +0300 Subject: [PATCH 13/24] Moved Company, Dev, and their association table into separate files. --- lib/models/association.py | 10 ++++++++++ lib/models/company.py | 18 ++++++++++++++++++ lib/models/dev.py | 17 +++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 lib/models/association.py diff --git a/lib/models/association.py b/lib/models/association.py new file mode 100644 index 000000000..20888f33b --- /dev/null +++ b/lib/models/association.py @@ -0,0 +1,10 @@ +from sqlalchemy import Table, Column, Integer, ForeignKey +from .base import Base + +# Associations table => many-to-many relationship +company_dev = Table( + 'company_dev', + Base.metadata, + Column('company_id', Integer, ForeignKey('companies.id'), primary_key=True), + Column('dev_id', Integer, ForeignKey('devs.id'), primary_key=True) +) \ No newline at end of file diff --git a/lib/models/company.py b/lib/models/company.py index e69de29bb..4458f1c40 100644 --- a/lib/models/company.py +++ b/lib/models/company.py @@ -0,0 +1,18 @@ +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import relationship, backref + +from .base import Base +from .association import company_dev + +class Company(Base): + __tablename__ = 'companies' + + id = Column(Integer(), primary_key=True) + name = Column(String(), nullable=False) + founding_year = Column(Integer(), nullable=False) + + freebies = relationship('Freebie', backref=backref('company')) + devs = relationship('Dev', secondary=company_dev, back_populates='companies') + + def __repr__(self): + return f'' diff --git a/lib/models/dev.py b/lib/models/dev.py index e69de29bb..119e6c1ad 100644 --- a/lib/models/dev.py +++ b/lib/models/dev.py @@ -0,0 +1,17 @@ +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import relationship, backref + +from .base import Base +from .association import company_dev + +class Dev(Base): + __tablename__ = 'devs' + + id = Column(Integer(), primary_key=True) + name= Column(String(), nullable=False) + + freebies = relationship('Freebie', backref=backref('dev')) + companies = relationship('Company', secondary=company_dev, back_populates='devs') + + def __repr__(self): + return f'' \ No newline at end of file From c7c9ca3d9adf82382877d44216661600d36741e1 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sat, 24 May 2025 23:56:05 +0300 Subject: [PATCH 14/24] Moved Freebie class into separate files. --- lib/models/freebie.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/models/freebie.py b/lib/models/freebie.py index e69de29bb..4cdaf0c99 100644 --- a/lib/models/freebie.py +++ b/lib/models/freebie.py @@ -0,0 +1,16 @@ +from sqlalchemy import Column, Integer, String, ForeignKey +from .base import Base + +class Freebie(Base): + __tablename__ = 'freebies' + + id = Column(Integer(), primary_key=True) + item_name = Column(String(), nullable=False) + value = Column(Integer(), nullable=False) + company_id = Column(Integer(), ForeignKey('companies.id'), nullable=False) + dev_id = Column(Integer(), ForeignKey('devs.id'), nullable=False) + + def __repr__(self): + dev_name = self.dev.name if self.dev else "unknown Dev" + company_name = self.company.name if self.company else "unknown Company" + return f"<{dev_name} owns a {self.item_name} from {company_name}>" \ No newline at end of file From 74848d523be19f25454d4cff6470018536004178 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sun, 25 May 2025 00:04:22 +0300 Subject: [PATCH 15/24] Modularized imports via init file & tested models. --- lib/models/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/models/__init__.py b/lib/models/__init__.py index e69de29bb..572336a21 100644 --- a/lib/models/__init__.py +++ b/lib/models/__init__.py @@ -0,0 +1,8 @@ +# expose all models for easier imports +from .base import Base +from .company import Company +from .dev import Dev +from .freebie import Freebie +from .association import company_dev + +__all__ = ["Base", "Company", "Dev", "Freebie", "company_dev"] \ No newline at end of file From 11936284469f3717cea5f0590faeee13dd65eca2 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sun, 25 May 2025 08:55:41 +0300 Subject: [PATCH 16/24] Added aggregate method to print details. --- lib/models/freebie.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/models/freebie.py b/lib/models/freebie.py index 4cdaf0c99..4b2d5c8d3 100644 --- a/lib/models/freebie.py +++ b/lib/models/freebie.py @@ -11,6 +11,12 @@ class Freebie(Base): dev_id = Column(Integer(), ForeignKey('devs.id'), nullable=False) def __repr__(self): - dev_name = self.dev.name if self.dev else "unknown Dev" - company_name = self.company.name if self.company else "unknown Company" - return f"<{dev_name} owns a {self.item_name} from {company_name}>" \ No newline at end of file + return ( + f'Freebie(id={self.id},' + f'item_name={self.item_name},' + f'value={self.value})' + ) + + def print_details(self): + return f'{self.dev.name} owns a {self.item_name} from {self.company.name}' + \ No newline at end of file From 26aa5409087c1582de15a02a9797652224c37f5f Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sun, 25 May 2025 09:22:08 +0300 Subject: [PATCH 17/24] Added aggregate methods and refactored Company with cascades. --- lib/models/company.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/models/company.py b/lib/models/company.py index 4458f1c40..734f5e908 100644 --- a/lib/models/company.py +++ b/lib/models/company.py @@ -1,18 +1,38 @@ from sqlalchemy import Column, Integer, String -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import relationship, backref, Session from .base import Base from .association import company_dev class Company(Base): + """ + In Hackathons, a company gives out freebies to developers. + Company has a one-to-many relationship with Freebie. + Has a many-to-many relationships with Dev + """ __tablename__ = 'companies' id = Column(Integer(), primary_key=True) name = Column(String(), nullable=False) founding_year = Column(Integer(), nullable=False) - freebies = relationship('Freebie', backref=backref('company')) + freebies = relationship('Freebie', backref=backref('company'), cascade='all, delete-orphan') devs = relationship('Dev', secondary=company_dev, back_populates='companies') def __repr__(self): - return f'' + return ( + f'Company(id={self.id},' + f'name={self.name},' + f'founding_year={self.founding_year})' + ) + + def give_freebie(self, dev, item_name, value): + from .freebie import Freebie + return Freebie(item_name=item_name, value=value, + company=self, dev=dev + ) + + @classmethod + def oldest_company(cls, session: Session): + return session.query(cls).order_by(cls.founding_year).first() + From 858ad9ee970e8b1b4ea5caf1d1fa133ebb849771 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Sun, 25 May 2025 09:55:34 +0300 Subject: [PATCH 18/24] Added aggregate methods and refactored Dev with cascades. --- lib/models/dev.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/models/dev.py b/lib/models/dev.py index 119e6c1ad..3b3f984a6 100644 --- a/lib/models/dev.py +++ b/lib/models/dev.py @@ -10,8 +10,31 @@ class Dev(Base): id = Column(Integer(), primary_key=True) name= Column(String(), nullable=False) - freebies = relationship('Freebie', backref=backref('dev')) + freebies = relationship('Freebie', backref=backref('dev'), cascade='all, delete-orphan') companies = relationship('Company', secondary=company_dev, back_populates='devs') def __repr__(self): - return f'' \ No newline at end of file + return ( + f'Dev(id={self.id},' + f'name={self.name})' + ) + + def received_one(self, item_name:str) -> bool: + """ + Checks if a dev has a freebie with the indicated item name. + """ + return any(freebie.item_name == item_name for freebie in self.freebies) + + def give_away(self, dev, freebie): + """ + if a dev already owns a certain freebie, this method allows the dev + to transfer such a freebie to another dev. + """ + if freebie in self.freebies: + freebie.dev = dev + return freebie + else: + raise ValueError(f"{self.name} does not own this freebie.") + + + \ No newline at end of file From 58657a59fa72b81719fd0ff876405a10d9227b47 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Mon, 26 May 2025 02:03:31 +0300 Subject: [PATCH 19/24] Added helper functions to improve queries. --- lib/debug.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/lib/debug.py b/lib/debug.py index 5c6c5a462..5114cf8e5 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from sqlalchemy import create_engine +from sqlalchemy import create_engine, func from sqlalchemy.orm import sessionmaker from models import Company, Dev, Freebie @@ -10,4 +10,88 @@ Session = sessionmaker(bind=engine) session = Session() - import ipdb; ipdb.set_trace() + # queries + all_companies = session.query(Company).all() + all_devs = session.query(Dev).all() + + def create_dev(session, dev_name): + """ + create and persist a new dev in database + """ + new_dev = Dev(name=dev_name) + session.add(new_dev) + session.commit() + return new_dev + + def update_freebie_value(session, freebie_id, new_value): + """ + Update a certain freebie's value + """ + freebie = session.query(Freebie).get(freebie_id) + if freebie: + freebie.value = new_value + session.commit() + return freebie + + def delete_dev(session, dev_id): + """ + Delete a dev, and due to cascade, freebies should also be removed + """ + dev = session.query(Dev).get(dev_id) + if dev: + session.delete(dev) + session.commit() + return f"Deleted developer {dev.name}" + return "Developer not found" + + def get_freebies_by_desc_value(session): + """ + return all freebies sorted in a descending manner + """ + return session.query(Freebie).order_by(Freebie.value.desc()).all() + + def get_freebies_by_value(session, max_value): + """ + returning freebies based on a particular value. + for instance: get_freebies_by_value(50) + """ + return session.query(Freebie).filter(Freebie.value < max_value).all() + + def companies_with_several_freebies(session): + """ + returning companies giving out more than 2 of the same freebie. + Begins by grouping by company and item name and counting, then + extracting company_ids from results to query. + """ + results = ( + session.query(Freebie.company_id, Freebie.item_name, + func.count(Freebie.id).label('counts')) + .group_by(Freebie.company_id, Freebie.item_name) + .having(func.count(Freebie.id) >= 2) + .all() + ) + + company_ids = set([result.company_id for result in results]) + return session.query(Company).filter(Company.id.in_(company_ids)).all() + + def get_devs_with_this_freebie(session, item_name): + """ + Returns all devs who own a certain freebie + """ + return ( + session.query(Dev).join(Dev.freebies) + .filter(Freebie.item_name == item_name) + .distinct().all() + ) + + def devs_owning_costly_freebies(session, min_value): + """ + returns devs with freebies worth more than stated value + """ + return ( + session.query(Dev).join(Dev.freebies) + .filter(Freebie.value > min_value).distinct().all() + ) + + + import pdb; pdb.set_trace() From 606f0d5abb00ae8b1ad38e31006f62893317d85d Mon Sep 17 00:00:00 2001 From: shirocodes Date: Mon, 26 May 2025 02:06:48 +0300 Subject: [PATCH 20/24] Successfully tested CRUD statements using queries. --- lib/freebies.db | Bin 32768 -> 32768 bytes ...d2778_add_company_dev_association_table.py | 34 +++++++++++ lib/models.py | 57 ------------------ lib/seed.py | 2 +- 4 files changed, 35 insertions(+), 58 deletions(-) create mode 100644 lib/migrations/versions/360be7ed2778_add_company_dev_association_table.py delete mode 100644 lib/models.py diff --git a/lib/freebies.db b/lib/freebies.db index ec10c9b90dcab0185b01833ff0be3fa4abd79e13..27a6123a4c2a1630fbbbdfe0f0483affbb9c4e74 100644 GIT binary patch delta 864 zcmYL{KWG#|6vk&~=FMc2+`Zf64ht0*MI=Q?p_N@cB|$DBL?mV0+zq)kn_bzx5H8J~ zm0Cp+VkI^~1A?I9pGrH?LP1avQPiMfBO+Q^`DU+3vp?pW@6CI&-*mgY+vS&cJH;z| zs!s9lnVVy!Hp8Fz9-rd_yn&anecvn9u~XE79XOe^{Zq{#j+3~o1RJZFBo5MsKUJ?M z>2g?KDg&w2w4JqrXh-#=U?Wo|3gXcClS$MtX-NonXgppd(Vq#TWs`cGOx31on>aT9 zTr+GL&mrkV6s`peM@_3~Tdx)vV-?I8{=>ic3xD7jn)MC7#Ao;vALBi|jhlEKH*g&< z;(1)djOHK54pfBW2n}TdS1?Qf1w#a-z$0h{C4vM%yOK~O2nhuOmvT_Jv^QWK68fzs zz$JiyK7tlflS6>A4h}#N9Hez{A%yR`k_*Sx(xETdTL5k`{EhGM1wO(pnsNi<0w`8- z9B0PF*>S~7Qn9^`>r5vtvydc9v$`O53>^3_uGk3qPcN8sv=L_YWF>B8(^?mt9p7Ot zY(I@1o9u@?*-gBP9qJikXGlwSkWVHnnHil3(s~%^@2Di($7hqXLFU(LRs9;l3dho9 zHTG+*hW_eF$qw+zFp6wZY*PJsNZ5nsf>xHq{zRIroYULLZC##bUbpQzwr;AvXiU#@ zM1ShfGo827y~11gklx)Qe#C1yk8kk~UZAYM;S)@775`Aq>-prP*rA_kGp*$0s+<$l za89M@VzjT7ms3Kix0aYw$YM?|3re~a+F;Kn2YRyT&k4e2W&`faZ9?~KT~Ws?y&uo}jK_F9#>XYEI9aNB#h3S< zUyP0r{Du>F4{zWFJb|NEain1sQy-Q0Ww*=KkTrPP6DA6MbfK1r)C$g8iOd89K3d!5 zRx7qGZKC@TaY$XZ6E0HLOt|SszEdu%ju8)8U8bopfL&3YXIbK;t4=!Q`DnGy&5$+w zf*V^z2w@btJx*Y9Tw0pG3$Tgc7kqR+;asn+fJ9zSSq_7#Q3~q_e!?es3s0TNJFxC7G8nB= zf_Cr?-jhZ=s<0~$xU!6^4r^wjp9tkLpdVI3f@-*x2;uHrVGXTQF@sv}%#!yk{jm~E zkp?&J92Is=XvIxu#{7$oemzGB+QKz$vgxF4ZZfT|&yZVua6eJ>%YskP8s61P*lJyL qq~i4B0-kD)b|Tv`deEnzPkRKFadY5?yM3#zp-Y{9FF2e3 None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('company_dev', + sa.Column('company_id', sa.Integer(), nullable=False), + sa.Column('dev_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['company_id'], ['companies.id'], name=op.f('fk_company_dev_company_id_companies')), + sa.ForeignKeyConstraint(['dev_id'], ['devs.id'], name=op.f('fk_company_dev_dev_id_devs')), + sa.PrimaryKeyConstraint('company_id', 'dev_id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('company_dev') + # ### end Alembic commands ### diff --git a/lib/models.py b/lib/models.py deleted file mode 100644 index 3e049060d..000000000 --- a/lib/models.py +++ /dev/null @@ -1,57 +0,0 @@ -from sqlalchemy import ForeignKey, Table, Column, Integer, String, MetaData -from sqlalchemy.orm import relationship, backref -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(metadata=metadata) - -# Associations table => many-to-many relationship -company_dev = Table( - 'company_dev', - Base.metadata, - Column('company_id', Integer, ForeignKey('companies.id'), primary_key=True), - Column('dev_id', Integer, ForeignKey('devs.id'), primary_key=True) -) - -class Company(Base): - __tablename__ = 'companies' - - id = Column(Integer(), primary_key=True) - name = Column(String(), nullable=False) - founding_year = Column(Integer(), nullable=False) - - freebies = relationship('Freebie', backref=backref('company')) - devs = relationship('Dev', secondary=company_dev, back_populates='companies') - - def __repr__(self): - return f'' - -class Dev(Base): - __tablename__ = 'devs' - - id = Column(Integer(), primary_key=True) - name= Column(String(), nullable=False) - - freebies = relationship('Freebie', backref=backref('dev')) - companies = relationship('Company', secondary=company_dev, back_populates='devs') - - def __repr__(self): - return f'' - -class Freebie(Base): - __tablename__ = 'freebies' - - id = Column(Integer(), primary_key=True) - item_name = Column(String(), nullable=False) - value = Column(Integer(), nullable=False) - company_id = Column(Integer(), ForeignKey('companies.id'), nullable=False) - dev_id = Column(Integer(), ForeignKey('devs.id'), nullable=False) - - def __repr__(self): - dev_name = self.dev.name if self.dev else "unknown Dev" - company_name = self.company.name if self.company else "unknown Company" - return f"<{dev_name} owns a {self.item_name} from {company_name}>" \ No newline at end of file diff --git a/lib/seed.py b/lib/seed.py index ae78e1ab8..d29a08605 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -46,7 +46,7 @@ # freebies has-manys freebies = [] item_names = ['Pens', 'Tshirts', 'Uber-discounts', 'Notebook', 'stickers'] - for _ in range(10): + for _ in range(5): freebie = Freebie( item_name=random.choice(item_names), value=random.randint(1,100), From c9e8e3bca0de8942441081bc0802f134fca52285 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Mon, 26 May 2025 09:35:28 +0300 Subject: [PATCH 21/24] Provided accessibility instructions. --- README.md | 212 ++++++++++++++++++------------------------------------ 1 file changed, 70 insertions(+), 142 deletions(-) diff --git a/README.md b/README.md index b598b7784..671c80a08 100644 --- a/README.md +++ b/README.md @@ -1,182 +1,110 @@ -# Phase 3 Mock Code Challenge: Freebie Tracker +# Freebie Tracker -## Learning Goals +## Description -- Write SQLAlchemy migrations. -- Connect between tables using SQLAlchemy relationships. -- Use SQLAlchemy to run CRUD statements in the database. +A SQLAlchemy-based app that tracks developers, companies, and the +freebies companies give to developers. The project explores one-to-many and many-to-many relationships, model-level methods, and CLI-based querying via `debug.py`. *** -## Key Vocab - -- **Schema**: the blueprint of a database. Describes how data relates to other - data in tables, columns, and relationships between them. -- **Persist**: save a schema in a database. -- **Engine**: a Python object that translates SQL to Python and vice-versa. -- **Session**: a Python object that uses an engine to allow us to - programmatically interact with a database. -- **Transaction**: a strategy for executing database statements such that - the group succeeds or fails as a unit. -- **Migration**: the process of moving data from one or more databases to one - or more target databases. - -*** - -## Introduction - -For this assignment, we'll be working with a freebie domain. - -As developers, when you attend hackathons, you'll realize they hand out a lot of -free items (informally called _freebies_, or swag)! Let's make an app for -developers that keeps track of all the freebies they obtain. - -We have three models: `Company`, `Dev`, and `Freebie` - -For our purposes, a `Company` has many `Freebie`s, a `Dev` has many `Freebie`s, -and a `Freebie` belongs to a `Dev` and to a `Company`. - -`Company` - `Dev` is a many to many relationship. - -**Note**: You should draw your domain on paper or on a whiteboard _before you -start coding_. Remember to identify a single source of truth for your data. - -## Instructions - -To get started, run `pipenv install && pipenv shell` while inside of this -directory. - -Build out all of the methods listed in the deliverables. The methods are listed -in a suggested order, but you can feel free to tackle the ones you think are -easiest. Be careful: some of the later methods rely on earlier ones. - -**Remember!** This mock code challenge does not have tests. You cannot run -`pytest` and you cannot run `learn test`. You'll need to create your own sample -instances so that you can try out your code on your own. Make sure your -relationships and methods work in the console before submitting. - -We've provided you with a tool that you can use to test your code. To use it, -run `python debug.py` from the command line. This will start an `ipdb` session -with your classes defined. You can test out the methods that you write here. You -are also encouraged to use the `seed.py` file to create sample data to test your -models and associations. - -Writing error-free code is more important than completing all of the -deliverables listed- prioritize writing methods that work over writing more -methods that don't work. You should test your code in the console as you write. - -Similarly, messy code that works is better than clean code that doesn't. First, -prioritize getting things working. Then, if there is time at the end, refactor -your code to adhere to best practices. - -**Before you submit!** Save and run your code to verify that it works as you -expect. If you have any methods that are not working yet, feel free to leave -comments describing your progress. +## Tech Stack +- python 3.12 as the core programming language +- SQLAlchemy -> ORM for managing models and database relationships +- SQLite -> Lightweight relational database +- Alembic -> Database migrations +- Faker -> Generating fake names, companies, and values +- pipenv -> Dependency and virtual environment manager +- pdb -> Interactive debugging in Python *** -## What You Already Have - -The starter code has migrations and models for the initial `Company` and `Dev` -models, and seed data for some `Company`s and `Dev`s. The schema currently looks -like this: +## Entity Relationship Diagram (ERD) -### companies Table +To identify Freebie as the `Source of Truth` and understand the relationships, interact with the following ERD diagram: -| Column | Type | -| ------------- | ------- | -| name | String | -| founding_year | Integer | +![View ERD on dbdiagram.io](https://dbdiagram.io/d/68301318b9f7446da3ce581e) + +*** -### devs Table +## Model Overview -| Column | Type | -| ------ | ------ | -| name | String | +The model is such that: a `Company` has many `Freebie`s, a `Dev` has many `Freebie`s, +and a `Freebie` belongs to a `Dev` and to a `Company`. -You will need to create the migration for the `freebies` table using the -attributes specified in the deliverables below. +`Company` - `Dev` is a many to many relationship, facilitated by the `company-dev` Association Table. *** -## Deliverables +## Usage -Write the following methods in the classes in the files provided. Feel free to -build out any helper methods if needed. +To get started: +### Fork and clone this repository: -Remember: SQLAlchemy gives your classes access to a lot of methods already! -Keep in mind what methods SQLAlchemy gives you access to on each of your -classes when you're approaching the deliverables below. +```console +$ git clone https://github.com//.git +cd /lib +``` -### Migrations +### Install dependencies and activate virtual environment: +Inside your directory, run `pipenv install && pipenv shell`. -Before working on the rest of the deliverables, you will need to create a -migration for the `freebies` table. +### Reset and seed the database: -- A `Freebie` belongs to a `Dev`, and a `Freebie` also belongs to a `Company`. - In your migration, create any columns your `freebies` table will need to - establish these relationships using the right foreign keys. -- The `freebies` table should also have: - - An `item_name` column that stores a string. - - A `value` column that stores an integer. +```console +$ cd lib/ +$ python3 reset_db.py +# => Deleted old database: freebies.db +# => Running alembic migrations ... +# => INFO [alembic.runtime.migration] Running upgrade ... +# => Seeded database. +# => Reset complete! Begin querying. +``` -After creating the `freebies` table using a migration, use the `seed.py` file to -create instances of your `Freebie` class so you can test your code. +This will: +- Delete the old SQLite database (freebies.db) -**After you've set up your `freebies` table**, work on building out the following -deliverables. +- Run all Alembic migrations -### Relationship Attributes and Methods +- Seed new fake data using Faker -Use SQLAlchemy's `ForeignKey`, `relationship()`, and `backref()` objects to -build relationships between your three models. +### Start the interactive debugger: -**Note**: The plural of "freebie" is "freebies" and the singular of "freebies" -is "freebie". +This lets you test queries and helper functions in a live session. -#### Freebie +```console +$ cd lib/ +$ python3 debug.py +``` -- `Freebie.dev` returns the `Dev` instance for this Freebie. -- `Freebie.company` returns the `Company` instance for this Freebie. +### Try out a few example queries: +Once inside the debugger `(pdb> prompt)`, you can run things like: -#### Company +```console +session.query(Dev).first() +get_devs_with_this_freebie(session, 'Pens') +create_dev(session, "Jane Doe") +update_freebie_value(session, 3, 75) +``` -- `Company.freebies` returns a collection of all the freebies for the Company. -- `Company.devs` returns a collection of all the devs who collected freebies - from the company. +**note** You can exit debugger session with `exit()` -#### Dev +*** -- `Dev.freebies` returns a collection of all the freebies that the Dev has collected. -- `Dev.companies`returns a collection of all the companies that the Dev has collected - freebies from. +## Contributions +- Create a feature branch: `git checkout -b your-feature` -Use `python debug.py` and check that these methods work before proceeding. For -example, you should be able to retrieve a dev from the database by its -attributes and view their companies with `dev.companies` (based on your seed -data). +- Commit your changes: `git commit -m "Add feature"` -### Aggregate Methods +- Push to your fork: `git push origin your-feature` -#### Freebie +- Open a Pull Request -- `Freebie.print_details()`should return a string formatted as follows: - `{dev name} owns a {freebie item_name} from {company name}`. +**Caution:** Please ensure changes are tested and maintain clean code practices. -#### Company +## License +This project is licensed under the *MIT License*. -- `Company.give_freebie(dev, item_name, value)` takes a `dev` (an instance of - the `Dev` class), an `item_name` (string), and a `value` as arguments, and - creates a new `Freebie` instance associated with this company and the given - dev. -- Class method `Company.oldest_company()`returns the `Company` instance with - the earliest founding year. +## Author +Developed with ❤️ by *Wanjiru Muchiri* -#### Dev -- `Dev.received_one(item_name)` accepts an `item_name` (string) and returns - `True` if any of the freebies associated with the dev has that `item_name`, - otherwise returns `False`. -- `Dev.give_away(dev, freebie)` accepts a `Dev` instance and a `Freebie` - instance, changes the freebie's dev to be the given dev; your code should only - make the change if the freebie belongs to the dev who's giving it away From a8c0ff472ec3382dafcbc08c1b132989b27bd2d7 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Mon, 26 May 2025 09:39:55 +0300 Subject: [PATCH 22/24] Updated README file. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 671c80a08..2d5e7e9b9 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ freebies companies give to developers. The project explores one-to-many and many *** ## Tech Stack -- python 3.12 as the core programming language -- SQLAlchemy -> ORM for managing models and database relationships -- SQLite -> Lightweight relational database -- Alembic -> Database migrations -- Faker -> Generating fake names, companies, and values -- pipenv -> Dependency and virtual environment manager -- pdb -> Interactive debugging in Python +- *python 3.12*: core programming language +- *SQLAlchemy*: ORM for managing models and database relationships +- *SQLite*: Lightweight relational database +- *Alembic*: Database migrations +- *Faker*: Generating fake names, companies, and values +- *pipenv*: Dependency and virtual environment manager +- *pdb*: Interactive debugging in Python *** From 18ff520d592f4731188926ee1539f6feff6ffe15 Mon Sep 17 00:00:00 2001 From: shirocodes Date: Mon, 26 May 2025 09:46:31 +0300 Subject: [PATCH 23/24] Updated README file with ERD diagram. --- README.md | 2 +- assets/erd.png | Bin 0 -> 37639 bytes assets/freebie-ERD.png | Bin 35459 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100755 assets/erd.png delete mode 100755 assets/freebie-ERD.png diff --git a/README.md b/README.md index 2d5e7e9b9..cb1fe2274 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ freebies companies give to developers. The project explores one-to-many and many To identify Freebie as the `Source of Truth` and understand the relationships, interact with the following ERD diagram: -![View ERD on dbdiagram.io](https://dbdiagram.io/d/68301318b9f7446da3ce581e) +![ERD Diagram](./assets/erd.png) *** diff --git a/assets/erd.png b/assets/erd.png new file mode 100755 index 0000000000000000000000000000000000000000..7bf78eee27927b30f908f365d852aeefad9782f8 GIT binary patch literal 37639 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�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 literal 0 HcmV?d00001 diff --git a/assets/freebie-ERD.png b/assets/freebie-ERD.png deleted file mode 100755 index 93c666ed72c74d46c9a7e466bf8313a33abbe014..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35459 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 Date: Mon, 26 May 2025 09:47:41 +0300 Subject: [PATCH 24/24] Project ready for assessment. --- Pipfile | 1 + Pipfile.lock | 35 ++++++++++++++++++++++++++++++----- freebies.db | 0 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 freebies.db diff --git a/Pipfile b/Pipfile index 63c79cd98..f31991967 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ importlib-metadata = "6.0.0" importlib-resources = "5.10.0" ipdb = "0.13.9" sqlalchemy = "1.4.42" +faker = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 3923f4a97..e892f77dc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "fa62c636b7ae5acb4993fc7b007a651c55b562aa29962a9a8585ec314b352ae6" + "sha256": "e6c61c0f5c1b795f874456f2c675d57c67594f1614a5e1eb4bc37717a41b1762" }, "pipfile-spec": 6, "requires": { @@ -61,6 +61,15 @@ ], "version": "==1.2.0" }, + "faker": { + "hashes": [ + "sha256:0a79ebe8f0ea803f7bd288d51e2d445b86035a2480e048daee1bffbd4d69b32b", + "sha256:94216ce3d8affdc0a8fd0ea8219c184c346a1dcf07b03f193e52f3116186621e" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==35.2.2" + }, "importlib-metadata": { "hashes": [ "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad", @@ -226,13 +235,21 @@ "markers": "python_version >= '3.6'", "version": "==2.14.0" }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -304,6 +321,14 @@ "markers": "python_version >= '3.7'", "version": "==5.8.1" }, + "typing-extensions": { + "hashes": [ + "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", + "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.13.2" + }, "wcwidth": { "hashes": [ "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", diff --git a/freebies.db b/freebies.db new file mode 100644 index 000000000..e69de29bb