{ "cells": [ { "cell_type": "markdown", "id": "8202c274", "metadata": {}, "source": [ "# Create Your Package" ] }, { "cell_type": "markdown", "id": "3fbb2a43", "metadata": {}, "source": [ "Creating your package locally should take less than 5 minutes. Full deployment on the different websites (GitHub, Codecov, Pypi) will take a bit more time, but likely less than 20 minutes (maybe more the first time)." ] }, { "cell_type": "markdown", "id": "733e7210", "metadata": {}, "source": [ "## Before Starting: Decide What You Want" ] }, { "cell_type": "markdown", "id": "6ffd51b9", "metadata": {}, "source": [ "PH3 will ask you some information. It is best to think ahead of the answers:\n", "\n", "- **Name** / **email** / **github username**. You should know the answer of these questions. If you do not want to re-renter them everytime you make a new project you can write a [user config file](https://balouf.github.io/package-helper-3/faq.html#Do-I-need-to-enter-my-name/email/github_login-everytime-I-make-a-package?).\n", "- **Project Name**. This is the name of your project in *human language*. It will be displayed in the documentation. You can (should?) use **title case and spaces** if you have multiple words. For example, *Package Helper 3*.\n", "- **project-slug**. This is where you *store* your package. It will typically be the name of the directory where you develop the package, and the name of your GitHub/PyPi repositories. The convention is to use **lower case and dashes**. Unless you have specific reasons, you should adopt a project slug consistent with your project name. For example, `package-helper-3`. Also, you should **check that your project slug is not used in [PyPI](https://pypi.org/)**, even if you do not plan to use PyPI for the moment (e.g. to avoid any possible package collision).\n", "- **package_name**. This is the `python` name of your package. It will typically be the name of the subdirectory of your project that contains the actual code. The convention is to use **lower case and underscores**. Unless you have specific reasons, you should adopt a package name consistent with your project name/slug. For example, `import package_helper_3 as ph`.\n", "- **Project description**. One or two sentences that describes the purpose of your project. For example, \"*Package Helper 3 explains how to create, develop, and maintain a Python package with Poetry.*\"\n", "- **Version**. PH3 adopts the [semantic versioning](https://semver.org/) convention. As they suggest, we recommend to start your initial development release at 0.1.0.\n", "- **Documentation theme**. Decide the skin you want for your documentation: [PyData][PD] or [ReadTheDocs][RTD].\n", "- **Command line interface (CLI)**. If your package is intended to be always executed from Python, you do not need to care about the CLI. If you intend to execute commands directly from a terminal (Anaconda Prompt, Bash, Windows Powershell, etc), some Python packages are here to help you. Two of the most popular ones are `click` (recommended) or `argparse`.\n", "- **Dockerfile**. Do you plan to deploy your package on docker?\n", "- **License**. Which open source license (if any) do you want for your package? Check https://choosealicense.com/ if you need advice.\n", "\n", "[RTD]: https://sphinx-rtd-theme.readthedocs.io/en/stable/\n", "[PD]: https://pydata-sphinx-theme.readthedocs.io/en/stable/" ] }, { "cell_type": "markdown", "id": "0752cd22", "metadata": {}, "source": [ "## Generate Your Package with Package Helper 3" ] }, { "cell_type": "markdown", "id": "b5213e6b", "metadata": {}, "source": [ "In a terminal (Anaconda Prompt, Bash, Windows Powershell, etc):\n", "\n", " 1. Go to the parent directory of where you want to put the directory of your package, e.g. `D:\\GitHub\\`, `/home/login/git/`.\n", "\n", " 2. Call Package Helper 3:\n", "\n", "```console\n", "$ ph3\n", "```\n", "\n", " 3. Answer the questions (see above for the meaning). If you leave a question blank, the default proposal (in parenthesis) will be used. Here is an example:\n", "\n", "```console\n", "PS C:\\Users\\fabienma\\git\\github> ph3\n", " [1/12] full_name (Fabien Mathieu):\n", " [2/12] email (loufab@gmail.com):\n", " [3/12] github_username (balouf):\n", " [4/12] project_name (First Sandbox): My First PH3 Package\n", " [5/12] project_slug (my-first-ph3-package):\n", " [6/12] package_name (my_first_ph3_package):\n", " [7/12] project_description (Sandboxes are cool!): PH3 packages are quick to make!\n", " [8/12] version (0.1.0):\n", " [9/12] Select documentation_theme\n", " 1 - PyData\n", " 2 - ReadTheDocs\n", " Choose from [1/2] (1):\n", " [10/12] Select command_line_interface\n", " 1 - No command-line interface\n", " 2 - Click\n", " 3 - Argparse\n", " Choose from [1/2/3] (1): 2\n", " [11/12] dockerfile [y/n] (n):\n", " [12/12] Select open_source_license\n", " 1 - MIT license\n", " 2 - GNU General Public License v3\n", " 3 - BSD license\n", " 4 - ISC license\n", " 5 - Apache Software License 2.0\n", " 6 - Not open source\n", " Choose from [1/2/3/4/5/6] (1):\n", "```\n", "\n", " 4. Optionally, in your file manager, open the directory of your package: the whole file structure is now in there!" ] }, { "cell_type": "markdown", "id": "2b59e078", "metadata": {}, "source": [ "## Install Your Project" ] }, { "cell_type": "markdown", "id": "a4bd7530", "metadata": {}, "source": [ "Developing a project and using a package are two different things, so you should install your package twice, once in a dedicated environment, one in your main space!" ] }, { "cell_type": "markdown", "id": "6142237b", "metadata": {}, "source": [ "### Install a dedicated environment" ] }, { "cell_type": "markdown", "id": "fb8d5379", "metadata": {}, "source": [ "In a terminal prompt, go to the project directory (named as `project-slug`) and enter:\n", "\n", "```console\n", "$ poetry install\n", "```\n", "\n", "Wait for the installation to complete and you're done!" ] }, { "cell_type": "markdown", "id": "47352bab", "metadata": {}, "source": [ "You can test that your environment is well configured by launching a shell:\n", "\n", "```console\n", "$ poetry shell\n", "```\n", "\n", "Your prompt should indicate the virtual environnment in parenthesis, for example:\n", "\n", "```console\n", "(my-first-ph3-package-py3.11) C:\\...\\my-first-ph3-package>\n", "```\n", "\n", "If you choose a CLI, you can try and run the default script:\n", "\n", "```console\n", "(my-first-ph3-package-py3.11) PS C:\\...\\my-first-ph3-package> my_first_ph3_package --help\n", "Usage: my_first_ph3_package [OPTIONS]\n", "\n", " Console script for my-first-ph3-package.\n", "\n", "Options:\n", " --help Show this message and exit.\n", "```\n", "\n", "You can leave the virtual environment with the command\n", "\n", "```console\n", "$ exit\n", "```" ] }, { "cell_type": "markdown", "id": "5893cf1b", "metadata": {}, "source": [ "Note that poetry will install your package in *editable* mode. That means that everytime you load the package inside the dedicated environment, the current version will be used, not the one from the installation time." ] }, { "cell_type": "markdown", "id": "c3de157f", "metadata": {}, "source": [ "#### [Windows users]: `unable to load` error" ] }, { "cell_type": "markdown", "id": "1a7076fe", "metadata": {}, "source": [ "Windows users may see an error when they try the `poetry shell` command. This is usually due to the fact that you use a `Powershell` terminal, which by default has limited execution policies. Two options here (adapted from https://support.enthought.com/hc/en-us/articles/360058403072-Windows-error-activate-ps1-cannot-be-loaded-because-running-scripts-is-disabled-UnauthorizedAccess-):\n", "\n", "- Don't use Powershell. The old-fashioned Command Prompt does not rise the error.\n", "- Elevate your Powershell rights by running the following command in a Powershell terminal:\n", "\n", "```console\n", "Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted\n", "```" ] }, { "cell_type": "markdown", "id": "95c4c318", "metadata": {}, "source": [ "### Install on your default Python distribution" ] }, { "cell_type": "markdown", "id": "d645dbbf", "metadata": {}, "source": [ "Right now, your Python package is only available in its dedicated environment. To use it more broadly (e.g. if you want to run Jupyter Notebooks, combine it with other packages, etc...):" ] }, { "cell_type": "markdown", "id": "352b8d65", "metadata": {}, "source": [ "- Use a terminal prompt **outside** your dedicated environment (`exit` if you are within the dedicated environment);\n", "- Go to the project directory and install your package:\n", "\n", "```console\n", "$ pip install -e .\n", "```" ] }, { "cell_type": "markdown", "id": "79447eae", "metadata": {}, "source": [ "The `-e` option means that you will install the package in *editable* mode, like for the dedicated environment.\n", "\n", "The `.` (dot) tells that the package to install is in the working directory." ] }, { "cell_type": "markdown", "id": "b65de266", "metadata": {}, "source": [ "Your package is now available outside your dedicated environment! For example, if you chose a CLI:\n", "\n", "```console\n", "PS C:\\>my_first_ph3_package\n", "Replace this message by putting your code into my_first_ph3_package.cli.main\n", "See click documentation at https://click.palletsprojects.com/\n", "```" ] }, { "cell_type": "markdown", "id": "d3d9c9fa", "metadata": {}, "source": [ "You can also try to open the Notebook `your-project-slug/docs/tutorials/tutorial.ipynb` and check that the calls to the package are working fine." ] }, { "cell_type": "markdown", "id": "d80f253d", "metadata": {}, "source": [ "## Try Your Project with PyCharm" ] }, { "cell_type": "markdown", "id": "578725ef", "metadata": {}, "source": [ "To open your project:\n", "\n", "- Inside PyCharm, you can choose \"Open\" and select the directory of your project (`project-slug`).\n", "- Alternatively, if you installed PyCharm with context menu, you can right-click the directory in your favorite file manager and select \"Open folder as PyCharm project\"\n", "\n", "\n", "In the bottom right part of the window, you will probably see some background tasks running. Wait until they are finished. In particular, wait until PyCharm discovered your dedicated environment, e.g. when `` is replaced by something like `Python 3.11 (my-first-ph3-package)` (it is usually displayed in the lower right corner).\n", "\n", "**Note:** Some users reported that PyCharm sometimes fails to locate the dedicated environment. If that happens, try to open (in PyCharm) the main Python file of the project, PyCharm should then propose to use the environment in a banner." ] }, { "cell_type": "markdown", "id": "e690fa35", "metadata": {}, "source": [ "### Run tests and documentation locally" ] }, { "cell_type": "markdown", "id": "bfc417a5", "metadata": {}, "source": [ "- In PyCharm: menu Run → Run → All Tests. It runs all the tests of the project. PH3 provides in its template examples that you can re-use if you are new to testing (e.g. in `my_class_1.py`).\n", "\n", "\n", "- Open the file `cov/index.html` (in your web browser). It displays what parts of your code are covered by the tests.\n", "\n", "- In PyCharm: menu Run → Run → Generate docs. It generates the html documentation of your project. The template provides examples of classes, such as the file my_class_1.py. You can use them as models if you are new to Sphinx documentation.\n", "\n", "- Open the file build/index.html (in your web browser). It displays the html documentation of your project." ] }, { "cell_type": "markdown", "id": "f964fa32", "metadata": {}, "source": [ "## Go GitHub" ] }, { "cell_type": "markdown", "id": "6f23887f", "metadata": {}, "source": [ "First thing to do is create the GitHub repo of your project. In PyCharm:\n", "\n", "- From the file `pyproject.toml` of your package, copy the `description` value, e.g. \"PH3 packages are quick to make!\" in the example above. You will paste it in the form below.\n", "- Menu Version Control → Share project on GitHub.\n", "- Fill in the form and validate, e.g.:\n", "```console\n", "New repository name: my_toy_package\n", "Remote name: origin\n", "Description: PH3 packages are quick to make!\n", "```\n", "\n", "- Accept to add all the files as proposed by PyCharm.\n", "\n", "In a browser, you can go to your GitHub account to check that everything is there.\n", "\n", "**Check repo visibility**: GitHub repos can be private or public. Some features like publishing your documentation on GitHub pages are only available on public repos unless you have a paid account. If you set up your repo to private, you can switch visibility to public on the settings menu (the *Danger Zone* down the screen)." ] }, { "cell_type": "markdown", "id": "c8d407ff", "metadata": {}, "source": [ "### Check GitHub actions" ] }, { "cell_type": "markdown", "id": "216314cf", "metadata": {}, "source": [ "In GitHub:\n", "\n", "- GitHub page of your package → Actions.\n", "- Check that the actions are successes (it may take several minutes). \n", " - The \"build\" action runs the tests of the package. Default PH3 triggers are:\n", " - When something is pushed on the default branch (\"main\" or \"master\").\n", " - When there is a pull request.\n", " - At 5:30 am the 1st and 15th of each month.\n", " - The \"docs\" actions builds the documentation when something is pushed. It publishes it online if the branch is your default git branch (\"main\" or \"master\").\n", " - The \"release\" action should not have been triggered at this point. It will run when you release a version to publish it on PyPi." ] }, { "cell_type": "markdown", "id": "b8df9569", "metadata": {}, "source": [ "### Declare your documentation" ] }, { "cell_type": "markdown", "id": "fa4a5c68", "metadata": {}, "source": [ "Tell GitHub Pages that the documentation files are in the \"gh-pages\" branch of your project:\n", "\n", "- Settings → Pages → Build and deployment\n", "- Check that Source is \"deploy from a branch\".\n", "- Branch → \"gh-pages\", \"/ (root)\".\n", "- Save." ] }, { "cell_type": "markdown", "id": "37ef7d9c", "metadata": {}, "source": [ "### Share a few secrets" ] }, { "cell_type": "markdown", "id": "52653587", "metadata": {}, "source": [ "Configure Codecov so you can publicly expose the test coverage of your package:\n", "\n", "- On [Codecov's website](https://app.codecov.io/gh/), log in with your GitHub account. On your main page, locate your project and click the corresponding `Setup repo >` link.\n", "- You may have to wait for syncing before it appears. If it takes too long try the `Can't find your repo? Try re-sync` link (but you may have to wait anyway).\n", "- In your project, copy the Codecov token.\n", "- Back to GitHub: Settings → Secrets and variables → Actions → New repository secret. Name: `CODECOV_TOKEN`. Value: paste the codecov token. " ] }, { "cell_type": "markdown", "id": "50d8f168", "metadata": {}, "source": [ "Register your PyPI token, so that GitHub can automatically publish your package on PyPI for you:\n", "\n", "- Copy your PyPi token from the safe place where you stored it.\n", "- In GitHub: Settings → Secrets and variables → Actions → New repository secret. Name: `PYPI_TOKEN`. Value: paste your PyPi token." ] }, { "cell_type": "markdown", "id": "b6408559", "metadata": {}, "source": [ "### Test on a first commit" ] }, { "cell_type": "markdown", "id": "434ec244", "metadata": {}, "source": [ "In PyCharm, make a slight modification, e.g. in the file `README.rst`. Then commit/push:\n", "\n", "- From the VC menu, choose Commit (or `Ctrl+k`).\n", "- Enter a commit message, click on `Commit`.\n", "- From the VC menu, choose Push (or `Ctrl+Shift+K`).\n", "- Click on Push.\n", "\n", "Then check that everything is working online:\n", "\n", "- GitHub page of your package → Actions. Wait until your actions are finished.\n", "- Check the documentation:\n", " - GitHub page of your package (main page) → near the bottom of the page, follow the link to your documentation. Check that the documentation is there.\n", " - In the table of contents, click on the first page (e.g. My Toy Package). Depending on your initial choice of options, you should have up to five badges:\n", " - PyPI: package or version not found (there will be the version number after your first release).\n", " - Build: passing.\n", " - Docs: passing.\n", " - License: the license you choose.\n", " - Codecov: with a percentage.\n", " - In the table of contents: Reference → MyClass1. You should see the documentation of the first example of class provided by the template.\n", "\n", "- Check your test coverage:\n", " - GitHub page of your package (main page) → near the bottom of the page, click on the Codecov badge.\n", " - You can navigate in your project to see what parts of the code are covered by the tests.\n", "\n", "If you wish, you are now ready to release your first version! (cf [Make a Release](https://balouf.github.io/package-helper-3/maintain.html))." ] }, { "cell_type": "markdown", "id": "542959cc", "metadata": {}, "source": [ "## Time to Code!\n", "\n", "- Replace the toy code provided by PH3 with your actual code.\n", "- [Add/remove dependencies](https://balouf.github.io/package-helper-3/faq.html#How-to-manage-dependencies-(third-party-packages)?) according to your needs.\n", "- Right click on PyCharm's project tree to create new *modules* (i.e. Python files) and *(sub)packages* (directories with a `__init__.py` file).\n", "- If you manually copy files in your project, don't forget to add them to Git.\n", "- Keep in mind that your project is not just your source code\n", " - Update your documentation:\n", " - Check that your `reference` section is consistent with your actual package.\n", " - Update your documentation notebooks if you have some.\n", " - Check the sphinx warnings when you build the documentation.\n", " - Update your tests:\n", " - Update your docstrings.\n", " - Update your `tests` directory.\n", " - Check your coverage." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 5 }