Skip to content

Commit c168a3f

Browse files
committed
chore: finish up lesson 1
Signed-off-by: Henry Schreiner <henryfs@princeton.edu>
1 parent c70c50f commit c168a3f

1 file changed

Lines changed: 146 additions & 4 deletions

File tree

content/basic-packaging/01_setup.md

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
---
22
downloads:
3-
- file: index.md
4-
title: Source File
53
- url: slides/1_01_setup.html
64
static: false
75
title: Slides
@@ -288,7 +286,8 @@ have to remember to update things too!
288286

289287
Another thing we can do is single-file scripts. They look like this:
290288

291-
```python
289+
```{code} python
290+
:filename: simple.py
292291
# /// script
293292
# dependencies = ["numpy"]
294293
# ///
@@ -329,11 +328,154 @@ How you specify your dependencies depends a bit on the tool you are using,
329328
since it is not standardized like libraries are. But you can always use `[project]`
330329
and make it an unpublishable library by adding this:
331330

332-
```toml
331+
```{code} toml
332+
:filename: pyproject.toml
333333
[project]
334334
classifiers = [
335335
"Private :: Do Not Upload",
336336
]
337337
```
338338

339339
### Libraries
340+
341+
A library is something that can be shared with others. The key feature is that
342+
it must be able to share an environment with whatever else the user of the
343+
library needs. If a user is expected to `import` your code, it's a library.
344+
345+
Later sections will explain a lot more about packages, so let's just present a basic `pyproject.toml`:
346+
347+
```{code} toml
348+
:filename: pyproject.toml
349+
[build-system]
350+
requires = ["hatchling"]
351+
build-backend = "hatchling.build"
352+
353+
[project]
354+
name = "example"
355+
version = "0.1.0"
356+
```
357+
358+
Notice the `[build-system]` section; this tells tools how to build the package
359+
into something you can distribute and install. There are quite a few build
360+
backends; the one above uses `hatchling`.
361+
362+
There are a lot of places to put dependencies; here's an expanded version with annotations:
363+
364+
```{code} toml
365+
:linenos:
366+
:filename: pyproject.toml
367+
:emphasize-lines: 2,8,11,14
368+
[build-system]
369+
requires = ["hatchling"] # 1
370+
build-backend = "hatchling.build"
371+
372+
[project]
373+
name = "example"
374+
version = "0.1.0"
375+
dependencies = [] # 2
376+
377+
[project.optional-dependencies]
378+
extra = [] # 3
379+
380+
[dependency-groups]
381+
dev = [] # 4
382+
```
383+
384+
:::{glossary}
385+
build-system.requires
386+
: Requirements that are installed when building distributions. This is your
387+
build-backend, and anything else require to assemble your package from source.
388+
These are not available at runtime for users. Noted with `# 1` above.
389+
390+
project.dependencies
391+
: Libraries that must always be installed to use your package. Any installation
392+
of your package will also install these. Noted with `# 2` above.
393+
394+
project.optional-dependencies
395+
: This is a table with arbitrary keys. When a user is installing your package,
396+
they can add `[extra]` to install the list of dependencies named `extra`.
397+
These will not neccisarly be present if the user didn't request then. Also
398+
known as "extras". These are part of the public package metadata. Noted with
399+
`# 3` above.
400+
401+
dependency-groups
402+
: This is a table with arbitrary keys. Unlike project.optional-dependencies, these
403+
do not become part of the package metadata; you must have the `pyproject.toml` file
404+
to install them. They also do not require you install the package. Commonly
405+
used for development dependencies, like tests, docs, coverage, and the like.
406+
Noted with `# 4` above.
407+
:::
408+
409+
A quick comparison:
410+
411+
| | `b-s.r` | `p.d` | `p.o-d` | `d-g` |
412+
| ------------------------------ | ------- | ----- | ------- | ----- |
413+
| Public metadata |||||
414+
| Always installed |||||
415+
| Named groups |||||
416+
| Can be independently installed |||||
417+
418+
#### High level project management with uv
419+
420+
When dependency-groups were introduced, uv made a brilliant decision: if there's
421+
a group named `dev`, it is installed by default when using `uv run`. This enables
422+
the following file:
423+
424+
```{code} toml
425+
:filename: pyproject.toml
426+
[build-system]
427+
requires = ["hatchling"]
428+
build-backend = "hatchling.build"
429+
430+
[project]
431+
name = "example"
432+
version = "0.1.0"
433+
requires-python = ">=3.12"
434+
dependencies = ["numpy"]
435+
436+
[dependency-groups]
437+
dev = ["pytest"]
438+
```
439+
440+
to be all you need to use `uv run`. For example,
441+
442+
```bash
443+
uv run pytest
444+
```
445+
446+
will:
447+
448+
- Download Python if you don't have a valid copy
449+
- Make a virtual environment at `.venv` if it doesn't exist
450+
- Install dependencies from a `uv.lock` lockfile if it exists
451+
- Create a `uv.lock` file from `project.dependencies` and `dependency-groups.dev` if it doesn't exist and install
452+
- Make an editable install of your project
453+
- Run whatever command you give it from that virtual environment
454+
455+
If you edit the dependencies, then the lockfile and `.venv` will be updated
456+
when you `uv run` again.
457+
458+
Any command works, so `uv run python` starts up `python`, if you have command
459+
line apps you can use `uv run ...`, etc.
460+
461+
:::{exercise} Development environment
462+
:label: uv-dev-env
463+
464+
The most downloaded Python package in the world is probably `packaging` (not
465+
counting AWS). Download the `https://github.com/pypa/packaging` repository from
466+
github, and run the test suite with `pytest` using uv.
467+
468+
:::
469+
470+
:::{solution} uv-dev-env
471+
:class: dropdown
472+
473+
Assuming a unix-like system and bash:
474+
475+
```bash
476+
git clone https://github.com/pypa/packaging
477+
cd packaging
478+
uv run pytest
479+
```
480+
481+
:::

0 commit comments

Comments
 (0)