3 A library

Read the tutorial. Build it yourself is the other way to read this part.

just setup

top keeps the most common words of any counts. Another program that counts words could use it too. So it goes into a library of its own, talib, in the same workspace. A workspace is a set of packages that share one uv.lock.

The code before this part. The code after it.

3.1 A library, with uv init

uv init --lib makes a library. In a folder of a project, it also makes the library a member of the project's workspace. --author-from none leaves out the name of the author, which uv would take from your git settings.

This change came from running uv init --lib --author-from none packages/talib.

The folder packages/talib holds a new project, with its own pyproject.toml and a function hello. The pyproject.toml of tally names it under [tool.uv.workspace].

uv.lock does not name talib yet, so it is out of date until the next move. A uv run would lock it again, so the check reads the file:

grep -A2 'tool.uv.workspace' pyproject.toml

It prints the table [tool.uv.workspace], with members = [ and "packages/talib", under it.

What changed

  • packages/talib/README.md: empty, for the words about the library. New, 0 lines. It comes from uv init --lib --author-from none packages/talib. The file.
  • packages/talib/pyproject.toml: the project of the library. New, 11 lines. It comes from uv init --lib --author-from none packages/talib. The file.
  • packages/talib/src/talib/__init__.py: a first function, hello. New, 2 lines. It comes from uv init --lib --author-from none packages/talib. The file.
  • packages/talib/src/talib/py.typed: empty, to say that the library has types. New, 0 lines. It comes from uv init --lib --author-from none packages/talib. The file.
  • pyproject.toml: the workspace, with talib as its member. 5 lines added. It comes from uv init --lib --author-from none packages/talib. The file.
    • look at "packages/talib",
The diff of packages/talib/pyproject.toml
@@ -0,0 +1,11 @@
+[project]
+name = "talib"
+version = "0.1.0"
+description = "Add your description here"
+readme = "README.md"
+requires-python = ">=3.12"
+dependencies = []
+
+[build-system]
+requires = ["uv_build>=0.12.13,<0.13.0"]
+build-backend = "uv_build"
The diff of packages/talib/src/talib/__init__.py
@@ -0,0 +1,2 @@
+def hello() -> str:
+    return "Hello from talib!"
The diff of pyproject.toml
@@ -28,3 +28,8 @@ line-length = 100
 
 [tool.uv]
 default-groups = ["dev", "docs"]
+
+[tool.uv.workspace]
+members = [
+    "packages/talib",
+]

This change on GitHub. The code after it.

3.2 tally depends on talib

tally cannot import talib until it depends on it. uv add finds talib in the workspace, and takes it from there:

This change came from running uv add talib.

pyproject.toml lists talib under dependencies, and a table [tool.uv.sources] says that it comes from the workspace. uv.lock names both packages.

uv run python -c "import talib; print(talib.hello())"

It prints Hello from talib!.

What changed

  • pyproject.toml: talib, from the workspace. 4 lines added. It comes from uv add talib. The file.
    • look at talib = { workspace = true }
  • uv.lock: talib, as a member of the workspace. 16 lines added, 1 removed. It comes from uv add talib. The file.
The diff of pyproject.toml
@@ -6,6 +6,7 @@ readme = "README.md"
 requires-python = ">=3.12"
 dependencies = [
     "tabulate>=0.9,<0.10",
+    "talib",
 ]
 
 [project.scripts]
@@ -33,3 +34,6 @@ default-groups = ["dev", "docs"]
 members = [
     "packages/talib",
 ]
+
+[tool.uv.sources]
+talib = { workspace = true }

This change on GitHub. The code after it.

3.3 top goes into talib

top is still in tally, and talib has only hello. One patch moves top across, in both packages.

A patch to packages/talib/src/talib/__init__.py.

A patch to src/tally/__init__.py.

top is now in packages/talib/src/talib/__init__.py. src/tally/__init__.py imports it from talib, so the tests that import top from tally still find it.

uv run python -m unittest discover -s tests -q

All four tests pass.

head -4 packages/talib/src/talib/__init__.py
"""talib: small helpers for the counts of words."""


def top(counts: dict[str, int], n: int) -> list[tuple[str, int]]:

It prints the docstring of the library, two blank lines, and the first line of top.

What changed

  • packages/talib/src/talib/__init__.py: top, in place of hello. 6 lines added, 2 removed. The file.
    • look at def top(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
  • src/tally/__init__.py: top comes from talib. 1 line added, 5 removed. The file.
    • look at from talib import top
The diff of packages/talib/src/talib/__init__.py
@@ -1,2 +1,6 @@
-def hello() -> str:
-    return "Hello from talib!"
+"""talib: small helpers for the counts of words."""
+
+
+def top(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
+    "The n most common words, most common first."
+    return sorted(counts.items(), key=lambda pair: (-pair[1], pair[0]))[:n]
The diff of src/tally/__init__.py
@@ -3,6 +3,7 @@
 import sys
 
 from tabulate import tabulate
+from talib import top
 
 
 def count(text: str) -> dict[str, int]:
@@ -13,11 +14,6 @@ def count(text: str) -> dict[str, int]:
     return counts
 
 
-def top(counts: dict[str, int], n: int) -> list[tuple[str, int]]:
-    "The n most common words, most common first."
-    return sorted(counts.items(), key=lambda pair: (-pair[1], pair[0]))[:n]
-
-
 def main() -> None:
     "Count the words of a file, or of the standard input. Options: --top N, and --table."
     args = sys.argv[1:]

This change on GitHub. The code after it.