From e069796ec7d7ede46805dee83b825ef651d1b5ac Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Tue, 5 Dec 2023 11:29:54 -0700 Subject: [PATCH 01/19] Add: second lesson and images to pr --- images/tutorials/code-to-script-diagram.png | Bin 0 -> 34904 bytes tutorials/1-installable-code.md | 469 ++++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 images/tutorials/code-to-script-diagram.png create mode 100644 tutorials/1-installable-code.md diff --git a/images/tutorials/code-to-script-diagram.png b/images/tutorials/code-to-script-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..5f14f3ed321d6b6a105c61d53e4342f32373e0d8 GIT binary patch literal 34904 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 N1fH&bF6*2UngGYq3AF$K literal 0 HcmV?d00001 diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md new file mode 100644 index 000000000..7dc0d5e64 --- /dev/null +++ b/tutorials/1-installable-code.md @@ -0,0 +1,469 @@ +# Make your Python code pip installable + +The first step in creating a Python package based on code that you +have is to make that code pip installable. You will learn how to make +your code pip installable in this lesson. + + + +:::{figure-md} code-to-script + +Diagram showing the basic steps to creating an installable package. There are 4 boxes with arrows pointing towards the right. The boxes read, your code, create package structure, add metadata to pyproject.toml and pip install package. + +A basic installable package needs a few things. Code, a specific package structure and a `pyproject.toml` containing your package's name and version. Once you have these items in the correct directory structure, you can pip install your package into any environment on your computer. +::: + +:::{admonition} Learning Objectives +:class: tip + +In this lesson you will learn: + +- How to make your code installable into a Python environment +- How to create a basic `pyproject.toml` file to declare dependencies and metadata +- How to declare a build backend which will be used to build and install your package (learn more about what build back ends are here - link to guide) +- How to install your package in editable mode for interactive development + +To complete this lesson you will need a local Python (development) +environment. You are welcome to use any environment manager that you chose. + +* [If you need guidance creating a Python environment, review this lesson](extras/1-create-environment.md) which walks you through creating an environment using both `venv` and `conda`. +* If you aren't sure which environment manager to use and +you are a scientist, we suggest that you use `conda`. +::: + +## Make your package installable + + +:::{figure-md} packages-environment + +This diagram has two smaller boxes with arrows pointing to the right to a python environment. The small boxes read your-package and pip install package. The environment box on the right reads - your python environment. It them lists your-package along with a few other core packages such as matplotlib, numpy, pandas, xarray and geopandas. + +Making your code pip installable is the first step towards creating a Python package. Once it is pip installable, you can add it to any Python environment on your computer and import that package in the same way that you might import a package such as `Pandas` or `Geopandas`. +::: + +## Make a basic Python package + +It’s time to create the most basic version of a Python package. +While this code can't be yet published to PyPI or conda and +is not documented, it will be installable on your computer or +anyone elses. + +### What does a basic package directory structure look like? +To make your code installable you need: + +- A `pyproject.toml` file +- An (optional but recommended) `__init__.py` file in your code directory +- A specific directory structure +- Some code. + +The directory structure you’ll create in this first section looks like this: + +```bash +pyospackage/ + └─ pyproject.toml + └─ src/ # The src directory ensures your tests always run on the installed + └── pyospackage/ # Package directory where code lives, use the package name + ├── __init__.py + ├── add_numbers.py + └── # Add any other .py modules that you want here +``` + +Below, you will learn about each element of the above package structure. + +### About the basic package directory structure + +Notice a few things about the above layout: + +1. Your package code lives within a `src/packagename` directory. We suggest that you use `src/` directory as it ensure you are running tests on the installed version of your code. However, you are welcome to instead use a [flat layout](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#about-the-flat-python-package-layout) which does not have a src/ directory at the root. [Learn more here.](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#the-src-layout-and-testing) +2. Within the `src/` directory you have a package directory called `pyospackage/`. Use the name of your package for that directory name. +3. In your package directory, you have an `__init__.py` file and all of your Python modules. +4. The `pyproject.toml` file lives at the root directory of your package. + +## Init.py and pyproject.toml files + +The `__init__.py` and `pyproject.toml` files in the above layout +are important to understand. More on that below. + +### What is an init.py file? + +The `__init__.py` file tells Python that the directory it’s in should be treated as a Python package. +The `__init__.py` file also: + +- Allows you to organize multiple modules within the package. +- Allows you to create shortcuts for importing specific functions, and classes into your code (more on that later!) +- Allows you to create a version object for people to call **version** + +:::{admonition} The **init**.py file +:class: tip + +Since Python 3.3 came out, you can install a package without an `__init__.py` file. However, we suggest that you include it in your package structure as it allows you to customize your package’s user experience. +::: + +### What is a pyproject.toml file? + +The **pyproject.toml** file is: + +- Where you store your project’s metadata (including its name, authors, license, etc) +- Where you store dependencies (the packages that it depends on) +- Used to specify and configure what build back end you want to use to build your package distributions that are used for PyPI publication. + +After the `__init__.py` and `pyproject.toml` files have been added, your package can be built and distributed as an installable Python package using tools such as pip. Note that the `pyproject.toml` file needs to have the a few basic items defined for it to be installable including: + +- The `build-backend` that you want to use, +- The project `name` and `version`. + +:::{admonition} Why the pyproject.toml file is important +:class: tip + +The `pyproject.toml` file replaces some of the functionality of both the setup.py file and setup.cfg files. +If you try to pip install a package with no `pyproject.toml` you will get the following error: + +```bash +GitHub/pyospackage/testme +➜ pip install . +ERROR: Directory '.' is not installable. +Neither 'setup.py' nor 'pyproject.toml' found. +``` + +::: + +## Try it yourself - Create your package! + +Now that you understand the basics, it's time to create a Python package! Below you will create a directory structure similar to the structure described above. + +If you don’t wish to create each of the files and directories below, you can always [fork and clone and customize the pyOpenSci example package, here.](https://github.com/pyOpenSci/pyosPackage) + +### Step 1: Set Up the Package Directory Structure + +Create a new directory for your package. Choose a name for your package, preferably in lowercase and without spaces (e.g., "pyospackage_yourname"). + +Inside the package directory, + +- Create a `src/` directory +- Within the `src/` directory, create a directory that is named after your package. This subdirectory will contain your package’s code. +- It is ok if the main directory of your package and the directory in `src/` have the same name + +Next create two files: + +- Inside the package directory, create a new file named `__init__.py` . This file ensures Python sees this directory as a package. You will use this file to customize how parts of your package are imported and to declare your package’s version in a future lesson. +- At the root of your directory, create a file called `pyproject.toml` + +Your final package directory structure should look like this: + +``` +pyospackage/ + └─ pyproject.toml + └─ src/ + └── pyospackage_yourname/ + ├── __init__.py +``` + +### Step 2: Add code to your package + +Within the `pyospackage` subdirectory, add 1 or more Python modules +(.py files) containing the code that you want your package to access and run. +If you don't have code already and are just learning how to +create a Python +package, then create an empty `add_numbers.py` file. + +:::{admonition} Python modules and the __init__.py file +:class: tip + +When you see the word module, we are referring to a `.py` file containing Python +code. + +The _init_.py allows Python to recognize that a directory contains at least one +module that may be imported and used in your code. A package can have multiple +modules. + +[Learn more about Python packages and modules in the python documentation.](https://docs.python.org/3/tutorial/modules.html#packages ) + +::: + +``` +pyospackage/ + └─ pyproject.toml + └─ src/ + └── pyospackage/ + ├── __init__.py + ├── add_numbers.py + +``` + +### Step 3. Add code to your `add_numbers` module + +If you are following along and making a Python package from scratch then you can add the code below to your `add_numbers.py` module. The function below adds two integers together and returns the result. Notice that the code below has a few features that we will review in future tutorials: + +1. It has a [numpy-style docstring ](https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/document-your-code-api-docstrings.html#three-python-docstring-formats-and-why-we-like-numpy-style) +2. It uses [typing](https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/document-your-code-api-docstrings.html#adding-type-hints-to-your-docstrings) + +If you aren’t familiar with docstrings or typing yet, that is ok. We will get +to it later in our tutorial series. Or, you can review the pyOpenSci [packaging guide](https://www.pyopensci.org/python-package-guide/documentation/write-user-documentation/document-your-code-api-docstrings.html) +for an overview. + +```python +def add_num(a: int, b: int) -> int: + """ + Add two numbers. + + Parameters + ---------- + a : int + The first number to be added. + b : int + The second number to be added. + + Returns + ------- + int + The sum of the two input numbers (a + b). + + Examples + -------- + >>> add_num(3, 5) + 8 + >>> add_num(-2, 7) + 5 + """ + return a + b +``` + +### Step 4. Add metadata to your `pyproject.toml` file + +Next, you will add some metadata (information) to your `pyproject.toml` file. You are +are welcome to copy the file we have in our example repo here. + +:::{admonition} Brief overview of the TOML file +:class: tip + +The TOML format consists of tables and variables. Tables are sections of information denoted by square brackets: + +`[this-is-a-table]`. + +Tables can contain variables within them defined by an variable name and +an `=` sign. For +instance, a `build-system` table most often holds 2 variables: + +1. `requires = `, which tells a build tool what tools it needs to install prior to building your package. in this case is + [hatchling](https://pypi.org/project/hatchling/) +2. `build-backend` is used to define specific build-backend name, (in this example we are using `hatchling.build`). + +TOML organizes data structures, defining relationships within a configuration +file. You will learn more about the `pyproject.toml` format in the +[next lesson when you add additional metadata / information to this file.](5-pyproject-toml.md) + +```toml +# An example of the build-system table which contains two variables - requires and build-backend +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" +``` + +[Learn more about the pyproject.toml format here.](../package-structure-code/pyproject-toml-python-package-metadata) +::: + +- Open up your `pyproject.toml` file in your favorite text editor. +- Add the metadata below to your `pyproject.toml` + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pyospackage_gh_user_name" # rename this if you plan to publish to test PyPI +# Here you add the package version manually. You will learn how to setup # dynamic versioning in a followup tutorial. +version="1.1" + +``` + +Note that above you manually add your package's version number to the +`pyproject.toml` file. You will learn how to automate defining a package +version using git tags in the version and release your package lesson. + +:::{admonition} The bare minimum needed in a pyproject.toml file +:class: tip + +The core basic information that you need in a `pyproject.toml` file in order to publish on PyPI is your **package's name** and the **version**. However, we suggest that you flesh out your metadata early on in the `pyproject.toml` file. + +Once you have your project metadata in the pyproject.toml file, you will +rarely update it. In the next lesson you’ll add more metadata and structure to this file. +::: + +### Step 5. Install your package locally + +At this point you should have: + +1. A project directory structure with a `pyproject.toml` file at the root +2. A package directory containing an empty `__init__.py` file and +3. At least one Python module (e.g. `add_numbers.py`) + +You are now ready to install (and build) your Python package! + +Let’s try it out. + +- First open bash and `cd` into your package directory +- Activate the Python environment that you wish to use. If you need help with working with virtual environments [check out this lesson](extras/1-create-environment.md). +- Finally run `python -m pip install -e .` + +```bash +# Activate your environment using conda or venv +# Below we use conda but you can do the same thing with venv! +> conda activate pyosdev +(pyosdev) +>> conda info + active environment : pyosdev + active env location : /Users/your-path/mambaforge/envs/pyosdev +# Install the package +>> python -m pip install -e . + +Obtaining file:///Users/leahawasser/Documents/GitHub/pyos/pyosPackage + Installing build dependencies ... done + Checking if build backend supports build_editable ... done + Getting requirements to build editable ... done + +# Check to see if the package is installed +> conda list +# use pip list instead of conda list here if you are working in an venv environment rather than a conda envt +``` + +:::{admonition} What does `pip install -e .` do? +:class: tip + +Let's break down `pip install -e .` + +`pip install -e .` installs your package into the current active +Python environment in **editable mode** (`-e`). Installing your package in +editable mode, allows you to work on your code and then test the updates +interactively in your favorite Python interface. One important caveat of editable mode is that every time you update your code, you may need to restart your Python kernel. + +If you wish to install the package regularly (not in editable +mode) you can use: + +- `python -m pip install . ` + +**Using `python -m` when calling `pip`** + +Above, you use`python -m` to call the version of pip installed into your +current active environment. `python -m` is important to ensure that you are +calling the version of pip installed in your current environment. +::: + +#### Look for pyospackage in your environment + +Once you have installed your package, you can view it in your current +environment. If you are using `venv` or `conda`, `pip` list will allow you +to see your current package installations. + +Note that because pyospackage is installed in editable mode (`-e`) pip will show you the past to where you package installation's code +is. + +```bash +$ pip list + +➜ pip list +Package Version Editable project location +----------------------------- -------------- -------------------------------------------------------------- +... +arrow 1.2.3 +... +... +mamba 1.1.0 +markdown-it-py 2.2.0 +MarkupSafe 2.1.2 +matplotlib 3.7.1 +msgpack 1.0.5 +mypy 1.4.1 +nox 2021.10.1 +numpy 1.24.2 +packaging 23.0 +pandas 1.5.3 +pyosPackage 0.1.0 /Users/yourusername/path/here/pyosPackage +... +... +... +``` + +### 6. Test out your new package + +After installing your package, type “python” at the command prompt to start +a Python session in your active Python environment. + +You can now import your package and access the `add_num` function. + +```bash +➜ python +Python 3.11.4 | packaged by conda-forge | (main, Jun 10 2023, 18:08:41) [Clang 15.0.7 ] on darwin +Type "help", "copyright", "credits" or "license" for more information. +>>> import pyospackage +>>> pyospackage.add_num(1, 2) +3 +``` + + + + +## OPTIONAL: Customize access to Python functions using the `__init__.py` file + +Let's make one more tweak to the code. + +If `add_num` is a function that you think users will use often, you may want to add it to your `__init__.py` file to allow them to import the function directly from the package rather than from the module. + +### Add functions to your `__init__.py` file + +To make a function or class available at the package level to a user, you can add it to the `__init__.py` file. + +- Open the `__init__.py` file . +- At the top of the file add the import below. + +```python +from pyospackage.add_numbers import add_num +``` + +Save the file. + +Now, open up a NEW Python terminal or restart your Python kernel. + +:::{admonition} Don't forget to restart your Python kernel! +:class: important + +It's important that you restart your Python kernel if you wish to access the changes to your code that you just made. +::: + +```python +> python +Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:41:52) [Clang 15.0.7 ] on darwin +Type "help", "copyright", "credits" or "license" for more information. +>>> from pyospackage import add_num +>>> add_num(1,2) +3 +``` + +The decision to add specific functions, methods or classes to your +`__init__.py` file is up to you. However be sure that you do this thoughtfully +considering what functionality in your package you want to "elevate" to the top +level vs. what makes the most sense to keep in individual modules. + +### Congratulations! You created (the beginning of) your first Python package + +You did it! You have now created a Python package that you can install into any Python environment. While there is still more to do, you have completed the first major step. + +In the upcoming lessons you will: + +* Add a [README file](2-add-readme.md) and [LICENSE ](4-add-license-file.md) to your package +* [Add more metadata to your `pyproject.toml`](5-pyproject-toml.md) file to support PyPI publication. +* [Learn how to build your your package distribution](6-publish-pypi.md) files (**sdist** and **wheel**) and publish to **test PyPI**. +* Finally you will learn how to publish to **conda-forge** from **PyPI**. + +If you have a package that is ready for the mainstream user then +you can also publish your package on PyPI. From 3de31e324be10be27a114a98f07845ddbda0f983 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Tue, 5 Dec 2023 16:44:03 -0700 Subject: [PATCH 02/19] Fix: small edits and reorg --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index 7dc0d5e64..276a36717 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -5,7 +5,7 @@ have is to make that code pip installable. You will learn how to make your code pip installable in this lesson. +2. Does this lesson run as expected on windows and mac? +::: -:::{figure-md} code-to-script +:::{figure-md} code-to-python-package -Diagram showing the basic steps to creating an installable package. There are 4 boxes with arrows pointing towards the right. The boxes read, your code, create package structure, add metadata to pyproject.toml and pip install package. +Diagram showing the basic steps to creating an installable package. There are 4 boxes with arrows pointing towards the right. The boxes read, your code, create package structure, add metadata to pyproject.toml and pip install package. + +A basic installable package needs a few things: code, a [specific package file structure](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html) and a `pyproject.toml` containing your package's name and version. Once you have these items in the correct directory structure, you can pip install your package into any environment on your computer. You will learn how to create a basic installable package in this lesson. -A basic installable package needs a few things: code, a specific package structure and a `pyproject.toml` containing your package's name and version. Once you have these items in the correct directory structure, you can pip install your package into any environment on your computer. ::: :::{admonition} Learning Objectives @@ -23,9 +30,9 @@ A basic installable package needs a few things: code, a specific package structu In this lesson you will learn: -- How to make your code pip-installable into a Python environment +- How to make your code installable into any Python environment both locally and from GitHub - How to create a basic `pyproject.toml` file to declare dependencies and metadata -- How to declare a build backend which will be used to build and install your package (learn more about what build back ends are here - link to guide) +- How to declare a [build backend](build_backends) which will be used to [build](build-package) and install your package - How to install your package in editable mode for interactive development To complete this lesson you will need a local Python (development) @@ -33,86 +40,99 @@ environment. You are welcome to use any environment manager that you choose. * [If you need guidance creating a Python environment, review this lesson](extras/1-create-environment.md) which walks you through creating an environment using both `venv` and `conda`. * If you aren't sure which environment manager to use and -you are a scientist, we suggest that you use `conda`. -::: +you are a scientist, we suggest that you use `conda`, particularly if you are working with any sort of spatial data. -## Make your package installable +In the upcoming lessons you will learn how to + +* Add a README file to your package to support community use +* Add project metadata to your package to support PyPI publication +* Publish your package to PyPI +::: :::{figure-md} packages-environment This diagram has two smaller boxes with arrows pointing to the right to a python environment. The small boxes read your-package and pip install package. The environment box on the right reads - your python environment. It them lists your-package along with a few other core packages such as matplotlib, numpy, pandas, xarray and geopandas. -Making your source code pip-installable is the first step towards creating a Python package. Once your code is pip-installable, it is a Python package and can be added to any Python environment on your computer and imported in the same way that you might import a package such as `Pandas` or `Geopandas`. +Making your code installable is the first step towards +creating a publishable Python package. Once your code is +installable, it is a Python package and can be added to +any Python environment on your computer and imported in +the same way that you might import a package such as +Pandas or GeoPandas. If your code is on GitHub or GitLab +you can also install it directly from there. ::: -## Make a basic Python package -It’s time to create the most basic version of a Python package. -While this code can't be yet published to PyPI or conda and -is not documented, it will be installable on your computer or -anyone elses. +## About the Python package directory structure -### What does a basic package directory structure look like? -To make your code installable you need: +To make your Python code installable you need to create a specific directory structure with the following elements: -- A `pyproject.toml` file -- An (optional but recommended) `__init__.py` file in your code directory -- A specific directory structure +- A `pyproject.toml` file. +- A specific directory structure. - Some code. +- An `__init__.py` file in your code directory. -The directory structure you’ll create in this first section looks like this: +The directory structure you’ll create below looks like this: ```bash -pyospackage/ +pyospackage/ # Your project directory └─ pyproject.toml - └─ src/ # The src directory ensures your tests always run on the installed - └── pyospackage/ # Package directory where code lives, use the package name + └─ src/ # The source (src) directory ensures your tests always run on the installed version of your code + └── pyospackage/ # Package directory where code lives ├── __init__.py ├── add_numbers.py └── # Add any other .py modules that you want here ``` -Below, you will learn about each element of the above package structure. - ### About the basic package directory structure Notice a few things about the above layout: -1. Your package code lives within a `src/packagename` directory. We suggest that you use `src/` directory as it ensure you are running tests on the installed version of your code. However, you are welcome to instead use a [flat layout](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#about-the-flat-python-package-layout) which does not have a src/ directory at the root. [Learn more here.](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#the-src-layout-and-testing) -2. Within the `src/` directory you have a package directory called `pyospackage/`. Use the name of your package for that directory name. -3. In your package directory, you have an `__init__.py` file and all of your Python modules. -4. The `pyproject.toml` file lives at the root directory of your package. +1. Your package code lives within a `src/packagename` directory. We suggest that you use `src` directory as it ensures that you are running tests on the installed version of your code. However, you are welcome to instead use a [flat layout](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#about-the-flat-python-package-layout) which does not have a src/ directory at the root. [Learn more here.](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#the-src-layout-and-testing) +1. Within the `src` directory you have a package directory called `pyospackage`. Use the name of your package for that directory name. +1. In your package directory, you have an `__init__.py` file and all of your Python modules. You will learn more about the __init__.py file below. +1. The `pyproject.toml` file lives at the root directory of your package. +1. The name of the root directory for the package is **pyospackage** which is the name of the package. This is not a requirement but you will often see that the GitHub / GitLab repo and the root directory name are the same as the package name. + +### What is an __init__.py file? -## Init.py and pyproject.toml files +When a directory contains an `__init__.py` file, it can be imported directly into Python. -The `__init__.py` and `pyproject.toml` files in the above layout -are important to understand. More on that below. +For example, following the file structure example above which has an `__init__.py` file within it, you can run: -### What is an init.py file? +```python +import pyospackage +``` -The `__init__.py` file tells Python that the directory it’s in should be treated as a Python package. -The `__init__.py` file also: +The `__init__.py` file tells Python that a directory should be treated +as a Python package. -- Allows you to organize multiple modules within the package. -- Allows you to create shortcuts for importing specific functions, and classes into your code (more on that later!) -- Allows you to create a version object for people to call **version** -:::{admonition} The **init**.py file +:::{admonition} The **__init__**.py file :class: tip -Since Python 3.3 came out, you can install a package without an `__init__.py` file. However, we suggest that you include it in your package structure as it allows you to customize your package’s user experience. +The __init__.py file does not need to contain any code, it can be +empty. Since Python 3.3 came out, you can install a package without an +`__init__.py` file. However, we suggest that you include empty __init__.py files in your +package structure as it allows you to customize your package’s user +experience. ::: + ### What is a pyproject.toml file? The **pyproject.toml** file is: -- Where you store your project’s metadata (including its name, authors, license, etc) -- Where you store dependencies (the packages that it depends on) -- Used to specify and configure what build back end you want to use to build your package distributions that are used for PyPI publication. +- Where you define your project’s metadata (including its name, authors, license, etc) +- Where you define dependencies (the packages that it depends on) +- Used to specify and configure what build back end you want to use to [build your package](../package-structure-code/python-package-distribution-files-sdist-wheel). -After the `__init__.py` and `pyproject.toml` files have been added, your package can be built and distributed as an installable Python package using tools such as pip. Note that the `pyproject.toml` file needs to have the a few basic items defined for it to be installable including: +After the `__init__.py` and `pyproject.toml` files have been added, +your package can be built and distributed as an installable Python +package using tools such as pip. Note that the `pyproject.toml` file +needs to have a few basic items defined for the package to be +installable including: - The `build-backend` that you want to use, - The project `name` and `version`. @@ -132,38 +152,59 @@ Neither 'setup.py' nor 'pyproject.toml' found. ::: -## Try it yourself - Create your package! +## Time to create your Python package! + +Now that you understand the basics of the Python package directory structure, it's time to create a Python package! Below you will create a directory structure similar to the structure described above. -Now that you understand the basics, it's time to create a Python package! Below you will create a directory structure similar to the structure described above. +If you don’t wish to create each of the files and directories below, you can always [fork and clone and customize the pyOpenSci example package.](https://github.com/pyOpenSci/pyosPackage) -If you don’t wish to create each of the files and directories below, you can always [fork and clone and customize the pyOpenSci example package, here.](https://github.com/pyOpenSci/pyosPackage) +## Step 1: Set Up the Package Directory Structure -### Step 1: Set Up the Package Directory Structure +Below you create the basic directory structure required +for your Python package. Note that there are instructions for creating the files and directories using shell. However you can also create files and directories in your preferred file directory tool (e.g. Finder on MAC or File Explorer on Windows) if you wish. -Create a new directory for your package. Choose a name for your package, preferably in lowercase and without spaces (e.g., "pyospackage_yourname"). +Create a new project directory for your package. Choose a +name for your package, preferably in lowercase and +without spaces (e.g., "pyospackage"). -Inside the package directory, +Inside the project directory: -- Create a `src/` directory -- Within the `src/` directory, create a directory that is named after your package. This subdirectory will contain your package’s code. -- It is ok if the main directory of your package and the directory in `src/` have the same name +- Create a directory called `src` +- Within the `src` directory, create a directory that is named after your package. This subdirectory will contain your package’s code. +- It is ok if the project directory for your package and the directory in `src` have the same name + +```bash +# Create a project directory in shell and a src directory within +mkdir -R pyospackage/src/pyospackage +# Change directory into pyospackage project dir +cd pyospackage +# View the current file structure +ls +``` Next create two files: - Inside the package directory, create a new file named `__init__.py` . This file ensures Python sees this directory as a package. You will use this file to customize how parts of your package are imported and to declare your package’s version in a future lesson. -- At the root of your directory, create a file called `pyproject.toml` - -Your final package directory structure should look like this: +- At the root of your project, create a file called `pyproject.toml` +```bash +# Create a pyproject.toml file in your project directory +touch pyproject.toml +# Create an empty init file within your src/pyospackage directory +touch src/pyospackage/__init__.py ``` -pyospackage/ + +Your final project directory structure should look like this: + +```bash +pyospackage/ # This is your project directory └─ pyproject.toml - └─ src/ - └── pyospackage_yourname/ + └─ src/ # This is your package directory where your code lives + └── pyospackage/ ├── __init__.py ``` -### Step 2: Add code to your package +## Step 2: Add code to your package Within the `pyospackage` subdirectory, add 1 or more Python modules (.py files) containing the code that you want your package to access and run. @@ -177,11 +218,11 @@ package, then create an empty `add_numbers.py` file. When you see the word module, we are referring to a `.py` file containing Python code. -The _init_.py allows Python to recognize that a directory contains at least one +The `__init__.py` allows Python to recognize that a directory contains at least one module that may be imported and used in your code. A package can have multiple modules. -[Learn more about Python packages and modules in the python documentation.](https://docs.python.org/3/tutorial/modules.html#packages ) +[Learn more about Python packages and modules in the Python documentation.](https://docs.python.org/3/tutorial/modules.html#packages ) ::: @@ -195,7 +236,7 @@ pyospackage/ ``` -### Step 3. Add code to your `add_numbers` module +## Step 3. Add code to your `add_numbers` module If you are following along and making a Python package from scratch then you can add the code below to your `add_numbers.py` module. The function below adds two integers together and returns the result. Notice that the code below has a few features that we will review in future tutorials: @@ -233,10 +274,10 @@ def add_num(a: int, b: int) -> int: return a + b ``` -### Step 4. Add metadata to your `pyproject.toml` file +## Step 4. Add metadata to your `pyproject.toml` file Next, you will add some metadata (information) to your `pyproject.toml` file. You are -are welcome to copy the file we have in our example repo here. +are welcome to copy the file we have in our [example pyospackage GitHub repository](https://github.com/pyOpenSci/pyosPackage). :::{admonition} Brief overview of the TOML file :class: tip @@ -276,8 +317,9 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "pyospackage_gh_user_name" # rename this if you plan to publish to test PyPI -# Here you add the package version manually. You will learn how to setup # dynamic versioning in a followup tutorial. +name = "pyospackage_gh_user_name" # rename this if you plan to publish to test PyPI +# Here you add the package version manually. +# You will learn how to setup dynamic versioning in a followup tutorial. version="1.1" ``` @@ -295,7 +337,7 @@ Once you have your project metadata in the pyproject.toml file, you will rarely update it. In the next lesson you’ll add more metadata and structure to this file. ::: -### Step 5. Install your package locally +## Step 5. Install your package locally At this point you should have: @@ -354,7 +396,7 @@ current active environment. `python -m` is important to ensure that you are calling the version of pip installed in your current environment. ::: -#### Look for pyospackage in your environment +### Look for pyospackage in your environment Once you have installed your package, you can view it in your current environment. If you are using `venv` or `conda`, `pip` list will allow you @@ -389,7 +431,7 @@ pyosPackage 0.1.0 /Users/yourusername/path/here/pyosP ... ``` -### 6. Test out your new package +## 6. Test out your new package After installing your package, type “python” at the command prompt to start a Python session in your active Python environment. @@ -405,58 +447,9 @@ Type "help", "copyright", "credits" or "license" for more information. 3 ``` - - - -## OPTIONAL: Customize access to Python functions using the `__init__.py` file - -Let's make one more tweak to the code. - -If `add_num` is a function that you think users will use often, you may want to add it to your `__init__.py` file to allow them to import the function directly from the package rather than from the module. - -### Add functions to your `__init__.py` file - -To make a function or class available at the package level to a user, you can add it to the `__init__.py` file. - -- Open the `__init__.py` file . -- At the top of the file add the import below. - -```python -from pyospackage.add_numbers import add_num -``` - -Save the file. - -Now, open up a NEW Python terminal or restart your Python kernel. - -:::{admonition} Don't forget to restart your Python kernel! -:class: important - -It's important that you restart your Python kernel if you wish to access the changes to your code that you just made. -::: - -```python -> python -Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:41:52) [Clang 15.0.7 ] on darwin -Type "help", "copyright", "credits" or "license" for more information. ->>> from pyospackage import add_num ->>> add_num(1,2) -3 -``` - -The decision to add specific functions, methods or classes to your -`__init__.py` file is up to you. However be sure that you do this thoughtfully -considering what functionality in your package you want to "elevate" to the top -level vs. what makes the most sense to keep in individual modules. - -### Congratulations! You created (the beginning of) your first Python package +## Congratulations! You created your first Python package -You did it! You have now created a Python package that you can install into any Python environment. While there is still more to do, you have completed the first major step. +You did it! You have now created a Python package that you can install into any Python environment. While there is still more to do if you want to publish your package, you have completed the first major step. In the upcoming lessons you will: @@ -467,3 +460,14 @@ In the upcoming lessons you will: If you have a package that is ready for the mainstream user then you can also publish your package on PyPI. + + +:::{admonition} Installing packages from GitHub + +If you wish to share your code without publishing to PyPI you can +always install packages directly from GitHub using the syntax: + +```bash +pip install git+https://github.com/user/repo.git@branch_or_tag +``` +::: diff --git a/tutorials/intro.md b/tutorials/intro.md index 5137f5f33..f6fdcbc9b 100644 --- a/tutorials/intro.md +++ b/tutorials/intro.md @@ -23,6 +23,7 @@ understanding the steps involved in creating a Python package. * In the second series, you will learn about infrastructure and documentation needed to support package maintenance. + :::{toctree} :hidden: :caption: Python Packaging 101 @@ -336,3 +337,5 @@ The elements above are also important for future maintenance of your package. In In future lessons you will learn more about the infrastructure around a published Python package that makes it both easier to maintain, easier for others to contribute to and easier for other scientists to use. However, first we want to get you to your initial goal of publishing a Python package. In this next lesson you will learn how to create a basic installable Python package. +Make your code pip installable <1-installable-code> +::: From f64675d9874c89f28a6b317b6466e1d8790e2325 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 15:57:29 -0700 Subject: [PATCH 10/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index fcebffd41..c9ffba011 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -402,7 +402,7 @@ Once you have installed your package, you can view it in your current environment. If you are using `venv` or `conda`, `pip` list will allow you to see your current package installations. -Note that because pyospackage is installed in editable mode (`-e`) pip will show you the past to where you package installation's code +Note that because pyospackage is installed in editable mode (`-e`) pip will show you the directory path to your project's code is. ```bash From 21875772b09eff835edb1ae7d7a310076abb2559 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 15:57:57 -0700 Subject: [PATCH 11/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 1 - 1 file changed, 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index c9ffba011..19f4be0e4 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -403,7 +403,6 @@ environment. If you are using `venv` or `conda`, `pip` list will allow you to see your current package installations. Note that because pyospackage is installed in editable mode (`-e`) pip will show you the directory path to your project's code -is. ```bash $ pip list From 9a3713a1f472d7725c5dbdd37d231aa910a243ba Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 15:58:36 -0700 Subject: [PATCH 12/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index 19f4be0e4..a5dfd0810 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -290,7 +290,7 @@ Tables can contain variables within them defined by an variable name and an `=` sign. For instance, a `build-system` table most often holds 2 variables: -1. `requires = `, which tells a build tool what tools it needs to install prior to building your package. in this case is +1. `requires = `, which tells a build tool what tools it needs to install prior to building your package. In this case [hatchling](https://pypi.org/project/hatchling/) 2. `build-backend` is used to define specific build-backend name, (in this example we are using `hatchling.build`). From 9168e82ba3c452d73e14ebcc2b380bdb61f70a5d Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 16:00:17 -0700 Subject: [PATCH 13/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index a5dfd0810..ec237f7c1 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -292,7 +292,7 @@ instance, a `build-system` table most often holds 2 variables: 1. `requires = `, which tells a build tool what tools it needs to install prior to building your package. In this case [hatchling](https://pypi.org/project/hatchling/) -2. `build-backend` is used to define specific build-backend name, (in this example we are using `hatchling.build`). +2. `build-backend = `, which is used to define the specific build-backend name, (in this example we are using `hatchling.build`). TOML organizes data structures, defining relationships within a configuration file. You will learn more about the `pyproject.toml` format in the From 5e0cd00abf63be3c860d7edd695deb701ef42a63 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 16:03:26 -0700 Subject: [PATCH 14/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index ec237f7c1..41ba2a629 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -320,7 +320,7 @@ build-backend = "hatchling.build" name = "pyospackage_gh_user_name" # rename this if you plan to publish to test PyPI # Here you add the package version manually. # You will learn how to setup dynamic versioning in a followup tutorial. -version="1.1" +version = "1.1" ``` From 9f1c142e049be6bea9aafac83bb946aed4e5b588 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 16:04:01 -0700 Subject: [PATCH 15/19] Update tutorials/1-installable-code.md Co-authored-by: Jeremy Paige --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index 41ba2a629..a5117b204 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -331,7 +331,7 @@ version using git tags in the version and release your package lesson. Date: Thu, 28 Dec 2023 17:42:04 -0700 Subject: [PATCH 16/19] Fix: edits to cleanup flow --- tutorials/1-installable-code.md | 124 +++++++++++++++++--------------- 1 file changed, 67 insertions(+), 57 deletions(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index a5117b204..92bcdf490 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -15,6 +15,7 @@ will have the bare minimum elements needed to be installable into a Python envir 1. Is it clear where to add commands? bash vs. Python console Bash vs zsh is different 2. Does this lesson run as expected on windows and mac? +3. ADD: note about what makes something "package worthy", with a common misconception being that a package should be production-ready code that's valuable to a broad audience. this may not be a pervasive misconception in python, but a quick break-out with an explanation of what a package can consist of would be helpful. ::: :::{figure-md} code-to-python-package @@ -25,27 +26,34 @@ A basic installable package needs a few things: code, a [specific package file s ::: -:::{admonition} Learning Objectives +:::{admonition} About this lesson :class: tip In this lesson you will learn: - How to make your code installable into any Python environment both locally and from GitHub -- How to create a basic `pyproject.toml` file to declare dependencies and metadata +- How to create a basic `pyproject.toml` file that includes package dependencies and metadata. This file is required to make your package installable. - How to declare a [build backend](build_backends) which will be used to [build](build-package) and install your package - How to install your package in editable mode for interactive development -To complete this lesson you will need a local Python (development) -environment. You are welcome to use any environment manager that you choose. +**What you need to complete this lesson** + +To complete this lesson you will need a local Python +environment and shell on your computer. + +You are welcome to use any Python environment manager that you choose. +If you are using Windows or are not familiar with Shell, you may want to [consult the Carpentries shell lesson.](https://swcarpentry.github.io/shell-novice/). Windows users will likely need to configure a tool for any Shell and git related steps. * [If you need guidance creating a Python environment, review this lesson](extras/1-create-environment.md) which walks you through creating an environment using both `venv` and `conda`. * If you aren't sure which environment manager to use and -you are a scientist, we suggest that you use `conda`, particularly if you are working with any sort of spatial data. +you are a scientist, we suggest that you use `conda`, particularly if you are working with spatial data. -In the upcoming lessons you will learn how to +**What comes next** + +In the upcoming lessons you will learn how to: * Add a README file to your package to support community use -* Add project metadata to your package to support PyPI publication +* Add additional project metadata to your package to support PyPI publication * Publish your package to PyPI ::: @@ -73,7 +81,7 @@ To make your Python code installable you need to create a specific directory str - Some code. - An `__init__.py` file in your code directory. -The directory structure you’ll create below looks like this: +The directory structure you’ll create in this lesson will look like this: ```bash pyospackage/ # Your project directory @@ -89,7 +97,7 @@ pyospackage/ # Your project directory Notice a few things about the above layout: -1. Your package code lives within a `src/packagename` directory. We suggest that you use `src` directory as it ensures that you are running tests on the installed version of your code. However, you are welcome to instead use a [flat layout](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#about-the-flat-python-package-layout) which does not have a src/ directory at the root. [Learn more here.](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#the-src-layout-and-testing) +1. Your package code lives within a `src/packagename` directory. We suggest that you use `src` (short for **source code**) directory as it [ensures that you are running tests on the installed version of your code](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#the-src-layout-and-testing). However, you are welcome to instead use a [flat layout](https://www.pyopensci.org/python-package-guide/package-structure-code/python-package-structure.html#about-the-flat-python-package-layout) which does not have a `src` directory at the root. 1. Within the `src` directory you have a package directory called `pyospackage`. Use the name of your package for that directory name. 1. In your package directory, you have an `__init__.py` file and all of your Python modules. You will learn more about the __init__.py file below. 1. The `pyproject.toml` file lives at the root directory of your package. @@ -97,7 +105,10 @@ Notice a few things about the above layout: ### What is an __init__.py file? -When a directory contains an `__init__.py` file, it can be imported directly into Python. +The `__init__.py` file tells Python that a directory +should be treated as a Python package. As such, a directory with an `__init__.py` file can be imported +directly into Python. The __init__.py file does not need +to contain any code in order for Python to recognize it; it can be empty. For example, following the file structure example above which has an `__init__.py` file within it, you can run: @@ -105,21 +116,6 @@ For example, following the file structure example above which has an `__init__.p import pyospackage ``` -The `__init__.py` file tells Python that a directory should be treated -as a Python package. - - -:::{admonition} The **__init__**.py file -:class: tip - -The __init__.py file does not need to contain any code, it can be -empty. Since Python 3.3 came out, you can install a package without an -`__init__.py` file. However, we suggest that you include empty __init__.py files in your -package structure as it allows you to customize your package’s user -experience. -::: - - ### What is a pyproject.toml file? The **pyproject.toml** file is: @@ -140,7 +136,8 @@ installable including: :::{admonition} Why the pyproject.toml file is important :class: tip -The `pyproject.toml` file replaces some of the functionality of both the setup.py file and setup.cfg files. +The `pyproject.toml` file replaces some of the functionality of both the +`setup.py` file and `setup.cfg` files. If you try to pip install a package with no `pyproject.toml` you will get the following error: ```bash @@ -154,18 +151,21 @@ Neither 'setup.py' nor 'pyproject.toml' found. ## Time to create your Python package! -Now that you understand the basics of the Python package directory structure, it's time to create a Python package! Below you will create a directory structure similar to the structure described above. +Now that you understand the basics of the Python package directory +structure, and associated key files (`__init__.py` and `pyproject.toml`), +it's time to create your Python package! Below you will create a directory +structure similar to the structure described above. -If you don’t wish to create each of the files and directories below, you can always [fork and clone and customize the pyOpenSci example package.](https://github.com/pyOpenSci/pyosPackage) +If you don’t wish to create each of the files and directories below, you +can always [fork and clone and customize the pyOpenSci example package.](https://github.com/pyOpenSci/pyosPackage) ## Step 1: Set Up the Package Directory Structure Below you create the basic directory structure required -for your Python package. Note that there are instructions for creating the files and directories using shell. However you can also create files and directories in your preferred file directory tool (e.g. Finder on MAC or File Explorer on Windows) if you wish. +for your Python package. Note that there are instructions for creating the files and directories using shell. However you can also create files and directories in your preferred file directory tool (e.g. Finder on MAC or File Explorer on Windows or even a tool such as VSCode or Spyder) if you wish. -Create a new project directory for your package. Choose a -name for your package, preferably in lowercase and -without spaces (e.g., "pyospackage"). +### Create your package's project directory structure +* Create a new project directory for your package. Choose a name for your package, preferably in lowercase and without spaces. For this tutorial we'll use `pyospackage`. Inside the project directory: @@ -176,12 +176,14 @@ Inside the project directory: ```bash # Create a project directory in shell and a src directory within mkdir -R pyospackage/src/pyospackage + # Change directory into pyospackage project dir cd pyospackage + # View the current file structure ls ``` - +### Add your `__init__.py` and `pyproject.toml` files Next create two files: - Inside the package directory, create a new file named `__init__.py` . This file ensures Python sees this directory as a package. You will use this file to customize how parts of your package are imported and to declare your package’s version in a future lesson. @@ -212,7 +214,7 @@ If you don't have code already and are just learning how to create a Python package, then create an empty `add_numbers.py` file. -:::{admonition} Python modules and the __init__.py file +:::{admonition} Python modules and the `__init__.py` file :class: tip When you see the word module, we are referring to a `.py` file containing Python @@ -226,6 +228,7 @@ modules. ::: +Your project directory should now look like this: ``` pyospackage/ └─ pyproject.toml @@ -236,7 +239,7 @@ pyospackage/ ``` -## Step 3. Add code to your `add_numbers` module +## Step 3. Add code to your `add_numbers.py` module If you are following along and making a Python package from scratch then you can add the code below to your `add_numbers.py` module. The function below adds two integers together and returns the result. Notice that the code below has a few features that we will review in future tutorials: @@ -282,7 +285,7 @@ are welcome to copy the file we have in our [example pyospackage GitHub reposito :::{admonition} Brief overview of the TOML file :class: tip -The TOML format consists of tables and variables. Tables are sections of information denoted by square brackets: +[The TOML format](https://toml.io/en/) consists of tables and variables. Tables are sections of information denoted by square brackets: `[this-is-a-table]`. @@ -317,7 +320,7 @@ requires = ["hatchling"] build-backend = "hatchling.build" [project] -name = "pyospackage_gh_user_name" # rename this if you plan to publish to test PyPI +name = "pyospackage" # rename this if you plan to publish to test PyPI # Here you add the package version manually. # You will learn how to setup dynamic versioning in a followup tutorial. version = "1.1" @@ -349,7 +352,7 @@ You are now ready to install (and build) your Python package! Let’s try it out. -- First open bash and `cd` into your package directory +- First open your preferred shell (Windows users may be using something like gitbash) and `cd` into your project directory - Activate the Python environment that you wish to use. If you need help with working with virtual environments [check out this lesson](extras/1-create-environment.md). - Finally run `python -m pip install -e .` @@ -358,11 +361,13 @@ Let’s try it out. # Below we use conda but you can do the same thing with venv! > conda activate pyosdev (pyosdev) ->> conda info +> conda info active environment : pyosdev active env location : /Users/your-path/mambaforge/envs/pyosdev -# Install the package ->> python -m pip install -e . +# Cd into your project directory +> cd pyospackage +# Install your package +> python -m pip install -e . Obtaining file:///Users/leahawasser/Documents/GitHub/pyos/pyosPackage Installing build dependencies ... done @@ -382,7 +387,7 @@ Let's break down `pip install -e .` `pip install -e .` installs your package into the current active Python environment in **editable mode** (`-e`). Installing your package in editable mode, allows you to work on your code and then test the updates -interactively in your favorite Python interface. One important caveat of editable mode is that every time you update your code, you may need to restart your Python kernel. +interactively in your favorite Python interface. One important caveat of editable mode is that every time you update your code, you may need to restart Python. If you wish to install the package regularly (not in editable mode) you can use: @@ -432,7 +437,7 @@ pyosPackage 0.1.0 /Users/yourusername/path/here/pyosP ## 6. Test out your new package -After installing your package, type “python” at the command prompt to start +After installing your package, type “python” at the command prompt in your chosen terminal to start a Python session in your active Python environment. You can now import your package and access the `add_num` function. @@ -446,20 +451,6 @@ Type "help", "copyright", "credits" or "license" for more information. 3 ``` -## Congratulations! You created your first Python package - -You did it! You have now created a Python package that you can install into any Python environment. While there is still more to do if you want to publish your package, you have completed the first major step. - -In the upcoming lessons you will: - -* Add a [README file](2-add-readme.md) and [LICENSE ](4-add-license-file.md) to your package -* [Add more metadata to your `pyproject.toml`](5-pyproject-toml.md) file to support PyPI publication. -* [Learn how to build your your package distribution](6-publish-pypi.md) files (**sdist** and **wheel**) and publish to **test PyPI**. -* Finally you will learn how to publish to **conda-forge** from **PyPI**. - -If you have a package that is ready for the mainstream user then -you can also publish your package on PyPI. - :::{admonition} Installing packages from GitHub @@ -469,4 +460,23 @@ always install packages directly from GitHub using the syntax: ```bash pip install git+https://github.com/user/repo.git@branch_or_tag ``` + +To make your package github installable, you can simply: + +1. Create a new GitHub repo +2. Push the contents of the project directory that you created above, to GitHub +3. Finally install the package from GitHub using the command above. ::: + +## Congratulations! You created your first Python package + +You did it! You have now created a Python package that you can install +into any Python environment. While there is still more to do if you want +to publish your package, you have completed the first major step. + +In the upcoming lessons you will: + +* Add a [README file](2-add-readme.md) and [LICENSE](4-add-license-file.md) to your package +* [Add more metadata to your `pyproject.toml`](5-pyproject-toml.md) file to support PyPI publication. +* [Learn how to build your package distribution](6-publish-pypi.md) files (**sdist** and **wheel**) and publish to **test PyPI**. +* Finally you will learn how to publish to **conda-forge** from **PyPI**. From 831a28584fc2648a3ea3d7b278c15c45f2734817 Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Thu, 28 Dec 2023 17:53:55 -0700 Subject: [PATCH 17/19] Fix: spellcheck and final cleanup --- tutorials/1-installable-code.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index 92bcdf490..f39793963 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -13,9 +13,9 @@ will have the bare minimum elements needed to be installable into a Python envir :::{todo} 1. Is it clear where to add commands? bash vs. Python console -Bash vs zsh is different +Bash vs. Zsh is different 2. Does this lesson run as expected on windows and mac? -3. ADD: note about what makes something "package worthy", with a common misconception being that a package should be production-ready code that's valuable to a broad audience. this may not be a pervasive misconception in python, but a quick break-out with an explanation of what a package can consist of would be helpful. +3. ADD: note about what makes something "package worthy", with a common misconception being that a package should be production-ready code that's valuable to a broad audience. this may not be a pervasive misconception in Python, but a quick break-out with an explanation of what a package can consist of would be helpful. ::: :::{figure-md} code-to-python-package @@ -60,7 +60,7 @@ In the upcoming lessons you will learn how to: :::{figure-md} packages-environment -This diagram has two smaller boxes with arrows pointing to the right to a python environment. The small boxes read your-package and pip install package. The environment box on the right reads - your python environment. It them lists your-package along with a few other core packages such as matplotlib, numpy, pandas, xarray and geopandas. +This diagram has two smaller boxes with arrows pointing to the right to a Python environment. The small boxes read your-package and pip install package. The environment box on the right reads - your Python environment. It them lists your-package along with a few other core packages such as Matplotlib, NumPy, Pandas, Xarray and GeoPandas. Making your code installable is the first step towards creating a publishable Python package. Once your code is @@ -162,7 +162,7 @@ can always [fork and clone and customize the pyOpenSci example package.](https:/ ## Step 1: Set Up the Package Directory Structure Below you create the basic directory structure required -for your Python package. Note that there are instructions for creating the files and directories using shell. However you can also create files and directories in your preferred file directory tool (e.g. Finder on MAC or File Explorer on Windows or even a tool such as VSCode or Spyder) if you wish. +for your Python package. Note that there are instructions for creating the files and directories using shell. However you can also create files and directories in your preferred file directory tool (e.g. Finder on MAC or File Explorer on Windows or even a tool such as VS Code or Spyder) if you wish. ### Create your package's project directory structure * Create a new project directory for your package. Choose a name for your package, preferably in lowercase and without spaces. For this tutorial we'll use `pyospackage`. From 1cb0c6354e0cfb25e425ca949bffe01c498ef51f Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Wed, 3 Jan 2024 13:44:20 -0700 Subject: [PATCH 18/19] Fix: correct bash instructions --- tutorials/1-installable-code.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutorials/1-installable-code.md b/tutorials/1-installable-code.md index f39793963..c4c59b85f 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/1-installable-code.md @@ -175,7 +175,7 @@ Inside the project directory: ```bash # Create a project directory in shell and a src directory within -mkdir -R pyospackage/src/pyospackage +mkdir -p pyospackage/src/pyospackage # Change directory into pyospackage project dir cd pyospackage From af6c0ace7783965281e338671384a4d1182ba2aa Mon Sep 17 00:00:00 2001 From: Leah Wasser Date: Wed, 3 Jan 2024 14:01:17 -0700 Subject: [PATCH 19/19] Fix: TOC --- index.md | 11 +++-------- tutorials/intro.md | 1 + 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/index.md b/index.md index 5abdaafe6..bb97c24dc 100644 --- a/index.md +++ b/index.md @@ -71,7 +71,8 @@ by the community now! Join our community review process or watch development of :::{card} ✿ Tutorials ✿ :class-card: left-aligned -[What is a Python package?](/tutorials/intro) +* [What is a Python package?](/tutorials/intro) +* [Make your code installable](/tutorials/1-installable-code) ::: :::: @@ -188,13 +189,6 @@ If you have questions about our peer review process or packaging in general, you This is a living guide that is updated as tools and best practices evolve in the Python packaging ecosystem. We will be adding new content over the next year. -```{toctree} -:hidden: -:caption: Testing - -Tutorials - -``` ```{toctree} :hidden: @@ -202,6 +196,7 @@ Tutorials Tutorials ``` + ```{toctree} :hidden: :caption: Documentation diff --git a/tutorials/intro.md b/tutorials/intro.md index f6fdcbc9b..31a1154a3 100644 --- a/tutorials/intro.md +++ b/tutorials/intro.md @@ -29,6 +29,7 @@ understanding the steps involved in creating a Python package. :caption: Python Packaging 101 What is a Python package? +Make your code installable. <1-installable-code> ::: :::{admonition} Learning Objectives