Python Programming
40 subscribers
5 photos
2 links
🐍 Python Programming | Code. Learn. Grow.

Welcome to Python Programming — your go-to Telegram community for mastering Python, from basics to advanced!
Download Telegram
🚀 One‑Sitting Python Project: “Smart Downloads Organizer”

Ever stare at a chaotic Downloads folder and wish the files would just file themselves? Build a tiny daemon that watches the folder, inspects each new item, and instantly ships it to a sensible sub‑directory (e.g., Images, Docs, Archives). All in under an hour, no GUI required.

Why it works

1. File‑system events are push‑based – the watchdog library creates an Observer that runs in a background thread and calls you only when something actually changes. No need for a busy‑wait loop that burns CPU.
2. MIME types give intent – Python’s built‑in mimetypes module maps a filename (or its contents via magic if you want extra accuracy) to a high‑level type like image/jpeg or application/pdf. Those top‑level strings (image, video, text, application) become the basis for your target folders.
3. Atomic moves keep things safe – shutil.move works across filesystems and automatically falls back to a copy‑+‑delete when a simple rename isn’t possible, so the script stays robust even if the user redirects downloads to a network drive.
4. Configurable rules = extensibility – By storing a tiny JSON/YAML mapping (.pdf → Documents, *.zip → Archives) you let the user tweak the behavior without touching code. The core loop stays unchanged.

What you’ll need

* watchdog – watches directories for create/modify/delete events.
* mimetypes (standard library) – guesses the file’s type from its name.
* shutil & pathlib – safe, cross‑platform path handling and moving.
* (Optional) python-magic – for content‑based MIME detection if you want extra accuracy.

High‑level flow

1. Load / create a mapping (type → folder).
2. Start a watchdog.Observer on the target directory.
3. For each FileCreatedEvent:
a. Wait a tiny moment (time.sleep(0.5)) to let the download finish.
b. Ask mimetypes.guess_type for the MIME string.
c. Derive the top‑level category (image, text, application, …).
d. Resolve the destination folder (fallback to Others).
e. shutil.move the file there, creating the folder if needed.
4. Keep the observer alive with a clean try/except KeyboardInterrupt block.

Code snapshot

(continued below 👇)
import json
import time
import shutil
from pathlib import Path
import mimetypes

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# ---- Config ---------------------------------------------------------
# You can edit this file to change where each type lands.
CONFIG_PATH = Path.home() / ".downloads_organizer.json"
DEFAULT_MAP = {
"image": "Images",
"video": "Videos",
"text": "Documents",
"application": "Archives", # e.g. zip, pdf, exe
"audio": "Audio"
}
if CONFIG_PATH.exists():
TYPE_MAP = json.loads(CONFIG_PATH.read_text())
else:
TYPE_MAP = DEFAULT_MAP
CONFIG_PATH.write_text(json.dumps(DEFAULT_MAP, indent=2))

# ---- Event handler --------------------------------------------------
class DownloadHandler(FileSystemEventHandler):
def __init__(self, watch_path: Path):
self.watch_path = watch_path

def on_created(self, event):
# We only care about files, not sub‑folders.
if event.is_directory:
return

src = Path(event.src_path)

# Give the OS a moment to finish writing the file.
time.sleep(0.5)

# Guess MIME type → "image/jpeg" → "image"
mime, _ = mimetypes.guess_type(src.name)
category = mime.split("/")[0] if mime else "unknown"

# Resolve destination folder (fallback to "Others")
folder_name = TYPE_MAP.get(category, "Others")
dest_dir = self.watch_path / folder_name
dest_dir.mkdir(exist_ok=True)

dest = dest_dir / src.name
try:
shutil.move(str(src), str(dest))
print(f"🗂️ Moved {src.name} → {dest_dir.name}/")
except Exception as exc:
print(f"⚠️ Failed to move {src.name}: {exc}")

# ---- Main loop ------------------------------------------------------
def main():
downloads = Path.home() / "Downloads"
handler = DownloadHandler(downloads)
observer = Observer()
observer.schedule(handler, str(downloads), recursive=False)
observer.start()
print(f"👀 Watching {downloads} … Press Ctrl+C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

if __name__ == "__main__":
main()


What’s happening under the hood?

Observer.schedule registers handler for the Downloads* directory.
* When the OS creates a new file, on_created fires once – no polling.
* mimetypes.guess_type simply looks at the file extension; it’s fast and requires no extra binaries.
* shutil.move abstracts away os.rename vs. copy‑+‑delete, so you don’t need to think about cross‑disk moves.

⚡️ Take it further (optional challenges)

* Replace mimetypes with magic.from_file(..., mime=True) for content‑based detection.
Add a dry‑run* flag that only prints actions.
* Hook into the system notification center (plyer or notify2) to pop a toast each time a file lands.
Persist a log* (sqlite or plain CSV) of moved files for later analytics.

Give it a spin, watch your Downloads tidy itself, and feel the instant gratification of a script that actually does something useful every time you grab a file. Happy hacking! 🎉
👋 Hey Python newbies!
Ever needed to go through a list and also know which position you’re at?
You might have tried something like this:

for i in range(len(names)):
print(i, names[i])


It works, but there’s a cleaner, safer way that Python gives you for free – enumerate(). Let’s unpack what it does and why it’s often the better choice.

🔎 What is enumerate()?
enumerate() takes any iterable (a list, tuple, string, etc.) and turns it into a sequence of pairs:

first element → the index (starting at 0 by default)
second element → the actual item from your original iterable

So instead of juggling two separate things (range and indexing), you get both in one tidy package.

🛠️ How to use it

names = ["Alice", "Bob", "Charlie"]

for idx, name in enumerate(names):
print(f"#{idx + 1}: {name}")


What happens step‑by‑step?

1. enumerate(names) creates an iterator that yields (0, "Alice"), then (1, "Bob"), then (2, "Charlie").
2. The for loop unpacks each pair into idx and name.
3. Inside the block you can use both variables directly.

💡 Why is enumerate() nicer than range(len(...))?

✅ enumerate() — ❌ range(len(...))

Readability – “for each index and item” is obvious. — You have to remember to index the list manually.
Safety – Won’t raise an error if the list changes size during the loop. — If you accidentally change the list length, range may become out‑of‑sync.
Flexibility – You can start counting from any number (e.g., enumerate(seq, start=1)). — You’d need extra arithmetic (i+1).
Works with any iterable – even generators that don’t support len(). — Requires something that has a length (len()).

🔧 Quick variations

Start counting from 1 (often nicer for human‑readable output):

for pos, fruit in enumerate(["apple", "banana", "cherry"], start=1):
print(f"{pos}. {fruit}")


Enumerate a string (each character gets an index):

for i, ch in enumerate("hello"):
print(i, ch)


🚀 Mini challenge

Try printing the items of the list tasks = ["code", "debug", "test"] in reverse order while still showing the original index (0‑based). Hint: combine enumerate() with reversed().

tasks = ["code", "debug", "test"]

for idx, task in enumerate(reversed(tasks)):
# your code here
pass


Solution:

for idx, task in enumerate(reversed(tasks)):
print(f"Original index {len(tasks)-1-idx}: {task}")


👍 Takeaway
Whenever you need both the position and the value while looping, reach for enumerate(). It makes your code clearer, less error‑prone, and works with any kind of iterable – a tiny tool that pays big dividends as you write more Python. Happy coding! 🚀
🛡️ Power‑up your network calls with a universal retry‑with‑backoff decorator

When you call external APIs, databases, or even local services, transient failures are the norm—not the exception. A bare‑bones try/except quickly becomes noisy and hard to maintain. Below is a single, reusable decorator that:

* Works for both sync and async functions (no duplicate code).
* Implements exponential back‑off with optional jitter (randomness) to avoid thundering‑herd effects.
* Lets you fine‑tune max attempts, base delay, and which exception types trigger a retry.
* Preserves the original function’s signature and metadata so tools like IDEs, help(), and type‑checkers stay happy.

👉 Why the pieces matter

1. functools.wraps – Copies __name__, __doc__, and the signature from the wrapped function to the wrapper. Without it, introspection (e.g. inspect.signature) would see the generic wrapper signature, breaking static analysis and docs.

2. Closure over is_async – We detect iscoroutinefunction once when the decorator is applied, not on every call. This avoids the overhead of re‑checking and lets us choose the correct await/sleep path.

3. Exponential back‑off formula


delay = base * (2 ** attempt)



Each retry doubles the wait time, giving the failing service time to recover.

4. Jitter (random.uniform(0, jitter)) – Adding a small random component prevents many clients from retrying exactly at the same moment, which could otherwise cause another spike of failures.

5. Separate sleep helpers – time.sleep blocks the thread; asyncio.sleep yields control back to the event loop. By abstracting them, the same retry logic works for both sync and async call‑sites.

6. Configurable exceptions – By default we retry on any Exception, but you can restrict it to, say, requests.exceptions.RequestException or a custom TransientError.

📦 The code

(continued below 👇)
❤1
import time
import random
import asyncio
import functools
import inspect
from typing import Callable, Type, Tuple, Union

def retry(
*,
attempts: int = 5,
base_delay: float = 0.5,
max_delay: float = 30.0,
jitter: float = 0.1,
exceptions: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = Exception,
) -> Callable:
"""
Decorator that retries the wrapped function with exponential back‑off.

Parameters
----------
attempts: int
Maximum number of attempts (first try + retries).
base_delay: float
Starting delay in seconds.
max_delay: float
Upper bound for the back‑off delay.
jitter: float
Max random extra seconds added to each delay.
exceptions: Exception or tuple
Exception types that trigger a retry.
"""
def decorator(func: Callable) -> Callable:
is_async = asyncio.iscoroutinefunction(func)

@functools.wraps(func)
def sync_wrapper(*args, **kwargs):
for attempt in range(attempts):
try:
return func(*args, **kwargs)
except exceptions as exc:
if attempt + 1 == attempts:
raise # no more retries
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, jitter)
# Debug-friendly message – can be replaced with a logger
print(f"[retry] {func.__name__} failed ({exc}), retry {attempt+1}/{attempts} after {delay:.2f}s")
time.sleep(delay)

@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
for attempt in range(attempts):
try:
return await func(*args, **kwargs)
except exceptions as exc:
if attempt + 1 == attempts:
raise
delay = min(base_delay * (2 ** attempt), max_delay)
delay += random.uniform(0, jitter)
print(f"[retry] async {func.__name__} failed ({exc}), retry {attempt+1}/{attempts} after {delay:.2f}s")
await asyncio.sleep(delay)

# Return the appropriate wrapper based on the original function type
return async_wrapper if is_async else sync_wrapper

return decorator


🚀 Quick demo

import requests
import asyncio

# ------------------- sync example -------------------
@retry(attempts=4, base_delay=1, jitter=0.2, exceptions=requests.exceptions.RequestException)
def fetch_status(url: str) -> int:
"""Fetch a URL and return HTTP status, retrying on network errors."""
resp = requests.get(url, timeout=2)
resp.raise_for_status()
return resp.status_code

# ------------------- async example -------------------
@retry(attempts=3, base_delay=0.5, jitter=0.1, exceptions=ConnectionError)
async def async_fetch(url: str) -> str:
"""Async fetch that raises ConnectionError on failure."""
reader, writer = await asyncio.open_connection(url, 80)
# pretend we do something useful …
writer.close()
await writer.wait_closed()
return "ok"

# Running the sync function
try:
print(fetch_status("https://httpbin.org/status/503"))
except Exception as e:
print("Final failure:", e)

# Running the async function
async def main():
try:
print(await async_fetch("example.com"))
except Exception as e:
print("Async final failure:", e)

asyncio.run(main())


What you’ll see

* On each failure the wrapper prints a concise retry log (replace print with logging in production).
* The delay grows 1 s → 2 s → 4 s (capped by max_delay if you set one).
* Jitter adds a small random offset, so two concurrent workers won’t line up perfectly.

(continued below 👇)
❤1
📌 Take‑away checklist

* Preserve signatures – always wrap with functools.wraps.
* Detect async once – saves a tiny but measurable cost inside the retry loop.
* Cap the back‑off – prevents runaway sleeps on stubborn services.
* Add jitter – a simple random.uniform can dramatically improve system stability under load.
* Make the decorator configurable – keep it reusable across projects rather than copy‑pasting ad‑hoc try/except blocks.

Drop this into a shared utilities module, and your codebase will instantly gain robust, self‑documenting retry logic without littering the business code with repetitive error handling. Happy coding! 🚀
🚀 One‑Sitting Python Project: “Smart Batch‑Renamer”

Ever stared at a folder full of screenshots, receipts, or logs and thought “There has to be a faster way to give them sensible names”? In a single sitting you can build a tiny CLI tool that scans a directory, extracts information with a regular‑expression, and renames every file in one go.

🎯 What the script does
1. Walks the target folder with pathlib.Path.rglob – a modern, object‑oriented wrapper around the filesystem that avoids the quirks of os.listdir.
2. Matches each filename against a user‑supplied pattern (e.g., IMG_(\d{4})(\d{2})(\d{2})_.*\.jpg). The capture groups let you rearrange parts of the original name.
3. Builds a new name using an f‑string template (e.g., f"{year}-{month}-{day}_{counter}.jpg"). Because f‑strings are evaluated at runtime, you can inject any variable you like – a counter, the current date, etc.
4. Renames safely with Path.rename. If a conflict appears, the script auto‑increments a numeric suffix so nothing gets overwritten.

> Why it works:
> * pathlib returns Path objects that behave like strings but also expose methods (suffix, stem, rename) – no need to concatenate strings manually.
> * re (the regex engine) returns a Match object; its .group() method extracts exactly the pieces you asked for, letting you transform the original filename without hard‑coding positions.
> * argparse gives a self‑documenting command line (the -p pattern, -t template, -d directory). Users can see --help and immediately understand the options.

🛠️ Core libraries / concepts
- pathlib – modern filesystem paths, cross‑platform.
- re – powerful pattern matching, essential for pulling out dates, IDs, etc.
- argparse – turn a script into a friendly CLI.
- itertools.count – an infinite counter for unique suffixes.

📦 Minimal skeleton (copy‑paste & run)

(continued below 👇)
#!/usr/bin/env python3
import argparse
import re
from pathlib import Path
from itertools import count

def parse_args():
parser = argparse.ArgumentParser(
description="Batch‑rename files using a regex pattern and a template."
)
parser.add_argument(
"-d", "--directory", type=Path, default=Path.cwd(),
help="Folder containing files to rename (default: current directory)."
)
parser.add_argument(
"-p", "--pattern", required=True,
help="Regex with capture groups to extract parts of the original name."
)
parser.add_argument(
"-t", "--template", required=True,
help=(
"New name template using group names or numbers. "
"Example: '{year}-{month}-{day}_{i}{ext}'"
),
)
parser.add_argument(
"-r", "--recursive", action="store_true",
help="Search sub‑folders as well."
)
return parser.parse_args()

def rename_files(dir_path: Path, regex: re.Pattern, tmpl: str, recursive: bool):
files = dir_path.rglob("*") if recursive else dir_path.iterdir()
counter = count(1)

for file in files:
if not file.is_file():
continue
m = regex.search(file.name)
if not m:
continue

# Build a dict that can be used in the template
groups = {str(i): v for i, v in enumerate(m.groups(), start=1)}
groups.update(m.groupdict())
groups["i"] = next(counter) # fallback counter
groups["ext"] = file.suffix
groups["stem"] = file.stem

new_name = tmpl.format(**groups)
new_path = file.with_name(new_name)

# Avoid overwriting existing files
while new_path.exists():
groups["i"] = next(counter)
new_name = tmpl.format(**groups)
new_path = file.with_name(new_name)

print(f"Renaming: {file.name} → {new_path.name}")
file.rename(new_path)

def main():
args = parse_args()
compiled = re.compile(args.pattern)
rename_files(args.directory, compiled, args.template, args.recursive)

if __name__ == "__main__":
main()


How to try it:
1. Save the script as renamer.py and chmod +x renamer.py.
2. Place a few test files (e.g., IMG_20230921_001.jpg, IMG_20230922_002.jpg).
3. Run:

./renamer.py -p "IMG_(\d{4})(\d{2})(\d{2})_.*\.jpg" \
-t "{1}-{2}-{3}_{i}{ext}" -d ./photos


You’ll see the files renamed to 2023-09-21_1.jpg, 2023-09-22_2.jpg, …

🌟 Why you’ll love it
- Instant payoff – open a folder and instantly see a clean naming scheme.
- Fully customizable – change the regex or template to suit receipts, logs, audio files, etc.
- No external dependencies – everything lives in the Python standard library, so the script works on any machine with Python 3.8+.

Give it a spin, tweak the pattern, and you’ve got a reusable tool that saves minutes (or hours) every time you download a batch of files. Happy renaming! 🎉
❤2
Hey Python friends! 🌱
Today we’re diving into one of the simplest yet most powerful tricks for making your code readable and fun: f‑strings (short for “formatted string literals”).

Why do we need them?

When you want to show the value of a variable inside a piece of text, you have a few old‑school ways:

* Concatenation – "Hello, " + name + "!"
Works, but you have to remember to convert non‑strings with str() and it quickly becomes messy with many variables.

* % formatting – "Hello, %s!" % name
A relic from C‑style formatting; it’s harder to read when the format gets complex.

* str.format() – "Hello, {}!".format(name)
Better, but you still have to count placeholders or name them.

All of these are fine, but they force you to look away from the natural sentence you’re trying to build. That’s where f‑strings shine: they let you write the exact sentence you want, and Python fills in the blanks for you.

The magic behind f‑strings

1. Prefix the string with f (or F).


f"This is an f‑string"


2. Place any Python expression inside curly braces {}.
When the interpreter sees the string, it evaluates each expression and inserts its result right there.

3. The result is a new string (the original f‑string stays unchanged).

Because the evaluation happens at runtime, you can use any valid expression – not just variable names, but also calculations, function calls, or even conditional logic.

Simple example – greeting a user

name = "Ada"
age = 28

greeting = f"Hello, {name}! You are {age} years old."
print(greeting)


Output

Hello, Ada! You are 28 years old.


What’s happening?

- The f tells Python: “Hey, treat this as a template.”
- {name} is replaced by the current value of name.
- {age} is replaced by the current value of age.

No need for + or .format() – the text stays exactly as you’d read it.

Going a step further – expressions inside {}

width = 5
height = 3
area_msg = f"The area of a {width}×{height} rectangle is {width * height}."
print(area_msg)


Output

The area of a 5×3 rectangle is 15.


Here {width * height} is an expression. Python computes 5 * 3 and injects 15 right into the string.

Formatting numbers – keep things tidy

You can add a format specifier after a colon : inside the braces.

price = 9.99
tax = 0.075
total = price * (1 + tax)

msg = f"Subtotal: ${price:.2f}\nTax (7.5%): ${price*tax:.2f}\nTotal: ${total:.2f}"
print(msg)


Output

Subtotal: $9.99
Tax (7.5%): $0.75
Total: $10.74


Why does :.2f work?

- : introduces the format spec.
- .2f means “floating‑point number with 2 digits after the decimal”.
- Python applies that rule to the value before inserting it.

A quick do‑and‑don’t checklist

Do

- ✅ Use f‑strings for any string that includes variables or expressions.
- ✅ Keep the expression simple; complex logic belongs in a separate line.
- ✅ Add formatting specs (:.2f, :>10, etc.) for numbers, dates, or alignment.

Don’t

- ❌ Forget the leading f – without it the {} stay literal characters.
- ❌ Over‑nest braces ({{ and }} are only needed when you actually want a { or } in the final output).
- ❌ Use f‑strings for very large, multi‑megabyte texts where performance matters; plain concatenation or templates may be faster.

TL;DR – One‑liner cheat sheet

- Basic: f"Hello, {name}!"
- Expression: f"The sum is {a + b}"
- Number formatting: f"{value:.2f}" → two decimals
- Alignment: f"{text:>10}" → right‑align in a width of 10

Give it a try right now in your REPL:

(continued below 👇)
❤1
first = "Python"
second = "Rocks"
print(f"{first} {second}!".upper())


You’ll see:

PYTHON ROCKS!


Takeaway: f‑strings let you write what you mean instead of how to stitch it together. They make your code clearer, shorter, and more Pythonic – exactly the vibe we love as beginners.

Happy coding, and may your strings always be f‑abulous! 🚀
❤1
🌟 Python tip for absolute beginners: How to loop over a list and know the position of each item using enumerate

When you write a for‑loop you usually care about the value you’re iterating over:

fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)


But sometimes you also need to know where that value sits in the list – its index (0 for the first item, 1 for the second, …).

The “old‑school” way (and why it’s clunky)

for i in range(len(fruits)):
print(i, fruits[i])


* Why it works: range(len(fruits)) creates a sequence of numbers from 0 up to the length of the list minus one. Inside the loop you use that number (i) to fetch the item with fruits[i].
* Why it feels awkward: you have to write the length of the list twice, you risk an “off‑by‑one” mistake, and the code mixes two different ideas (counting and accessing) in one line.

enumerate to the rescue

for index, fruit in enumerate(fruits):
print(index, fruit)


What’s happening?

* enumerate(fruits) takes the original list and produces a new sequence of pairs.
* Each pair is a tuple: the first element is the current index, the second element is the original item.
* The for loop automatically unpacks that tuple into the variables index and fruit.

Because enumerate builds the pairs on the fly, it never creates a whole extra list in memory – it’s efficient even for very long sequences.

Starting the count at a different number

Sometimes you want the numbering to start at 1 (or any other number). enumerate lets you set a custom start:

for pos, fruit in enumerate(fruits, start=1):
print(f"{pos}. {fruit}")


Output:

1. apple
2. banana
3. cherry


Why it works: the optional start argument tells enumerate what value to use for the first index. After that it simply adds 1 for each subsequent item.

Quick checklist – when to use enumerate

- You need the position of each item (e.g., to label output, to compare with another list, or to skip a specific index).
- You want cleaner code that avoids manual range(len(...)) gymnastics.
- You care about performance on large data – enumerate is lazy and memory‑friendly.

Mini‑exercise

Try this on your own:

words = ["hello", "world", "python", "rocks"]
for i, w in enumerate(words, start=5):
print(f"Item {i}: {w.upper()}")


What do you see? Experiment by changing start and by removing it entirely. Notice how the output changes and how the code stays readable.

Takeaway: enumerate is a tiny built‑in tool that makes looping with indices both clear and safe. Keep it in your toolbox – you’ll reach for it every time you need “the item and its place” together. 🚀
❤1
🚀 One‑Sitting Python Project: “Smart Folder Organizer”

Ever looked at a messy Downloads folder and thought, “I wish my files would tidy themselves up”? In a single sitting you can build a tiny utility that scans any directory, decides where each file belongs (pictures, docs, archives, etc.) and moves it into a matching sub‑folder. The result is an instant declutter that you can run whenever you need – and the code itself is a great playground for the core ideas every new Python coder should master.

What you’ll learn (and why it matters)

Concept — Why it’s useful
**️⃣ pathlib.Path — Gives you an object‑oriented way to work with file system paths – clearer than the old os.path functions and works the same on Windows, macOS and Linux.
🔀 shutil.move — Handles moving files across directories (even across different drives) with a single call; you don’t have to reinvent copy‑and‑delete logic.
📂 Dictionaries — Perfect for mapping file extensions (e.g., .jpg) to destination folders (e.g., Images). This shows how data structures drive program flow.
🔁 Loops & conditionals — The backbone of any automation – you’ll iterate over every file, check its type, and decide what to do.
⚠️ Basic error handling — Using try/except protects you from permission errors or stray files that can’t be moved.

All of these are building blocks you’ll keep using in bigger projects, so mastering them now pays off later.

Quick sketch of the approach

1. Pick a folder – you can pass it as a command‑line argument or hard‑code a path for a quick test.
2. Define categories – a small dictionary where keys are tuples of extensions and values are the folder name you want them to end up in.
3. Iterate over every entry in the chosen directory with Path.iterdir().
4. Identify the file type by looking at Path.suffix.lower().
5. Create the target sub‑folder if it doesn’t already exist (Path.mkdir(exist_ok=True)).
6. Move the file with shutil.move().
7. Handle exceptions so the script never crashes on a single problematic file.

That’s it – roughly 30‑40 lines of code, and you have a reusable tool you can run manually or schedule with a cron job / Windows Task Scheduler.

Minimal but complete code snippet

import shutil
from pathlib import Path

# 1️⃣ Folder you want to clean up
SOURCE = Path.home() / "Downloads" # change to any folder you like

# 2️⃣ Mapping of extensions → destination folder name
CATEGORIES = {
("jpg", "jpeg", "png", "gif", "bmp"): "Images",
("pdf", "doc", "docx", "txt", "odt"): "Documents",
("mp3", "wav", "flac"): "Audio",
("mp4", "mov", "avi", "mkv"): "Videos",
("zip", "tar", "gz", "rar"): "Archives",
}

def get_category(ext: str) -> str:
"""Return the folder name for a given file extension, or 'Other'."""
for exts, folder in CATEGORIES.items():
if ext in exts:
return folder
return "Other"

def organize_folder(folder: Path) -> None:
"""Walk through *folder* and move each file into its category."""
if not folder.is_dir():
raise NotADirectoryError(f"{folder} is not a directory")

for item in folder.iterdir():
# Skip sub‑folders – we only want to move files
if item.is_dir():
continue

ext = item.suffix.lower().lstrip('.') # e.g. 'jpg'
target_folder = folder / get_category(ext)
target_folder.mkdir(exist_ok=True) # create if missing

try:
shutil.move(str(item), str(target_folder))
print(f"✅ Moved {item.name} → {target_folder.name}/")
except Exception as e:
print(f"⚠️ Could not move {item.name}: {e}")

if __name__ == "__main__":
organize_folder(SOURCE)


Why this works

(continued below 👇)
- Path.suffix gives the file extension including the leading dot (.pdf). Stripping the dot and lower‑casing makes the comparison case‑insensitive (PHOTO.JPG still matches “Images”).
- target_folder.mkdir(exist_ok=True) creates the destination only once; subsequent runs won’t raise an error.
- shutil.move automatically decides whether it can perform a simple rename (same drive) or must copy‑then‑delete (different drives).
- The try/except block catches any unexpected problem (locked file, permission denied) and lets the script continue processing the rest of the files.

How to take it further (optional fun)

- Add a date‑based rule: move files older than 30 days into an “Old” sub‑folder.
- GUI with tkinter: let the user pick the source folder via a dialog box.
- Configuration file (json or yaml) so you can tweak categories without touching code.
- Dry‑run mode: print the actions without actually moving anything – great for testing.

💡 TL;DR: In under an hour you’ll have a script that turns a chaotic download folder into a tidy hierarchy, while learning path handling, dictionary look‑ups, file operations, and basic error handling. Run it, watch the files shuffle into place, and feel the instant reward of automation. Happy coding!
👋 Hey Python newcomers!
Ever wanted to build a sentence that talks back to the user, like “Hello Alice, you are 23 years old!” without a bunch of confusing plus‑signs?

🎯 What we’ll learn
f‑strings – a super‑simple way to insert variables (or even tiny calculations) straight into a piece of text.

🤔 Why do f‑strings work?

1. They’re just strings – a piece of text that Python knows how to print.
2. The f before the quotes tells Python “look inside” – every part that sits between { and } is treated as a normal Python expression.
3. Python evaluates those expressions first, turns the result into text, and then stitches everything together into one final string.

Think of it like a magic template: you write the shape of the sentence once, and Python fills in the blanks each time you use it.

🛠️ The basic syntax

name = "Alice"
age = 23
message = f"Hello {name}, you are {age} years old!"
print(message)


What happens here?

- The leading f tells Python to pay attention to the curly braces.
- {name} is replaced by the value of the variable name.
- {age} is replaced by the value of age.
- The whole thing becomes the single string "Hello Alice, you are 23 years old!", which print then shows on the screen.

✨ A few handy tricks

- Do math inside the braces – no need for extra variables.



x = 5
print(f"The double of {x} is {x * 2}.")


- Call functions – anything that returns a value works.



def greet(who):
return f"Hey, {who}!"

print(f"{greet('Bob')} You have {len('Bob')} letters in your name.")


- Control formatting – you can tell Python how many decimal places you want, how wide a field should be, etc.



price = 7.5
print(f"The price is ${price:.2f}") # → The price is $7.50


📌 Common pitfalls (and how to avoid them)

- Forgot the f?


name = "Sam"
# Oops – missing the leading f
print("Hi {name}") # prints literally {name}


Fix: add the f → print(f"Hi {name}").

- Mismatched braces – every { needs a matching }.


print(f"Value is {value")   # SyntaxError


- Using backslashes inside the braces – keep the expression simple; if it gets messy, compute it first in a separate variable.

🚀 Try it yourself right now!

# Ask the user for their favorite fruit and how many they ate today
fruit = input("What fruit do you like? ")
count = int(input("How many did you eat today? "))

print(f"You ate {count} {fruit}(s) today. Good job!")


What you’ll see:
- input() grabs what the user types.
- int() turns the text “3” into the number 3 so we can treat it as a number later.
- The final print uses an f‑string to build a friendly sentence in one line.

👍 Bottom line:
Whenever you need to combine text with variable values (or quick calculations), reach for an f‑string. It makes your code cleaner, easier to read, and saves you from fiddling with the + operator or the older format() method.

Happy coding, and may your strings always be f‑abulous! 🎉
🚀 One‑Sitting Python Project: “Pomodoro Companion”

You’ve probably heard of the Pomodoro Technique: work 25 minutes → break 5 minutes, repeat, and after four cycles take a longer rest. It’s a proven way to keep focus, and a tiny script can turn your computer into a personal timer without any fancy UI.

What the script will do

1. Ask how many cycles you want (default 4).
2. Count down the work‑period (25 min) and the short break (5 min).
3. Play a short beep (or show a desktop notification) at every transition.
4. Log each start/stop time to a plain‑text file so you can look back on how many minutes you actually spent focused.

All of this fits into a single Python file and can be typed out in under 30 minutes – perfect for a beginner who already knows variables, loops, and functions.

Core ideas you’ll see in action

Concept — Why it matters
time.sleep() — Pauses execution for a given number of seconds – the simplest way to create a countdown.
datetime.now() — Gives you the exact current timestamp; we use it to write human‑readable log entries.
while loops — Repeats the work/break cycle until the desired number of Pomodoros is reached.
Functions (run_timer, beep) — Keep the code tidy and reusable; each function does one clear job.
Optional third‑party library (plyer) — Provides cross‑platform desktop notifications (works on Windows, macOS, Linux). If you prefer a simple beep, the standard library winsound (Windows) or os.system('afplay /path/to/sound.wav') (macOS) can be used instead.

> Tip: If you haven’t installed any external package before, just run pip install plyer. The rest of the script only uses the built‑in standard library.

How it works – step by step (plain language)

1. Collect user input – input() returns a string; int() converts it to a number we can count with.
2. Define a tiny helper beep() – depending on the OS, we either call the built‑in winsound.Beep (Windows) or ask the OS to play a default sound file. If you installed plyer, we call notification.notify() instead, which pops a small banner on the screen.
3. run_timer(name, seconds) does the heavy lifting:
* It records the start time (datetime.now()).
* Enters a while loop that keeps subtracting one second from the remaining time, printing the minutes:seconds left on the same line (end='\r' rewrites the line).
* When the countdown hits zero, it logs the end time, calls beep() to signal the transition, and returns.
4. Main loop – for each Pomodoro we:
* Call run_timer('Work', 25*60).
* Call run_timer('Break', 5*60).
* Increment a counter and optionally print a friendly “Cycle X complete!” message.

All timestamps are written to a file called pomodoro_log.txt in the same folder, e.g.:

2026-09-23 10:00:00 – Work started
2026-09-23 10:25:00 – Work ended
2026-09-23 10:25:00 – Break started
2026-09-23 10:30:00 – Break ended


You can open that file later and see exactly how many focused minutes you accumulated.

Minimal code you can copy‑paste and run

(continued below 👇)
import time
import sys
from datetime import datetime

# ---------- optional notification ----------
try:
from plyer import notification
def alert(title, message):
notification.notify(title=title, message=message, timeout=3)
except ImportError:
# fallback: simple console beep
def alert(title, message):
sys.stdout.write('\a') # terminal beep
sys.stdout.flush()
print(f'\n--- {title}: {message} ---')
# ------------------------------------------

LOG_FILE = "pomodoro_log.txt"

def log(msg: str):
"""Append a timestamped line to the log file."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"{timestamp} – {msg}\n")

def run_timer(name: str, seconds: int):
"""Count down `seconds` while printing a live timer."""
log(f"{name} started")
end_time = time.time() + seconds
while True:
remaining = int(end_time - time.time())
if remaining <= 0:
break
mins, secs = divmod(remaining, 60)
# carriage return rewrites the same line
print(f"{name}: {mins:02d}:{secs:02d}", end='\r')
time.sleep(1)
print(f"{name}: 00:00 ")
log(f"{name} ended")
alert(name, "Time's up!")

def main():
try:
cycles = int(input("How many Pomodoros? (default 4) ").strip() or "4")
except ValueError:
cycles = 4

WORK_SEC = 25 * 60 # 25 minutes
BREAK_SEC = 5 * 60 # 5‑minute short break

for i in range(1, cycles + 1):
print(f"\n=== Pomodoro {i} of {cycles} ===")
run_timer("Work", WORK_SEC)
if i < cycles: # no break after the final cycle
run_timer("Break", BREAK_SEC)

print("\nAll done! 🎉 Check", LOG_FILE, "for your session summary.")
alert("Pomodoro", "All cycles completed!")

if __name__ == "__main__":
main()


Why this works

- time.time() gives the current Unix timestamp (seconds since 1970). Adding the desired duration yields an absolute end moment, so the loop never drifts even if sleep(1) is a tiny bit off.
- print(..., end='\r') rewrites the same console line, creating a live countdown without flooding the terminal.
- with open(..., "a") opens the log file in append mode, meaning each run adds new lines instead of overwriting old data.
- try / except ImportError lets the script gracefully fall back to a simple beep if the optional plyer library isn’t present – a nice pattern for “optional features”.

Ready, set, code!

Take the snippet, paste it into a file called pomodoro.py, run `python pom
🚀 One‑Sitting Python Project: “Mini‑Expense Tracker”

Ever wish you could just jot down what you spent on a coffee, a bus ticket or a new book without opening a spreadsheet? Build a tiny command‑line tool that saves every entry to a CSV file and can instantly show you today’s total, a category breakdown, or the whole history. You can finish it in a single sitting and keep extending it later.

Why this project works for beginners

* CSV = plain text – No database required. Python’s built‑in csv module lets you read and write rows as simple lists.
* pathlib for file paths – It gives you an object‑oriented way to point at “expenses.csv” that works on Windows, macOS and Linux alike.
* argparse for a friendly CLI – You can call the script like a tiny app: python expense.py add … or python expense.py summary.
* datetime for dates – Store dates in ISO‑format (YYYY‑MM‑DD) so they sort naturally and are easy to parse later.

All of these modules are part of Python’s standard library, so you don’t need to install anything extra.

The core idea in plain English

1. Add a record – The user supplies a category (e.g., “food”), an amount (e.g., 4.50), and an optional note. The script records the current date and writes a new line to expenses.csv.
2. Show recent entries – When the user runs view, the script reads the file, turns each line into a dictionary, and prints a tidy list.
3. Summarise – summary groups rows by category and adds the amounts, giving you a quick glance at where the money went.

Because the data lives in a CSV, you can open it later in Excel, Google Sheets, or any text editor.

What you’ll need to know (and a quick definition)

* csv.writer / csv.DictReader – Tools that convert Python objects to CSV rows and back.
* Path (from pathlib) – Represents a file path; you can call .exists() to check if the file is already there.
* argparse.ArgumentParser – Parses command‑line arguments like add, view, summary. Think of it as the “menu” of your app.
* defaultdict (from collections) – A dictionary that automatically creates a default value for a new key; perfect for summing amounts per category.

A tiny code sketch (just enough to get you started)

(continued below 👇)
#!/usr/bin/env python3
import csv
import argparse
from pathlib import Path
from datetime import datetime
from collections import defaultdict

DATA_FILE = Path("expenses.csv")

def ensure_file():
"""Create the CSV with a header if it doesn't exist yet."""
if not DATA_FILE.exists():
with DATA_FILE.open("w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["date", "category", "amount", "note"])

def add_entry(args):
"""Append a new expense line."""
ensure_file()
today = datetime.now().date().isoformat() # e.g. '2026-09-24'
with DATA_FILE.open("a", newline="") as f:
writer = csv.writer(f)
writer.writerow([today, args.category, f"{args.amount:.2f}", args.note or ""])
print(f"✅ Added {args.amount:.2f} to {args.category}")

def view_entries(_):
"""Print every row nicely."""
if not DATA_FILE.exists():
print("📂 No data yet – add something first!")
return
with DATA_FILE.open() as f:
reader = csv.DictReader(f)
for row in reader:
print(f"{row['date']} | {row['category']:10} | ${row['amount']:>6} | {row['note']}")

def summary(_):
"""Show total spent per category."""
if not DATA_FILE.exists():
print("📂 No data yet – add something first!")
return
totals = defaultdict(float)
with DATA_FILE.open() as f:
for row in csv.DictReader(f):
totals[row["category"]] += float(row["amount"])
print("💰 Expense summary:")
for cat, amount in sorted(totals.items(), key=lambda x: -x[1]):
print(f" {cat:10}: ${amount:,.2f}")

def main():
parser = argparse.ArgumentParser(
description="A tiny one‑file expense tracker."
)
sub = parser.add_subparsers(dest="cmd", required=True)

# ---- add command ----
add = sub.add_parser("add", help="Add a new expense")
add.add_argument("category", help="e.g. food, transport, books")
add.add_argument("amount", type=float, help="Amount spent")
add.add_argument("-n", "--note", help="Optional short note")
add.set_defaults(func=add_entry)

# ---- view command ----
view = sub.add_parser("view", help="Show all entries")
view.set_defaults(func=view_entries)

# ---- summary command ----
summ = sub.add_parser("summary", help="Total per category")
summ.set_defaults(func=summary)

args = parser.parse_args()
args.func(args)

if __name__ == "__main__":
main()


How the code works (the “why”)

* ensure_file() checks whether expenses.csv exists; if not, it creates the file once with a header line. This prevents “file not found” errors on the first run.
* add_entry() uses datetime.now().date().isoformat() – the ISO format sorts alphabetically exactly like chronological order, so later you can simply sort the file and the dates will line up.
* csv.writer writes a list of strings; f"{args.amount:.2f}" guarantees two decimal places, making the CSV tidy.
* view_entries() reads with csv.DictReader, which gives each row as a dictionary keyed by the header names, so you can refer to row["category"] instead of remembering column indexes.
* summary() accumulates amounts in a defaultdict(float). When a new category appears, the default value 0.0 is automatically created, letting you add directly with +=. The sorted(..., key=lambda x: -x[1]) orders categories by descending spend.

Next‑step ideas (once you’ve got the basics working)

* Add a --date flag so you can back‑date an expense.
* Export a simple bar chart with matplotlib (still a one‑liner).
* Store the CSV in a hidden folder (~/.expense_tracker/expenses.csv) using Path.home().
* Add a delete sub‑command that removes a line by its row number.

Give it a try, run the script a few times, and watch your own expense history grow
👋 Hey Python beginners!
Ever wanted to loop over a list and know which position each item is at?
You could count manually, but Python gives you a neat helper called enumerate().

🎯 What enumerate() does
When you write a normal for loop like:

for fruit in fruits:
print(fruit)


you get each element, but you have no idea which index (0, 1, 2…) it came from.
enumerate() wraps the original list and, on each step, produces a pair:

1. the current index
2. the actual item

So the loop receives two values at once. This is why you often see for i, item in … – i is the index, item is the value.

🛠️ How to use it (step‑by‑step)

1. Call enumerate() on any iterable (list, string, tuple, etc.).
2. Optionally give a starting number – the default is 0.
3. Unpack the two values inside the for statement.

fruits = ["apple", "banana", "cherry", "date"]

# start counting at 0 (default)
for index, fruit in enumerate(fruits):
print(f"Item #{index} is {fruit}")

print("---")

# start counting at 1 (more natural for humans)
for pos, fruit in enumerate(fruits, start=1):
print(f"Item #{pos} is {fruit}")


What you’ll see

Item #0 is apple
Item #1 is banana
Item #2 is cherry
Item #3 is date
---
Item #1 is apple
Item #2 is banana
Item #3 is cherry
Item #4 is date


💡 Why it’s useful

- No manual counters – you don’t need a separate variable that you += 1 each loop.
- Less error‑prone – forgetting to increment or using the wrong variable is a common beginner mistake.
- Works with any iterable – not just lists. You can enumerate characters in a string, rows in a CSV reader, etc.

📚 Quick checklist

- Use enumerate() when you need the position and the value.
- Pick a start number that matches how you’ll show the result (0 for programming, 1 for user‑facing lists).
- Remember to unpack two variables (index, item) inside the loop.

Give it a try! Replace any loop where you’re manually tracking an index with enumerate() and watch your code become cleaner and easier to read.

Happy coding! 🚀
❤2
🚀 One‑Sitting Python Project: “Instant Currency Converter”

Ever opened a web page, typed a number, and wondered “what’s that in euros?” – only to jump to Google, type the conversion, and copy‑paste the result? Let’s eliminate that extra step with a tiny command‑line tool that talks to a free exchange‑rate API and gives you an instant answer. It can be written, tested, and run in a single sitting, even if you’ve only scratched the surface of Python.

🎯 What the script will do
1. Read three arguments from the terminal – the amount, the source currency code (e.g., USD), and the target currency code (e.g., EUR).
2. Call a public API (https://api.exchangerate.host) that returns the latest rates in JSON format.
3. Extract the needed rate, multiply it by the amount, and print a friendly message.
4. (Optional) Save the last 5 conversions to a tiny local text file so you have a quick history.

All of this can be done with the built‑in json and urllib modules, but we’ll use the very popular third‑party library requests because it makes HTTP calls readable and robust.

🛠️ Core concepts you’ll see
Concept — Why it matters — Quick definition

Command‑line arguments (sys.argv) — Lets the user control the script without opening the code. — A list that contains everything you typed after the script name.
HTTP GET request (requests.get) — Fetches data from the internet. — “Give me the resource at this URL, please.”
JSON parsing (response.json()) — Turns the raw text the API sends into Python objects (dicts, lists). — JSON = JavaScript Object Notation, a lightweight data‑exchange format.
String interpolation (f‑strings) — Makes output readable and dynamic. — f"{value}" inserts the value directly into the string.
File I/O (open(..., 'a')) — Stores a tiny history without any database. — “Append” mode adds to the end of a file.
Error handling (try/except) — Prevents the program from crashing on bad input or network hiccups. — Catches exceptions and lets you respond gracefully.

📦 What you need to install

pip install requests


requests is pure Python, widely used, and works on every platform.

🧩 Sketch of the solution (no full tutorial – just the big picture)

1. Grab arguments – sys.argv[1:] gives you a list like ['100', 'USD', 'EUR'].
2. Validate – make sure the amount is a number and the currency codes are three‑letter strings.
3. Build the API URL – the service expects something like
https://api.exchangerate.host/convert?from=USD&to=EUR&amount=100.
4. Send the request – requests.get(url) returns a Response object.
5. Decode JSON – resp.json() gives a dict; the conversion result lives under ['result'].
6. Print – use an f‑string: print(f"💰 100 USD = 92.34 EUR").
7. Log – open a file history.txt in append mode and write the same line with a timestamp.

💡 Why it works

- requests.get handles the whole HTTP handshake for you – DNS lookup, TCP connection, sending headers, receiving the response, and closing the socket.
- The API returns JSON, which maps naturally onto Python dictionaries, so resp.json()['result'] is just a number you can multiply or display.
- sys.argv is already populated by Python before your script runs, so you don’t need any extra parsing library for a simple three‑parameter tool.
- By wrapping the network call in try/except, you catch problems like “no internet” or “invalid currency code” and can show a helpful message instead of a cryptic traceback.
- Appending to a text file is the easiest form of persistence; you avoid dealing with databases while still getting a useful history.

(continued below 👇)