From 849fbc45179cad5a2519a6aaf4ac3b00667765b3 Mon Sep 17 00:00:00 2001 From: Jeff Soules Date: Mon, 23 Sep 2024 12:45:47 -0400 Subject: [PATCH] Add example project, update slides. --- .../assets/bit.ly_sciware-sep2024.png | Bin 0 -> 31232 bytes 34_PyPackaging/assets/sample-layout.png | Bin 0 -> 39226 bytes .../example_project_root/.gitignore | 115 ++++++++++++++ 34_PyPackaging/example_project_root/LICENSE | 4 + 34_PyPackaging/example_project_root/README.md | 5 + .../example_project_root/docs/index.md | 10 ++ .../example_project_root/pyproject.toml | 27 ++++ .../src/SciwarePackage/__init__.py | 1 + .../src/SciwarePackage/api.py | 24 +++ .../src/SciwarePackage/util/enums/__init__.py | 2 + .../src/SciwarePackage/util/enums/mode.py | 5 + .../SciwarePackage/util/enums/precision.py | 5 + .../src/SciwarePackage/util/formatting.py | 4 + .../example_project_root/src/separate_file.py | 0 .../example_project_root/test/test_main.py | 0 .../test/util/test_formatting.py | 0 34_PyPackaging/main.md | 141 ++++++++++-------- 17 files changed, 280 insertions(+), 63 deletions(-) create mode 100644 34_PyPackaging/assets/bit.ly_sciware-sep2024.png create mode 100644 34_PyPackaging/assets/sample-layout.png create mode 100644 34_PyPackaging/example_project_root/.gitignore create mode 100644 34_PyPackaging/example_project_root/LICENSE create mode 100644 34_PyPackaging/example_project_root/README.md create mode 100644 34_PyPackaging/example_project_root/docs/index.md create mode 100644 34_PyPackaging/example_project_root/pyproject.toml create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/__init__.py create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/api.py create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/__init__.py create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/mode.py create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/precision.py create mode 100644 34_PyPackaging/example_project_root/src/SciwarePackage/util/formatting.py create mode 100644 34_PyPackaging/example_project_root/src/separate_file.py create mode 100644 34_PyPackaging/example_project_root/test/test_main.py create mode 100644 34_PyPackaging/example_project_root/test/util/test_formatting.py diff --git a/34_PyPackaging/assets/bit.ly_sciware-sep2024.png b/34_PyPackaging/assets/bit.ly_sciware-sep2024.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbcdc68e7628afcd54269406140e518622c02eb GIT binary patch literal 31232 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 AX#fBK literal 0 HcmV?d00001 diff --git a/34_PyPackaging/assets/sample-layout.png b/34_PyPackaging/assets/sample-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..a9cd552372a2cf36d9bfb2dbc1297583177cd237 GIT binary patch literal 39226 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 literal 0 HcmV?d00001 diff --git a/34_PyPackaging/example_project_root/.gitignore b/34_PyPackaging/example_project_root/.gitignore new file mode 100644 index 0000000..7dd4f81 --- /dev/null +++ b/34_PyPackaging/example_project_root/.gitignore @@ -0,0 +1,115 @@ +## This is just an example! +# You probably aren't using most of this tooling! + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +.pytest_cache/ + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Data files +*npy + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +coverage.lcov +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +python/test/unit/plots/* + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# PyCharm project settings +.idea + +# Rope project settings +.ropeproject + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +.vscode +deprecated +setup* diff --git a/34_PyPackaging/example_project_root/LICENSE b/34_PyPackaging/example_project_root/LICENSE new file mode 100644 index 0000000..70ee192 --- /dev/null +++ b/34_PyPackaging/example_project_root/LICENSE @@ -0,0 +1,4 @@ +This should almost certainly be one of the standard licenses. + +Which one to pick is outside the scope of this session, but may +be discussed in future sessions about distributing your code. diff --git a/34_PyPackaging/example_project_root/README.md b/34_PyPackaging/example_project_root/README.md new file mode 100644 index 0000000..7508ff7 --- /dev/null +++ b/34_PyPackaging/example_project_root/README.md @@ -0,0 +1,5 @@ +# README + +Note that this would be visible both on GitHub and +on the package distribution network you publish to +(e.g. [PyPI](https://pypi.org/), the Python Package Index). diff --git a/34_PyPackaging/example_project_root/docs/index.md b/34_PyPackaging/example_project_root/docs/index.md new file mode 100644 index 0000000..0768b2d --- /dev/null +++ b/34_PyPackaging/example_project_root/docs/index.md @@ -0,0 +1,10 @@ +# Documentation + +For your project would go here. + +Documentation best practices and tooling are outside the +scope of this tutorial, however Sciware has other +presentations on these topics, +[Sciware 20](https://github.com/flatironinstitute/sciware/tree/main/20_Documentation) +in particular. + diff --git a/34_PyPackaging/example_project_root/pyproject.toml b/34_PyPackaging/example_project_root/pyproject.toml new file mode 100644 index 0000000..8d6c043 --- /dev/null +++ b/34_PyPackaging/example_project_root/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "SciwarePackage" +version = "0.0.1" +description = "Example package for Sciware 34" +authors = [ + { name = "Jeff Soules", email = "jsoules@flatironinstitute.org" } +] +readme = "README.md" +classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] +requires-python = ">=3.8" +dependencies = [ + "numpy>=1.24.0", +] + +[project.license] +file = "LICENSE" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend="setuptools.build_meta" + +[tool.setuptools] +package-dir = {"" = "src"} +packages = ["SciwarePackage"] diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/__init__.py b/34_PyPackaging/example_project_root/src/SciwarePackage/__init__.py new file mode 100644 index 0000000..fa9069e --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/__init__.py @@ -0,0 +1 @@ +from SciwarePackage.api import * \ No newline at end of file diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/api.py b/34_PyPackaging/example_project_root/src/SciwarePackage/api.py new file mode 100644 index 0000000..114d975 --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/api.py @@ -0,0 +1,24 @@ + +from SciwarePackage.util.formatting import canonicalize_string +from SciwarePackage.util.enums import Mode + + +def multiply(a: int | float, b: int | float): + return float(a * b) + + +def describe_operation(desc: str, left_operand: int | float, right_operand: int | float): + canonical_string = canonicalize_string(desc) + product = multiply(left_operand, right_operand) + print(f"{canonical_string}\n\t{product}") + + +def main(mode: Mode, l: int | float, r: int | float): + if mode == Mode.SIMPLE: + describe_operation("times", l, r) + else: + describe_operation("multiplication of two numbers", l, r) + + +if __name__ == "__main__": + main(Mode.ADVANCED, 3, 5) diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/__init__.py b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/__init__.py new file mode 100644 index 0000000..3bcdf09 --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/__init__.py @@ -0,0 +1,2 @@ +from .mode import Mode as Mode +from .precision import Precision as Precision diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/mode.py b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/mode.py new file mode 100644 index 0000000..e2e966d --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/mode.py @@ -0,0 +1,5 @@ +from enum import Enum + +class Mode(Enum): + SIMPLE = 'simple' + ADVANCED = 'advanced' diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/precision.py b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/precision.py new file mode 100644 index 0000000..96af6b6 --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/util/enums/precision.py @@ -0,0 +1,5 @@ +from enum import Enum + +class Precision(Enum): + LOW = 1 + HIGH = 2 diff --git a/34_PyPackaging/example_project_root/src/SciwarePackage/util/formatting.py b/34_PyPackaging/example_project_root/src/SciwarePackage/util/formatting.py new file mode 100644 index 0000000..65061c0 --- /dev/null +++ b/34_PyPackaging/example_project_root/src/SciwarePackage/util/formatting.py @@ -0,0 +1,4 @@ +def canonicalize_string(base_string: str) -> str: + if (base_string == ''): + return "[empty string]" + return base_string.capitalize() diff --git a/34_PyPackaging/example_project_root/src/separate_file.py b/34_PyPackaging/example_project_root/src/separate_file.py new file mode 100644 index 0000000..e69de29 diff --git a/34_PyPackaging/example_project_root/test/test_main.py b/34_PyPackaging/example_project_root/test/test_main.py new file mode 100644 index 0000000..e69de29 diff --git a/34_PyPackaging/example_project_root/test/util/test_formatting.py b/34_PyPackaging/example_project_root/test/util/test_formatting.py new file mode 100644 index 0000000..e69de29 diff --git a/34_PyPackaging/main.md b/34_PyPackaging/main.md index 5e995bc..4141d03 100644 --- a/34_PyPackaging/main.md +++ b/34_PyPackaging/main.md @@ -75,13 +75,13 @@ No. You `import ThePackage` and it Just Works. Running your own code should be that simple too. -What we'll show today helps get you ready for *distributing* your +- What we'll show today helps get you ready for *distributing* your work on a package archive like PyPI. -But we'll leave the fine details of +- But we'll leave the fine details of that for a future Sciware about distributing code. -For today, we just want you to be able to `import` your own code as easily +- For today, we just want you to be able to `import` your own code as easily as you do someone else's. @@ -99,34 +99,33 @@ For today, we use these to mean: - A `module` is any file that has Python code. - We won't use this term. - A `package` is a bundle of Python code you can *import*. - - Can be one or more files (the user doesn't need to care) - - Can be downloaded from a repository or installed locally + - One or more files (the user doesn't need to care) + - Downloaded from a repository or installed locally In short: -We'll use "project" to refer to something you're editing, and "package" +- We'll use "project" to refer to something you're editing, and "package" to refer to something you want to import. -Our goal for today is to show how easy and beneficial it is to make your +- Our goal for today is to show how easy and beneficial it is to make your *projects* into (locally) importable *packages*. ### Why Packages? -We've said packages are "stuff you can import." +- We've said packages are "stuff you can import." -So the point of packages is *code reuse*. They are +- So the point of packages is *code reuse*. They are libraries of pre-written code. -A big part of Python's success is its robust package ecosystem! +- A big part of Python's success is its robust package ecosystem! -That comic is from *2007*. There have been a lot of changes and complications -to the Python import system in that time! +That comic is from *2007*. There have been a lot of changes since! The system as a whole is still trying to solve 3 problems: @@ -144,7 +143,7 @@ The system as a whole is still trying to solve 3 problems: - Package publishing -### Version control +### (Installed) Version Control - Python version management - i.e. interpreter. Python 2 is not 3.6 is not 3.12 @@ -175,23 +174,25 @@ The system as a whole is still trying to solve 3 problems: - **Package building** - Package publishing -For today, the tools we're focusing on are -`pip` and (a little bit of) `setuptools`. +Each of these offers many tools, but for today +we're really only talking about `pip` +(and maybe a little bit of [setuptools]https://setuptools.pypa.io/en/latest/). ### namespaces -A [namespace](https://docs.python.org/3/glossary.html#term-namespace) creates a hierarchy +- A [namespace](https://docs.python.org/3/glossary.html#term-namespace) creates a hierarchy of names. -Namespaces let packages define variables, functions, and classes without worrying about uniqueness. +- Namespaces let packages define variables, functions, and classes without worrying about uniqueness. Example: - `numpy.linalg.norm()` is one function - `torch.norm()` is a different function - Both compute norms, but they have different parameters and work on different objects -- You can use both because the namespace (`numpy` vs `torch`) clarifies what you mean. +- You can use both in the same script because the +namespace (`numpy` vs `torch`) clarifies what you mean. ### global vs local namespaces @@ -223,7 +224,7 @@ print(f'{MyClass.y}') # prints 10 An [import](https://docs.python.org/3/reference/import.html) does two things: -- Finds the code you're importing, and +- Finds the code you want to import, and - Attaches that code to a name in the namespace Let's talk about the second point first. @@ -268,13 +269,13 @@ Why is this so brittle? ### Finding the code to import -- When you `import FOO`, Python looks for a module named `FOO` +- When you `import FOO`, Python looks for a *module* named `FOO` - It looks in the list of locations defined in `sys.path` -- This list includes various standard locations + - This list includes various standard locations - It also includes your current working directory - But that changes with every `cd`! -Reliable imports require the package to be in one of the standard locations. +Reliable imports require the code to be in one of the standard locations. ### Package installation @@ -284,9 +285,20 @@ Reliable imports require the package to be in one of the standard locations. - Places it in a standard location (in `sys.path`) +`pip` can also install *your project* as a package, using *edit mode*: + +`$ pip install -e /path/to/my/project` + +- the base directory of your project gets added to `sys.path` +- Now regular import patterns work! +- First you just have to tell `pip` how to bundle your project + +We do that through `pyproject.toml`. But first... + + ### A bit more about environments -`sys.path` is actually how virtual +`sys.path` is actually how environments work. Let's take a look at a `venv` virtual environment and @@ -295,25 +307,29 @@ what happens when I install packages in it. [LIVE] -`pip` can also install *your project* as a package, using *edit mode*: +## Properly Handling Python Projects -`$ pip install -e /path/to/my/project` + -- the base directory of your project gets added to `sys.path` -- Now regular import patterns work! -- First you just have to tell `pip` how to bundle your project -We do that through `pyproject.toml`. +### Pythons Organized Neatly +To make this example concrete, we'll work with an example project using a standard layout. +You can find this example in this repository at `example_project_root`. -## Properly Handling Python Projects - + -### TODO: An example directory structure for a Python project -Which we'll show and refer to for the below +The highlights: +- The root of the project is `example_project_root` (this name doesn't matter) +- Package code is in the `src` directory. + - Specifically, in a `SciwarePackage` sub-directory + - That name matches the package name + - `separate_file.py` is not part of the package +- Test code is in a `test` directory that's not part of the package +- `pyproject.toml` goes at the top level--the project root ### pyproject.toml @@ -338,13 +354,11 @@ very positive why you need it, you might just have outdated instructions. ```toml [project] -name = "MY_PROJECT" +name = "SciwarePackage" version = "0.0.1" requires-python = ">=3.8" dependencies = [ "numpy>=1.24.0", - "scipy>=1.10.0", - "scikit-learn>=1.3.0" ] ``` @@ -357,9 +371,9 @@ Additional fields for distributing your package: ```toml [project] ... -description = "Describe your package here" +description = "Example package for Sciware 34" authors = [ - { name = "Your Name", email = "you@your.email" } + { name = "Jeff Soules", email = "jsoules@flatironinstitute.org" } ] readme = "README.md" classifiers = [ @@ -370,14 +384,14 @@ classifiers = [ [project.license] file = "LICENSE" ``` -- These help other users find your uploaded project +- These help others find your uploaded project - `readme` can be text, a file, or even `dynamic` (see later) -- The `license` field grants others rights to use your code +- The `license` grants others rights to use your code -### build system +### Build system -Alongside the `[project]` section, you also need a `[build-system]`: +You also need a `[build-system]` section: ```toml [build-system] @@ -386,45 +400,43 @@ build-backend="setuptools.build_meta" [tool.setuptools] package-dir = {"" = "src"} -packages = ["MY_PROJECT"] +packages = ["SciwarePackage"] ``` -This tells `pip` what tools to use to bundle up your code. `setuptools` is a -well-supported and painless option. +This states the tools to use to bundle up your code (`setuptools` here). Then we have another config block for the `setuptools` tool. -(`pyproject.toml` combines tool-specific config into the same file) +(`pyproject.toml` collects most tools' config into the same file) ```toml [tool.setuptools] package-dir = {"" = "src"} -packages = ["MY_PROJECT"] +packages = ["SciwarePackage"] ``` -This block is specific to `setuptools`. It just defines: +This block is specific to `setuptools`. It defines: - the root directory of the code to distribute (the `src` directory adjacent to where this file is located) - the packages that should be bundled (matches the `name` field of the `[project]` section) That's it! With this minimal `pyproject.toml` config in place, you can install your project as a package. -- Assume our project is at `~/MY_PROJECT` -- We have `~/MY_PROJECT/pyproject.toml` defined as above -- And the code lives in `~/MY_PROJECT/src/`. Then: +- Assume our project is at `~/example_project_root` +- We have `~/example_project_root/pyproject.toml` defined as above +- And the code lives in `~/example_project_root/src/`. Then: ```bash -$ cd ~/MY_PROJECT -$ pip install -e . +$ pip install -e ~/example_project_root/ ``` Now, in *any* Python file *anywhere*, you can just ```python -from MY_PROJECT import my_cool_stuff -from MY_PROJECT.util import my_cool_util +from SciwarePackage import describe_operation +from SciwarePackage.util.formatting import canonicalize_string ``` and those functions will be just as smooth and simple to use as the fancy store-bought ones you @@ -433,15 +445,16 @@ got from a package off PyPI. ### But that's not all! -Now that you have `pyproject.toml` set up in your project, consider configuring other code quality -tools there! In particular, I like: +Now that you have `pyproject.toml` set up in your project, consider configuring other +tools there! Here are some code quality tools that support `pyproject.toml` configuration: -- `[tool.pytest]` to set default options for running tests -- `[tool.coverage.run]` to specify what files should be considered for test coverage reports -- `[tool.pylint]` to set what style rules to enforce and limit unnecessary messages -- `[tool.mypy]` to customize type-checking strictness +- [pytest](https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml) for automated testing +- [pytest-cov](https://coverage.readthedocs.io/en/latest/config.html) for test coverage reports +- [pylint](https://pylint.pycqa.org/en/latest/user_guide/configuration/all-options.html) the classic linter +- [mypy](https://mypy.readthedocs.io/en/stable/config_file.html) for type-checking +- [ruff](https://docs.astral.sh/ruff/configuration/), monolithic linter and formatter -There's many more! It's worth looking into for any other tooling your project is using. +There's many more! It's worth looking into for any tool you use. (And if you don't have any, now's a great time to consider it!) @@ -454,4 +467,6 @@ There's many more! It's worth looking into for any other tooling your project is ## Survey Please give us some feedback! -NEED TO UPDATE SURVEY LINK + + +[https://bit.ly/sciware-sep2024](https://bit.ly/sciware-sep2024)