From 88e97029753a8918b0e9b93b0f6c792d50e1d2f6 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 25 May 2025 11:17:35 +0300 Subject: [PATCH 1/5] updates --- alembic.ini | 103 ++++++++++++++++++ freebies.db | Bin 0 -> 16384 bytes lib/debug.py | 8 +- lib/migrations/env.py | 3 +- lib/models.py | 49 ++++++++- lib/seed.py | 26 +++++ migrations/README | 1 + migrations/__pycache__/env.cpython-310.pyc | Bin 0 -> 1745 bytes migrations/env.py | 77 +++++++++++++ migrations/script.py.mako | 24 ++++ .../3391b0431425_create_freebies_table.py | 31 ++++++ ...1425_create_freebies_table.cpython-310.pyc | Bin 0 -> 1000 bytes 12 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 alembic.ini create mode 100644 freebies.db create mode 100644 migrations/README create mode 100644 migrations/__pycache__/env.cpython-310.pyc create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/3391b0431425_create_freebies_table.py create mode 100644 migrations/versions/__pycache__/3391b0431425_create_freebies_table.cpython-310.pyc diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 000000000..08cf30b8f --- /dev/null +++ b/alembic.ini @@ -0,0 +1,103 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +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() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/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 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. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = sqlite:///freebies.db + + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/freebies.db b/freebies.db new file mode 100644 index 0000000000000000000000000000000000000000..3f410b304019a6fc2e3187e58b3de849791d3265 GIT binary patch literal 16384 zcmeI&L2ueH7zSXQRO>*Z*^N`B?^20~sTxgp*kMvr_3iP9gX^jvm7dGeAzic4z7W5=vK=}XZ)^-b2i zuj7yuCqfFpBL<|S*^_G@KVfbCeXO^ig{5 zMB|6iJkOT%uw1NN3e4I5rFq=yx^w#v(=Rj#KmY;|fB*y_009U<00Izz00cHfV24-s zT=!jj_~y`UA0E7|>3!k+zad{PwgCYMKmY;|fB*y_009U<00Izr5U{Pi>gxI5{Qj^1 d^p6Gs2tWV=5P$##AOHafKmY;|fWQU|`~&^!vMK-o literal 0 HcmV?d00001 diff --git a/lib/debug.py b/lib/debug.py index 4f922eb69..6ff4bbfdb 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, Base + +engine = create_engine('sqlite:///freebies.db') +Session = sessionmaker(bind=engine) +session = Session() if __name__ == '__main__': - engine = create_engine('sqlite:///freebies.db') import ipdb; ipdb.set_trace() diff --git a/lib/migrations/env.py b/lib/migrations/env.py index c7aab9656..2244c60f2 100644 --- a/lib/migrations/env.py +++ b/lib/migrations/env.py @@ -18,9 +18,10 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -from models import Base +from models import Base # make sure this import works target_metadata = Base.metadata + # other values from the config, defined by the needs of env.py, # can be acquired: # my_important_option = config.get_main_option("my_important_option") diff --git a/lib/models.py b/lib/models.py index 2681bee5a..59bba17fd 100644 --- a/lib/models.py +++ b/lib/models.py @@ -6,7 +6,6 @@ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", } metadata = MetaData(naming_convention=convention) - Base = declarative_base(metadata=metadata) class Company(Base): @@ -16,14 +15,56 @@ class Company(Base): name = Column(String()) founding_year = Column(Integer()) + freebies = relationship("Freebie", backref="company", cascade="all, delete-orphan") + + @property + def devs(self): + return list({freebie.dev for freebie in self.freebies}) + + def give_freebie(self, dev, item_name, value): + return Freebie(item_name=item_name, value=value, dev=dev, company=self) + + @classmethod + def oldest_company(cls, session): + return session.query(cls).order_by(cls.founding_year).first() + def __repr__(self): - return f'' + return f"" class Dev(Base): __tablename__ = 'devs' id = Column(Integer(), primary_key=True) - name= Column(String()) + name = Column(String()) + + freebies = relationship("Freebie", backref="dev", cascade="all, delete-orphan") + + @property + def companies(self): + return list({freebie.company for freebie in self.freebies}) + + def received_one(self, item_name): + return any(f.item_name == item_name for f in self.freebies) + + def give_away(self, other_dev, freebie): + if freebie in self.freebies: + freebie.dev = other_dev + + def __repr__(self): + return f"" + +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}" def __repr__(self): - return f'' + return f"" diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..f961f3a90 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,29 @@ #!/usr/bin/env python3 # Script goes here! +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from models import Base, Company, Dev, Freebie + +engine = create_engine('sqlite:///freebies.db') +Session = sessionmaker(bind=engine) +session = Session() + +# Drop and recreate all tables +Base.metadata.drop_all(engine) +Base.metadata.create_all(engine) + +# Sample data +c1 = Company(name="Google", founding_year=1998) +c2 = Company(name="Amazon", founding_year=1994) + +d1 = Dev(name="Alice") +d2 = Dev(name="Bob") + +f1 = Freebie(item_name="T-shirt", value=10, company=c1, dev=d1) +f2 = Freebie(item_name="Mug", value=5, company=c2, dev=d1) +f3 = Freebie(item_name="Sticker", value=1, company=c1, dev=d2) + +session.add_all([c1, c2, d1, d2, f1, f2, f3]) +session.commit() +print("🌱 Database seeded!") diff --git a/migrations/README b/migrations/README new file mode 100644 index 000000000..98e4f9c44 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/__pycache__/env.cpython-310.pyc b/migrations/__pycache__/env.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fc09b7c706513f39521a2af8a8bd57d60e06b56 GIT binary patch literal 1745 zcmZux&2J+$6t^dnNis=4wm`K9w6a!6OSCir$`K*h0@6yTTD1#^Tq0-eB+hy~gYD_Z zsy&qphiWhU0qK!{h7+f`a^f#QVnOBEle8%?qj~fEp8Y=T-_P0FT8!ZN^_LH)e>4#K z%?(zc00y7JXL1k>F)UGv2N*j&kU?4-)Vy8;JsgBy4`n@#1`$R)nl%Ouj(KC&yl`vk zgBFWe;|DZ|S(CNk-{#T0bA|LSk1vp$afo<(sdeMuAuHg!$ilkzqsTG+r zL@4qo8;>Ov{fML)ynyh|0jC z$QOPK-h(AsF`3?42t=ew!gEVUlKWLtt~sHG9CImqggiMV=7q>}p(Z|I6^SI8`)d7? zV@_D69$CVVxFU*k2336i$6RSi)gI&=`&~Z8BL-^Zb??yys(R)FW1fYlFDSt)6-s|aXoZhUXV0dhmDsWLQi^U8HysNwA)cd>T(`Wfka@cZ+iKh09!pU_b;%8cnV z*h!h?DOa|i00R2?lp4O<&rjh5)lR;nnX-ap3{2z-WZi`6az;6q~`gT3Rm_JSGVk{N0fc=33Z7r>hvFAEPHkM4|N{@&_* zH>X>F`Q!hQNd1RHPZeMYKyMPRs1_N(yaKEPqr)_SbxD^S5>aEaM0lS=tO8)bkye~r zA>k5-?G+q$gEGoBAB&T+!AMdwRW)Tu9fRDn*j7Dy{c&K;8Vu88&Wmt%WnX~ z(ve82qJ7_+i&owJJ#>Vv+Hj!s3Cay&mN*`|n{lNF-;Hmi`7yW($v<(oxVaCagE~P6 UcY-L4!!T+#oA;aBF^+?O05y8qM*si- literal 0 HcmV?d00001 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..70518a2ee --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,77 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + 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 + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 000000000..2c0156303 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +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)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/3391b0431425_create_freebies_table.py b/migrations/versions/3391b0431425_create_freebies_table.py new file mode 100644 index 000000000..b6875ec88 --- /dev/null +++ b/migrations/versions/3391b0431425_create_freebies_table.py @@ -0,0 +1,31 @@ +"""Create freebies table + +Revision ID: 3391b0431425 +Revises: +Create Date: 2025-05-25 11:06:30.233752 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3391b0431425' +down_revision = None +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()), + sa.Column('value', sa.Integer()), + 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/migrations/versions/__pycache__/3391b0431425_create_freebies_table.cpython-310.pyc b/migrations/versions/__pycache__/3391b0431425_create_freebies_table.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..670dae7d1a03373f077d6cd8584fc21619d18e45 GIT binary patch literal 1000 zcmZuvPjAyO6pxcMNxgPL1MP;?YY$!8hJvUNLfV8hA@#sG^%6PpqYX>qV5ddhnJeF6 zJ#yeP@D(`w%89SA6VFYjRRUXnet-O)pMO7#ZkJj(e*79Qf7+Jy+Zcz#LE{ZBWdyh8 z7AM@!Nny`zVss~W<}P>VE$+-cTt2RVQ|@v9t2=L_7VtJ|lsi{e|LBIFOJFthK1zTj z1J$cpk^`mhVV$W=l)cM~X)lUij*{WCX!LA6X(FJeJ-Sc5!2D@%JRDC3!^vPg>5WFy z;qz%UJR3*Ri^-VMKiI3)C)yU(Om_~eX-`7El4WX69FAi&&T*+FirTs*oYeLwhugJ% z>uxRXaQB;oy2D#2TU%>uA`bVO_}=mx3#G?{E!j31?E^10-mQn&Sq6`p4(^fAcE&$o z(?^xe3MM!4HEeV{tD%TXR)B7;S-t}8aahM0zwvQbXBpQt6-C9$jd6Aw9dX4--iRK4pYG)td>kc6jqyhDat_=4fd}Z)RLvwAj2YCNLC}kDqMp!Osnva zn0VjhJsM57XVpggs|r&&JT~k+Lg5mdoRDLjr#OQ<`2?N+5NbZ|j!?sFL+F<5VYg?p zh2Fm107SVIRdeCPn;Xqss@q(ADR(_QMN=72kBK~OD!91K`bXN2V=mG-);`OjNV0Ta zTYb)1o-U!-$Sxk$f!zOe-8E(72Q!hZOqX%a63CUN94aWeibc75;O@TvwimoA1YhOw WnqYlpNOP@@9}w4diJ8 Date: Sun, 25 May 2025 20:39:14 +0300 Subject: [PATCH 2/5] changes --- lib/seed.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/seed.py b/lib/seed.py index f961f3a90..67cc463bd 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -9,11 +9,11 @@ Session = sessionmaker(bind=engine) session = Session() -# Drop and recreate all tables +# Base.metadata.drop_all(engine) Base.metadata.create_all(engine) -# Sample data + c1 = Company(name="Google", founding_year=1998) c2 = Company(name="Amazon", founding_year=1994) From e9a8af3766834e28296f704cc1ecc063ae467fb2 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 25 May 2025 22:34:28 +0300 Subject: [PATCH 3/5] changes --- lib/__pycache__/models.cpython-310.pyc | Bin 0 -> 3147 bytes lib/__pycache__/models.cpython-38.pyc | Bin 0 -> 3147 bytes lib/freebies.db | Bin 20480 -> 24576 bytes .../__pycache__/env.cpython-310.pyc | Bin 0 -> 1848 bytes .../3df5cc1db709_create_freebie_table.py | 37 +++++++++++++ ...db709_create_freebie_table.cpython-310.pyc | Bin 0 -> 1188 bytes ...f48c_create_companies_devs.cpython-310.pyc | Bin 0 -> 1055 bytes .../7a71dbf71c64_create_db.cpython-310.pyc | Bin 0 -> 684 bytes lib/models.py | 32 +++++------ lib/seed.py | 50 +++++++++++------- 10 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 lib/__pycache__/models.cpython-310.pyc create mode 100644 lib/__pycache__/models.cpython-38.pyc create mode 100644 lib/migrations/__pycache__/env.cpython-310.pyc create mode 100644 lib/migrations/versions/3df5cc1db709_create_freebie_table.py create mode 100644 lib/migrations/versions/__pycache__/3df5cc1db709_create_freebie_table.cpython-310.pyc create mode 100644 lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-310.pyc create mode 100644 lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-310.pyc diff --git a/lib/__pycache__/models.cpython-310.pyc b/lib/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a25011d6d33979d8cfb5ba0eab6c533b659febfa GIT binary patch literal 3147 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 diff --git a/lib/migrations/__pycache__/env.cpython-310.pyc b/lib/migrations/__pycache__/env.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3326534e2b91f4b78e71435f0ca594925cf5db87 GIT binary patch literal 1848 zcmZux&5ztP6t^>(%uMEEw}n46JE^aG?yXjQePi0L8nByVOsb(~;3 z-EP&M$^}8y8|U34e+~CsIq@%0P+yPgg(p3Hief zPPPsXzQk^RL6MW3N>b1nrA`lIP;_P;uXj)nXQ9_a*)5`3LCfBUksK89EXGywdN>>9qr9IFehg=0)Z%=I+61D5twW-(!T32r&)vHOCL1jo|A`rq zgf{U)NVrj|g;?U~G^nLekS=stq}gURtjbcNwTCkn9@|~i!|U8Y6KSxuR+)3QX%rPa z3~~dzxsSq<1DexA`i$6M-FZrEm(_4IVzy*+ zboYo0=^`9ea)V2W`InpYci=H(wS`X-=EFlFtdF)7&ZDu~hcDfnk7zP2D$B~+R<%8H z`eb5$kX&XfDE4=1E$=mP#WgRC-IvgG6)zx9!lr|!X3KKD1e+FMdCo0w#!^_&T&8oO za?^}*!6jriEh}qwlU@_G=iY4jU0f(G35eR6E-JM)Z65`|JL6RTeN^@_9WkjhW_nlCrD{P;5hOE8DM({ zF^%JyJK8`^W=KBCW)T>^nX-R|6$TCg-Al^n+z1! zqC~K_uyv4h$VSL+Nb`nD+?X;G-sW(t5FU83E%Q!Laf8O;2{a}_6IB`(;&C%@?@%Vu z*}bIG#K(?WcGl0f(Hx0`G?6Im{i8q1wFdF`Op#K%X~7VFFCAp?uK z$lBz6{o6++yZ1hIgl;1DK1!n_=cm7+b6cguv5((u3A2HI>NcDx(HHCM?0=4?TEZ9Z e@i!l!7?DvhqN5-RT}h)TqVXU;*FPK6IQSQN==TKx literal 0 HcmV?d00001 diff --git a/lib/migrations/versions/3df5cc1db709_create_freebie_table.py b/lib/migrations/versions/3df5cc1db709_create_freebie_table.py new file mode 100644 index 000000000..c0e4e1acb --- /dev/null +++ b/lib/migrations/versions/3df5cc1db709_create_freebie_table.py @@ -0,0 +1,37 @@ +"""Create Freebie table + +Revision ID: 3df5cc1db709 +Revises: 5f72c58bf48c +Create Date: 2025-05-25 22:19:50.971667 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3df5cc1db709' +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=True), + sa.Column('value', sa.Integer(), nullable=True), + sa.Column('dev_id', sa.Integer(), nullable=True), + sa.Column('company_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') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('freebies') + # ### end Alembic commands ### diff --git a/lib/migrations/versions/__pycache__/3df5cc1db709_create_freebie_table.cpython-310.pyc b/lib/migrations/versions/__pycache__/3df5cc1db709_create_freebie_table.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee1356f36a6657f859a9f1e3d479d2d668d25ef6 GIT binary patch literal 1188 zcmZuw&2G~`5cb-R?YLPb(@l=jSxa=RjER%5Jenvv24#KF|~Kit`n3> z+_>@%&5>8yD<|Fnabo;a8YpY+dgj~NZ+>=`udllrj`!)ol$o0Ly--$%fyyhKvRFVH zX+S_vNUD!?QpiSPj7%^`4QPxkFh({`2iCv_=ZiUNqSOQzB^QiKt<$>ZCxQk!ZJ!BB z!vwzocCR@9hlc~#{efX?9inueU8vS7vudp}DofCOCRkM1uZ~Sr zPH@T_TypJ(0Lk?a2I#qdV=gr?!2D{INCS}uBGwd93`m$kd zNuX8oTi_tRUgI0L_>BYlk}R8e85$ON^Fle}sDhue`k3qQ+PqTn6P`++ea!lne?zG2O{w<|{XH|=6gf7g2^V&wP^2nprBb5Wc z*y}Wfj#X5xsB8kiynLn0Kv^d|nWaqG=PaiaDwK7ZifloQm>@PXr`LL zKd~~7XQ;}e{2RJ>S}`>6chOl>w(kRvd|%l?LensgYHs;436f|^(}j3|rpgiZGga#a zt$(M6B4E+f$2OyhR4&krGLSxJWrC%@|AyzhNIA?B`jQ~O+``4wTDC(>(wcK6$v?_0BG)6=kBeC;iNo0|4bO?Df`wC6a4 zGDMqcL?=e7XU5FXm2alz%p%sTMrt#gm@@~5OZJFE+)vi5j$WO3=y}Av)Y^^rS22gM zfL6@1JfsPT$1MWa!t*ZRIuQw@t@E>SYd9bE;^ApDADqVJ3W#xQ6poHbG#?$uCj)O% z;+!GaxYg_S`kijSb3AMv569h;aj$#yWH1<>o_OAOOk8X0(qQ>iHg=I^V;51{9EugE zvD&}e6hRXl;v=tl!cnjfA|w>Rh@?(=bXwS~SeywQSDWijssbf;EI(T8ibi!wsxK zHcAnKVtc?4p|mF~U1d}{=d^$YaA{u_91qLGS3Jo=zJ3Yo38SLmVM2>98b3gst@-i6 zU$PARMHsCjCWMa{LCW$BXyM0J2#bS2I$;V~l*ARfxJtt`UP89!P24J7Ui}}ksepsKX~aVsF9Q@G sQXxG8IZz@3MzfEhUNxMMY0zKj$?}5kTJ073UDbaTOZB@Uw2>EWvxjCA=)h-PH$q86sw^nE8 zwq;_cb#fQn`~sYOqTSQ(1BD5AU)_AFBOj(ZqTt+-#q7(6N+26CfRfT%+&8k}eemuk z3pQ*MLwUc6tM#kX3Cb*>*QfLweJ2Z6%T*FClQ2$$IL(%6mZn!}yj~|Ur9ZSjvapoR zhpoCiucjsf$DQaalW}gkUvjPer>gSkrMM`5~_(YeUt^e+XJ zSHnShyFsS|zA)-+4X!1odi!q+;=+iY82DTMiTe6LO@V*zk7Zw)JTrGO)3C6G`GDE3SH)baCxdxY9dcNhlu4NW=p1HpPWJjU- literal 0 HcmV?d00001 diff --git a/lib/models.py b/lib/models.py index 59bba17fd..d76b1030a 100644 --- a/lib/models.py +++ b/lib/models.py @@ -15,13 +15,12 @@ class Company(Base): name = Column(String()) founding_year = Column(Integer()) - freebies = relationship("Freebie", backref="company", cascade="all, delete-orphan") - - @property - def devs(self): - return list({freebie.dev for freebie in self.freebies}) + + freebies = relationship("Freebie", back_populates="company") + devs = relationship("Dev", secondary="freebies", back_populates="companies", viewonly=True) def give_freebie(self, dev, item_name, value): + from models import Freebie return Freebie(item_name=item_name, value=value, dev=dev, company=self) @classmethod @@ -29,7 +28,8 @@ def oldest_company(cls, session): return session.query(cls).order_by(cls.founding_year).first() def __repr__(self): - return f"" + return f'' + class Dev(Base): __tablename__ = 'devs' @@ -37,21 +37,20 @@ class Dev(Base): id = Column(Integer(), primary_key=True) name = Column(String()) - freebies = relationship("Freebie", backref="dev", cascade="all, delete-orphan") - - @property - def companies(self): - return list({freebie.company for freebie in self.freebies}) + + freebies = relationship("Freebie", back_populates="dev") + companies = relationship("Company", secondary="freebies", back_populates="devs", viewonly=True) def received_one(self, item_name): - return any(f.item_name == item_name for f in self.freebies) + return any(freebie.item_name == item_name for freebie in self.freebies) def give_away(self, other_dev, freebie): if freebie in self.freebies: freebie.dev = other_dev def __repr__(self): - return f"" + return f'' + class Freebie(Base): __tablename__ = 'freebies' @@ -59,12 +58,15 @@ class Freebie(Base): 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')) + + dev = relationship("Dev", back_populates="freebies") + company = relationship("Company", back_populates="freebies") + def print_details(self): return f"{self.dev.name} owns a {self.item_name} from {self.company.name}" def __repr__(self): - return f"" + return f'' diff --git a/lib/seed.py b/lib/seed.py index 67cc463bd..24420ffcc 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,29 +1,39 @@ -#!/usr/bin/env python3 - -# Script goes here! from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker + from models import Base, Company, Dev, Freebie +# Create the engine and bind it to the database engine = create_engine('sqlite:///freebies.db') +Base.metadata.bind = engine + +# Create a session Session = sessionmaker(bind=engine) session = Session() -# -Base.metadata.drop_all(engine) -Base.metadata.create_all(engine) - - -c1 = Company(name="Google", founding_year=1998) -c2 = Company(name="Amazon", founding_year=1994) - -d1 = Dev(name="Alice") -d2 = Dev(name="Bob") - -f1 = Freebie(item_name="T-shirt", value=10, company=c1, dev=d1) -f2 = Freebie(item_name="Mug", value=5, company=c2, dev=d1) -f3 = Freebie(item_name="Sticker", value=1, company=c1, dev=d2) - -session.add_all([c1, c2, d1, d2, f1, f2, f3]) +# Clear old data (optional: helpful for reseeding) +session.query(Freebie).delete() +session.query(Dev).delete() +session.query(Company).delete() + +# Create some companies +company1 = Company(name="Google", founding_year=1998) +company2 = Company(name="Amazon", founding_year=1994) +company3 = Company(name="OpenAI", founding_year=2015) + +# Create some developers +dev1 = Dev(name="Alice") +dev2 = Dev(name="Bob") +dev3 = Dev(name="Charlie") + +# Create some freebies +freebie1 = Freebie(item_name="T-shirt", value=20, company=company1, dev=dev1) +freebie2 = Freebie(item_name="Sticker", value=5, company=company2, dev=dev2) +freebie3 = Freebie(item_name="Mug", value=15, company=company3, dev=dev1) +freebie4 = Freebie(item_name="Notebook", value=10, company=company1, dev=dev3) + +# Add and commit everything +session.add_all([company1, company2, company3, dev1, dev2, dev3, freebie1, freebie2, freebie3, freebie4]) session.commit() -print("🌱 Database seeded!") + +print("✅ Database seeded successfully!") From 7ba0912524b39e1fd3a392cd98469d5d97722dd7 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 25 May 2025 22:40:05 +0300 Subject: [PATCH 4/5] updates --- lib/__pycache__/models.cpython-38.pyc | Bin 3147 -> 3592 bytes lib/debug.py | 29 +++++++++++++++----- lib/freebies.db | Bin 24576 -> 24576 bytes lib/models.py | 37 +++++++++++++------------- 4 files changed, 40 insertions(+), 26 deletions(-) diff --git a/lib/__pycache__/models.cpython-38.pyc b/lib/__pycache__/models.cpython-38.pyc index 5d6118ce22f5a7bb94392084afcb1f50cb564d00..fbafb4032062f8a0b6ee633430d235f7b5acc49d 100644 GIT binary patch literal 3592 zcmb_f&yU+g6rQo2#PKHC6iTT|fj|K@&?bc|0Q2J@h@g(4e7{+sG-;Vk_lVXA2Lyw%_GxoVeE)CjMwyd9pi>*V%*f@wrJgD z-Sxj=4eM6&>a8T&k4Jg!t5GJmrOK;!GZl`ubLUQ?WVk*0T26BN8=3jH{mkzgc|*y_ z&%$Js?u6sKHt>U8C5K&}w}lKMUqN;ty@8)f*BI_jKOgRTZ(q**K_tDAAIolvVW9W) zt}SM;gj7ljZ&|a;-81+G)_6fOI*=pU7CYH7%n>jrphGM_bFhb)mCWUWxrX4bDJ@w! z0>Hxj&2Xz2)sEN^ckN;xy}EP=WPob`WTc@ZcDCZP64h%$*VI zfU=#}ZYA;9A5HRlpgW;VzdeSx#wv_`HSu-bJO0YBvT*zd-kb(bA8pk@kr zaBSp8?^<3P7RIPEP}j{rTm79Rmi=vius=xBv=3z&CF2;N_k%>p{&)v2z1|;BvYljf zb$tEmY>lg#LI_CJkHSGehGvxZ#(-iYl^Gp%^Bmblj;S((O<$j3)x!p9ZzDMN8%utw129ga9+9jpd~S+Od2p=v47)TrZ+Ss8Sw2 zusA!k4vj2rM`LgSD>hS#6)$2i74 z1Ha}I2EX$ym|xk0#S_;~l#t3BO!DS1*&hi?tBLegUKxfe&2SPkh|;{4$}~luJ#B9* ziG)luWTK9BI%Wy{xzgTMm6a$ZM9B?viv?NA{zOhvaO2l}RMFs1TN|_WIz$*ZrtK@8 z6@zd5!#82AMN;(^)>N-hGmqsWJZ#TXa;!YB17Tq)pSDX&OW{`cg&dl+l;`D+=fRo# zpeiqKc;4Q=AI&69uVAKPs0e>$qREX=C<-<8E;ZVk8uc!a&>pK~ELAp9uhQHbG`9wl zOj9t}P9oH0ntqR(h4s2eK2>XI=uK&4bIWLPhdV~YFt}sd7PVR@fOzF0gKpwYe?W68 z8m!OB&%q5-l3!y+e$9gV+}4cv)@Q_L-j)aYXy2+Pb}Hgr=SCaDXZ%lqY5)I^)TV($ zs!}ql7pYkwsX=@hb64>$D5wLgdqo(7sZZb_Md^4_P^kt4FD6#Z8?(n z$7=H$twq*Y#KD)yY9#{+1`=K}l1mtrUl1MOO4D)NH40YK8)A3 zL#jQ!NkB-H5A{iL9uDAO(7|q1wZX)Ohl_i5Kx|IAq$#y)F45~tG`s5TysjD3|H+?x z0I5DPL(WXKpXRdX2PP5qf|wqe`nH`q8#qfrMyd-9gqzOx6KF>#^GCGz42A~5uwa|E zx&oo1fK)wd2(ks8D00*ipUkO5y-xxPKlL#+pHTB$;-JvEgjbgXGvX*|1H_?79^hSh z=A<#e^r=Wg*HpT8(mkr6P@+)%RE{fp+z?e!L%q~23RTdE17Oz*x*+Ek^f9dostH0d zZ7i}g6wi2Tev`k0PcvN`-7|23(1l{oE)U$CU?@k$d7^@8Bdndfnrk8zKWclw-!phM8!&{2UXGT zg(Zo+pP{;({})kpQh>eXKL|yC*j(!i)U*2f)ameaji!l3Gn~Cq)C8MU!svqYB5(7C QX)xno%cKjreZGC}Kc~j&ssI20 delta 1573 zcma)6QE%H+6!x{9#CB{a8EZRr-D(gB0$EyU*%%EIjI9%@G$GL;6p$=8_qKIO?C3hA zBtohXd*Edc)qPr?Rt|au)9;TWoav^WyOzSBVr$V$YElQ|P?FVMtr4B9M*V-QQsY|{4+E=7qp+2p^w<@iEpw(-#^lsYgN6ARq z9c9EU+dhFGT-1XN>F-3HPs4uN|EwDgSSF2IY$&Z=&e#sbofy*2SSL+r#7F!*R3fcm z%ns9}I}+a*w@FPrHWsc+gR&t%2kyEqi@lVxZpLf)T99VOy1RS{Uic!yMFjMnXna{b z(zktgFCMaR^2>Q~$J~M9el%B?moZd90G9OAE@jyu%y%(b6n~j(qEWKN@AmvXyEWNm z@He>uyEBuh$4&^pJY8e9kQIiU^?4YgGt&VyGOkYYo2SmZ^_uv#uzqCV3MPQmW6BHo z)ZPmj`p;u^L8U)`5ppU24n7#&1$H@gL4r##GUa^bjJZ`!TR z{Im(iMk9D$u?cfDJ1ov3^^SPxUVi=D|K@MDqAvb)FTAg)R0or){w>_XnIxiQu3`00 z36)ey6{vJ|%iuNfj<' - + return f"" class Dev(Base): __tablename__ = 'devs' @@ -37,20 +38,21 @@ class Dev(Base): id = Column(Integer(), primary_key=True) name = Column(String()) - - freebies = relationship("Freebie", back_populates="dev") - companies = relationship("Company", secondary="freebies", back_populates="devs", viewonly=True) + freebies = relationship('Freebie', backref='dev') + + @property + def companies(self): + return list({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, other_dev, freebie): + def give_away(self, dev, freebie): if freebie in self.freebies: - freebie.dev = other_dev + freebie.dev = dev def __repr__(self): - return f'' - + return f"" class Freebie(Base): __tablename__ = 'freebies' @@ -58,15 +60,12 @@ class Freebie(Base): 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')) - - dev = relationship("Dev", back_populates="freebies") - company = relationship("Company", back_populates="freebies") - def print_details(self): - return f"{self.dev.name} owns a {self.item_name} from {self.company.name}" + print(f"{self.dev.name} owns a {self.item_name} from {self.company.name}") def __repr__(self): - return f'' + return f"" From 20c89a19a2eb2d3e2c220258455a5b816e08c831 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sun, 25 May 2025 22:42:23 +0300 Subject: [PATCH 5/5] updates --- lib/debug.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/debug.py b/lib/debug.py index 7f6349c4b..fe2cb37fe 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -6,23 +6,23 @@ Session = sessionmaker(bind=engine) session = Session() -# Test class method + print("Oldest company:", Company.oldest_company(session)) -# Test instance method (give_freebie) + c1 = session.query(Company).first() d1 = session.query(Dev).first() f1 = c1.give_freebie(d1, "Water Bottle", 20) session.add(f1) session.commit() -# Test freebie method (print_details) + f1.print_details() -# Test give_away method + receiver = session.query(Dev).filter(Dev.id != d1.id).first() d1.give_away(receiver, f1) session.commit() -# Confirm it worked + f1.print_details()