Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions absolute-vs-relative-python-imports/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Absolute vs Relative Imports in Python

This folder provides the code examples for the Real Python tutorial [Absolute vs Relative Imports in Python](https://realpython.com/absolute-vs-relative-python-imports/).

The code requires Python 3.3 or later, because `package1/` has no `__init__.py` file and relies on [namespace packages](https://peps.python.org/pep-0420/).

## Example Project

The tutorial's `project/` tree comes in two parallel copies so you can compare both import styles:

- [`absolute/project/`](absolute/project/) uses absolute imports.
- [`relative/project/`](relative/project/) uses explicit relative imports.

Both copies contain the same files:

- `package1/module1.py`: Imports `function1` from `module2` and prints its result.
- `package1/module2.py`: Defines `function1()`.
- `package2/__init__.py`: Defines `class1`.
- `package2/module3.py`: Imports `class1` and `function2` and prints them.
- `package2/module4.py`: Part of the tutorial's tree, but empty because no example uses it.
- `package2/subpackage1/module5.py`: Defines `function2()`.
- `main.py`: Entry point that runs the imports. In `absolute/project/`, it also holds the tutorial's block of absolute import examples.

Run the examples from inside a `project/` folder so that `package1` and `package2` are importable:

```sh
$ cd absolute/project/
$ python main.py
Absolute imports:
Hello from function1() in package1/module2.py
<class1 from package2/__init__.py>
Hello from function2() in package2/subpackage1/module5.py

module1.main():
Hello from function1() in package1/module2.py

module3.main():
<class1 from package2/__init__.py>
Hello from function2() in package2/subpackage1/module5.py
```

You can also run a single module with the `-m` switch, which works for both copies:

```sh
$ cd relative/project/
$ python -m package2.module3
<class1 from package2/__init__.py>
Hello from function2() in package2/subpackage1/module5.py
```

Running a module as a file path, such as `python package1/module1.py`, fails in both copies. The relative version raises `ImportError: attempted relative import with no known parent package`. The absolute version raises `ModuleNotFoundError` because `project/` isn't on the import path.

## Import Styling

- [`import_style.py`](import_style.py): The tutorial's example of PEP 8 import grouping and ordering.

This file is only a style reference and isn't meant to run. It imports third-party Flask packages and the placeholder modules `local_module` and `local_package`, which don't exist. A `# ruff: noqa: F401` comment at the top stops the linter from flagging the imports as unused.
17 changes: 17 additions & 0 deletions absolute-vs-relative-python-imports/absolute/project/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from package1 import module1
from package1.module2 import function1
from package2 import class1
from package2.subpackage1.module5 import function2

# Also import module3 to run the absolute imports inside it
from package2 import module3

print("Absolute imports:")
print(function1())
print(class1())
print(function2())

print("\nmodule1.main():")
module1.main()
print("\nmodule3.main():")
module3.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# package1/module1.py

from package1.module2 import function1


def main():
print(function1())


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def function1():
return "Hello from function1() in package1/module2.py"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class class1:
def __repr__(self):
return "<class1 from package2/__init__.py>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# package2/module3.py

from package2 import class1
from package2.subpackage1.module5 import function2


def main():
print(class1())
print(function2())


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# The tutorial's tree includes this module, but none of the examples use it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def function2():
return "Hello from function2() in package2/subpackage1/module5.py"
19 changes: 19 additions & 0 deletions absolute-vs-relative-python-imports/import_style.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ruff: noqa: F401
"""Illustration of good import statement styling.

Note that the imports come after the docstring.

"""

# Standard library imports
import datetime
import os

# Third party imports
from flask import Flask
from flask_restful import Api
from flask_sqlalchemy import SQLAlchemy

# Local application imports
from local_module import local_class
from local_package import local_function
10 changes: 10 additions & 0 deletions absolute-vs-relative-python-imports/relative/project/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Relative imports only work inside packages, so this top-level script
# imports the two packages' modules with absolute imports. The relative
# imports live in package1/module1.py and package2/module3.py.
from package1 import module1
from package2 import module3

print("module1.main():")
module1.main()
print("\nmodule3.main():")
module3.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# package1/module1.py

from .module2 import function1


def main():
print(function1())


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def function1():
return "Hello from function1() in package1/module2.py"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class class1:
def __repr__(self):
return "<class1 from package2/__init__.py>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# package2/module3.py

from . import class1
from .subpackage1.module5 import function2


def main():
print(class1())
print(function2())


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# The tutorial's tree includes this module, but none of the examples use it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def function2():
return "Hello from function2() in package2/subpackage1/module5.py"
43 changes: 43 additions & 0 deletions python-application-layouts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Python Project Structure: How to Choose the Right Layout

This folder provides the code examples for the Real Python tutorial [Python Project Structure: How to Choose the Right Layout](https://realpython.com/python-application-layouts/).

Each subfolder turns one of the tutorial's example directory trees into a small, working project that you can open, install, and run:

| Folder | Project Type | Layout | Try It |
|--------------|-----------------|--------|--------------------------------------------------------|
| `statkit/` | Library | `src/` | `python -m pytest` |
| `weatherly/` | CLI application | Flat | `weatherly lisbon` or `python -m weatherly lisbon` |
| `portfolio/` | Django project | Flat | `python manage.py runserver` |
| `bloggy/` | Flask project | Flat | `flask --app bloggy run` |
| `bookapi/` | FastAPI project | Flat | `uvicorn bookapi.main:app` |
| `notely/` | GUI application | Flat | `notely` or `python -m notely` |

The projects are minimal skeletons. They contain just enough code to make each tree real, and each one has its own `README.md` with details.

## Installation

The projects don't share a top-level `requirements.txt`. Instead, each project declares its own pinned dependencies in its `pyproject.toml`, just like a standalone repository would. Create a separate virtual environment for each project, change into its folder, and install it in editable mode along with its test dependencies:

```console
$ cd statkit/
$ python -m venv venv/
$ source venv/bin/activate
(venv) $ python -m pip install -e ".[dev]"
(venv) $ python -m pytest
```

The same steps work for `weatherly/`, `bloggy/`, `bookapi/`, and `notely/`. The Django `portfolio/` project isn't meant to be installed, so you only install Django there:

```console
(venv) $ python -m pip install django==6.1.2
(venv) $ python manage.py check
```

## Notes

- The `weatherly` CLI doesn't call any real weather API. Its only provider returns canned data, so it works offline.
- Folders that would otherwise be empty, such as `portfolio/static/` and `notely/notely/resources/translations/`, contain a `.gitkeep` file so that Git keeps them.
- The tests in `notely/` use Qt's `offscreen` platform, so they don't open any windows.

The code requires Python 3.10 or later, and the Django project requires Python 3.12 or later because of Django 6.1.
29 changes: 29 additions & 0 deletions python-application-layouts/bloggy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# bloggy

A minimal Flask blog in a flat layout. The package's `__init__.py` holds the
application factory, `create_app()`, which registers the `auth` and `posts`
blueprints. Each blueprint has a matching subdirectory under `templates/`.

## Installation

```console
$ python -m pip install -e ".[dev]"
```

## Usage

```console
$ flask --app bloggy init-db
Initialized the database.
$ flask --app bloggy run --debug
```

Then visit `http://127.0.0.1:5000/` and `http://127.0.0.1:5000/auth/login`.
Flask stores the SQLite database in the `instance/` folder, which it creates
next to the package.

## Running the Tests

```console
$ python -m pytest
```
26 changes: 26 additions & 0 deletions python-application-layouts/bloggy/bloggy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from pathlib import Path

from flask import Flask


def create_app(test_config=None):
"""Create and configure the Flask app."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY="dev",
DATABASE=str(Path(app.instance_path) / "bloggy.sqlite"),
)
if test_config is None:
app.config.from_pyfile("config.py", silent=True)
else:
app.config.from_mapping(test_config)

Path(app.instance_path).mkdir(parents=True, exist_ok=True)

from bloggy import auth, db, posts

db.init_app(app)
app.register_blueprint(auth.bp)
app.register_blueprint(posts.bp)

return app
8 changes: 8 additions & 0 deletions python-application-layouts/bloggy/bloggy/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask import Blueprint, render_template

bp = Blueprint("auth", __name__, url_prefix="/auth")


@bp.route("/login")
def login():
return render_template("auth/login.html")
43 changes: 43 additions & 0 deletions python-application-layouts/bloggy/bloggy/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sqlite3

import click
from flask import current_app, g

SCHEMA = """
DROP TABLE IF EXISTS post;

CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL
);
"""


def get_db():
if "db" not in g:
g.db = sqlite3.connect(current_app.config["DATABASE"])
g.db.row_factory = sqlite3.Row
return g.db


def close_db(exception=None):
db = g.pop("db", None)
if db is not None:
db.close()


def init_db():
get_db().executescript(SCHEMA)


@click.command("init-db")
def init_db_command():
"""Clear the existing data and create new tables."""
init_db()
click.echo("Initialized the database.")


def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)
11 changes: 11 additions & 0 deletions python-application-layouts/bloggy/bloggy/posts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from flask import Blueprint, render_template

from bloggy.db import get_db

bp = Blueprint("posts", __name__)


@bp.route("/")
def index():
posts = get_db().execute("SELECT title, body FROM post ORDER BY id DESC")
return render_template("posts/index.html", posts=posts.fetchall())
5 changes: 5 additions & 0 deletions python-application-layouts/bloggy/bloggy/static/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
body {
font-family: sans-serif;
max-width: 40rem;
margin: 0 auto;
}
14 changes: 14 additions & 0 deletions python-application-layouts/bloggy/bloggy/templates/auth/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "base.html" %}

{% block title %}Log In{% endblock %}

{% block content %}
<h1>Log In</h1>
<form method="post">
<label for="username">Username</label>
<input name="username" id="username" required>
<label for="password">Password</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
</form>
{% endblock %}
15 changes: 15 additions & 0 deletions python-application-layouts/bloggy/bloggy/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} | Bloggy</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('posts.index') }}">Bloggy</a>
<a href="{{ url_for('auth.login') }}">Log In</a>
</nav>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
Loading
Loading