Skip to content

Commit 9b70523

Browse files
iansedanoKateFinegangahjelle
authored
What's a Namespace Package? (#354)
* namespace packages hello world * snake shop idea + fake data generation * adding README * renaming to snake-corp * adding namespace plugins and @gahjelle prototype * experimenting with plugins * data-repo v2, snake business cleanup, plugins example * adding to business example * using both packages in business demo * updates while writing article * fixing import * TR updates * Corporation instead of corp * @gahjelle TR fixes * Adding *a* s to docstrings * README LE * rename repo to repos * typo fix * Final QA tweaks * Lint --------- Co-authored-by: KateFinegan <[email protected]> Co-authored-by: Geir Arne Hjelle <[email protected]>
1 parent d7af94e commit 9b70523

File tree

23 files changed

+13773
-0
lines changed

23 files changed

+13773
-0
lines changed

Diff for: namespace-package/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# What's a Python Namespace Package, and What's It For?
2+
3+
Materials for the corresponding Real Python tutorial, [What's a Python Namespace Package, and What's It For?](https://realpython.com/python-namespace-package/).
4+
5+
You have the source code for the Snake Corporation example that's hosted on PyPI. Aditionally, as an extra, you have the code to generate the fake data used.
6+
7+
You need to install every subpackage to get the code working properly.
8+
9+
## Examples
10+
11+
In this repository, you'll find three different examples showing ways that namespace packages work or can be used.
12+
13+
### Business
14+
15+
A typical use case of namespace packages is for large businesses that want to make sure all their code is packaged under a namespace. In this and subsequent examples, you play the role of the fictional Snake Corporation.
16+
17+
For this example, the Snake Corporation has two separate utility packages, but when both are installed, both are available under the same `snake_corp` namespace:
18+
19+
```py
20+
>>> from snake_corp import dateutil
21+
>>> from snake_corp import magic
22+
```
23+
24+
### Distributed Data Repository
25+
26+
In this example, you make a namespace package that serves as a wrapper around your data files. This allows you to create separate packages that only contain data.
27+
28+
This way, an organization like Real Python can have some core functionality in the main package, like the functions to read the data, and then have many satellite data packages that just include a particular dataset and insert it into the namespace so it can be read by the core package.
29+
30+
### Plugin System
31+
32+
In this example, you take the data repository example further by adding a plugin system for the readers so that you can support your own custom formats.

Diff for: namespace-package/business/pyproject.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[tool.black]
2+
line-length=79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[build-system]
2+
requires = ["setuptools", "setuptools-scm"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[tool.setuptools.packages.find]
6+
where = ["."]
7+
include = ["snake_corp"]
8+
namespaces = true
9+
10+
[project]
11+
name = "snake-corp-dateutil"
12+
version = "1.0.3"
13+
description = "Date Utilities for the Snake Corporation"
14+
license = { text = "Snake Corp Software License Agreement" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from datetime import datetime
2+
3+
4+
def days_to_snake_day(from_date=None):
5+
"""Return the number of days until World Snake Day (July 16th)."""
6+
if from_date is None:
7+
from_date = datetime.now()
8+
if from_date.month > 7 or (from_date.month == 7 and from_date.day > 16):
9+
day_of_snake = datetime(from_date.year + 1, 7, 16)
10+
else:
11+
day_of_snake = datetime(from_date.year, 7, 16)
12+
return (day_of_snake - from_date).days
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[build-system]
2+
requires = ["setuptools", "setuptools-scm"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[tool.setuptools.packages.find]
6+
where = ["."]
7+
include = ["snake_corp"]
8+
namespaces = true
9+
10+
[project]
11+
name = "snake-corp-magic-numbers"
12+
version = "1.1.8"
13+
description = "Super Magic Secret Numbers for the Snake Corporation"
14+
license = { text = "Snake Corp Software License Agreement" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import random
2+
3+
4+
def secret_number_generator():
5+
# Insert special Snake Corporation sauce here ...
6+
return random.randint(0, 9999)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from snake_corp import dateutil, magic
2+
3+
print(magic.secret_number_generator())
4+
print(dateutil.days_to_snake_day())

Diff for: namespace-package/data-repos-demo/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
```s
2+
$ python -m venv venv
3+
$ source venv/bin/activate
4+
(venv) $ python -m pip install -e data-repos/
5+
(venv) $ python -m pip install -e snake-corp-product-data/
6+
```
7+
8+
```powershell
9+
PS> python -m venv venv
10+
PS> venv\scripts\activate
11+
(venv) PS> python -m pip install -e "data-repos\"
12+
(venv) PS> python -m pip install -e "snake-corp-product-data\"
13+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from data_repos import read
2+
3+
read.data("products")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from importlib import resources
2+
3+
import pandas as pd
4+
5+
6+
def data(name):
7+
"""Get a data file."""
8+
data_path = path(name)
9+
file_type = data_path.suffix.lstrip(".")
10+
return readers[file_type](data_path)
11+
12+
13+
def path(name):
14+
"""Find the path to a data file."""
15+
for resource in resources.files(__package__).iterdir():
16+
if resource.stem == name:
17+
return resource
18+
raise FileNotFoundError(f"{name} not found in {__package__}")
19+
20+
21+
def csv(data_path):
22+
"""Read a CSV file from a path."""
23+
return pd.read_csv(data_path)
24+
25+
26+
def json(data_path):
27+
"""Read a JSON file from a path."""
28+
return pd.read_json(data_path)
29+
30+
31+
readers = {"csv": csv, "json": json}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[build-system]
2+
requires = ["setuptools", "setuptools-scm"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[tool.setuptools.packages.find]
6+
where = ["."]
7+
include = ["data_repos"]
8+
namespaces = true
9+
10+
[project]
11+
name = "data-repos"
12+
version = "1.0.0"
13+
authors = [
14+
{ name = "Ian Currie", email = "[email protected]" },
15+
{ name = "Geir Arne Hjelle", email = "[email protected]" },
16+
]
17+
description = "A namespace provider for distributed data repositories"
18+
readme = "README.md"
19+
requires-python = ">=3.7"
20+
keywords = ["namespace", "data"]
21+
license = { text = "MIT" }
22+
classifiers = ["Programming Language :: Python :: 3"]
23+
dependencies = ["pandas"]

0 commit comments

Comments
 (0)