diff --git a/python-import/README.md b/python-import/README.md index 03ae246408..406141eadb 100644 --- a/python-import/README.md +++ b/python-import/README.md @@ -2,3 +2,16 @@ 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 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/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/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/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.py b/python-import/population_quiz/population.py similarity index 65% rename from python-import/population.py rename to python-import/population_quiz/population.py index e73a8cd667..95ef6ee248 100644 --- a/python-import/population.py +++ b/python-import/population_quiz/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/population_quiz.py b/python-import/population_quiz/population_quiz.py index ddea1252a1..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 @@ -37,7 +32,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 @@ -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()