From d0f71aef8e00cebde4a44b01654259660fc4c914 Mon Sep 17 00:00:00 2001 From: MELODY SIMWA Date: Tue, 4 Mar 2025 09:59:20 +0300 Subject: [PATCH] create freebies table --- alembic.ini | 105 ++++++++++++++++++ freebies.db | Bin 0 -> 24576 bytes lib/__pycache__/models.cpython-312.pyc | Bin 0 -> 4783 bytes lib/migrations/env.py | 3 + .../versions/7a71dbf71c64_create_db.py | 14 ++- lib/models.py | 69 ++++++++++-- lib/seed.py | 56 +++++++++- migrations/README | 1 + migrations/__pycache__/env.cpython-312.pyc | Bin 0 -> 2565 bytes migrations/env.py | 78 +++++++++++++ migrations/script.py.mako | 24 ++++ .../f4c2af393f6c_empty_init.cpython-312.pyc | Bin 0 -> 712 bytes .../versions/f4c2af393f6c_empty_init.py | 24 ++++ 13 files changed, 358 insertions(+), 16 deletions(-) create mode 100644 alembic.ini create mode 100644 freebies.db create mode 100644 lib/__pycache__/models.cpython-312.pyc create mode 100644 migrations/README create mode 100644 migrations/__pycache__/env.cpython-312.pyc create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/__pycache__/f4c2af393f6c_empty_init.cpython-312.pyc create mode 100644 migrations/versions/f4c2af393f6c_empty_init.py diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 000000000..953863ddd --- /dev/null +++ b/alembic.ini @@ -0,0 +1,105 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# 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 +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(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..0b1e9d3542b71d829e4196772a5c89b742bb80cf GIT binary patch literal 24576 zcmeI4O>7%Q6o6-cyzAY>J9g5@0vctiC~B+(q&BFc{JYz2n$~erCp4-AwZ^;Sc$NJz zyK6(ZRN?2qfg5*(gy4#L0mKCf331{ChzkNF#1$kY#F1HV-NaVg3lfL$MqcgAn>X*h z`DV4!o0VtlTFp%uX@`+d6S9JeND$ETgdoJL2r2v;7MWMlYgmL~G2Z%pUZ^6aee)_y`;9&1O8wOket z?~2*5s@oRXu3o5FWGE$zy5>4$t-fuoS`AX)*e3NCYPDs;#R>D90reTVUTv6{tE0Vr z>h;(?$2qLuJT#eFvO~X1gZ(k-W@Bx=+PFckS~tj|>r_%rbECes-Kg?#NPDk246>P- zOm8u{w9&BER_p1o#Y|oy4eOHCuE z491$`NMvK3X?%BvHG`f%d=M8`&Q?Yq)gzD1n1yC{&n6?lj@YZ{m1jhyd~HcUZs4$2 z2?y=wxUn;10*SwB^6BfQz%K#+f%p#!5C8%|00;m9AOHk_01yBIKmZ5; z0U&Tp2pEF6(0<%LOWP~wSK8-nozDyM|6{VaFbyC81b_e#00KY&2mk>f00e*l5C8&q z2w-VJJDUHe-~V+yiSUp3Yy1&@7x(cFw(uD|$AwUU01yBIKmZ5;0U!VbfB+Bx0zd!= z976&nT}JcbPVdppj_Y}DH&#C^YBD+{UZnvet5MkNs$WdyWwam;HApRS)L)9~Kr3)} zI|-@PO!d22MMh7FW{3JM{^x;ZvO_(O(7++D zEGp(xN!STUBW$?=Pxnizgeq649kz%`{cekSo}ox+zV6zg$4I*MFq4ze8M70)aT0br zJh|Vb5w}&6`ADfQwl0` z9Y5?PolBBxl(M$Rj{ppw)^Za?1H{7of_ywk&Fn&Jdx|%;Rp`dx*pG4Gg(=pU@w0uEN{Rl#v+U!L! zU(_-quHTKqJ}XJeWIi*(zr~-E#G;z@Q`_d-2g$R#T2d4=o4E}f8aZVxk8=Yht;KBG xi`f$rmea`#0_}GB^e*d~A!W05B1Zj^nlFfVq6ov}W=@$96jb8+;U3m={{YQ&@}~d* literal 0 HcmV?d00001 diff --git a/lib/__pycache__/models.cpython-312.pyc b/lib/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd00590c80db7d3b5f75bcce5eeb3e710ca3ec78 GIT binary patch literal 4783 zcmbtYU2GKB6~41Sv$Oy9+8E;4WKx{K0``)UmL_qaU}HiEw!wykm8$7@=h|MpGrQaw zo9@a;TNNoriiDBc*ijqRc{9RQc}$;5U)o50S=URAN0rj3d1&7p%tOjk&$+X+7~`l_ zI?|lI_uO;;&d+zw?4P625P@g#lRu7~2@~=U?6{A=*4X$fH0Fp#G$BWHAuZ^hv`6=* zy}B>$)BS0`E~Z5R$33}#E~O?S3tT6fL}PcnHMuzF27w#e#BJc*FmNNAxQ$vgoq%;kny9bZ1pOdwT8h;< z&073AN$)5&Bpd&QnszYN@@d zrP|R9qpC#}YK&zKn)KNbMsuo_%^T);wqQq0YMRi|)d`s1pwUcDWiUKNl@ZmXc3?!! zOfWiXdqyWpZ;VbTFYU9`ksMVFRi{Z4x*2{1<rfil>3U(vRfx{`8yfH-$ zJOVH!9ZssjNi&WT9a^{O&!Im@C`k(%NqaOQ?FAkAs9*CedGB}};k3wk0nGjR5gr!KG8_}Ym`KT5H8l!Qv!=2g*(+y}3JJ6HY3#w7H zgBjk)QgisWV22AVtE;T2OweK<^d)05mqV4=QXivq1jOusGo$!8q2_W2WsT;jMLYAX zFs>T6JuCtnfsATqRE^r+UOHtFqBCyK8rn9}7p}>^`(I{+cv5YU1MZ;|rUX3eEkI-ZdXnB! ze-95cQ&wezUX^Q>ma~RwsYZs%s%hpkS+J2NU(H(MvNfJH<(e1Bs-Xdc%3~0A26)3% z7sP2WYf+sCmF=HWbCci*Fv3t7{Yfv|3BR^i)ihg#W%R6-6xqwj3)CW-bwZCFKyr}t zT@OO_$oye@aUg=*Dz(v4v#b5h+~+~g+y|sgzD%?(T)Io|HGQU46GJnB2U7fI_(pj4 zR8?xjcI-y%zO;)aK{!*3MQAz(;YMN5nM0-4&BDhynIc%)JpMjm!&`4Uhrq2f@YWwUhBu~w^{rD%@R{@xg7gv8wx*9TPahGU zKB7E{$1rKIHrSjU12W8-fc7!OH6F}PygrBBRwP`3sCc#;$p4G%{%$~OnPDnmdyu>Y z#6fh&UTD`+1cd6an?eZ}c3Wnxx#F$j?8HK2CBCO3?RhrB(T#Y7{}3AQ3!8y=Mi><| z;kpDFXfq~&96r7E%<4uz#H*+DmK(3bY?8{^Q69J8gEC|mnb8ad*wSQGt|6C;t1gVz z;j`-*-5rKaH@boRLO4SNL?iMeu*Nk3PzevT<+_#9>7mZgTRhrnPqF|ELMU&a$FiGh2~e`^0jdo?kz5+A5Y1CFV2OZUOm zxv2-C0qXN^Us_BNwhQvDSE)n&f6b*r5$;qP7-p(rLKW$NwQFH352-Q_nUZ0WbX~$7 z9f3l`7=syk?kSl`s6h>jr&COrJd}7&M8{709AzO&JCTRx<9fx^=qN78bD(-vEtaCF zwR#tk(L{_alfQ=(v)Y_~OJ5G}dC=NEbLxXL44(Nu-0TuRj@Quj5{C zAVJ?_Zz4gha_nVCvGoLe>$uAjIKW}|ZEU@R53cy4O1l!hc23rSU z{gq=w)%Y*~Y(v}prMvq-yI5@)E}uq#?RY49{DZ=VVZ||<``@yH;r`GSMe>c5)Y;gJ5VCCq!%DMBE_)tX} zs`+{BfA;fwZOTxkwg@&+z$Bqsfdp;G?YB5PR=(V<7)9p0K+xHDZ*lg9mf5LAf3=~j ze0nXCm_4#^p&B_*KDiccnmzSVdNKIv>1uo5O7v8@Z>@3XeBhJT#r}JjsxJ<#G!B;E z-Vg=v_cXD>O9+-bGt7K ztL_SJI)rZw!q7HQg!)dvcNL362NdOe+QT=bEjr)#TY#bd_0PY=O?C;U4Zv$Q11Xcg z`K0&H{`BmsuWi}a_N$BceJ)nD*de&En#+t+y~wSz38?UR41=FZK# zJLT None: - pass - - + op.create_table( + 'freebies', + sa.Column('id', sa.Integer, primary_key=True), + sa.Column('item_name', sa.String, nullable=False), + sa.Column('value', sa.Integer, nullable=False), + sa.Column('dev_id', sa.Integer, sa.ForeignKey('devs.id', ondelete="CASCADE")), + sa.Column('company_id', sa.Integer, sa.ForeignKey('companies.id', ondelete="CASCADE")), + ) + def downgrade() -> None: - pass + op.drop_table('freebies') diff --git a/lib/models.py b/lib/models.py index 2681bee5a..8e8cbbe69 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,29 +1,76 @@ -from sqlalchemy import ForeignKey, Column, Integer, String, MetaData -from sqlalchemy.orm import relationship, backref -from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import ForeignKey, Column, Integer, String, MetaData, create_engine +from sqlalchemy.orm import relationship, sessionmaker, declarative_base, backref + convention = { - "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "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): __tablename__ = 'companies' + + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) + founding_year = Column(Integer, nullable=False) - id = Column(Integer(), primary_key=True) - name = Column(String()) - founding_year = Column(Integer()) + freebies = relationship("Freebies", backref="company", cascade="all, delete-orphan") + devs = relationship("Dev", secondary="freebies", back_populates="companies") def __repr__(self): - return f'' + return f"" + + def give_freebie(self, session, dev, item_name, value): + freebie = Freebies(item_name=item_name, value=value, dev=dev, company=self) + session.add(freebie) + session.commit() + + @classmethod + def oldest_company(cls, session): + return session.query(cls).order_by(cls.founding_year).first() class Dev(Base): __tablename__ = 'devs' + + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) - id = Column(Integer(), primary_key=True) - name= Column(String()) + freebies = relationship("Freebies", backref="dev", cascade="all, delete-orphan") + companies = relationship("Company", secondary="freebies", back_populates="devs") def __repr__(self): - return f'' + return f"" + + def received_one(self, item_name): + return any(freebie.item_name == item_name for freebie in self.freebies) + + def give_away(self, session, dev, freebie): + if freebie in self.freebies: + freebie.dev = dev + session.commit() + +class Freebies(Base): + __tablename__ = 'freebies' + + id = Column(Integer, primary_key=True) + item_name = Column(String, nullable=False) + value = Column(Integer, nullable=False) + dev_id = Column(Integer, ForeignKey('devs.id', ondelete="CASCADE")) + company_id = Column(Integer, ForeignKey('companies.id', ondelete="CASCADE")) + + + def __repr__(self): + return f"{self.dev.name} owns a {self.item_name} from {self.company.name}." + + +DATABASE_URL = 'sqlite:///freebies.db' +engine = create_engine(DATABASE_URL) + +Session = sessionmaker(bind=engine) + +def create_tables(): + Base.metadata.create_all(engine) diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..c83a8f16f 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,57 @@ #!/usr/bin/env python3 -# Script goes here! +from faker import Faker +import random +from sqlalchemy.orm import sessionmaker +from models import engine, Freebies, Dev, Company, create_tables + +# Ensure tables exist before inserting data +create_tables() + +Session = sessionmaker(bind=engine) +session = Session() + +# Clear existing data +session.query(Company).delete() +session.query(Dev).delete() +session.query(Freebies).delete() +session.commit() # Commit deletion to avoid integrity issues + +fake = Faker() + +# Seed companies +companies = [ + Company(name=fake.company(), founding_year=random.randint(1950, 2023)) + for _ in range(10) +] +session.add_all(companies) # Use add_all instead of bulk_save_objects +session.commit() # Commit to get assigned IDs + +# Refresh objects to ensure IDs are populated +for company in companies: + session.refresh(company) + +# Seed developers +devs = [Dev(name=fake.name()) for _ in range(10)] +session.add_all(devs) # Use add_all instead of bulk_save_objects +session.commit() # Commit to get assigned IDs + +# Refresh objects to ensure IDs are populated +for dev in devs: + session.refresh(dev) + +# Seed freebies +freebies = [ + Freebies( + item_name=fake.word(), + value=random.randint(10, 100), + dev_id=random.choice(devs).id, # Now IDs are guaranteed to exist + company_id=random.choice(companies).id + ) + for _ in range(20) +] +session.add_all(freebies) # Use add_all instead of bulk_save_objects +session.commit() + +print("Seeding complete!") +session.close() 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-312.pyc b/migrations/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0531444859e379b2e47c3b17c004ad1ea3445568 GIT binary patch literal 2565 zcmZuz&1>976d$d$yOQ?HA6+N0T{CuVZ??ovNYb+Mk{Sc zv$i)R;6e`#6iQENX%D#;=h%P1fu4HFVuy8vb0{T+-dyZp@TqU4mF2kBg66$9Z{8cd zH^1NPADN7b;Q8t2-z%47g#O}6_=sI$ZwG`&$Uuf*p}Mdj2waX?vAVb*`mzYJv>^Gi zWX0==g@k}eqLvLqMdLz}Bx^m99!g*L#SjrrpIa9KFNhysPz>2fd?PKWFeYJ4!Ma54 zY#1jR*SjK>!9xu-0kz_wr;h7IEm=^&0TnoE*%|^qtD}KTt64oj4ck3@%A=q&20>fmP*w`k-fL&alyL$5mW)5D2 zaLbx$@eJKBxu#hb?B5BuKTTGN-elx_5&P*$-DG~Yipe9ojQZ&vZu2v`IyD;%J5APT zvTo3OS@$bTv-B#dx2Buay6veAjcRq5wJhSrZLLm>qU4D%<>i=0D}Z1=sv1tqWGZ{VtW=n5Lhm> zo<_#^1=bhNKM^AiIJSyc{=^f-2bPKA51eoVHS`3Qk@zpla8hqcCt2sazJPU3+FMeW zjpFwno<>FX|Cs284M+s)aCKs9)O3I>0@!m%b7la@hos}ws4pOk%4{@M^y33soBjcev4x8J^&dkk-8&~pk}#L=&%2 z@g*91g@$%zpsMdj9*(RfK$O&uIM@~kpNW$%r0loLo!n$QH@PJhpxXUs)^dA2d0}*K ireB!Yy?~^lwGRUKEphmTH27Q^{!=>lTpFU2km*0~o>T$= literal 0 HcmV?d00001 diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 000000000..6626bfd0c --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,78 @@ +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. +if config.config_file_name is not None: + 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() -> None: + """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() -> None: + """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..55df2863d --- /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() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/__pycache__/f4c2af393f6c_empty_init.cpython-312.pyc b/migrations/versions/__pycache__/f4c2af393f6c_empty_init.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19d35101e4ccaa769c195a4e4ab67be9a28ce603 GIT binary patch literal 712 zcmaJcX!Xv-}la+UDqKvIv+n>yrzVFRg2$4+mp>X zk}JYUNEl^WMsqFHsN#C2hXymk7Slr$r-jpI7HfSl!Zt<|qtoa;`|{0;oY#$)7HRD` zuV9r{X<2wLCu47Zk__W{Fbd{p$?gQIvFAJ&5ZB;M5XRncFg)!K0(`vV)A3+5K6!c+ zjLwb+1IPKMRQ9OUNYCkC%_!acI0Pgcej5P3hQ!mP*&x8J40L)V8*CI~dg7EY7Lc}%NtNaxR<+Jkd z%Zfm?B2uk5;*GSH9P=3nbrFx0oEE~G+!(g7k^SF=FfnE8Vh8FC(MFJMR=zKGm0etS z)j>yrO6h6R6i_ckG3|Av6-BH}qDWeC2Kg*agzA)Zs&`qOB@4(Ksa3JG#s1!;t7=Do zmopI;$s)?)8Dy1o7;q?96_v%-lu+OQN8X)%o|kNy!84(h;jc%kA;PUrDgCaQ)cE;; h(C!z~y|u}~leK;G@bTK+fqS@iHYTO*A2!0)&o5A~v{nEB literal 0 HcmV?d00001 diff --git a/migrations/versions/f4c2af393f6c_empty_init.py b/migrations/versions/f4c2af393f6c_empty_init.py new file mode 100644 index 000000000..0c87b5d1c --- /dev/null +++ b/migrations/versions/f4c2af393f6c_empty_init.py @@ -0,0 +1,24 @@ +"""Empty init + +Revision ID: f4c2af393f6c +Revises: +Create Date: 2025-03-03 15:09:47.396100 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f4c2af393f6c' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass