diff --git a/.git-bestPractices b/.git-bestPractices new file mode 100644 index 0000000..95bafbc --- /dev/null +++ b/.git-bestPractices @@ -0,0 +1,5 @@ +#after every update and before any commit/push, run: +- git fetch upstream +- git rebase upstream/main +- git push origin master + diff --git a/.github/workflows/sync_roadmap_status.yml b/.github/workflows/sync_roadmap_status.yml index c987cd2..cf021a9 100644 --- a/.github/workflows/sync_roadmap_status.yml +++ b/.github/workflows/sync_roadmap_status.yml @@ -38,6 +38,14 @@ jobs: with: ref: main token: ${{ secrets.ROADMAP_PAT }} + runs-on: ubuntu-latest + steps: + if: github.event_name == 'issues' || github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main - uses: actions/setup-python@v5 with: @@ -62,6 +70,7 @@ jobs: run: python scripts/roadmap/sync_discord.py env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + DISCORD_MESSAGE_IDS: ${{ vars.DISCORD_MESSAGE_IDS }} reconcile: @@ -96,4 +105,5 @@ jobs: run: python scripts/roadmap/sync_discord.py env: DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + DISCORD_MESSAGE_IDS: ${{ vars.DISCORD_MESSAGE_IDS }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index a22f7b1..e6df35e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .obsidian/** .obsidian -.trash \ No newline at end of file +.trash +.git-bestPractices +.vscode/extensions.json \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..0bbab56 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "bowlerr.offline-markdown-preview" + ] +} \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index 8d1a06a..e983007 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,7 +11,9 @@ This may be subject to change. 1. Introduction * [x] How course works? * [x] Motivation and Mindset - * [ ] 🚧 Asking for Help — #32 👤 tkcompani + * [ ] Asking for Help — #32 + * [ ] Asking for Help (WIP) — #32 + * [ ] Asking for Help — #32 * [ ] Join the Community — #33 * [x] Computer Science basics, Programming and Python 2. Prerequisites @@ -29,11 +31,11 @@ This may be subject to change. * [x] User Input and Conditionals * [x] Loops 5. Data Structures - * [ ] 🚧 Lists — #57 👤 razorblade23 - * [ ] 🚧 Tuples — #58 👤 razorblade23 - * [ ] 🚧 Sets — #59 👤 razorblade23 - * [ ] 🚧 Dictionaries — #60 👤 razorblade23 - * [ ] 🚧 Coprehensions (`list`, `dict` `set`) — #61 👤 razorblade23 + * [ ] Lists — #57 + * [ ] Tuples — #58 + * [ ] Sets — #59 + * [ ] Dictionaries — #60 + * [ ] Coprehensions (`list`, `dict` `set`) — #61 6. Code Organization * [x] Functions — #62 * [ ] 🚧 Scope and Namespaces (LEGB rule) — #63 👤 tajirdev @@ -62,6 +64,66 @@ This may be subject to change. 11. Capstone Project 3 * [ ] Capstone Project 12. Built-in and Modules + * [-] Data Structures + * [-] List + * [-] Tuple + * [-] Set + * [-] Dictionary + * [ ] Functions + * [ ] Project 1 +5. Modules, Working with Files and Error Handling + * [ ] Working with multiple files (`__init.py__`, directory structure, etc...) + * [ ] Opening, Closing and Manipulating files + * [ ] Error Handling (`try-except-else-finally`) + * [ ] Custom Exceptions + * [ ] Defensive Coding patterns + * [ ] Working with structured data (`json`, `csv`, etc...) + * [ ] Project 2 +6. OOP, Virtual Environments and third party libraries, Working with APIs + * [ ] Classes and OOP + * [ ] Usage and setup of virtual environments + * [ ] Installation of third party libraries + * [ ] Working with APIs + * [ ] Project 3 - *probably: * weather app +7. Working on Projects, Solving bugs, Reading errors + * [ ] Motivation and Mindset + * [ ] Asking for Help (WIP) + * [x] Join the Community + * [x] Computer Science basics, Programming and Python +2. Prerequisites + * [x] Software Installation + * [x] Text and Code Editors + * [x] Command Line Basics + * [x] Python - REPL vs Scripts +3. Git Basics + * [x] Introduction to Git + * [x] Setting up Git and Github + * [x] Git Basics +4. Foundations + * [x] Output, Variables, Core Datatypes + * [x] Arithmetic and String Operations, Comparisons + * [x] User Input and Conditionals + * [x] Loops +5. Data Structures + * [x] Lists + * [x] Tuples + * [x] Sets + * [x] Dictionaries +6. Comprehensions + * [ ] List Coprehension + * [ ] Set Coprehension + * [ ] Dict Coprehension +7. Code Organization + * [ ] Functions + * [ ] Scope and Namespaces (LEGB rule) + * [ ] Understanding Errors and Error Handling + * [ ] File Handling + * [ ] Modules +8. Capstone Project 1 + * [ ] Problem Solving + * [ ] Problem Solving - Fizz-Buzz Example + * [ ] Capstone Project +9. Built-in and Modules * [ ] `math` module * [ ] `json` module * [ ] `random` module @@ -74,6 +136,53 @@ This may be subject to change. * [ ] Virtual Environments * [ ] Using `pip` and `requirements.txt` * [ ] Creating python package - `pyproject.toml` file +10. Capstone Project 2 + * [ ] Clean Code Priciples + * [ ] Capstone Project +11. Object Oriented Programming - Part 1 + * [ ] Introduction to classes and instances + * [ ] Methods (instance, class, static) + * [ ] Dunder Methods (`__str__`, `__repr__`, etc...) +12. Capstone Project 3 + * [ ] Capstone Project +13. Object Oriented Programming - Part 2 + * [ ] Four Pillars of Object-Oriented Programming (Encapsulation, Abstraction, Inheritance, Polymorphism) +18. Capstone Project 6 +10. Third-party Modules + * [ ] Virtual Environments + * [ ] Using `pip` and `requirements.txt` + * [ ] Creating python package - `pyproject.toml` file +11. Capstone Project 2 + * [ ] Clean Code Priciples + * [ ] Capstone Project +12. Object Oriented Programming - Part 1 + * [ ] Introduction to classes and instances + * [ ] Methods (instance, class, static) + * [ ] Dunder Methods (`__str__`, `__repr__`, etc...) +13. Capstone Project 3 + * [ ] Capstone Project +14. Object Oriented Programming - Part 2 + * [ ] Four Pillars of Object-Oriented Programming (Encapsulation, Abstraction, Inheritance, Polymorphism) +15. Capstone Project 4 + * [ ] Capstone Project +10. Third-party Modules +9. Third-party Modules + * [ ] Virtual Environments + * [ ] Using `pip` and `requirements.txt` + * [ ] Creating python package - `pyproject.toml` file +10. Capstone Project 2 + * [ ] Clean Code Priciples + * [ ] Capstone Project +11. Object Oriented Programming - Part 1 + * [ ] Introduction to classes and instances + * [ ] Methods (instance, class, static) + * [ ] Dunder Methods (`__str__`, `__repr__`, etc...) +12. Capstone Project 3 + * [ ] Capstone Project +13. Object Oriented Programming - Part 2 + * [ ] Four Pillars of Object-Oriented Programming (Encapsulation, Abstraction, Inheritance, Polymorphism) +14. Capstone Project 4 + * [ ] Capstone Project * [ ] ? Linting, Formatting, Type Checking (`ruff`, `mypy`) ? 14. Capstone Project 4 * [ ] Clean Code Priciples @@ -90,4 +199,4 @@ This may be subject to change. 17. Object Oriented Programming - Part 2 * [ ] Four Pillars of Object-Oriented Programming (Encapsulation, Abstraction, Inheritance, Polymorphism) 18. Capstone Project 6 - * [ ] Capstone Project \ No newline at end of file + * [ ] Capstone Project diff --git a/curriculum/01-introduction/03-asking-for-help.md b/curriculum/01-introduction/03-asking-for-help.md index 51b555c..e3ee688 100644 --- a/curriculum/01-introduction/03-asking-for-help.md +++ b/curriculum/01-introduction/03-asking-for-help.md @@ -6,5 +6,110 @@ sidebar_position: 3 lesson: true isDraft: true --- -# Asking For Help -## Work in progress \ No newline at end of file + +# Asking For Help ❔ +**INTRODUCTION**: Getting stuck in your coding or even a learning is really part of the journey, and that is the number one why this lesson (ask for help) exists. + +## The Goal 🥅⚽ +In this lesson you will learn how to get help whenever you need it. And by the end of this lesson you should be familiar with getting stuck and how to seek for help. + + +## Lesson outline 📝 +* How to Get Help +* Who to Ask for Help +* Where to Ask for Help (**Under Construction...**) + +## How to Get Help ❔ +Steps to follow to get help: +* Before you ask, do some checks +* Use the question space +* format your code properly +* Know what to expect next + +### 1. Before You Ask ⤴➡ +Before you ask anything check if: +- your indentations are correct or valid +- your syntax are correct or valid +- your code is complete as you intended + +**If** any of above is False, Fix them and re-run your code. + +**Else**, if all of above is True and error still persist, proceed to the step 2(the question space) of this topic. + + +### 2. The Question space 📰❔ +Carefully study the `code` below: + +```python + +var1 = "12" +var2 = 12 + +results = var1 + var2 #this raises an error, why?—you will learn how to go about finding a soln—to the why, how, &what. + +``` +Your `code` should follow the pep8 style, as well as the `syntax` should be valid so you get an appropriate help response. + +### 3. How to Format Your Code 🔃 +- You are required to format your code (_python code_) using the markdown language in order to get the appropriate responses to your help requests. + +Steps to format your code: +- Type in three of the ` ``` ` character(three) to open it and close with another three of the ` ``` ` character. +- In between the opening and closing character sets, type `python` and enter a `space` character. You should have something like this: ` ```python ``` ` . +- Press `Enter` to go to a new line, now you are set to paste your `code` for submission—make sure the code syntax is valid, and is same as the one which raised the error; that is the `indentaion`, `spacing`, and `others` are all same. + + +### 4. What to Expect Next ➡ +Once you have submitted your help request, sit down tight and the appropriate help will be sent straight to you. + +If unfortunately the help response delays or never comes, use the next approach which is _Who To Ask_. + + +## Who to Ask for Help 🧔🏻❔ +subs: +* Ask yourself +* Ask someone with experience +* Ask a trusted community +* Ask an Ai agent + +### 1. Ask Yourself Before Anyone +You may say, why myself, i don't know?—Ofcourse you know all the answers to your own questions most of the times. **Ask yourself why, why did the error come; Ask yourself how, how did the error come; and Ask yourself what, what brought up a particular error** :(. You are not actually finding the correct solution to the cause of the error with these questions overnight (as if it was a magic :)), **but** what you are doing is you are training your thinking, which may help you think creatively gradually—to solve future problems. "Problem solving requires a creative and effective thinking"—Baldwin.J,. college success(2021). + +After you have had your answers to those three question, store them safely as we will learn how they might become useful later in this lesson. + +### 2. Ask Someone with Experience +Why ask someone with experience?—An experienced programmer, specifically python programmer, knows how to solve or debug almost every bug or error respectively. Therefore asking an experienced person means there is a higher probability you will get a solution to an/a error/bug. + +Now let see how your answers at the _ask yourself_ sub gets useful here. Compare your answers obtained at the _ask yourself_ sub to the ones you obtained from the experienced programmer and **Ask youself one more time—what did i missed?**—Keep this particular answer to yourself (treat it as important), it might help you in your future decisions when debugging other or similar errors later on. + +### 3. Ask a Trusted Community +Though there isn't a full guarantee that a community will contain only experienced programmers,—That is there would be beginners, intermediates and experts—but there are people with at least an experience if not much, these people might have already at least solved a bug which you just met, and could share with you how they handled it—which might be a fantastic approach you could learn also. + +Therefore asking verified communities like the stackoverflow, github, telegram groups, reddit, discord and others i could'nt mention—though one should proceed this with caution—is a must once you are learning the python or any other programming langauge. + +### 4. Ask an AI Agent +AI tools or agents are a smart way to debug errors faster, but the question is—Does the AI agent know the best approch to avoid future code breaks? + +This does not neccessarily mean using AI assistance for debugging errors is bad. But for best decision sake; That is if you know enough or understand how to select the best approach out of the many provided by the AI model, then you are encouraged to proceed with this approach—the advice is simple, that is use AI to recall the best solutions you already know quickly and not to decide the best solutions for you. + +This really counts in a serious project or even in the real world practices, but it is also a good thing to keep it in practice now—even if you are learning. + + +## Where to Ask for Help +Subs: +- Ask the social media +- Ask in learning centers + +### 1. Ask the Social Media +At least anyone who owns a smart device has an account registered on a social media platform—Some popularly known ones are: _WhatsApp, Facebook, Twitter, TikTok, Reddit, Discord, etc._—and on these platform so might be as well all kinds of programmers—experienced ones specifically. + +__Note__: +```text +In case it comes to pass that you are not registered on any of these platforms, just register on any of them to get started. +``` +**Don't just ASK** _"Hello everyone i need help with fixing a bug"_—without a link to code, media files, just this text or similar— this will actually lead others to either just leave a 'like' or do nothing. _What is the right way to ask the social media?_— **ASK by** including a clear title, clear content/message describing the error process and your expectations, a link to the code somewhere or a screenshot of the code, and optionally the methods you have used that never worked. + +### 2. Ask in Learning Centers + +- _work in progress_ + diff --git a/curriculum/05-data-structures/01-list.md b/curriculum/05-data-structures/01-list.md index 805873f..4adb8f6 100644 --- a/curriculum/05-data-structures/01-list.md +++ b/curriculum/05-data-structures/01-list.md @@ -7,7 +7,6 @@ lesson: true isDraft: true --- # List -## Introduction {#introduction} Up until now we have stored a single value in a variable, but what if we need to hold multiple values in some variable? Do we create multiple variables? Of course not, Python has a *built-in* mechanisms for dealing with collections of data which we call **data structures**. In this lesson we first take a look at **list**. Lists in Python are *built-in* data structure for storing ordered collections of items. They are **ordered** meaning they keep the order in which data came in. They are also **mutable** which means we can change them in place without creating a new copy. They can hold any other type of data including other lists or other data structures. Lists are probably the most used data structure in Python. @@ -147,19 +146,37 @@ for item in a: This is very useful as we often need to work with specific elements from the list. :::explore[Learn more about Python lists] - Learn more about Python Lists from these resources: -* [Google for Education - Python Lists](https://developers.google.com/edu/python/lists) -* [Official Python Documentation on Lists](https://docs.python.org/3/library/stdtypes.html#typesseq-list) -* [Official Python Documentation on Data Structures - more on Lists](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) - +* Learn about `zip()` function from [zip() function - RealPython](https://realpython.com/ref/builtin-functions/zip/) and [zip() function - official Python Documentation](https://docs.python.org/3.3/library/functions.html#zip) that allows for combining of multiple lists (or other iterables as we will see later) by producing **tuples** (we cover these in the next lesson) +* Learn about `.sort()` method from [official Python Documentation](https://docs.python.org/3/library/stdtypes.html#list.sort) which is very useful in sorting lists in-place. Sorting iterables is a very useful tehnique and there are multiple arguments and ways to sort iterables in Python. Be sure to read this [Sorting Tehniques - official Python documentation](https://docs.python.org/3/howto/sorting.html) to familiarize yourself with tehniques used. +* Go thrue all the available methods on the lists in [More on Lists - official Python documentation](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists), as these will come in handy when working on your projects. You do not need to remember them all, but just read thrue them and their intended purpouse so you will know what is out there. ::: ## Exercise -Complete [Exercise 05 — The Cozy Bakery Inventory](#) to practice list creation, indexing, modification, and using python documentation to solve list tasks. +Complete [[TODO] Exercise 05 — WorkingTitle](#) to practice list creation, indexing, modification, and using python documentation to solve list tasks. ## Assignment {#assignment} -**Todo** +1. Open `main.py` file in our `simple-python-shop` directory +2. Replace `item_name`, `item_price` and `item_stock` static values with an empty list. +3. In *infinite loop* ask the user to fill up the inventory by typing in a name, price and stock level of item. + * If user types **empty string**, break out of the loop and continue with program execution. + * User should enter all of the information on the same line. Use `.split()` method on the inputed string to catch all information needed. **Do not forget** to cast `price` to `float` and `stock` to `integer`. + * Append every information to their own respective list. +4. Display the inventory by using `zip()` function and combining all three lists to display items one by one. +5. Create variable `order` and assign it an empty list. +6. Create another *infinite loop* and inside the loop ask the user to enter the name of the item he wants to buy. + * If user enters empty string, break out of the loop. + * If the item user entered is in the `item_name` list, find its index position and assign it to variable, otherwise inform the user that there is no such item at the moment, and **continue** with next iteration of loop + * When you have an index of the item user asked, ask the user to enter quantity of the items. Check if entered item quantity is not bigger then actual item stock and if it is, tell the user that we do not have that many items in stock, then **continue** with new iteration. + * If we have the item and have enough of stock, add the total for item (item price multiplied by quantity) to `order` list. + * Decrease the stock number of items by the quantity ammount and if item stock reaches 0, remove the item (and its relevant info) from the inventory lists. +7. Calculate the total by adding all the numbers in `order` list using `sum()` function and calculate whether to give 10% discount (if `total > 100`). +8. Print out the final receipt in the following format: + ``` + Your total is: $ + ``` +9. Print the inventory after shopping. +10. Make sure your application works correctly, commit the changes and push your code to Github. ## What's Next {#next-lesson} Lists are ordered and mutable, which makes them ideal for collections that grow or change over time. But what if you need an ordered collection that **cannot** be modified once created? diff --git a/curriculum/05-data-structures/02-tuple.md b/curriculum/05-data-structures/02-tuple.md index d97d13e..8be316b 100644 --- a/curriculum/05-data-structures/02-tuple.md +++ b/curriculum/05-data-structures/02-tuple.md @@ -7,7 +7,6 @@ lesson: true isDraft: true --- # Tuple -## Introduction {#introduction} Tuple is an ordered and immutable collection data structure. **Ordered** meaning it keeps the order of elements in which they are inserted, and **immutable** means that once the tuple is created it cannot change, and you cannot add or remove elements from it. ## Lesson Overview {#overview} @@ -39,7 +38,7 @@ print(f"First element: {a[0]} - Last element: {a[-1]}") ``` ## Useful methods -Unlike functions, as we cannot modify tuples, we don't have a lot of methods available to us, but we will list a few. +Unlike lists, as we cannot modify tuples, we don't have a lot of methods available to us, but we will list a few. * `count()` - returns the number of times a specified value appears in tuple. ```python interactive numbers = (1, 2, 3, 3, 3, 4, 5) @@ -52,12 +51,24 @@ Unlike functions, as we cannot modify tuples, we don't have a lot of methods ava ``` :::explore[Learn more about Python Tuples] -* [Python built-in types: tuples from RealPython](https://realpython.com/ref/builtin-types/tuple/) -* [Tuples and Sequences from official Python documentation](https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences) +Deep dive into Python tuples in this [Deep Dive: tuples](https://realpython.com/python-tuple/) article by **Real Python** which takes you deep in topic of tuples and their usecases. ::: +## Exercise +Complete [[TODO] Exercise 06 — WorkingTitle](#) to practice tuples. + ## Assignment {#assignment} -**Todo** +Using lists to separatly hold multiple related values is brittle and will break if you do not pay attention to all lists containing data. In this assigment we will deal with those issues by using tuples. + +1. Open our `main.py` file we have been working on +2. Create a new empty list called `inventory`. This will hold tuples of related data `(product_name, product_price, product_stock)`. +3. Delete previusly defined `item_name`, `item_price` and `item_stock` variables, we wont need them anymore. +4. Just like before, in **infinite loop**, first check if the input is an *empty string* and if it is, break out of the loop, otherwise get the users input containing *name*, *price* and *stock* in a single line, then split the line using `split()` method to get a list of inputed data. +5. Instead of appending each data to their own list, create a tuple called `item` containing `name`, `price` and `stock` in that particular order and append that to our `inventory` list we declared earlier. Print out each `inventory` element using `for` loop. +6. On to customer ordering items now. Keep `order` list and instead of storing only totals for each item, we will now store `item_name`, `item_quantity` and `line_total` in **tuple** called `line_item` and append that to our `order` list so our shop can better keep track of the orders. **Keep in mind** - tuples are *immutable* which means that you will have to reduce `stock` of the item by replacing entire tuple with new `stock` value. +7. Same as before, calculate the `total` for all the items bought, apply a 10% discount if the `total` is over 100. +8. In the end, print out the inventory, to make sure that the items are actually removed once bought. +9. Make sure your program runs, then commit the changes and push to Github ## What's Next {#next-lesson} -**Todo** \ No newline at end of file +Now that you have seen data structures that can contain duplicates, it's time to meet **set** - a unique data structure that does not allows for duplicates. \ No newline at end of file diff --git a/curriculum/05-data-structures/03-set.md b/curriculum/05-data-structures/03-set.md index 6cc032c..9253acd 100644 --- a/curriculum/05-data-structures/03-set.md +++ b/curriculum/05-data-structures/03-set.md @@ -7,7 +7,6 @@ lesson: true isDraft: true --- # Set -## Introduction {#introduction} A **set** in Python is unordered collection of unique elements. **Unordered** means it does not keep the order of the elements and **unique** means it does not allow duplicates. Its mostly used when removing duplicates from list or performing quick membership tests (checking to see if some element belongs to both sets). @@ -73,13 +72,22 @@ print("Difference:", set1 - set2) ``` :::explore[Learn more about Python Sets] -Learn more about sets in these materials: -* [Python built-in data types from RealPython](https://realpython.com/ref/builtin-types/set/) -* [Python data structures: set from official Python documentation](https://docs.python.org/3/tutorial/datastructures.html#sets) +Explore Python sets deeper by visiting this [built-in: set] article from **Real Python** (especially the part about additional methods which come in handy when working with sets). + +Remember you do not need to remember all the methods there are. Just read through them, to know they exist. ::: +## Exercise +Complete [[TODO] Exercise 07 — WorkingTitle](#) to practice sets. + ## Assignment {#assignment} -**Todo** +In this assigment our task is simple one. We need to track distinct *unsold* items so we can better prepare for the next day in our little shop. + +1. Open `main.py` file we have been working on. +2. Define a set for all **sold items** (`item[0]` of element in `order` list) and define a set of **all inventory items** (`item[0]` of element in `inventory` list) - you can use set coprehension to do this in one line or use a for loop to iterate over each item in the lists. +3. Create a variable `unsold_items` and set its value to be `sold_items` substracted from `all_inventory_items` +4. Print out items that did not have a sale today. +5. Confirm the program works, commit and push to Github ## What's Next {#next-lesson} -**Todo** \ No newline at end of file +Now that we covered index based data structures, it's time to meet **dictionary** which is unique in its *key-value* approach and is probably most used data structure (besides *lists*). \ No newline at end of file diff --git a/curriculum/05-data-structures/04-dictionary.md b/curriculum/05-data-structures/04-dictionary.md index 0ce1129..34516ac 100644 --- a/curriculum/05-data-structures/04-dictionary.md +++ b/curriculum/05-data-structures/04-dictionary.md @@ -7,7 +7,6 @@ lesson: true isDraft: true --- # Dictionary -## Introduction {#introduction} Dictionaries in Python are mutable and unordered collection data structure. **Mutable** meaning it can be modified after creating and **unordered** meaning it does not keep track of it's elements. Dictionaries are something special, usually called **key-value** data structure because of the way you store data inside them. You will certanly do a lot of work involving dictionaries in your python applications and its a very powerful concept, so let's get started. @@ -124,12 +123,27 @@ for key, value in mydict.items(): print(key, value) ``` +:::explore[Learn more about Python dicts] +Read this [built-in: dict](https://realpython.com/ref/builtin-types/dict/) article from **Real Python** and pay special attention to sections **dict Operators** and **dict Methods**. +Remember, just read through, you do not have to know it all. +::: -## Assignment {#assignment} - - -## Deepen Your Knowlege {#learn-more} +## Exercise +Complete [[TODO] Exercise 08 — WorkingTitle](#) to practice dictionaries. - -## What's Next {#next-lesson} \ No newline at end of file +## Assignment {#assignment} +In this assignment, we will completely eliminate list index tracking by refactoring our inventory into a dictionary, mapping each product name directly to its price and stock. + +1. Open `main.py` in our project directory. +2. Replace `inventory` list with an empty dictionary `{}`. +3. When reading users input, assign the item directly to dictionary `inventory`. + ```python + inventory[name] = {"price": price, "stock": stock} + ``` +4. In customer order, look up if item is in the inventory directly with `if item_name in inventory`, verify stock availability with `inventory[item_name]["stock"]` and update stock count in place with `inventory[item_name]["stock"] -= 1` and if stock reaches `0` then delete the item from the inventory using `del inventory[item_name]`. +5. Keep `order` as a list of tuples, and update the `unsold_items` variable to check between keys of the dictionary and tuple elements - use `inventory.keys()` to get a *tuple-like* object of dictionary keys that you can use in this case to substract `order` elements from keys of dictionary. +6. Test your application, make sure it works, commit and push to Github. + +## What's Next {#next-lesson} +We are now done with basic data structures in Python, next we move on to something even more fun and useful, kind of a super power in Python: **coprehensions** \ No newline at end of file diff --git a/curriculum/05-data-structures/05-comprehensions.md b/curriculum/05-data-structures/05-comprehensions.md new file mode 100644 index 0000000..1aff5dd --- /dev/null +++ b/curriculum/05-data-structures/05-comprehensions.md @@ -0,0 +1,175 @@ +--- +id: comprehensions +title: Comprehensions +sidebar_label: Comprehensions +sidebar_position: 5 +lesson: true +isDraft: true +--- +# Comprehensions +Comprehensions in Python provide a short and clear way to create new sequences from existing iterable. Basically they are a *fancy* syntax for simple **for-loop** pattern. + +## Lesson Overview {#overview} +At the end of the lesson you will know: +* What are comprehensions in Python +* How to write `list`, `dict` or `set` comprehension +* How to use conditions in comprehensions + +The key to understanding list comprehensions is that they’re just `for-loops` over a collection expressed in a more terse and compact syntax. + +We will start with **list comprehension** as it is most common. + +## List Comprehension +Syntax for this looks like: +```python +[item for item in iterable] +``` + +Let's imagine we have a task of creating a list of **square** numbers from some other list of numbers. For example, let's assume we have a following list of numbers: +```python +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +``` +To create a new list with **square** of all numbers in a list, we need to first get each element in the list and apply a mathematical operation on it. So it would look something like this: +```python interactive +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared = [] +for nmb in nmb_list: + squared.append(nmb**2) + +print(squared) +``` +And the result is correct, but in Python, we can do better. Let's convert our `for-loop` to **list comprehension**. +```python interactive debug +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared = [nmb**2 for nmb in nmb_list] +print(squared) +``` +Result is completely the same, but our code is simpler and more concise. In this simple example we may not see the benefit, but let's add a check there, to only collect **even** numbers. + +If we use **for-loop** we may do something like this. +```python interactive +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared_even = [] +for nmb in nmb_list: + if nmb % 2 == 0: + squared_even.append(nmb ** 2) + +print(squared_even) +``` +If we use **list comprehension** it would look like this: +```python interactive debug +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared_even = [nmb ** 2 for nmb in nmb_list if nmb % 2 == 0] +print(squared_even) +``` +The result is again, completely the same, but the syntax is shorter and more concise. + +Let's look at the final type of list comprehension, which will produce one value if the condition is `True` or something else if the condition is `False`. For example, let's say we need to produce a list, containing `True` if the number is even or `False` if its odd. + +In classic python **for-loop** style, we would do something like this: +```python interactive +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +even_mask = [] +for nmb in nmb_list: + if nmb % 2 == 0: + even_mask.append(True) + else: + even_mask.append(False) +print(even_mask) +``` + +But Python let's us use its super power here also: +```python interactive debug +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +even_mask = [True for nmb in nmb_list if nmb % 2 == 0 else False] +print(even_mask) +``` + +:::tip[Superpowers work with any comprehension] +`if condition` and `if condition else` works will all comprehensions and not just with lists, and they work in the same manner. +::: + +You do not need to worry about understanding **comprehensions** right away, but they become extremely useful the more you write your code. + +Now that we learned what are list comprehensions, let's look at **dictionary comprehensions** which are very similar in syntax but allows us to create dictionaries similarly. + +## Dictionary Comprehension +When creating a new dictionary using dictionary comprehension, you can perform various operations using expressions to determine the data (key and/or value) that will be stored in the new dictionary. + +Syntax for this looks like: +```python +{key: value for (key,value) in iterable} +``` + +To demonstrate this, let's imagine that you are building a currency converter. You would maybe have a dictionary representing prices in USD and need to convert them to EUR. In traditional **for-loop** you would do something like this: +```python interactive +EUR_CONV_RATE = 0.92 +prices_in_usd = {'pen': 4, 'book': 15, 'keyboard': 60} +prices_in_eur = {} +for name, price in prices_in_usd.items(): + prices_in_eur[name] = round(price * EUR_CONV_RATE, 2) +print(prices_in_eur) +``` +To use **dict comprehension** we would rewrite the above code to: +```python interactive debug +EUR_CONV_RATE = 0.92 +prices_in_usd = {'pen': 4, 'book': 15, 'keyboard': 60} + +prices_in_eur = {key: round(value * EUR_CONV_RATE, 2) for (key, value) in prices_in_usd.items()} + +print(prices_in_eur) +``` +:::tip +`round()` function rounds the numbers decimal point to specified number of places. It takes in `float` and a number of decimal places - an `integer`. +```python interactive +a = 43.24214213213 +b = round(a, 2) +print(b) +``` +::: + +:::info +In the above code we use `EUR_CONV_RATE` to declare **constant**. Constants are just variables, but are not supposed to be changed during running of your program. They are useful for declaring things that would not change during runtime of your program, and it's a convention in Python to write them in `ALL_CAPS`. Unlike some other languages, in Python, these are considered just like regular variables and Python will not stop you from changing them during runtime, so you need to consider this when writing your application. + +The golden rule is: +* If the variable will change during your application runtime, its just a variable and should be written as `variable_name`. +* If the variable will **not** change during your application runtime, then you can consider it a **constant** and write them as `VARIABLE_NAME`. + +Remember that this is just a convention and it is not a rule you *must* follow. +::: + +Now that we covered dictionary comprehensions, we can finally meet **set comprehensions**. + +## Set Comprehension +Set comprehension works best when you want a clean transformation, and you also want duplicates to disappear without extra effort. The syntax for **set comprehension** is: +```python +{expression for item in iterable} +``` + +For example, let's use our *squared* example from before: +```python interactive +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared = {nmb ** 2 for nmb in nmb_list} +print(squared) +``` +We can also use conditionals to get only specific values: +```python interactive debug +nmb_list = [1, 2, 3, 4, 5, 6, 7, 8] +squared_even = {nmb ** 2 for nmb in nmb_list if nmb % 2 == 0} +print(squared_even) +``` + +:::warning +Comprehensions are very useful and can make your code smaller and easier to understand and reason about. But it also can make your code very difficult to read and understand. + +Use them with caution and remember: +* USE comprehensions while the code is readable +* DO NOT use comprehensions when the code starts to become unreadable and go back to **for-loop** for clarity. +::: + +## Exercise + +## Assignment {#assignment} + +## What's Next {#next-lesson} +Coprehensions are very useful in every day life as a Python programmer, but there is one thing that is universal accross all languages, so let's start a new chapter; *code organization*. First thing to learn are **functions** which enable us to write modular code. \ No newline at end of file diff --git a/scripts/roadmap/reconcile_roadmap.py b/scripts/roadmap/reconcile_roadmap.py index cb67744..930994e 100644 --- a/scripts/roadmap/reconcile_roadmap.py +++ b/scripts/roadmap/reconcile_roadmap.py @@ -17,6 +17,7 @@ That covers the common paths (assigned, merged-and-closed) even for history that predates the automation. """ + import json import os import sys @@ -24,13 +25,7 @@ import urllib.request sys.path.insert(0, os.path.dirname(__file__)) -from sync_roadmap import ( # noqa: E402 - LINE_RE, - ROADMAP_PATH, - Update, - apply_updates, - update_from_issue, -) +from sync_roadmap import LINE_RE, ROADMAP_PATH, apply_updates # noqa: E402 def find_linked_issues() -> set[int]: @@ -58,6 +53,14 @@ def fetch_issue(repo: str, token: str, number: int) -> dict: return json.load(resp) +def target_state(issue: dict) -> str: + if issue.get("state") == "closed": + return "x" + if issue.get("assignees"): + return "-" + return " " + + def main() -> None: repo = os.environ["GITHUB_REPOSITORY"] token = os.environ["GITHUB_TOKEN"] @@ -67,14 +70,17 @@ def main() -> None: print("No linked issues found in ROADMAP.md.") return - updates: dict[int, Update] = {} + updates: dict[int, str] = {} for number in sorted(issue_numbers): try: issue = fetch_issue(repo, token, number) except urllib.error.HTTPError as e: - print(f"#{number}: could not fetch issue ({e.code}) — skipping", file=sys.stderr) + print( + f"#{number}: could not fetch issue ({e.code}) — skipping", + file=sys.stderr, + ) continue - updates[number] = update_from_issue(issue) + updates[number] = target_state(issue) changed = apply_updates(updates) if not changed: @@ -87,4 +93,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/scripts/roadmap/sync_discord.py b/scripts/roadmap/sync_discord.py index 85727a1..b9f30de 100644 --- a/scripts/roadmap/sync_discord.py +++ b/scripts/roadmap/sync_discord.py @@ -23,6 +23,7 @@ import re import sys import urllib.error +import urllib.error import urllib.request ROADMAP_PATH = "ROADMAP.md" @@ -90,6 +91,8 @@ def build_embed(phases: list[Phase]) -> dict: def patch_message(webhook_url: str, message_id: str, embed: dict) -> None: url = f"{webhook_url}/messages/{message_id}" payload = json.dumps({"content": "", "embeds": [embed]}).encode("utf-8") + payload = json.dumps({"embeds": [embed]}).encode("utf-8") + payload = json.dumps({"content": "", "embeds": [embed]}).encode("utf-8") req = urllib.request.Request( url, data=payload, @@ -110,6 +113,12 @@ def patch_message(webhook_url: str, message_id: str, embed: dict) -> None: body = e.read().decode("utf-8", errors="replace") print(f"HTTP {e.code} updating message {message_id}: {body}", file=sys.stderr) raise + with urllib.request.urlopen(req) as resp: + if resp.status not in (200, 204): + print( + f"Unexpected status {resp.status} updating message {message_id}", + file=sys.stderr, + ) def main() -> None: diff --git a/scripts/roadmap/sync_roadmap.py b/scripts/roadmap/sync_roadmap.py index d2446d3..15aba49 100644 --- a/scripts/roadmap/sync_roadmap.py +++ b/scripts/roadmap/sync_roadmap.py @@ -7,6 +7,12 @@ [ ] Lists — #34 not started [ ] 🚧 Lists — #34 👤 alice, bob in progress (assigned / open PR) [x] Lists — #34 done (merged / issue closed) + [ ] Lists — #34 not started + [ ] 🚧 Lists — #34 in progress (assigned / open PR) + [x] Lists — #34 done (merged / issue closed) + [ ] Lists — #34 not started + [ ] 🚧 Lists — #34 👤 alice, bob in progress (assigned / open PR) + [x] Lists — #34 done (merged / issue closed) GitHub's task-list rendering only recognizes [ ] and [x] as real, interactive checkboxes — anything else (like a bare [-]) is just @@ -25,6 +31,18 @@ Lines with no "— #N" suffix are left alone — link an issue to a lesson by adding that suffix, and the automation picks it up from then on. """ +import json +import os +import re + +ROADMAP_PATH = "ROADMAP.md" +WIP_BADGE = "🚧" + +# Internal state codes: " " = not started, "-" = in progress, "x" = done. +# Only how they're RENDERED matters to GitHub's markdown. +# Internal state codes (unchanged from before): " " = not started, +# "-" = in progress, "x" = done. Only how they're RENDERED changes. + import json import os import re @@ -36,6 +54,7 @@ # Internal state codes: " " = not started, "-" = in progress, "x" = done. # Only how they're RENDERED matters to GitHub's markdown. + STATE_RENDER = { " ": {"check": " ", "badge": ""}, "-": {"check": " ", "badge": f"{WIP_BADGE} "}, @@ -45,6 +64,15 @@ # Matches e.g. " * [ ] 🚧 Lists — #34 👤 alice" and captures the checkbox, # an optional WIP badge, and the issue number. `rest` is everything after # the issue number (including any assignee suffix, handled separately). + +# Matches e.g. " * [ ] 🚧 Lists — #34" and captures the checkbox, +# an optional WIP badge, and the issue number — everything else on +# the line is left untouched. + +# Matches e.g. " * [ ] 🚧 Lists — #34 👤 alice" and captures the checkbox, +# an optional WIP badge, and the issue number. `rest` is everything after +# the issue number (including any assignee suffix, handled separately). + LINE_RE = re.compile( r"^(?P\s*\*\s*\[)(?P[ x])(?P\]\s*)" rf"(?P{WIP_BADGE}\s*)?" @@ -66,7 +94,6 @@ class Update(NamedTuple): # since a PR payload doesn't carry the linked issue's assignee list. assignees: list[str] | None = None - def current_state(check: str, badge: str | None) -> str: if check == "x": return "x" @@ -79,8 +106,20 @@ def state_for_issue(issue: dict) -> str: return "-" if issue.get("assignees") else " " -def assignee_logins(issue: dict) -> list[str]: - return [a["login"] for a in issue.get("assignees") or []] +def marker_for_issue_event(event: dict) -> dict[int, str]: + action = event["action"] + issue_number = event["issue"]["number"] + + if action == "assigned": + return {issue_number: "-"} + if action == "unassigned": + return {issue_number: " "} + if action == "closed": + return {issue_number: "x"} + if action == "reopened": + has_assignee = bool(event["issue"].get("assignees")) + return {issue_number: "-" if has_assignee else " "} + return {} def update_from_issue(issue: dict) -> Update: @@ -98,6 +137,33 @@ def updates_for_issue_event(event: dict) -> dict[int, Update]: def updates_for_pr_event(event: dict) -> dict[int, Update]: + +# Matches "Closes #12", "Fixes #34", "Resolves #7", etc. (repeatable, case-insensitive) +ISSUE_REF_RE = re.compile( + r"(?:clos(?:e[sd]?)|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)", re.IGNORECASE +) + + +def marker_for_issue_event(event: dict) -> dict[int, str]: + action = event["action"] + issue_number = event["issue"]["number"] + + if action == "assigned": + return {issue_number: "-"} + if action == "unassigned": + return {issue_number: " "} + if action == "closed": + return {issue_number: "x"} + if action == "reopened": + has_assignee = bool(event["issue"].get("assignees")) + return {issue_number: "-" if has_assignee else " "} + return {} + + +def marker_for_pr_event(event: dict) -> dict[int, str]: + +def marker_for_pr_event(event: dict) -> dict[int, str]: + action = event["action"] pr = event["pull_request"] body = pr.get("body") or "" @@ -106,6 +172,16 @@ def updates_for_pr_event(event: dict) -> dict[int, Update]: return {} if action in ("opened", "ready_for_review"): + update = Update("-") + elif action == "closed": + marker = "x" if pr.get("merged") else "-" + else: + return {} + + return {n: update for n in issue_numbers} + + return {n: marker for n in issue_numbers} + update = Update("-") elif action == "closed": update = Update("x") if pr.get("merged") else Update("-") @@ -121,6 +197,11 @@ def load_event() -> dict: def compute_updates() -> dict[int, Update]: + +def compute_updates() -> dict[int, str]: + +def compute_updates() -> dict[int, Update]: + event_name = os.environ["GITHUB_EVENT_NAME"] event = load_event() @@ -142,6 +223,15 @@ def render_suffix(update: Update, existing_suffix: str) -> str: def apply_updates(updates: dict[int, Update]) -> bool: + + return marker_for_issue_event(event) + if event_name == "pull_request" or event_name == "pull_request_target": + return marker_for_pr_event(event) + return {} + + +def apply_updates(updates: dict[int, str]) -> bool: + with open(ROADMAP_PATH, encoding="utf-8") as f: lines = f.readlines() @@ -151,6 +241,180 @@ def apply_updates(updates: dict[int, Update]) -> bool: eol = line[len(stripped):] m = LINE_RE.match(stripped) + + m = LINE_RE.match(line.rstrip("\n")) + + if not m: + continue + + issue = int(m.group("issue")) + if issue not in updates: + continue + + update = updates[issue] + old_state = current_state(m.group("check"), m.group("badge")) + if old_state == new_state: + continue + + render = STATE_RENDER[new_state] + lines[i] = ( + m.group("prefix") + + render["check"] + + m.group("mid") + + render["badge"] + + m.group("body") + + m.group("issue") + + m.group("rest") + + "\n" + ) + changed = True + print(f"#{issue}: [{old_state}] -> [{update.state}]{suffix}") + + print(f"#{issue}: [{old_state}] -> [{new_state}]") + + if changed: + with open(ROADMAP_PATH, "w", encoding="utf-8") as f: + f.writelines(lines) + + return changed + + +def main() -> None: + updates = compute_updates() + if not updates: + print("No linked roadmap issue affected by this event — nothing to do.") + changed = False + else: + changed = apply_updates(updates) + if not changed: + print("Matching roadmap line(s) already up to date.") + + github_output = os.environ.get("GITHUB_OUTPUT") + if github_output: + with open(github_output, "a", encoding="utf-8") as f: + f.write(f"changed={'true' if changed else 'false'}\n") + + +if __name__ == "__main__": + main() +#!/usr/bin/env python3 +""" +Reads the GitHub event that triggered this workflow run (an issue or PR +change) and updates the matching line in ROADMAP.md to one of three +states: + + [ ] Lists — #34 not started + [ ] 🚧 Lists — #34 in progress (assigned / open PR) + [x] Lists — #34 done (merged / issue closed) + +GitHub's task-list rendering only recognizes [ ] and [x] as real, +interactive checkboxes — anything else (like a bare [-]) is just +literal text with no checkbox widget. So the checkbox itself stays +strictly boolean (done / not done), and "in progress" is layered on +as a 🚧 badge in the visible text instead of a third bracket state. + +This only touches roadmap lines that already reference an issue, e.g.: + + * [ ] Lists — #34 + +Lines with no "— #N" suffix are left alone — link an issue to a lesson +by adding that suffix, and the automation picks it up from then on. +""" + +import json +import os +import re + +ROADMAP_PATH = "ROADMAP.md" +WIP_BADGE = "🚧" + +# Internal state codes (unchanged from before): " " = not started, +# "-" = in progress, "x" = done. Only how they're RENDERED changes. +STATE_RENDER = { + " ": {"check": " ", "badge": ""}, + "-": {"check": " ", "badge": f"{WIP_BADGE} "}, + "x": {"check": "x", "badge": ""}, +} + +# Matches e.g. " * [ ] 🚧 Lists — #34" and captures the checkbox, +# an optional WIP badge, and the issue number — everything else on +# the line is left untouched. +LINE_RE = re.compile( + r"^(?P\s*\*\s*\[)(?P[ x])(?P\]\s*)" + rf"(?P{WIP_BADGE}\s*)?" + r"(?P.*?—\s*#)(?P\d+)(?P.*)$" +) + + +def current_state(check: str, badge: str | None) -> str: + if check == "x": + return "x" + return "-" if badge else " " + + +# Matches "Closes #12", "Fixes #34", "Resolves #7", etc. (repeatable, case-insensitive) +ISSUE_REF_RE = re.compile( + r"(?:clos(?:e[sd]?)|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)", re.IGNORECASE +) + + +def marker_for_issue_event(event: dict) -> dict[int, str]: + action = event["action"] + issue_number = event["issue"]["number"] + + if action == "assigned": + return {issue_number: "-"} + if action == "unassigned": + return {issue_number: " "} + if action == "closed": + return {issue_number: "x"} + if action == "reopened": + has_assignee = bool(event["issue"].get("assignees")) + return {issue_number: "-" if has_assignee else " "} + return {} + + +def marker_for_pr_event(event: dict) -> dict[int, str]: + action = event["action"] + pr = event["pull_request"] + body = pr.get("body") or "" + issue_numbers = {int(n) for n in ISSUE_REF_RE.findall(body)} + if not issue_numbers: + return {} + + if action in ("opened", "ready_for_review"): + marker = "-" + elif action == "closed": + marker = "x" if pr.get("merged") else "-" + else: + return {} + + return {n: marker for n in issue_numbers} + + +def load_event() -> dict: + with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as f: + return json.load(f) + + +def compute_updates() -> dict[int, str]: + event_name = os.environ["GITHUB_EVENT_NAME"] + event = load_event() + + if event_name == "issues": + return marker_for_issue_event(event) + if event_name == "pull_request" or event_name == "pull_request_target": + return marker_for_pr_event(event) + return {} + + +def apply_updates(updates: dict[int, str]) -> bool: + with open(ROADMAP_PATH, encoding="utf-8") as f: + lines = f.readlines() + + changed = False + for i, line in enumerate(lines): + m = LINE_RE.match(line.rstrip("\n")) if not m: continue @@ -158,6 +422,25 @@ def apply_updates(updates: dict[int, Update]) -> bool: if issue not in updates: continue + new_state = updates[issue] + old_state = current_state(m.group("check"), m.group("badge")) + if old_state == new_state: + continue + + render = STATE_RENDER[new_state] + lines[i] = ( + m.group("prefix") + + render["check"] + + m.group("mid") + + render["badge"] + + m.group("body") + + m.group("issue") + + m.group("rest") + + "\n" + ) + changed = True + print(f"#{issue}: [{old_state}] -> [{new_state}]") + update = updates[issue] old_state = current_state(m.group("check"), m.group("badge")) render = STATE_RENDER[update.state] @@ -204,4 +487,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main()