Compare commits
3 Commits
6424ecaaf5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 06d7ed4b84 | |||
| 9003c283ec | |||
|
|
84fe4b56a3 |
68
README.md
68
README.md
@@ -5,6 +5,8 @@ to install them, how to upload them, how to maintain them, how to work
|
||||
with them. For details consult https://packaging.python.org, or
|
||||
https://py-pkgs.org, or other sites.
|
||||
|
||||
Before you create a package please consider building a [virtual
|
||||
environment](./venv.md) for your project.
|
||||
|
||||
## Why packages?
|
||||
|
||||
@@ -17,12 +19,14 @@ other scripts in the very same directory.
|
||||
|
||||
For example, consider a module `addition.py` and a script `analyze.py`
|
||||
both in the same directory:
|
||||
|
||||
```txt
|
||||
├── addition.py
|
||||
└── analyze.py
|
||||
```
|
||||
|
||||
In `addition.py` we define a function `add_two()`:
|
||||
|
||||
```
|
||||
def add_two(x):
|
||||
return x + 2
|
||||
@@ -30,6 +34,7 @@ def add_two(x):
|
||||
|
||||
We can use this function in `analyze.py` by importing it from
|
||||
`addition.py`:
|
||||
|
||||
```
|
||||
from addition import add_two
|
||||
|
||||
@@ -42,7 +47,6 @@ directory (or if modules are in sub-directories). To make modules
|
||||
available in other places of your file system or even for other
|
||||
people, you need to turn the modules into packages.
|
||||
|
||||
|
||||
## Minimal package
|
||||
|
||||
First we make a project directory for our new package. Usually the
|
||||
@@ -87,16 +91,20 @@ on your machine and import it from wherever you want.
|
||||
|
||||
To install the project, go to the project root, here `packagehowto/` and run
|
||||
from your shell
|
||||
|
||||
```sh
|
||||
pip3 install .
|
||||
```
|
||||
|
||||
This installs the package of the current project folder `.` somewhere
|
||||
in your home directory. From anywhere in your home file system you now
|
||||
can import this package. The import line of our `analyze.py` file
|
||||
needs to look like this:
|
||||
|
||||
```
|
||||
from numerix.addition import add_two
|
||||
```
|
||||
|
||||
From the addition module of the numerix package the function add_two
|
||||
is imported.
|
||||
|
||||
@@ -105,13 +113,14 @@ package, for exmple when you add a new function or when you just fix
|
||||
some package code. This is tedious, and that is why there is a `-e`
|
||||
option ("editable install") for `pip install`. So install your package
|
||||
with
|
||||
|
||||
```sh
|
||||
pip3 install -e .
|
||||
```
|
||||
|
||||
Then all future changes on your package are immediately available
|
||||
without the need to reinstall the package again.
|
||||
|
||||
|
||||
## The `__init__.py` file
|
||||
|
||||
This file is your package. In fact, you could write a package that
|
||||
@@ -119,11 +128,14 @@ only has an `__init__.py` file in the package directory. All code you
|
||||
want to make available in you package could be placed in the
|
||||
`__init__.py` file. For example, if you define an `add_four()` function
|
||||
in `__init__.py` like this:
|
||||
|
||||
```
|
||||
def add_four(x):
|
||||
return x + 4
|
||||
```
|
||||
|
||||
then it can be imported directly from the package:
|
||||
|
||||
```
|
||||
from numerix import add_four
|
||||
```
|
||||
@@ -132,34 +144,41 @@ Alternatively, you can define all your functions in module files, like
|
||||
we did with the function `add_two()` in the `addition.py` module. In
|
||||
the `__init__.py` file you can import this function and this way make
|
||||
it available directly from the package. With this line in `__init__.py`
|
||||
|
||||
```
|
||||
from .addition import add_two
|
||||
```
|
||||
|
||||
(note the `.` in front of `addition` - this makes it a relative
|
||||
import) you can import `add_two()` without specifying the module:
|
||||
|
||||
```
|
||||
from numerix import add_two
|
||||
```
|
||||
|
||||
|
||||
## Multiple modules within a package
|
||||
|
||||
You can have as many modules as you need in your package. Let's add a
|
||||
module `numbers.py` to the `numerix` package
|
||||
|
||||
```txt
|
||||
└── numerix
|
||||
├── __init__.py
|
||||
├── addition.py
|
||||
└── numbers.py
|
||||
```
|
||||
|
||||
with the following content:
|
||||
|
||||
```
|
||||
from .addition import add_two
|
||||
|
||||
def three():
|
||||
return add_two(1)
|
||||
```
|
||||
|
||||
This makes a function `three()` available that can be imported via
|
||||
|
||||
```
|
||||
from numerix.numbers import three
|
||||
```
|
||||
@@ -171,7 +190,6 @@ Note, that the `numbers.py` module imports a function from the
|
||||
And of course you can also import this function in `__init__.py` so
|
||||
that it can be imported from the package directly.
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
Your package needs a version number (for details see below). But how to
|
||||
@@ -187,7 +205,6 @@ by a dot. These numbers are incremented in the following way:
|
||||
- `minor`, when functionality is added in a backwards-compatible manner, and
|
||||
- `patch`, for backwards-compatible bug fixes.
|
||||
|
||||
|
||||
## Package version
|
||||
|
||||
For uploading your package to [PyPi](https://pypi.org/) you need to
|
||||
@@ -198,8 +215,8 @@ as well when you call them with a `--version' argument.
|
||||
|
||||
For the latter two cases, you can define a `__version__` variable in
|
||||
one of the modules, or even in a dedicated module, that you then
|
||||
import whereever you need it. Even better is to define it in the
|
||||
`__init__.py` file:
|
||||
import whereever you need it. For example, you may create a simple
|
||||
module called `version.py` file with the following content:
|
||||
|
||||
```
|
||||
__version__ = '1.4.2'
|
||||
@@ -209,6 +226,12 @@ __year__ = '2024'
|
||||
""" Year of the current numerix version as string. """
|
||||
```
|
||||
|
||||
In `__init__.py` you import the version:
|
||||
|
||||
```
|
||||
from .version import __version__
|
||||
```
|
||||
|
||||
Then a script can easily check the package's version like this:
|
||||
|
||||
```
|
||||
@@ -217,28 +240,34 @@ from numerix import __version__
|
||||
print(__version__)
|
||||
```
|
||||
|
||||
Other modules of the package need to import it from the `__init__.py`
|
||||
file by a relative import to be able to use it:
|
||||
Other modules of the package may also import the version by a relative
|
||||
import to be able to use it:
|
||||
|
||||
```
|
||||
from .__init__ import __version__
|
||||
from .version import __version__
|
||||
|
||||
|
||||
def about():
|
||||
print(f'{__name__} {__version__}')
|
||||
```
|
||||
|
||||
Why not defining `__version__` directly in `__init__.py`? Each module
|
||||
using the version string would import it from `__init__.py`. If you
|
||||
also want to import some functions from such a module in `__init__.py`
|
||||
to make them more easily importable from the package, you end up in
|
||||
cyclic imports. Python does not like them for a good reason...
|
||||
|
||||
But how do you get the very same version into the `pyproject.toml`
|
||||
file? It would be a very bad idea to write it there directly and
|
||||
update it whenever you change it in the `__init__.py` file. The
|
||||
update it whenever you change it in the `version.py` file. The
|
||||
version number should be set only in one place.
|
||||
|
||||
This is possible, of course. You need to specify the `version` field
|
||||
This is possible, of course. You need to specify the `version` field
|
||||
as `dynamic` in the `[project]` section. This tells the package tool
|
||||
that the version will be set dynamically by some code. There are
|
||||
several options to do so. The simplest one is to add a
|
||||
`[tool.setuptools.dynamic]` section where the version is read as an
|
||||
attribute from the `numerix` package, that is its `__init__.py`file:
|
||||
attribute from the `version` module of the `numerix` package:
|
||||
|
||||
```txt
|
||||
...
|
||||
@@ -249,7 +278,7 @@ dynamic = ["version"]
|
||||
...
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "numerix.__version__"}
|
||||
version = {attr = "numerix.version.__version__"}
|
||||
```
|
||||
|
||||
## Distribute your package
|
||||
@@ -272,18 +301,21 @@ python3 -m twine upload dist/*
|
||||
|
||||
token
|
||||
|
||||
## Using `poetry`
|
||||
|
||||
You have now seen how you can do everything "from scratch". This is of course
|
||||
good to know. But there are tools to significantly simplify package creation
|
||||
and management, such as [`poetry`](https://python-poetry.org/). Its worth
|
||||
checking it out. Heres a nice demo: https://python-poetry.org/docs/basic-usage/
|
||||
|
||||
## Unit tests
|
||||
|
||||
pytest
|
||||
|
||||
coverage
|
||||
|
||||
```sh
|
||||
pytest -v --cov-report html --cov numerix tests/
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Documentation = "https://whale.am28.uni-tuebingen.de/git/benda/packagehowto/src/
|
||||
#numerix = "numerix.numerix:main"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "numerix.__version__"}
|
||||
version = {attr = "numerix.version.__version__"}
|
||||
|
||||
#[tool.pytest.ini_options]
|
||||
#pythonpath = "src"
|
||||
|
||||
@@ -5,16 +5,7 @@ Fancy numerics made easy.
|
||||
"""
|
||||
|
||||
|
||||
__version__ = '1.4.2'
|
||||
""" Current version of the numerix package as string 'x.y.z'. """
|
||||
|
||||
__year__ = '2024'
|
||||
""" Year of the current numerix version as string. """
|
||||
|
||||
# Add them to documentation:
|
||||
__pdoc__ = {}
|
||||
__pdoc__['__version__'] = True
|
||||
__pdoc__['__year__'] = True
|
||||
from .version import __version__
|
||||
|
||||
|
||||
# Functions defined directly in __init__.py can be imported directly
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .__init__ import __version__
|
||||
from .version import __version__
|
||||
from .addition import add_two
|
||||
|
||||
|
||||
|
||||
10
src/numerix/version.py
Normal file
10
src/numerix/version.py
Normal file
@@ -0,0 +1,10 @@
|
||||
__version__ = '1.4.2'
|
||||
""" Current version of the numerix package as string 'x.y.z'. """
|
||||
|
||||
__year__ = '2024'
|
||||
""" Year of the current numerix version as string. """
|
||||
|
||||
# Add them to documentation:
|
||||
__pdoc__ = {}
|
||||
__pdoc__['__version__'] = True
|
||||
__pdoc__['__year__'] = True
|
||||
111
venv.md
Normal file
111
venv.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Why and how to use virtual environments?
|
||||
|
||||
## What is a virtual environment?
|
||||
|
||||
- A venv is **seperated** from your system packages / software, such as
|
||||
firefox, thunderbird, etc.
|
||||
- Packages that are installed cannot talk to system packages and vice versa ->
|
||||
you know what you use because you have to explicitly install **all** packages
|
||||
that your project requires.
|
||||
- They can be build and destroyed easily and quickly to test different versions
|
||||
of packages (or even python itself!).
|
||||
|
||||
## How to build a `venv`?
|
||||
|
||||
- PEP405 (=python style guide) virtual environments should be named "venv" or ".venv".
|
||||
|
||||
To create one, run:
|
||||
|
||||
```sh
|
||||
python -m venv <name> # e.g. venv or .venv
|
||||
```
|
||||
|
||||
You should now see a new directory in your current working directory called "venv" or ".venv".
|
||||
|
||||
To activate it, run this on linux or macos:
|
||||
|
||||
```sh
|
||||
source <name>/bin/activate
|
||||
```
|
||||
|
||||
On windows, you can run
|
||||
|
||||
```sh
|
||||
./<name>/Scripts/Activate.ps1
|
||||
```
|
||||
|
||||
You should now see an indicator in your shell prompt such as:
|
||||
|
||||
```sh
|
||||
(venv) user@host:~/path/to/your/project
|
||||
```
|
||||
|
||||
It is active as long as you do not explicitly deactivate it or close the shell.
|
||||
You can now `pip install <package>` as you normally would.
|
||||
To check which python version it uses, you can run this on linux/macos:
|
||||
|
||||
```sh
|
||||
which python3 && which pip
|
||||
```
|
||||
|
||||
Both paths should point to your virtual environment in the project directory.
|
||||
|
||||
To deactivate, simply run on linux/macos/windows:
|
||||
|
||||
```sh
|
||||
deactivate
|
||||
```
|
||||
|
||||
What we have now seen all comes with python but there are tools that make this
|
||||
more convenient: What if you also want to try another pyhton version? Or use
|
||||
the same venv in multiple projects? For this, there is `pyenv`:
|
||||
|
||||
### `venv` pitfalls
|
||||
|
||||
- You cannot move or rename **any** directory inside the path to your project
|
||||
directory or the `venv` breaks. You can rename a folder _inside_ the project
|
||||
directory as long as you dont change the path to the venv.
|
||||
- You cannot use the venv for multiple projects
|
||||
|
||||
`pyenv` fixes this:
|
||||
|
||||
## Using [`pyenv`](https://github.com/pyenv/pyenv.git) / [`pyenv-virtualenv`](https://github.com/pyenv/pyenv-virtualenv.git)
|
||||
|
||||
Use pyenv for managing python versions and pyenv-virtualenv for a slightly
|
||||
easier way of interacting with venvs.
|
||||
|
||||
To download another python version (without destroying your system), simply run:
|
||||
|
||||
```sh
|
||||
pyenv install python3.12
|
||||
```
|
||||
|
||||
To see which versions are installed, you can run
|
||||
|
||||
```sh
|
||||
pyenv versions
|
||||
```
|
||||
|
||||
To set this version as the default in a specific directory (and all its subdirectories), run:
|
||||
|
||||
```sh
|
||||
pyenv local 3.12
|
||||
```
|
||||
|
||||
Now you can either use the tutorial above to create a standard venv or use `pyenv-virtualenv` like this:
|
||||
|
||||
```sh
|
||||
pyenv virtualenv 3.12 <name>
|
||||
```
|
||||
|
||||
Now you can activate it by `pyenv activate <name>` or use it as the standard in the current and subdirectories with
|
||||
|
||||
```sh
|
||||
pyenv local <name>
|
||||
```
|
||||
|
||||
The convenient thing is, that this venv will always be automatically activated
|
||||
when you enter the directory. If you do this in multiple directories, you can
|
||||
have the same venv for multiple projects.
|
||||
|
||||
There are some alternatives to `pyenv` such as
|
||||
Reference in New Issue
Block a user