From 2bf74b61c8662c87606d38c68fe95ba363521506 Mon Sep 17 00:00:00 2001 From: realpython-bot <42617967+realpython-bot@users.noreply.github.com> Date: Sun, 18 Oct 2026 06:12:32 +0000 Subject: [PATCH 1/3] Sync python-import with the maintenance update of Python import: Advanced Techniques and Tips - population_quiz.py: pass list(population) to random.sample(), which no longer accepts dict views (Python 3.11+) - plugins.py: replace deprecated importlib.resources.contents() with files().iterdir() Co-Authored-By: Claude Opus 5.5 --- python-import/plugins.py | 2 +- python-import/population_quiz/population_quiz.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python-import/plugins.py b/python-import/plugins.py index 72cb3376b9..7e3d18b073 100644 --- a/python-import/plugins.py +++ b/python-import/plugins.py @@ -45,7 +45,7 @@ def _import(package, plugin): def _import_all(package): """Import all plugins in a package""" - files = resources.contents(package) + files = [path.name for path in resources.files(package).iterdir()] plugins = [f[:-3] for f in files if f.endswith(".py") and f[0] != "_"] for plugin in plugins: _import(package, plugin) diff --git a/python-import/population_quiz/population_quiz.py b/python-import/population_quiz/population_quiz.py index ddea1252a1..f38190ef12 100644 --- a/python-import/population_quiz/population_quiz.py +++ b/python-import/population_quiz/population_quiz.py @@ -37,7 +37,7 @@ def run_quiz(population, num_questions, num_countries): num_correct = 0 for q_num in range(num_questions): print(f"\n\nQuestion {q_num + 1}:") - countries = random.sample(population.keys(), num_countries) + countries = random.sample(list(population), num_countries) print("\n".join(f"{i}. {a}" for i, a in enumerate(countries, start=1))) # Get user input From 930b5e8710caa6ba088bedd2b28ca28a9fe3e939 Mon Sep 17 00:00:00 2001 From: Martin Breuss Date: Thu, 22 Oct 2026 15:41:12 +0000 Subject: [PATCH 2/3] Sync python-import with the full update of Python import - structure/: replace setup.cfg and setup.py with a minimal pyproject.toml - population_quiz.py, population.py: read the UN WPP2024 file with importlib.resources.files() and drop the Python 3.7 backport fallback - hello_gui/__main__.py: use files() and as_file() instead of path() - population_quiz/data/README.md: point at the WPP2024 download - README.md: list tested Python versions and pinned dependencies Co-Authored-By: Claude Opus 5.5 (1M context) --- python-import/README.md | 7 ++++++ python-import/hello_gui/__main__.py | 11 +++----- python-import/population.py | 25 ++++++------------- python-import/population_quiz/data/README.md | 2 +- .../population_quiz/population_quiz.py | 13 +++------- python-import/structure/pyproject.toml | 10 ++++++++ python-import/structure/setup.cfg | 6 ----- python-import/structure/setup.py | 5 ---- 8 files changed, 34 insertions(+), 45 deletions(-) create mode 100644 python-import/structure/pyproject.toml delete mode 100644 python-import/structure/setup.cfg delete mode 100644 python-import/structure/setup.py diff --git a/python-import/README.md b/python-import/README.md index 03ae246408..f90a60f6fa 100644 --- a/python-import/README.md +++ b/python-import/README.md @@ -2,3 +2,10 @@ This folder contains code and materials used in Real Python's [Python `import`: Advanced Techniques and Tips](https://realpython.com/python-import/) article. +The code was tested with Python 3.15.1 and 3.14.9. Install the third-party dependencies with: + +```console +$ python -m pip install PyYAML==6.0.3 matplotlib==3.11.2 colorama==0.4.6 +``` + +The `namespace_package/` examples expect `third_party/` and `local/` on your import path, for example by running `sys.path.extend(["third_party", "local"])` from inside that folder, as the tutorial shows. The population examples need the UN data file described in `population_quiz/data/README.md`. diff --git a/python-import/hello_gui/__main__.py b/python-import/hello_gui/__main__.py index 9f896676a5..bc1ba8e009 100644 --- a/python-import/hello_gui/__main__.py +++ b/python-import/hello_gui/__main__.py @@ -1,26 +1,23 @@ # hello_gui/__main__.py import tkinter as tk +from importlib import resources from tkinter import ttk -try: - from importlib import resources -except ImportError: - import importlib_resources as resources - class Hello(tk.Tk): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.wm_title("Hello") + images = resources.files("hello_gui.gui_resources") # Read image, store a reference to it, and set it as an icon - with resources.path("hello_gui.gui_resources", "logo.png") as path: + with resources.as_file(images / "logo.png") as path: self._icon = tk.PhotoImage(file=path) self.iconphoto(True, self._icon) # Read image, create a button, and store a reference to the image - with resources.path("hello_gui.gui_resources", "hand.png") as path: + with resources.as_file(images / "hand.png") as path: hand = tk.PhotoImage(file=path) button = ttk.Button( self, diff --git a/python-import/population.py b/python-import/population.py index e73a8cd667..95ef6ee248 100644 --- a/python-import/population.py +++ b/python-import/population.py @@ -8,21 +8,13 @@ class _Population: def __init__(self): - """Prepare to read the population file""" - self._data = {} + """Read the population file""" + self.data = {} self.variant = "Medium" - @property - def data(self): - """Read data from disk""" - if self._data: # Data has already been read, return it directly - return self._data - - # Read data and store it in self._data print(f"Reading population data for {self.variant} scenario") - with resources.open_text( - "data", "WPP2019_TotalPopulationBySex.csv" - ) as fid: + csv_file = resources.files("data") / "WPP2024_TotalPopulationBySex.csv" + with csv_file.open() as fid: rows = csv.DictReader(fid) # Read data, filter the correct variant @@ -30,15 +22,14 @@ def data(self): if int(row["LocID"]) >= 900 or row["Variant"] != self.variant: continue - country = self._data.setdefault(row["Location"], {}) + country = self.data.setdefault(row["Location"], {}) population = float(row["PopTotal"]) * 1000 country[int(row["Time"])] = round(population) - return self._data def get_country(self, country): """Get population data for one country""" - country = self.data[country] - years, population = zip(*country.items(), strict=False) + data = self.data[country] + years, population = zip(*data.items()) return years, population def plot_country(self, country): @@ -52,5 +43,5 @@ def order_countries(self, year): return sorted(countries, key=lambda c: countries[c], reverse=True) -# Instantiate the singleton +# Instantiate the Singleton data = _Population() diff --git a/python-import/population_quiz/data/README.md b/python-import/population_quiz/data/README.md index 6e1d3567d9..f08dcd6975 100644 --- a/python-import/population_quiz/data/README.md +++ b/python-import/population_quiz/data/README.md @@ -1,3 +1,3 @@ # Population Data -You should download [`WPP2019_TotalPopulationBySex.csv`](https://population.un.org/wpp/Download/Files/1_Indicators%20(Standard)/CSV_FILES/WPP2019_TotalPopulationBySex.csv) from [the UN web page](https://population.un.org/wpp/Download/Standard/CSV/). Save the CSV file in this folder. \ No newline at end of file +Download [`WPP2024_TotalPopulationBySex.csv.gz`](https://population.un.org/wpp/assets/Excel%20Files/1_Indicator%20(Standard)/CSV_FILES/WPP2024_TotalPopulationBySex.csv.gz) from the UN's [World Population Prospects 2024](https://population.un.org/wpp/). Unzip it and save `WPP2024_TotalPopulationBySex.csv` in this folder. diff --git a/python-import/population_quiz/population_quiz.py b/python-import/population_quiz/population_quiz.py index f38190ef12..61822ae8cf 100644 --- a/python-import/population_quiz/population_quiz.py +++ b/python-import/population_quiz/population_quiz.py @@ -2,11 +2,7 @@ import csv import random - -try: - from importlib import resources -except ImportError: - import importlib_resources as resources +from importlib import resources def read_population_file(year, variant="Medium"): @@ -14,9 +10,8 @@ def read_population_file(year, variant="Medium"): population = {} print(f"Reading population data for {year}, {variant} scenario") - with resources.open_text( - "data", "WPP2019_TotalPopulationBySex.csv" - ) as fid: + csv_file = resources.files("data") / "WPP2024_TotalPopulationBySex.csv" + with csv_file.open() as fid: rows = csv.DictReader(fid) # Read data, filter the correct year @@ -67,7 +62,7 @@ def run_quiz(population, num_questions, num_countries): def main(): """Read population data and run quiz""" - population = read_population_file("2020") + population = read_population_file("2024") num_correct = run_quiz(population, num_questions=10, num_countries=3) print(f"\nYou answered {num_correct} questions correctly") diff --git a/python-import/structure/pyproject.toml b/python-import/structure/pyproject.toml new file mode 100644 index 0000000000..94d14f7427 --- /dev/null +++ b/python-import/structure/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[project] +name = "local_structure" +version = "0.1.0" + +[tool.setuptools] +packages = ["structure"] diff --git a/python-import/structure/setup.cfg b/python-import/structure/setup.cfg deleted file mode 100644 index 25cc2230fa..0000000000 --- a/python-import/structure/setup.cfg +++ /dev/null @@ -1,6 +0,0 @@ -[metadata] -name = local_structure -version = 0.1.0 - -[options] -packages = structure diff --git a/python-import/structure/setup.py b/python-import/structure/setup.py deleted file mode 100644 index 1fa1856aff..0000000000 --- a/python-import/structure/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -# setup.py - -import setuptools - -setuptools.setup() From 6c9dc35044df48ef2b5be1b038ac13d3d5bf5c8e Mon Sep 17 00:00:00 2001 From: Martin Breuss Date: Thu, 22 Oct 2026 15:47:53 +0000 Subject: [PATCH 3/3] Move population.py next to the data package it reads population.py loads its CSV file through importlib.resources from the `data` package, which lives in population_quiz/. From the folder root, `import population` failed with ModuleNotFoundError. The README now also spells out the setup steps for the examples that need them. Co-Authored-By: Claude Opus 5.5 (1M context) --- python-import/README.md | 8 +++++++- python-import/{ => population_quiz}/population.py | 0 2 files changed, 7 insertions(+), 1 deletion(-) rename python-import/{ => population_quiz}/population.py (100%) diff --git a/python-import/README.md b/python-import/README.md index f90a60f6fa..406141eadb 100644 --- a/python-import/README.md +++ b/python-import/README.md @@ -8,4 +8,10 @@ The code was tested with Python 3.15.1 and 3.14.9. Install the third-party depen $ python -m pip install PyYAML==6.0.3 matplotlib==3.11.2 colorama==0.4.6 ``` -The `namespace_package/` examples expect `third_party/` and `local/` on your import path, for example by running `sys.path.extend(["third_party", "local"])` from inside that folder, as the tutorial shows. The population examples need the UN data file described in `population_quiz/data/README.md`. +The examples need Python 3.9 or later, since they use `importlib.resources.files()`. + +Some examples need a bit of setup before you run them: + +- **`namespace_package/`:** Start Python inside this folder and run `sys.path.extend(["third_party", "local"])` first, as the tutorial shows. +- **`population_quiz/`:** Download the UN data file described in `population_quiz/data/README.md`. Then run `python population_quiz.py` or `python -c "import population"` from inside `population_quiz/`, so that Python finds the `data` package. +- **`structure/`:** Run `python cli.py structure` from inside `structure/`, or install the project with `python -m pip install -e .` from that folder. diff --git a/python-import/population.py b/python-import/population_quiz/population.py similarity index 100% rename from python-import/population.py rename to python-import/population_quiz/population.py