From 748c49e69c3ca7d59bee3321131fa495c412f417 Mon Sep 17 00:00:00 2001 From: Paul McCarthy <pauldmccarthy@gmail.com> Date: Sun, 18 Feb 2018 12:18:48 +0000 Subject: [PATCH] Working on structuring talk --- talks/structuring/example_project1/LICENSE | 15 ++ talks/structuring/example_project1/README.rst | 6 + .../example_project1/myproject/__init__.py | 10 + .../example_project1/myproject/mymodule.py | 5 + .../example_project1/requirements.txt | 1 + talks/structuring/example_project1/setup.py | 0 talks/structuring/structuring.ipynb | 244 ++++++++++++++++++ talks/structuring/structuring.md | 227 ++++++++++++++++ 8 files changed, 508 insertions(+) create mode 100644 talks/structuring/example_project1/LICENSE create mode 100644 talks/structuring/example_project1/README.rst create mode 100644 talks/structuring/example_project1/myproject/__init__.py create mode 100644 talks/structuring/example_project1/myproject/mymodule.py create mode 100644 talks/structuring/example_project1/requirements.txt create mode 100644 talks/structuring/example_project1/setup.py create mode 100644 talks/structuring/structuring.ipynb create mode 100644 talks/structuring/structuring.md diff --git a/talks/structuring/example_project1/LICENSE b/talks/structuring/example_project1/LICENSE new file mode 100644 index 0000000..1e38db5 --- /dev/null +++ b/talks/structuring/example_project1/LICENSE @@ -0,0 +1,15 @@ +The example_project library + +Copyright 2016-2017 University of Oxford, Oxford, UK. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/talks/structuring/example_project1/README.rst b/talks/structuring/example_project1/README.rst new file mode 100644 index 0000000..f4aa13f --- /dev/null +++ b/talks/structuring/example_project1/README.rst @@ -0,0 +1,6 @@ +Example project +=============== + + +This is an example project, used to demonstrate the basics of how to structure +a Python project. diff --git a/talks/structuring/example_project1/myproject/__init__.py b/talks/structuring/example_project1/myproject/__init__.py new file mode 100644 index 0000000..b659477 --- /dev/null +++ b/talks/structuring/example_project1/myproject/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + + +__version__ = '0.1.0' + + +# The top-level __init__.py can +# be used to set up the overall +# namespace for your library. +from .mymodule import myfunction diff --git a/talks/structuring/example_project1/myproject/mymodule.py b/talks/structuring/example_project1/myproject/mymodule.py new file mode 100644 index 0000000..51f9c9c --- /dev/null +++ b/talks/structuring/example_project1/myproject/mymodule.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + + +def myfunction(a, b): + return a * b diff --git a/talks/structuring/example_project1/requirements.txt b/talks/structuring/example_project1/requirements.txt new file mode 100644 index 0000000..bd704f9 --- /dev/null +++ b/talks/structuring/example_project1/requirements.txt @@ -0,0 +1 @@ +numpy==1.* diff --git a/talks/structuring/example_project1/setup.py b/talks/structuring/example_project1/setup.py new file mode 100644 index 0000000..e69de29 diff --git a/talks/structuring/structuring.ipynb b/talks/structuring/structuring.ipynb new file mode 100644 index 0000000..5056719 --- /dev/null +++ b/talks/structuring/structuring.ipynb @@ -0,0 +1,244 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Structuring a Python project\n", + "\n", + "\n", + "If you are writing code that you are sure will never be seen or used by\n", + "anybody else, then you can structure your project however you want, and you\n", + "can stop reading now.\n", + "\n", + "\n", + "However, if you are intending to make your code available for others to use,\n", + "you will make their lives easier if you spend a little time organising your\n", + "project directory.\n", + "\n", + "\n", + "A Python project directory should, at the very least, have a structure that\n", + "resembles the following:\n", + "\n", + "\n", + "> ```\n", + "> myproject/\n", + "> mypackage/\n", + "> __init__.py\n", + "> mymodule.py\n", + "> README\n", + "> LICENSE\n", + "> requirements.txt\n", + "> setup.py\n", + "> ```\n", + "\n", + "\n", + "## The `mypackage/` directory\n", + "\n", + "\n", + "The first thing you should do is make sure that all of your python code is\n", + "organised into a sensibly-named\n", + "[_package_](https://docs.python.org/3.5/tutorial/modules.html#packages). This\n", + "is important, because it greatly reduces the possibility of naming collisions\n", + "when people install your library alongside other libraries. Hands up those of\n", + "you who have ever written a file called `utils.[py|m|c|cpp]`!\n", + "\n", + "\n", + "Check out the `advanced_topics/02_modules_and_packages.ipynb` practical for\n", + "more details on packages in Python.\n", + "\n", + "\n", + "## `README`\n", + "\n", + "\n", + "Every project should have a README file. This is simply a plain text file\n", + "which describes your project and how to use it. It is common and acceptable\n", + "for a README file to be written in plain text,\n", + "[reStructuredText](http://www.sphinx-doc.org/en/stable/rest.html)\n", + "(`README.rst`), or\n", + "[markdown](https://guides.github.com/features/mastering-markdown/)\n", + "(`README.md`).\n", + "\n", + "\n", + "## `LICENSE`\n", + "\n", + "\n", + "Having a LICENSE file makes it easy for people to understand the constraints\n", + "under which your code can be used.\n", + "\n", + "\n", + "## `requirements.txt`\n", + "\n", + "\n", + "This file is not strictly necessary, but is very common in Python projects.\n", + "It contains a list of the Python-based dependencies of your project, in a\n", + "standardised syntax.\n", + "\n", + "\n", + "## Tests\n", + "\n", + "\n", + "\n", + "There are no strict rules for where to put your tests (you have tests,\n", + "right?). There are two main conventions:\n", + "\n", + "\n", + "You can store your test files _inside_ your package directory:\n", + "\n", + "\n", + "> ```\n", + "> myproject/\n", + "> mypackage/\n", + "> __init__.py\n", + "> mymodule.py\n", + "> tests/\n", + "> __init__.py\n", + "> test_mymodule.py\n", + "> ```\n", + "\n", + "\n", + "\n", + "Or, you can store your test files _alongside_ your package directory:\n", + "\n", + "\n", + "> ```\n", + "> myproject/\n", + "> mypackage/\n", + "> __init__.py\n", + "> mymodule.py\n", + "> tests/\n", + "> test_mymodule.py\n", + "> ```\n", + "\n", + "\n", + "If you want your test code to be completely independent of your project's\n", + "code, then go with the second option. However, if you would like your test\n", + "code to be distributed as part of your project (so that e.g. end users can run\n", + "them), then the first option is probably the best.\n", + "\n", + "\n", + "But in the end, the standard Python unit testing frameworks\n", + "([`pytest`](https://docs.pytest.org/en/latest/) and\n", + "[`nose`](http://nose2.readthedocs.io/en/latest/)) are pretty good at finding\n", + "your test functions no matter where you've hidden them.\n", + "\n", + "\n", + "## Versioning\n", + "\n", + "\n", + "If you are intending to make your project available for public use (e.g. on\n", + "[PyPI](https://pypi.python.org/pypi) and/or\n", + "[conda](https://anaconda.org/anaconda/repo)), it is __very important__ to\n", + "manage the version number of your project. If somebody decides to build their\n", + "software on top of your project, they are not going to be very happy with you\n", + "if you make substantial, API-breaking changes without changing your version\n", + "number in an appropriate manner.\n", + "\n", + "\n", + "Python has [official standards](https://www.python.org/dev/peps/pep-0440/) on\n", + "what constitutes a valid version number. These standards can be quite\n", + "complicated but, in the vast majority of cases, a simple three-number\n", + "versioning scheme comprising _major_, _minor_, and _patch_ release\n", + "numbers should suffice. Such a version number has the form:\n", + "\n", + "\n", + "> major.minor.patch\n", + "\n", + "\n", + "For example, a version number of `1.3.2` has a _major_ release of 1, _minor_\n", + "release of 3, and a _patch_ release of 2.\n", + "\n", + "\n", + "If you follow some simple and rational guidelines for versioning\n", + "`your_project`, then people who use your project can, for instance, specify\n", + "that they depend on `your_project==1.*`, and be sure that their code will work\n", + "for _any_ version of `your_project` with a major release of 1. Following these\n", + "simple guidelines greatly improves software interoperability, and makes\n", + "everybody (i.e. developers of other projects, and end users) much happier!\n", + "\n", + "\n", + "Many modern Python projects use some form of [_semantic\n", + "versioning_](https://semver.org/). Semantic versioning is simply a set of\n", + "guidelines on how to manage your version number:\n", + "\n", + "\n", + " - The _major_ release number should be incremented whenever you introduce any\n", + " backwards-incompatible changes. In other words, if you change your code\n", + " such that some other code which uses your code would break, you should\n", + " increment the major release number.\n", + "\n", + " - The _minor_ release number should be incremented whenever you add any new\n", + " (backwards-compatible) features to your project.\n", + "\n", + " - The _patch_ release number should be incremented for backwards-compatible\n", + " bug-fixes and other minor changes.\n", + "\n", + "\n", + "If you like to automate things,\n", + "[`bumpversion`](https://github.com/peritus/bumpversion) is a simple tool that\n", + "you can use to help manage your version number.\n", + "\n", + "\n", + "### Deprecate, don't remove!\n", + "\n", + "\n", + "If you really want to change your API, but can't bring yourself to increment\n", + "your major release number, consider _deprecating_ the old API, and postponing\n", + "its removal until you are ready for a major release. This will allow you to\n", + "change your API, but retain backwards-compatilbiity with the old API until it\n", + "can safely be removed at the next major release.\n", + "\n", + "\n", + "You can use the built-in\n", + "[`warnings`](https://docs.python.org/3.5/library/exceptions.html#DeprecationWarning)\n", + "module to warn about uses of deprecated items. There are also some\n", + "[third-party libraries](https://github.com/briancurtin/deprecation) which make\n", + "it easy to mark a function, method or class as being deprecated.\n", + "\n", + "\n", + "## Cookiecutter\n", + "\n", + "\n", + "It is worth mentioning\n", + "[Cookiecutter](https://github.com/audreyr/cookiecutter), a little utility\n", + "program which you can use to generate a skeleton file/directory structure for\n", + "a new Python project.\n", + "\n", + "\n", + "You need to give it a template (there are many available templates, including\n", + "for projects in languages other than Python) - a couple of useful templates\n", + "are the [minimal Python package\n", + "template](https://github.com/kragniz/cookiecutter-pypackage-minimal), and the\n", + "[full Python package\n", + "template](https://github.com/audreyr/cookiecutter-pypackage) (although the\n", + "latter is probably overkill for most).\n", + "\n", + "\n", + "Here is how to create a skeleton project directory based off the minimal\n", + "Python packagetemplate:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pip install cookiecutter\n", + "\n", + "# tell cookiecutter to create a directory\n", + "# from the pypackage-minimal template\n", + "cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git\n", + "\n", + "# cookiecutter will then prompt you for\n", + "# basic information (e.g. projectname,\n", + "# author name/email), and then create a\n", + "# new directory containing the project\n", + "# skeleton." + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/talks/structuring/structuring.md b/talks/structuring/structuring.md new file mode 100644 index 0000000..0eaee61 --- /dev/null +++ b/talks/structuring/structuring.md @@ -0,0 +1,227 @@ +# Structuring a Python project + + +If you are writing code that you are sure will never be seen or used by +anybody else, then you can structure your project however you want, and you +can stop reading now. + + +However, if you are intending to make your code available for others to use, +you will make their lives easier if you spend a little time organising your +project directory. + + +A Python project directory should, at the very least, have a structure that +resembles the following: + + +> ``` +> myproject/ +> mypackage/ +> __init__.py +> mymodule.py +> README +> LICENSE +> requirements.txt +> setup.py +> ``` + + +## The `mypackage/` directory + + +The first thing you should do is make sure that all of your python code is +organised into a sensibly-named +[_package_](https://docs.python.org/3.5/tutorial/modules.html#packages). This +is important, because it greatly reduces the possibility of naming collisions +when people install your library alongside other libraries. Hands up those of +you who have ever written a file called `utils.[py|m|c|cpp]`! + + +Check out the `advanced_topics/02_modules_and_packages.ipynb` practical for +more details on packages in Python. + + +## `README` + + +Every project should have a README file. This is simply a plain text file +which describes your project and how to use it. It is common and acceptable +for a README file to be written in plain text, +[reStructuredText](http://www.sphinx-doc.org/en/stable/rest.html) +(`README.rst`), or +[markdown](https://guides.github.com/features/mastering-markdown/) +(`README.md`). + + +## `LICENSE` + + +Having a LICENSE file makes it easy for people to understand the constraints +under which your code can be used. + + +## `requirements.txt` + + +This file is not strictly necessary, but is very common in Python projects. +It contains a list of the Python-based dependencies of your project, in a +standardised syntax. + + +## Tests + + + +There are no strict rules for where to put your tests (you have tests, +right?). There are two main conventions: + + +You can store your test files _inside_ your package directory: + + +> ``` +> myproject/ +> mypackage/ +> __init__.py +> mymodule.py +> tests/ +> __init__.py +> test_mymodule.py +> ``` + + + +Or, you can store your test files _alongside_ your package directory: + + +> ``` +> myproject/ +> mypackage/ +> __init__.py +> mymodule.py +> tests/ +> test_mymodule.py +> ``` + + +If you want your test code to be completely independent of your project's +code, then go with the second option. However, if you would like your test +code to be distributed as part of your project (so that e.g. end users can run +them), then the first option is probably the best. + + +But in the end, the standard Python unit testing frameworks +([`pytest`](https://docs.pytest.org/en/latest/) and +[`nose`](http://nose2.readthedocs.io/en/latest/)) are pretty good at finding +your test functions no matter where you've hidden them. + + +## Versioning + + +If you are intending to make your project available for public use (e.g. on +[PyPI](https://pypi.python.org/pypi) and/or +[conda](https://anaconda.org/anaconda/repo)), it is __very important__ to +manage the version number of your project. If somebody decides to build their +software on top of your project, they are not going to be very happy with you +if you make substantial, API-breaking changes without changing your version +number in an appropriate manner. + + +Python has [official standards](https://www.python.org/dev/peps/pep-0440/) on +what constitutes a valid version number. These standards can be quite +complicated but, in the vast majority of cases, a simple three-number +versioning scheme comprising _major_, _minor_, and _patch_ release +numbers should suffice. Such a version number has the form: + + +> major.minor.patch + + +For example, a version number of `1.3.2` has a _major_ release of 1, _minor_ +release of 3, and a _patch_ release of 2. + + +If you follow some simple and rational guidelines for versioning +`your_project`, then people who use your project can, for instance, specify +that they depend on `your_project==1.*`, and be sure that their code will work +for _any_ version of `your_project` with a major release of 1. Following these +simple guidelines greatly improves software interoperability, and makes +everybody (i.e. developers of other projects, and end users) much happier! + + +Many modern Python projects use some form of [_semantic +versioning_](https://semver.org/). Semantic versioning is simply a set of +guidelines on how to manage your version number: + + + - The _major_ release number should be incremented whenever you introduce any + backwards-incompatible changes. In other words, if you change your code + such that some other code which uses your code would break, you should + increment the major release number. + + - The _minor_ release number should be incremented whenever you add any new + (backwards-compatible) features to your project. + + - The _patch_ release number should be incremented for backwards-compatible + bug-fixes and other minor changes. + + +If you like to automate things, +[`bumpversion`](https://github.com/peritus/bumpversion) is a simple tool that +you can use to help manage your version number. + + +### Deprecate, don't remove! + + +If you really want to change your API, but can't bring yourself to increment +your major release number, consider _deprecating_ the old API, and postponing +its removal until you are ready for a major release. This will allow you to +change your API, but retain backwards-compatilbiity with the old API until it +can safely be removed at the next major release. + + +You can use the built-in +[`warnings`](https://docs.python.org/3.5/library/exceptions.html#DeprecationWarning) +module to warn about uses of deprecated items. There are also some +[third-party libraries](https://github.com/briancurtin/deprecation) which make +it easy to mark a function, method or class as being deprecated. + + +## Cookiecutter + + +It is worth mentioning +[Cookiecutter](https://github.com/audreyr/cookiecutter), a little utility +program which you can use to generate a skeleton file/directory structure for +a new Python project. + + +You need to give it a template (there are many available templates, including +for projects in languages other than Python) - a couple of useful templates +are the [minimal Python package +template](https://github.com/kragniz/cookiecutter-pypackage-minimal), and the +[full Python package +template](https://github.com/audreyr/cookiecutter-pypackage) (although the +latter is probably overkill for most). + + +Here is how to create a skeleton project directory based off the minimal +Python packagetemplate: + + +``` +pip install cookiecutter + +# tell cookiecutter to create a directory +# from the pypackage-minimal template +cookiecutter https://github.com/kragniz/cookiecutter-pypackage-minimal.git + +# cookiecutter will then prompt you for +# basic information (e.g. projectname, +# author name/email), and then create a +# new directory containing the project +# skeleton. +``` -- GitLab