├── .circleci └── config.yml ├── .envrc ├── .github └── dependabot.yml ├── .gitignore ├── .pylintrc ├── .style.yapf ├── .vscode ├── extensions.json └── settings.json ├── .yapfignore ├── LICENSE ├── README.md ├── app ├── __init__.py ├── dummy.py └── dummy_test.py ├── dev-scripts ├── build ├── build-python ├── check-trailing-newline ├── check-trailing-whitespace ├── enable-git-hooks ├── format-python └── git-hooks │ └── pre-commit ├── dev_requirements.txt ├── flake.lock ├── flake.nix ├── main.py └── requirements.txt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | check_whitespace: 4 | docker: 5 | - image: cimg/base:2023.12 6 | steps: 7 | - checkout 8 | - run: 9 | name: Check for trailing whitespace 10 | command: ./dev-scripts/check-trailing-whitespace 11 | - run: 12 | name: Check that all text files end in a trailing newline 13 | command: ./dev-scripts/check-trailing-newline 14 | build_python: 15 | docker: 16 | - image: cimg/python:3.9.18 17 | steps: 18 | - checkout 19 | - run: 20 | name: Install requirements and run build script 21 | command: | 22 | mkdir -p ./venv 23 | virtualenv --python python3 ./venv 24 | . venv/bin/activate 25 | pip install --requirement requirements.txt 26 | pip install --requirement dev_requirements.txt 27 | ./dev-scripts/build-python 28 | - persist_to_workspace: 29 | root: ./ 30 | paths: 31 | - .coverage 32 | workflows: 33 | test: 34 | jobs: 35 | - check_whitespace 36 | - build_python 37 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake . 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | # We need to use a specific pylint version that's compatible with 11 | # DocStringChecker. 12 | - dependency-name: pylint 13 | versions: 14 | - ">= 0" 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # virtualenv 60 | venv/ 61 | 62 | # Vim 63 | *~ 64 | *.sw? 65 | 66 | # Mac OS 67 | *.DS_Store 68 | *.xcworkspace 69 | 70 | .direnv 71 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MAIN] 2 | 3 | # Pickle collected data for later comparisons. 4 | persistent=no 5 | 6 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 7 | # number of processors available to use. 8 | jobs=0 9 | 10 | # Allow loading of arbitrary C extensions. Extensions are imported into the 11 | # active Python interpreter and may run arbitrary code. 12 | unsafe-load-any-extension=no 13 | 14 | [MESSAGES CONTROL] 15 | 16 | # Disable the message, report, category or checker with the given id(s). You 17 | # can either give multiple identifiers separated by comma (,) or put this 18 | # option multiple times (only on the command line, not in the configuration 19 | # file where it should appear only once).You can also use "--disable=all" to 20 | # disable everything first and then reenable specific checks. For example, if 21 | # you want to run only the similarities checker, you can use "--disable=all 22 | # --enable=similarities". If you want to run only the classes checker, but have 23 | # no Warning level messages displayed, use"--disable=all --enable=classes 24 | # --disable=W" 25 | disable= 26 | fixme, 27 | useless-suppression, 28 | suppressed-message, 29 | arguments-differ, 30 | wildcard-import, 31 | locally-disabled, 32 | missing-module-docstring, 33 | too-many-public-methods, 34 | missing-class-docstring, 35 | superfluous-parens, 36 | multiple-imports, # We're using isort to lint imports 37 | ungrouped-imports, # We're using isort to lint imports 38 | wrong-import-order, # We're using isort to lint imports 39 | wrong-import-position # We're using isort to lint imports 40 | 41 | [REPORTS] 42 | 43 | # Set the output format. Available formats are text, parseable, colorized, msvs 44 | # (visual studio) and html. You can also give a reporter class, eg 45 | # mypackage.mymodule.MyReporterClass. 46 | output-format=text 47 | 48 | # Tells whether to display a full report or only the messages 49 | reports=no 50 | 51 | # Python expression which should return a note less than 10 (10 is the highest 52 | # note). You have access to the variables errors warning, statement which 53 | # respectively contain the number of errors / warnings messages and the total 54 | # number of statements analyzed. This is used by the global evaluation report 55 | # (RP0004). 56 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 57 | 58 | [BASIC] 59 | 60 | # Good variable names which should always be accepted, separated by a comma 61 | good-names=i,j,k,e,dt,_ 62 | 63 | # Bad variable names which should always be refused, separated by a comma 64 | bad-names=foo,bar,baz 65 | 66 | # Include a hint for the correct naming format with invalid-name 67 | include-naming-hint=no 68 | 69 | # List of decorators that produce properties, such as abc.abstractproperty. Add 70 | # to this list to register other decorators that produce valid properties. 71 | property-classes=abc.abstractproperty 72 | 73 | # Regular expression matching correct variable names 74 | variable-rgx=[a-z_][a-z0-9_]{2,60}$ 75 | 76 | # Regular expression matching correct class attribute names 77 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,60}|(__.*__))$ 78 | 79 | # Regular expression matching correct argument names 80 | argument-rgx=[a-z_][a-z0-9_]{2,60}$ 81 | 82 | # Regular expression matching correct module names 83 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 84 | 85 | # Regular expression matching correct constant names 86 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 87 | 88 | # Regular expression matching correct inline iteration names 89 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 90 | 91 | # Regular expression matching correct method names 92 | method-rgx=[a-z_][a-z0-9_]{2,80}$ 93 | 94 | # Regular expression matching correct function names 95 | function-rgx=[a-z_][a-z0-9_]{2,60}$ 96 | 97 | # Regular expression matching correct attribute names 98 | attr-rgx=[a-z_][a-z0-9_]{2,60}$ 99 | 100 | # Regular expression matching correct class names 101 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 102 | 103 | # Regular expression which should only match function or class names that do 104 | # not require a docstring. 105 | no-docstring-rgx=^test_ 106 | 107 | # Minimum line length for functions/classes that require docstrings, shorter 108 | # ones are exempt. 109 | docstring-min-length=60 110 | 111 | [STRING] 112 | check-quote-consistency=yes 113 | 114 | [ELIF] 115 | 116 | # Maximum number of nested blocks for function / method body 117 | max-nested-blocks=5 118 | 119 | [FORMAT] 120 | 121 | # Maximum number of characters on a single line. 122 | max-line-length=80 123 | 124 | # Regexp for a line that is allowed to be longer than the limit. 125 | # Allow long URLs if they're in comments or in string literals. 126 | ignore-long-lines=((# )|')?https?://\S+'?$ 127 | 128 | # Allow the body of an if to be on the same line as the test if there is no 129 | # else. 130 | single-line-if-stmt=y 131 | 132 | # Maximum number of lines in a module 133 | max-module-lines=1000 134 | 135 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 136 | # tab). 137 | indent-string=' ' 138 | 139 | # Number of spaces of indent required inside a hanging or continued line. 140 | indent-after-paren=4 141 | 142 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 143 | expected-line-ending-format=LF 144 | 145 | [LOGGING] 146 | 147 | # Logging modules to check that the string format arguments are in logging 148 | # function parameter format 149 | logging-modules=logging 150 | 151 | [MISCELLANEOUS] 152 | 153 | # List of note tags to take in consideration, separated by a comma. 154 | notes=FIXME,XXX,TODO 155 | 156 | [SIMILARITIES] 157 | 158 | # Minimum lines number of a similarity. 159 | min-similarity-lines=50 160 | 161 | # Ignore comments when computing similarities. 162 | ignore-comments=yes 163 | 164 | # Ignore docstrings when computing similarities. 165 | ignore-docstrings=yes 166 | 167 | # Ignore imports when computing similarities. 168 | ignore-imports=no 169 | 170 | [VARIABLES] 171 | 172 | # Tells whether we should check for unused import in __init__ files. 173 | init-import=no 174 | 175 | # A regular expression matching the name of dummy variables (i.e. expectedly 176 | # not used). 177 | dummy-variables-rgx=(_+[a-zA-Z0-9_]*?$)|dummy 178 | 179 | [CLASSES] 180 | 181 | # List of method names used to declare (i.e. assign) instance attributes. 182 | defining-attr-methods=__init__,__new__,setUp 183 | 184 | # List of valid names for the first argument in a class method. 185 | valid-classmethod-first-arg=cls 186 | 187 | # List of valid names for the first argument in a metaclass class method. 188 | valid-metaclass-classmethod-first-arg=mcs 189 | 190 | # List of member names, which should be excluded from the protected access 191 | # warning. 192 | exclude-protected=_asdict,_fields,_replace,_source,_make 193 | 194 | [DESIGN] 195 | 196 | # Maximum number of arguments for function / method 197 | max-args=10 198 | 199 | # Argument names that match this expression will be ignored. Default to name 200 | # with leading underscore 201 | ignored-argument-names=_.* 202 | 203 | # Maximum number of locals for function / method body 204 | max-locals=30 205 | 206 | # Maximum number of return / yield for function / method body 207 | max-returns=6 208 | 209 | # Maximum number of branch for function / method body 210 | max-branches=12 211 | 212 | # Maximum number of statements in function / method body 213 | max-statements=100 214 | 215 | # Maximum number of parents for a class (see R0901). 216 | max-parents=7 217 | 218 | # Maximum number of attributes for a class (see R0902). 219 | max-attributes=10 220 | 221 | # Minimum number of public methods for a class (see R0903). 222 | min-public-methods=0 223 | 224 | # Maximum number of boolean expressions in a if statement 225 | max-bool-expr=5 226 | 227 | [IMPORTS] 228 | 229 | # Analyse import fallback blocks. This can be used to support both Python 2 and 230 | # 3 compatible code, which means that the block might have code that exists 231 | # only in one or another interpreter, leading to false positives when analysed. 232 | analyse-fallback-blocks=no 233 | 234 | [EXCEPTIONS] 235 | 236 | # Exceptions that will emit a warning when being caught. Defaults to 237 | # "Exception" 238 | overgeneral-exceptions=builtins.Exception 239 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | [style] 2 | based_on_style = google 3 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-python.python", 4 | "esbenp.prettier-vscode", 5 | "eeyore.yapf", 6 | "jnoortheen.nix-ide" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[python]": { 4 | "editor.tabSize": 4 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.yapfignore: -------------------------------------------------------------------------------- 1 | venv/* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python3 Seed 2 | 3 | [![CircleCI](https://circleci.com/gh/mtlynch/python3_seed.svg?style=svg)](https://circleci.com/gh/mtlynch/python3_seed) 4 | [![License](https://img.shields.io/badge/license-Unlicense-blue)](LICENSE) 5 | 6 | ## Overview 7 | 8 | A boilerplate Python 3 project set up for unit tests and continuous integration. 9 | 10 | Specifically: 11 | 12 | * Enforces Python style rules with [YAPF](https://github.com/google/yapf) 13 | * Performs static analysis with [pyflakes](https://github.com/megies/pyflakes) and [pylint](https://github.com/PyCQA/pylint) 14 | * Sorts imports with [isort](https://github.com/timothycrosley/isort) 15 | 16 | ## Installation 17 | 18 | ```bash 19 | mkdir -p ./venv && \ 20 | virtualenv --python python3 ./venv && \ 21 | . venv/bin/activate && \ 22 | pip install --requirement dev_requirements.txt && \ 23 | ./dev-scripts/enable-git-hooks 24 | ``` 25 | 26 | ## Customization 27 | 28 | To customize this for your project: 29 | 30 | 1. Change `LICENSE` to [a license of your choosing](https://choosealicense.com/). 31 | 1. Change the CircleCI badge in `README.md` to your own Circle CI project badge. 32 | 1. Change the app name in `main.py` from `Python Seed` to your app's name. 33 | 1. Rename `app/dummy.py` and `app/dummy_test.py` to the module names of your choosing. 34 | 1. Begin working. 35 | 36 | ## Run 37 | 38 | ```bash 39 | ./main.py 40 | ``` 41 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtlynch/python3_seed/c33e1c293278727991c876aa2ae38d91f3f375d3/app/__init__.py -------------------------------------------------------------------------------- /app/dummy.py: -------------------------------------------------------------------------------- 1 | """Dummy module. 2 | 3 | Dummy module to exercise unit test code. Replace this with actual application 4 | logic. 5 | """ 6 | 7 | 8 | def dummy(): 9 | return 'dummy' 10 | -------------------------------------------------------------------------------- /app/dummy_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from app import dummy 4 | 5 | 6 | class DummyTest(unittest.TestCase): 7 | """Replace this with a real unit test class.""" 8 | 9 | def test_dummy(self): 10 | self.assertEqual('dummy', dummy.dummy()) 11 | -------------------------------------------------------------------------------- /dev-scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit build script on first failure. 4 | set -e 5 | 6 | # Echo commands to stdout. 7 | set -x 8 | 9 | # Exit on unset variable. 10 | set -u 11 | 12 | ./dev-scripts/check-trailing-whitespace 13 | ./dev-scripts/check-trailing-newline 14 | ./dev-scripts/build-python 15 | -------------------------------------------------------------------------------- /dev-scripts/build-python: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit on first failure. 4 | set -e 5 | 6 | # Exit on unset variable. 7 | set -u 8 | 9 | # Echo commands before executing them, by default to stderr. 10 | set -x 11 | 12 | # Location of app source files. 13 | readonly SOURCE_DIR=app 14 | 15 | # Location of virtualenv. 16 | readonly VIRTUALENV_DIR=venv 17 | 18 | # Delete pyc files from previous builds. 19 | find . \ 20 | -name "*.pyc" \ 21 | -type f \ 22 | -not -path "./${VIRTUALENV_DIR}/*" \ 23 | -delete 24 | 25 | # Run unit tests and calculate code coverage. 26 | coverage run \ 27 | --source "${SOURCE_DIR}" \ 28 | --omit "*_test.py" \ 29 | -m unittest discover --pattern "*_test.py" 30 | coverage report 31 | 32 | # Check that source has correct formatting. 33 | yapf --diff --recursive ./ 34 | 35 | # Check correct sorting for imports. 36 | isort \ 37 | . \ 38 | --force-single-line-imports \ 39 | --diff \ 40 | --check-only \ 41 | --skip-glob="${VIRTUALENV_DIR}/*" 42 | 43 | # Run static analysis for Python bugs/cruft. 44 | pyflakes "${SOURCE_DIR}/" 45 | 46 | # Check for other style violations. 47 | PYTHONPATH="${SOURCE_DIR}" pylint "${SOURCE_DIR}" 48 | -------------------------------------------------------------------------------- /dev-scripts/check-trailing-newline: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Verify that all text files end in a trailing newline. 4 | 5 | # Exit on first failure. 6 | set -e 7 | 8 | # Exit on unset variable. 9 | set -u 10 | 11 | # Change directory to repository root. 12 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 13 | readonly SCRIPT_DIR 14 | cd "${SCRIPT_DIR}/.." 15 | 16 | success=0 17 | 18 | while read -r line; do 19 | if ! [[ -s "${line}" && -z "$(tail -c 1 "${line}")" ]]; then 20 | printf "File must end in a trailing newline: %s\n" "${line}" >&2 21 | success=255 22 | fi 23 | done < <(git ls-files \ 24 | | xargs grep ".*" \ 25 | --files-with-matches \ 26 | --binary-files=without-match \ 27 | --exclude="*third-party*") 28 | 29 | exit "${success}" 30 | -------------------------------------------------------------------------------- /dev-scripts/check-trailing-whitespace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check for trailing whitespace 4 | 5 | # Exit on first failure. 6 | set -e 7 | 8 | # Exit on unset variable. 9 | set -u 10 | 11 | # Change directory to repository root. 12 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 13 | readonly SCRIPT_DIR 14 | cd "${SCRIPT_DIR}/.." 15 | 16 | while read -r line; do 17 | if grep \ 18 | "\s$" \ 19 | --line-number \ 20 | --with-filename \ 21 | --binary-files=without-match \ 22 | "${line}"; then 23 | echo "ERROR: Found trailing whitespace"; 24 | exit 1; 25 | fi 26 | done < <(git ls-files) 27 | -------------------------------------------------------------------------------- /dev-scripts/enable-git-hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit on first failure. 4 | set -e 5 | 6 | # Exit on unset variable. 7 | set -u 8 | 9 | # Echo commands before executing them, by default to stderr. 10 | set -x 11 | 12 | # Change directory to repository root. 13 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 14 | readonly SCRIPT_DIR 15 | cd "${SCRIPT_DIR}/.." 16 | 17 | # If there's an existing symlink, remove it. 18 | if [[ -L .git/hooks ]] 19 | then 20 | rm .git/hooks 21 | fi 22 | 23 | # If it's a regular directory, remove all files. 24 | if [[ -d .git/hooks ]] 25 | then 26 | rm -rf .git/hooks 27 | fi 28 | 29 | ln --symbolic --force ../dev-scripts/git-hooks .git/hooks 30 | -------------------------------------------------------------------------------- /dev-scripts/format-python: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit on first failure. 4 | set -e 5 | 6 | # Exit on unset variable. 7 | set -u 8 | 9 | # Echo commands before executing them, by default to stderr. 10 | set -x 11 | 12 | yapf \ 13 | --in-place \ 14 | --recursive \ 15 | ./ 16 | -------------------------------------------------------------------------------- /dev-scripts/git-hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./dev-scripts/build 4 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | coverage==7.8.2 2 | isort[requirements_deprecated_finder]==5.13.2 3 | pyflakes==3.3.2 4 | pylint==2.15.2 5 | yapf==0.43.0 6 | -r requirements.txt 7 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "pyproject-nix": { 22 | "flake": false, 23 | "locked": { 24 | "lastModified": 1698460532, 25 | "narHash": "sha256-Z6HVeVvbl2PzaBQSLEdy3WTvB8aiogP0X6/Q7hz+Ock=", 26 | "owner": "nix-community", 27 | "repo": "pyproject.nix", 28 | "rev": "4504c857e0200ee737edce7f3f4a3330b2658e7d", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "nix-community", 33 | "repo": "pyproject.nix", 34 | "type": "github" 35 | } 36 | }, 37 | "python-nixpkgs": { 38 | "locked": { 39 | "lastModified": 1696325386, 40 | "narHash": "sha256-ogs73SJAIePd7bljq1i1vKEOPn23Xe8aumMJKZjV45c=", 41 | "owner": "NixOS", 42 | "repo": "nixpkgs", 43 | "rev": "e2b8feae8470705c3f331901ae057da3095cea10", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "NixOS", 48 | "repo": "nixpkgs", 49 | "rev": "e2b8feae8470705c3f331901ae057da3095cea10", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "flake-utils": "flake-utils", 56 | "pyproject-nix": "pyproject-nix", 57 | "python-nixpkgs": "python-nixpkgs" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Create Nix development environment"; 3 | 4 | inputs = { 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | 7 | # Python 3.12.0 release 8 | python-nixpkgs.url = "github:NixOS/nixpkgs/e2b8feae8470705c3f331901ae057da3095cea10"; 9 | 10 | pyproject-nix = { 11 | url = "github:nix-community/pyproject.nix"; 12 | flake = false; 13 | }; 14 | }; 15 | 16 | outputs = { 17 | self, 18 | python-nixpkgs, 19 | flake-utils, 20 | pyproject-nix, 21 | } @ inputs: let 22 | pyproject = import (pyproject-nix + "/lib") {inherit (python-nixpkgs) lib;}; 23 | 24 | project = pyproject.project.loadRequirementsTxt { 25 | requirements = ./dev_requirements.txt; 26 | }; 27 | in 28 | flake-utils.lib.eachDefaultSystem (system: let 29 | python = python-nixpkgs.legacyPackages.${system}.python3; 30 | 31 | pythonEnv = python-nixpkgs.legacyPackages.${system}.python3.withPackages ( 32 | pyproject.renderers.withPackages { 33 | inherit project python; 34 | } 35 | ); 36 | in { 37 | formatter = python-nixpkgs.legacyPackages.${system}.alejandra; 38 | 39 | devShells.default = python-nixpkgs.legacyPackages.${system}.mkShell { 40 | packages = [ 41 | pythonEnv 42 | ]; 43 | 44 | shellHook = '' 45 | python --version 46 | ''; 47 | }; 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import logging 5 | 6 | from app import dummy 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def configure_logging(): 12 | root_logger = logging.getLogger() 13 | handler = logging.StreamHandler() 14 | formatter = logging.Formatter( 15 | '%(asctime)s %(name)-15s %(levelname)-4s %(message)s', 16 | '%Y-%m-%d %H:%M:%S') 17 | handler.setFormatter(formatter) 18 | root_logger.addHandler(handler) 19 | root_logger.setLevel(logging.INFO) 20 | 21 | 22 | def main(_): 23 | configure_logging() 24 | logger.info('Started runnning') 25 | print(dummy.dummy()) 26 | 27 | 28 | if __name__ == '__main__': 29 | parser = argparse.ArgumentParser( 30 | prog='Python Seed', 31 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 32 | main(parser.parse_args()) 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtlynch/python3_seed/c33e1c293278727991c876aa2ae38d91f3f375d3/requirements.txt --------------------------------------------------------------------------------