diff --git a/Pipfile b/Pipfile index 63c79cd98..c5b7f4dcb 100644 --- a/Pipfile +++ b/Pipfile @@ -7,8 +7,8 @@ name = "pypi" alembic = "1.8.1" importlib-metadata = "6.0.0" importlib-resources = "5.10.0" -ipdb = "0.13.9" sqlalchemy = "1.4.42" +ipdb = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 3923f4a97..a47c681b2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "fa62c636b7ae5acb4993fc7b007a651c55b562aa29962a9a8585ec314b352ae6" + "sha256": "932bb367ba9b55f09db8822137fe32f19ecfa9cb8dd7ed234933e266fe50f909" }, "pipfile-spec": 6, "requires": { @@ -34,10 +34,11 @@ }, "asttokens": { "hashes": [ - "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3", - "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c" + "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", + "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2" ], - "version": "==2.2.1" + "markers": "python_version >= '3.8'", + "version": "==3.0.0" }, "backcall": { "hashes": [ @@ -48,18 +49,19 @@ }, "decorator": { "hashes": [ - "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", - "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", + "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a" ], - "markers": "python_version < '3.11' and python_version >= '3.7'", - "version": "==5.1.1" + "markers": "python_version >= '3.8'", + "version": "==5.2.1" }, "executing": { "hashes": [ - "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc", - "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107" + "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", + "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755" ], - "version": "==1.2.0" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "importlib-metadata": { "hashes": [ @@ -79,27 +81,28 @@ }, "ipdb": { "hashes": [ - "sha256:c23b6736f01fd4586cc2ecbebdf79a5eb454796853e1cd8f2ed3b7b91d4a3e93", - "sha256:f74c2f741c18b909eaf89f19fde973f745ac721744aa1465888ce45813b63a9c" + "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4", + "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726" ], "index": "pypi", - "version": "==0.13.11" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.13" }, "ipython": { "hashes": [ - "sha256:da01e6df1501e6e7c32b5084212ddadd4ee2471602e2cf3e0190f4de6b0ea481", - "sha256:f3bf2c08505ad2c3f4ed5c46ae0331a8547d36bf4b21a451e8ae80c0791db95b" + "sha256:3910c4b54543c2ad73d06579aa771041b7d5707b033bd488669b4cf544e3b363", + "sha256:b0340d46a933d27c657b211a329d0be23793c36595acf9e6ef4164bc01a1804c" ], - "markers": "python_version < '3.11' and python_version >= '3.7'", - "version": "==8.8.0" + "markers": "python_version >= '3.8'", + "version": "==8.12.3" }, "jedi": { "hashes": [ - "sha256:203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e", - "sha256:bae794c30d07f6d910d32a7048af09b5a39ed740918da923c6b780790ebac612" + "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", + "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9" ], "markers": "python_version >= '3.6'", - "version": "==0.18.2" + "version": "==0.19.2" }, "mako": { "hashes": [ @@ -167,27 +170,27 @@ }, "matplotlib-inline": { "hashes": [ - "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", - "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" + "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", + "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" ], - "markers": "python_version >= '3.5'", - "version": "==0.1.6" + "markers": "python_version >= '3.8'", + "version": "==0.1.7" }, "parso": { "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", + "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" ], "markers": "python_version >= '3.6'", - "version": "==0.8.3" + "version": "==0.8.4" }, "pexpect": { "hashes": [ - "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", - "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" + "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", + "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" ], "markers": "sys_platform != 'win32'", - "version": "==4.8.0" + "version": "==4.9.0" }, "pickleshare": { "hashes": [ @@ -198,11 +201,11 @@ }, "prompt-toolkit": { "hashes": [ - "sha256:3e163f254bef5a03b146397d7c1963bd3e2812f0964bb9a24e6ec761fd28db63", - "sha256:aa64ad242a462c5ff0363a7b9cfe696c20d55d9fc60c11fd8e632d064804d305" + "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", + "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed" ], - "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.36" + "markers": "python_version >= '3.8'", + "version": "==3.0.51" }, "ptyprocess": { "hashes": [ @@ -213,18 +216,18 @@ }, "pure-eval": { "hashes": [ - "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", - "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" + "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", + "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42" ], - "version": "==0.2.2" + "version": "==0.2.3" }, "pygments": { "hashes": [ - "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297", - "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717" + "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", + "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" ], - "markers": "python_version >= '3.6'", - "version": "==2.14.0" + "markers": "python_version >= '3.8'", + "version": "==2.19.1" }, "six": { "hashes": [ @@ -283,33 +286,71 @@ }, "stack-data": { "hashes": [ - "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815", - "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8" + "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", + "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" ], - "version": "==0.6.2" + "version": "==0.6.3" }, "tomli": { "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", + "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", + "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", + "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", + "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", + "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", + "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", + "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", + "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", + "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", + "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", + "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", + "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", + "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", + "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", + "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", + "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", + "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", + "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", + "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", + "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", + "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", + "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", + "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", + "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", + "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", + "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", + "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", + "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", + "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", + "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", + "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7" ], - "markers": "python_version < '3.11' and python_version >= '3.7'", - "version": "==2.0.1" + "markers": "python_version >= '3.8'", + "version": "==2.2.1" }, "traitlets": { "hashes": [ - "sha256:32500888f5ff7bbf3b9267ea31748fa657aaf34d56d85e60f91dda7dc7f5785b", - "sha256:a1ca5df6414f8b5760f7c5f256e326ee21b581742114545b462b35ffe3f04861" + "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", + "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" ], - "markers": "python_version >= '3.7'", - "version": "==5.8.1" + "markers": "python_version >= '3.8'", + "version": "==5.14.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", + "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" + ], + "markers": "python_version >= '3.8'", + "version": "==4.13.2" }, "wcwidth": { "hashes": [ - "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", - "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" ], - "version": "==0.2.6" + "version": "==0.2.13" }, "zipp": { "hashes": [ diff --git a/README.md b/README.md index b598b7784..26820e2e5 100644 --- a/README.md +++ b/README.md @@ -1,182 +1,50 @@ -# Phase 3 Mock Code Challenge: Freebie Tracker +# Freebie Tracker -## Learning Goals +This is a simple Python CLI project using SQLAlchemy to track developer freebies from companies. It includes database migrations with Alembic and supports basic CRUD operations via CLI. -- Write SQLAlchemy migrations. -- Connect between tables using SQLAlchemy relationships. -- Use SQLAlchemy to run CRUD statements in the database. +## Project Structure -*** +python-p3-freebie-tracker/ +├── config/ +│ └── setup.py +├── db/ +│ └── store.db +├── lib/ +│ ├── models.py +│ ├── seed.py +│ └── debug.py +├── migrations/ +├── run.py +├── alembic.ini +└── README.md -## 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 +## ⚙️ Setup -For this assignment, we'll be working with a freebie domain. +```bash +pipenv install +pipenv shell +alembic upgrade head +python lib/seed.py # Populate initial data +python run.py # Start the CLI + Features +Track freebies given by companies to developers. -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. +View companies, devs, and their associations. -We have three models: `Company`, `Dev`, and `Freebie` +Seed and debug scripts included for testing and development. -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`. + ER Diagram +View the entity relationship diagram here: +https://dbdiagram.io/d/683732fbc07db17e77908287 -`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. +- GitHub Repository +Source Code: +https://github.com/jedaqsaul/python-p3-freebie-tracker -## 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. - -*** - -## 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: - -### companies Table - -| Column | Type | -| ------------- | ------- | -| name | String | -| founding_year | Integer | - -### devs Table - -| Column | Type | -| ------ | ------ | -| name | String | - -You will need to create the migration for the `freebies` table using the -attributes specified in the deliverables below. - -*** - -## Deliverables - -Write the following methods in the classes in the files provided. Feel free to -build out any helper methods if needed. - -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. - -### Migrations - -Before working on the rest of the deliverables, you will need to create a -migration for the `freebies` table. - -- 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. - -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. - -**After you've set up your `freebies` table**, work on building out the following -deliverables. - -### Relationship Attributes and Methods - -Use SQLAlchemy's `ForeignKey`, `relationship()`, and `backref()` objects to -build relationships between your three models. - -**Note**: The plural of "freebie" is "freebies" and the singular of "freebies" -is "freebie". - -#### Freebie - -- `Freebie.dev` returns the `Dev` instance for this Freebie. -- `Freebie.company` returns the `Company` instance for this Freebie. - -#### Company - -- `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. - -#### 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. - -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). - -### Aggregate Methods - -#### Freebie - -- `Freebie.print_details()`should return a string formatted as follows: - `{dev name} owns a {freebie item_name} from {company name}`. - -#### Company - -- `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. - -#### 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 diff --git a/lib/alembic.ini b/alembic.ini similarity index 97% rename from lib/alembic.ini rename to alembic.ini index 953863ddd..fb1563a78 100644 --- a/lib/alembic.ini +++ b/alembic.ini @@ -2,7 +2,7 @@ [alembic] # path to migration scripts -script_location = migrations +script_location = lib/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 @@ -55,7 +55,7 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne # are written from script.py.mako # output_encoding = utf-8 -sqlalchemy.url = sqlite:///freebies.db +sqlalchemy.url = sqlite://lib/freebies.db [post_write_hooks] diff --git a/freebies.db b/freebies.db new file mode 100644 index 000000000..e86a44534 Binary files /dev/null and b/freebies.db differ diff --git a/lib/__pycache__/models.cpython-38.pyc b/lib/__pycache__/models.cpython-38.pyc new file mode 100644 index 000000000..d400a8238 Binary files /dev/null and b/lib/__pycache__/models.cpython-38.pyc differ diff --git a/lib/__pycache__/seed.cpython-38.pyc b/lib/__pycache__/seed.cpython-38.pyc new file mode 100644 index 000000000..e02dcfd9f Binary files /dev/null and b/lib/__pycache__/seed.cpython-38.pyc differ diff --git a/lib/debug.py b/lib/debug.py old mode 100644 new mode 100755 index 4f922eb69..014b4d0d2 --- a/lib/debug.py +++ b/lib/debug.py @@ -2,8 +2,12 @@ from sqlalchemy import create_engine -from models import Company, Dev + +from models import Company, Dev,Freebie + + +print("\n Interactive Debug Mode. You can now test your models.") if __name__ == '__main__': - engine = create_engine('sqlite:///freebies.db') + import ipdb; ipdb.set_trace() diff --git a/lib/migrations/__pycache__/env.cpython-38.pyc b/lib/migrations/__pycache__/env.cpython-38.pyc new file mode 100644 index 000000000..34cb9387e Binary files /dev/null and b/lib/migrations/__pycache__/env.cpython-38.pyc differ diff --git a/lib/migrations/env.py b/lib/migrations/env.py index c7aab9656..49698f500 100644 --- a/lib/migrations/env.py +++ b/lib/migrations/env.py @@ -4,6 +4,10 @@ from sqlalchemy import pool from alembic import context +from lib.models import Base +from lib.seed import engine + + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -18,7 +22,7 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -from models import Base + target_metadata = Base.metadata # other values from the config, defined by the needs of env.py, @@ -27,7 +31,7 @@ # ... etc. -def run_migrations_offline() -> None: +def run_migrations_offline(): """Run migrations in 'offline' mode. This configures the context with just a URL @@ -51,22 +55,12 @@ def run_migrations_offline() -> None: 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: +def run_migrations_online(): + with engine.connect() as connection: context.configure( - connection=connection, target_metadata=target_metadata, render_as_batch=True, + connection=connection, + target_metadata=target_metadata, + render_as_batch=True, ) with context.begin_transaction(): diff --git a/lib/migrations/versions/99787e66492d_add_freebie_table.py b/lib/migrations/versions/99787e66492d_add_freebie_table.py new file mode 100644 index 000000000..6e38b3f76 --- /dev/null +++ b/lib/migrations/versions/99787e66492d_add_freebie_table.py @@ -0,0 +1,37 @@ +"""add freebie table + +Revision ID: 99787e66492d +Revises: 5f72c58bf48c +Create Date: 2025-05-28 18:05:39.806821 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '99787e66492d' +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('company_id', sa.Integer(), nullable=True), + 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') + ) + # ### 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__/5f72c58bf48c_create_companies_devs.cpython-38.pyc b/lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-38.pyc new file mode 100644 index 000000000..746430947 Binary files /dev/null and b/lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-38.pyc differ diff --git a/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-38.pyc b/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-38.pyc new file mode 100644 index 000000000..6f54eedf7 Binary files /dev/null and b/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-38.pyc differ diff --git a/lib/migrations/versions/__pycache__/99787e66492d_add_freebie_table.cpython-38.pyc b/lib/migrations/versions/__pycache__/99787e66492d_add_freebie_table.cpython-38.pyc new file mode 100644 index 000000000..d7abfd6de Binary files /dev/null and b/lib/migrations/versions/__pycache__/99787e66492d_add_freebie_table.cpython-38.pyc differ diff --git a/lib/models.py b/lib/models.py index 2681bee5a..fc713c5cb 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,7 +1,9 @@ from sqlalchemy import ForeignKey, Column, Integer, String, MetaData -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base +# naming conventions for constraints + convention = { "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", } @@ -15,6 +17,15 @@ class Company(Base): id = Column(Integer(), primary_key=True) name = Column(String()) founding_year = Column(Integer()) + # relationships + freebies=relationship("Freebie", back_populates="company") + + devs=relationship( + "Dev", + secondary="freebies", + back_populates="companies", + viewonly=True + ) def __repr__(self): return f'' @@ -25,5 +36,37 @@ class Dev(Base): id = Column(Integer(), primary_key=True) name= Column(String()) + # relationships + freebies=relationship("Freebie", back_populates="dev") + + companies=relationship( + "Company", + secondary="freebies", + back_populates="devs", + viewonly=True + ) + def __repr__(self): return f'' + + +class Freebie(Base): + __tablename__='freebies' + id =Column(Integer, primary_key=True) + item_name=Column(String) + value=Column(Integer) + + company_id=Column(Integer, ForeignKey('companies.id')) + dev_id=Column(Integer, ForeignKey('devs.id')) + + company=relationship("Company", back_populates='freebies') + dev=relationship("Dev", back_populates="freebies") + + def __repr__(self): + return f'' + + + # Here we added Freebie class because the project is called freebie tracker + # Establish relationship between companies, devs and freebies + + diff --git a/lib/seed.py b/lib/seed.py old mode 100644 new mode 100755 index b16becbbb..87393d97d --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,44 @@ #!/usr/bin/env python3 -# Script goes here! +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine + +from models import Company, Dev, Freebie + + +engine = create_engine("sqlite:///freebies.db", echo=False) + + +Session = sessionmaker(bind=engine) +session = Session() + +def seed_data(): + + c1 = Company(name="TechCorp", founding_year=2005) + c2 = Company(name="DevSolutions", founding_year=2010) + + + d1 = Dev(name="Alice") + d2 = Dev(name="Bob") + + + session.add_all([c1, c2, d1, d2]) + session.commit() + + + f1 = Freebie(item_name="Sticker Pack", value=0, company_id=c1.id, dev_id=d1.id) + f2 = Freebie(item_name="T-Shirt", value=20, company_id=c2.id, dev_id=d2.id) + f3 = Freebie(item_name="Mug", value=10, company_id=c1.id, dev_id=d2.id) + + + session.add_all([f1, f2, f3]) + session.commit() + + print("Seed data added successfully! 🌱") + + + + + +if __name__ == "__main__": + seed_data() diff --git a/run.py b/run.py new file mode 100755 index 000000000..0d6b2e36d --- /dev/null +++ b/run.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python3 + +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine +from lib.models import Dev, Company, Freebie + + +# set up DB session + +engine=create_engine("sqlite:///freebies.db") +Session =sessionmaker(bind=engine) +session=Session() + + +def print_line(): + print("_"*60) + # list all devs here + +def list_devs(): + devs=session.query(Dev).all() + print_line() + for dev in devs: + print(f"{dev.id}: {dev.name}") + print_line() + +def list_companies(): + companies=session.query(Company).all() + print_line() + for company in companies: + print(f"{company.id} : {company.name} (Founded {company.founding_year})") + print_line() + + # list all freebies + +def list_freebies(): + freebies = session.query(Freebie).all() + print_line() + for freebie in freebies: + print(f"{freebie.item_name} (worth ${freebie.value})") + print(f" - Given by: {freebie.company.name}") + print(f" - Received by: {freebie.dev.name}") + print_line() + + # show freebies for a developer + +def show_developer_freebies(): + name = input("Enter the developer's name: ") + + dev = session.query(Dev).filter(Dev.name == name).first() + + if not dev: + print(f"No developer found with the name {name}") + return + + if not dev.freebies: + print(f"{dev.name} has not received any freebies yet.") + return + + print(f"Freebies for {dev.name}:") + for freebie in dev.freebies: + print(f"- {freebie.item_name} from {freebie.company.name} (Worth: ${freebie.value})") + + # add a new dev +def add_dev(): + name = input("Enter the developer's name: ") + + if not name.strip(): + print("Name cannot be empty.") + return + + try: + dev = Dev(name=name) + session.add(dev) + session.commit() + printMessage("Developer added successfully!") + except Exception as e: + print(f"An error occurred: {e}") + session.rollback() + +def add_company(): + name = input("Enter company name: ") + founding_year = input("Enter founding year: ") + + if not founding_year.isdigit(): + printMessage("Founding year must be a number.") + return + + try: + company = Company( + name=name, + founding_year=int(founding_year) + ) + session.add(company) + session.commit() + printMessage("Company added successfully!") + except Exception as e: + printMessage(f"Error occurred: {e}") + session.rollback() + + # add a new freebie +def add_freebie(): + try: + print("\n--- Add a New Freebie ---") + + item_name = input("Enter the freebie item name: ") + + while True: + value = input("Enter the value of the freebie: ") + if value.isdigit(): + value = int(value) + break + else: + print("Value must be a number. Try again.") + + dev_id = input("Enter the ID of the developer receiving the freebie: ") + company_id = input("Enter the ID of the company giving the freebie: ") + + if not (dev_id.isdigit() and company_id.isdigit()): + print("Developer ID and Company ID must be numbers.") + return + + freebie = Freebie( + item_name=item_name, + value=value, + dev_id=int(dev_id), + company_id=int(company_id), + ) + + session.add(freebie) + session.commit() + printMessage(" Freebie added successfully!") + + except Exception as e: + print(f" An error occurred: {e}") + session.rollback() + +# delete a dev here +def delete_dev(): + try: + dev_id = input("Enter the ID of the developer to delete: ") + if not dev_id.isdigit(): + print(" ID must be a number.") + return + + dev = session.query(Dev).get(int(dev_id)) + if not dev: + print(" Developer not found.") + return + + session.delete(dev) + session.commit() + printMessage(f" Developer '{dev.name}' deleted successfully!") + + except Exception as e: + print(f" Error: {e}") + session.rollback() +# delete a company function +def delete_company(): + try: + company_id = input("Enter the ID of the company to delete: ") + if not company_id.isdigit(): + print("ID must be a number.") + return + + company = session.query(Company).get(int(company_id)) + if not company: + print("Company not found.") + return + + session.delete(company) + session.commit() + printMessage(f" Company '{company.name}' deleted successfully!") + + except Exception as e: + print(f" Error: {e}") + session.rollback() + +# delete a freebie function here +def delete_freebie(): + try: + freebie_id = input("Enter the ID of the freebie to delete: ") + if not freebie_id.isdigit(): + print(" ID must be a number.") + return + + freebie = session.query(Freebie).get(int(freebie_id)) + if not freebie: + print(" Freebie not found.") + return + + session.delete(freebie) + session.commit() + printMessage(f" Freebie '{freebie.item_name}' deleted successfully!") + + except Exception as e: + print(f"Error: {e}") + session.rollback() + +# update a dev here : +def update_dev(): + while True: + dev_id = input("Enter the developer ID: ") + if not dev_id.isdigit(): + print("Developer ID must be a number. Try again!") + continue + + try: + dev = session.query(Dev).filter_by(id=dev_id).one_or_none() + if dev: + name = input("Enter the updated name: ") + + dev.name = name or dev.name + session.commit() + printMessage("Developer updated successfully!") + return + else: + printMessage("Invalid Developer ID. Try again!") + except Exception as e: + print(f"Error occurred: {e}") + session.rollback() +# update company here: + +def update_company(): + while True: + company_id = input("Enter the company ID: ") + if not company_id.isdigit(): + print("Company ID must be a number. Try again!") + continue + + try: + company = session.query(Company).filter_by(id=company_id).one_or_none() + if company: + name = input("Enter the updated company name: ") + year = input("Enter the updated founding year: ") + + company.name = name or company.name + company.founding_year = int(year) if year.isdigit() else company.founding_year + + session.commit() + printMessage("Company updated successfully!") + return + else: + printMessage("Invalid Company ID. Try again!") + except Exception as e: + print(f"Error occurred: {e}") + session.rollback() + +# update freebie here + +def update_freebie(): + while True: + freebie_id = input("Enter the freebie ID: ") + if not freebie_id.isdigit(): + print("Freebie ID must be a number. Try again!") + continue + + try: + freebie = session.query(Freebie).filter_by(id=freebie_id).one_or_none() + if freebie: + name = input("Enter the updated item name: ") + value = input("Enter the updated value: ") + + freebie.item_name = name or freebie.item_name + freebie.value = int(value) if value.isdigit() else freebie.value + + session.commit() + printMessage("Freebie updated successfully!") + return + else: + printMessage("Invalid Freebie ID. Try again!") + except Exception as e: + print(f"Error occurred: {e}") + session.rollback() + + + + + + + + + + + +def printMessage(message): + print( + f"\n----------------------------------------------\n{message}\n-----------------------------------------------" + ) + + +def main(): + cli_actions = { + "1": list_devs, + "2": list_companies, + "3": list_freebies, + "4": show_developer_freebies, + "5": add_dev, + "6": add_company, + "7": add_freebie, + "8": delete_dev, + "9": delete_company, + "10": delete_freebie, + "11": update_dev, + "12": update_company, + "13": update_freebie, + + + +} + + while True: + print("\nFREEBIE TRACKER CLI") + print("1. List all developers") + print("2. List all companies") + print("3. List all freebies") + print("4. Show all freebies for a developer") + print("5. Add a new developer") + print("6. Add a new company") + print("7. Add a new freebie") + print("8. Delete a developer") + print("9. Delete a Company") + print("10. Delete a Freebie") + print("11. Update a developer") + print("12. Update a company") + print("13. Update a freebie") + print("0. Exit") + + choice=input("Select and option: ") + + if choice == "0": + print( + "-----------------------------------------------\nThank you for visiting us. Welcome again!\n------------------------------------------------" + ) + break + action=cli_actions.get(choice) + + if action: + action() + else: + print("Invalid option. Try again.") + print("*************************************************************") + +if __name__ == "__main__": + main() + + + + +