├── .gitignore
├── LICENSE
├── README.md
├── docs
└── pypi-publish.md
├── scripts
└── calc
├── setup.cfg
├── setup.py
├── src
└── pkg_example
│ ├── __init__.py
│ └── calculator_module.py
└── tests
└── test_calculator_module.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 CDT Data Science
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pkg-example
2 | An example python package as a starter for good research code.
3 |
4 |
5 | ## Installation
6 | Create and activate a virtual environment (venv), then pip install the package.
7 | For example, with conda:
8 |
9 | ```python
10 | conda create -n pex python=3
11 | conda activate pex
12 | ```
13 |
14 | Then to install the package, change directory to the root of the package and:
15 | ```python
16 | cd /where/the/package/lives/pkg-example
17 | pip install .
18 | # alternatively simply: pip install /where/the/package/lives/pkg-example
19 | ```
20 |
21 | ### Developer mode
22 |
23 | If you would like to edit the code and for changes to be reflected without the
24 | need to reinstall the package, use pip's `-e` flag:
25 | ```python
26 | pip install -e .
27 | ```
28 |
29 | --------
30 |
31 | :point_up: You'd write install instructions like the above in the `README.md` of
32 | every package you create. If your package required system installations (e.g.
33 | a c++ library), you'd list instructions for that install here too. For an
34 | example, see the install instructions for a package like
35 | [soundfile](https://pysoundfile.readthedocs.io/en/latest/#installation).
36 |
37 | --------
38 |
39 |
40 | ## So...what's happened?!
41 |
42 | ### The python package
43 | Your package, i.e. the code in `./src/pkg_example`, is available for use! Try
44 | it!
45 | ```bash
46 | # Check you're using the python of your venv
47 | which python
48 | # Spin it up!
49 | python
50 | >>> import pkg_example
51 | >>> # Handy tip - where is this installed package on your system?!
52 | >>> pkg_example.__file__
53 | ```
54 |
55 | How? You listed it as a package to install in `setup.cfg`. Relevant lines:
56 | ```
57 | packages = find:
58 | package_dir =
59 | =src
60 | ```
61 | Any folder inside `./src` with an `__init__.py` inside will have been picked up
62 | as a package to install.
63 |
64 | Importantly, this means you don't need to mess around with relative imports
65 | **within** your package (e.g. `from .calculator_module import Calculator`), just
66 | reference it like a normal package e.g.
67 | `from pkg_example.calculator_module import Calculator`.
68 |
69 | > A gotcha to note - you cant have a `-` in your package name for python, only
70 | underscores. The minus sign is a reserved character. I used a minus sign for the
71 | top level folder name to make it clear that this folder is *not* the python
72 | package; the folder `./src/pkg_example` is the folder containing the python
73 | package code.
74 |
75 | ### Scripts
76 | The script `./scripts/calc` is also available on your path:
77 | ```bash
78 | # calc is in the bin of your venv
79 | which calc
80 | # note that the venv bin folder is on your path
81 | echo $PATH
82 | ```
83 | It will be in the `bin` folder of your venv, which will have been added
84 | to your [`$PATH`](http://www.linfo.org/path_env_var.html), so, wherever you are
85 | located on your system, if the venv is activated, you can just type calc to
86 | use your package. Neat.
87 |
88 | How? Again, you listed this script as something to install in `setup.cfg`.
89 | Relevant lines:
90 | ```
91 | scripts =
92 | scripts/calc
93 | ```
94 |
95 | ### The virtual environment
96 | In addition, your venv has all the packages installed that your package
97 | requires. Relevant lines in `setup.cfg`:
98 |
99 | ```
100 | install_requires =
101 | black
102 | fire
103 | ipython
104 | pylint
105 | pytest
106 | ...
107 | [options.extras_require]
108 | dev =
109 | twine
110 | ```
111 |
112 | That `options.extras_require` is fancy: as a developer, you'll likely want to
113 | install packages which wont be necessary for the general user. This is an
114 | optional flag you can use on the pip install, e.g.
115 | ```bash
116 | pip install -e /where/the/package/lives/pkg-example[dev]
117 | ```
118 | will install the package in development mode (`-e`), install all the
119 | `install_requires` packages, *and* the `dev` option package(s), just `twine`
120 | here.
121 |
122 |
123 | ## What next?
124 | You can upload your package to PyPI! See [docs/pypi-publish.md](docs/pypi-publish.md)
125 | for more information. Once you do that, everyone in the world can mash
126 |
127 | ```bash
128 | pip install your_package_name
129 | ```
130 |
131 | into their console, and just crack on. Neat huh?!
132 |
133 | ## Linting & Testing
134 | You will have installed `pytest`, `black`, and `pylint` as part of the installation.
135 |
136 | * Running `pytest` from the root directory will run all the tests in `./test`.
137 | * Running `black .` will reformat your code to fit with a specific standard (e.g.
138 | line lengths etc.)
139 | * Running `pylint pkg_example` will check the package conforms to pep8 standards.
140 |
141 | Motivation for testing and linting:
142 |
143 | * Python is permissive and not precompiled
144 | * If your code doesn't need to 'go there' it wont
145 | * ...so it wont error till you're 28 hours into the job
146 | * you know, the very important job due for yesterday?
147 |
148 | So imagine the scenario:
149 | * You renamed a variable
150 | * Your code is much more understandable, great job!
151 | * ...oops you missed one (your `ctrl` + `f` regex didn't account for bracket first)
152 | * What would have saved your ass:
153 | * having written a test that runs that part of the code
154 | * simply running pylint - `unused-variable (W0612)`
155 | * YOU LITERALLY JUST SAVED 24-26 HOURS (You spent 2-4 writing better code -
156 | this gets significantly faster with practice)
157 |
158 | A pylint tip - you can exclude some of the warnings using the `--disable` flag. Given that
159 | `black` doesn't actually conform to PEP8 (:grimacing:), you may want to call pylint as below
160 | to remove some guff:
161 | ```
162 | pylint --disable=C0330 pkg_example
163 | ```
164 |
165 |
166 | ## References
167 |
168 | * [The (crappy) slides from the talk][1]
169 | * [A great blog post on pacakging your python project][2]
170 | * [The Python Fire Project - super easy CLIs][3]
171 | * [Comments & docstrings a la the **Google Style Guide**][4]
172 | * [...but combine the above with **type hinting** and you're golden][5]
173 | * [More on setup.cfg][6]
174 |
175 |
176 | [1]: https://docs.google.com/presentation/d/1dZXcjCLKMEpq_HbjI0L4A_ZVMjBmEVN3mbOytily-78/edit?usp=sharing "Slides from the talk"
177 | [2]: https://realpython.com/pypi-publish-python-package/ "Packaging your python project"
178 | [3]: https://github.com/google/python-fire "python fire"
179 | [4]: http://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings "Google code styleguide - conmments and docstrings"
180 | [5]: https://docs.python.org/3/library/typing.html "Type hints"
181 | [6]: https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files "setup.cfg"
182 |
--------------------------------------------------------------------------------
/docs/pypi-publish.md:
--------------------------------------------------------------------------------
1 | **This doc is a work in progress! SORRY! For now, follow this great blog post**
2 |
3 | Nice tutorial:
4 |
5 | And for more detail, read the official docs (pretty concise actually):
6 | 1.
7 | 2.
8 |
9 | The below will be a practical adaptation of the above links for this project:
10 |
11 | # How to publish your package on PyPI
12 |
13 | 1. make your python package
14 | 2. test it locally
15 | 3. create a 'source archive' for distribution
16 | 4. test *that*
17 | 5. publish on pypi test
18 | 6. publish 4 realz
19 |
20 | ## Creating an sdist
21 |
22 | ## Testing your sdist
23 | tar tzf realpython-reader-1.0.0.tar.gz
24 |
25 | ## Publishing to test pypi
26 |
27 | ## Publishing to public pypi
28 |
29 |
30 | ## See also
31 |
32 | bdist_wheel
33 |
--------------------------------------------------------------------------------
/scripts/calc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import fire
3 |
4 | from pkg_example.calculator_module import Calculator
5 |
6 | if __name__ == '__main__':
7 | fire.Fire(Calculator)
8 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = pkg-example
3 | version = 0.1b1
4 | license = MIT
5 | license_files = LICENSE
6 | url = https://github.com/cdt-data-science/pkg-example
7 | author = James Owers
8 | author_email = james.f.owers@gmail.com
9 | keywords = template python-packaging best-practice
10 | description = An example python package as a starter for good research code.
11 | long_description = file: README.md
12 | long_description_content_type = text/markdown
13 | classifiers =
14 | License :: MIT
15 | Programming Language :: Python :: 3
16 |
17 | [options]
18 | zip_safe = False
19 | include_package_data = True
20 | install_requires =
21 | black
22 | fire
23 | ipython
24 | pylint
25 | pytest
26 | setup_requires =
27 | pytest-runner
28 | tests_require =
29 | pytest
30 | packages = find:
31 | package_dir =
32 | =src
33 | scripts =
34 | scripts/calc
35 |
36 | [options.extras_require]
37 | dev =
38 | twine
39 |
40 | [options.packages.find]
41 | where = src
42 |
43 | [bdist_wheel]
44 | universal = 0
45 |
46 | [aliases]
47 | test = pytest
48 |
49 | [tool:pytest]
50 | testpaths = tests
51 | pythonfiles = test_*.py
52 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """Packaging setup script.
2 |
3 | This file is required by setuptools. All configuration data is in the setup.cfg file.
4 | """
5 |
6 | from setuptools import setup
7 |
8 | setup()
9 |
--------------------------------------------------------------------------------
/src/pkg_example/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cdt-data-science/pkg-example/a78724acd33346d7955ad0029eec72b134f7c578/src/pkg_example/__init__.py
--------------------------------------------------------------------------------
/src/pkg_example/calculator_module.py:
--------------------------------------------------------------------------------
1 | """Calculator class and friends"""
2 |
3 | class Calculator:
4 | """A simple calculator class."""
5 |
6 | def double(self, number):
7 | """Doubles the input number."""
8 | return 2 * number
9 |
--------------------------------------------------------------------------------
/tests/test_calculator_module.py:
--------------------------------------------------------------------------------
1 | from pkg_example.calculator_module import Calculator
2 |
3 | def test_double():
4 | calc = Calculator()
5 | assert calc.double(3) == 6
6 | assert calc.double(3.0) == 6.0
7 |
--------------------------------------------------------------------------------