├── .github └── CODEOWNERS ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cookiecutter.json ├── hooks └── post_gen_project.py ├── requirements-dev.txt ├── tests ├── __init__.py ├── conftest.py ├── test_makefile.py ├── test_mypy.py ├── test_package.py ├── test_readme.py └── test_travis.py └── {{cookiecutter.project_slug}} ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .pylintrc ├── .travis.yml ├── Dockerfile ├── Makefile ├── Pipfile ├── README.md ├── bin ├── install_hooks.sh └── pre-push ├── mypy.ini ├── requirements-dev.txt ├── requirements.txt ├── tests └── __init__.py └── {{cookiecutter.package_name}} ├── __init__.py └── __main__.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @banksalad/core-backend-server 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#*\# 3 | .\#* 4 | .idea/ 5 | .DS_Store 6 | ._* 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | [._]*.s[a-v][a-z] 11 | [._]*.sw[a-p] 12 | [._]s[a-rt-v][a-z] 13 | [._]ss[a-gi-z] 14 | [._]sw[a-p] 15 | .netrwhist 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: python 4 | 5 | dist: xenial 6 | 7 | python: 8 | - "3.7" 9 | - "3.6" 10 | 11 | cache: 12 | - pip 13 | 14 | before_install: 15 | - pip install -U pip 16 | 17 | install: 18 | - pip install -r requirements-dev.txt 19 | 20 | script: 21 | - pytest 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rainist Co., Ltd. 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 | # Rainist Python Project Template 2 | 3 | [![Build Status](https://travis-ci.org/Rainist/python.svg?branch=master)](https://travis-ci.org/Rainist/python) 4 | 5 | ## Usage 6 | 7 | ```bash 8 | $ pip install cookiecutter 9 | $ cookiecutter https://github.com/rainist/python 10 | ``` 11 | 12 | ### Options 13 | 14 | ``` 15 | project_name [Python Project]: My Python Project 16 | ``` 17 | 18 | 프로젝트 이름을 설정할 수 있습니다. 보통의 이름을 적듯 `-` 와 `_` 없이 설정합니다. 19 | 20 | ``` 21 | project_slug [my-python-project]: sample-python 22 | ``` 23 | 24 | 프로젝트 폴더 이름을 설정할 수 있습니다. 보통 GitHub Repository로 쓰이는 이름과 같게 설정합니다. 25 | 26 | ``` 27 | package_name [samplepython]: app 28 | ``` 29 | 30 | 프로젝트 폴더 안에 파이썬 코드가 담길 패키지 폴더 이름을 설정할 수 있습니다. 이 패키지 이름을 기반으로 `pylint`, 커버리지 측정, `Dockerfile` 설정이 이루어집니다. 31 | 32 | ``` 33 | Select python_version: 34 | 1 - 3.7 35 | 2 - 3.6 36 | Choose from 1, 2 [1]: 37 | ``` 38 | 39 | 사용할 파이썬 런타임 버전을 설정할 수 있습니다. `Dockerfile`, `mypy` 등의 파이썬 버전을 설정하는 데 사용됩니다. 40 | 41 | ``` 42 | use_travis [y]: 43 | ``` 44 | 45 | [`Travis-CI`](https://travis-ci.org) 사용 여부를 설정할 수 있습니다. 46 | 47 | * 설정 시 [`codecov`](https://codecov.io)를 사용한 커버리지 측정 관련 내용도 자동으로 추가 된 상태입니다. 커버리지 측정을 원하지 않는다면 `.travis.yml`과 `README.md`에서 관련 내용을 삭제해야합니다. 48 | * `README.md`에 포함된 travis, codecov 뱃지의 URL을 적절하게 수정해야합니다. 49 | * Private project를 생성한다면 `README.md`의 travis, codecov 뱃지, `.travis.yml`의 codecov 부분에 토큰을 추가해야합니다. 50 | 51 | ``` 52 | use_docker [y]: 53 | ``` 54 | 55 | Dockerfile 사용 여부를 설정할 수 있습니다. 56 | - 설정시 `Dockerfile`이 생성됩니다. 57 | 58 | ``` 59 | Select use_mypy: 60 | 1 - do not use 61 | 2 - beginner 62 | 3 - expert 63 | Choose from 1, 2, 3 [1]: 3 64 | ``` 65 | 66 | [`mypy`](https://github.com/python/mypy) 사용 여부를 설정할 수 있습니다. 설정 시 `pre-push` hook과 `make check` 과정에 `mypy` 가 추가됩니다. 67 | 68 | * `beginner`: 타입 힌팅이 있는 함수들만 가지고 타입 검사를 수행합니다. 69 | * `expert`: 타입 힌팅이 없는 함수까지 경고를 발생시킵니다. 70 | 71 | ``` 72 | use_black [n]: y 73 | ``` 74 | 75 | [`black`](https://github.com/ambv/black) 사용 여부를 설정할 수 있습니다. 설정 시 `pre-push` hook과 `make check`, `make format` 과정에 `black` 이 추가됩니다. 76 | 77 | ``` 78 | use_pipenv [n]: y 79 | ``` 80 | 81 | [`pipenv`](https://github.com/pypa/pipenv) 사용 여부를 설정할 수 있습니다. 82 | 83 | * `black`과 `pipenv`를 같이 사용할 경우, `black`이 현재 프리릴리즈 상태이므로 `pipenv lock --pre` 명령어로 `Pipfile.lock` 파일을 생성해야합니다. 84 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "Python Project", 3 | "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '-') }}", 4 | "package_name": "{{ cookiecutter.project_slug.replace('-', '') }}", 5 | "python_version": ["3.7", "3.6"], 6 | "use_travis": "y", 7 | "use_docker": "y", 8 | "use_mypy": ["do not use", "beginner", "expert"], 9 | "use_black": "n", 10 | "use_pipenv": "n" 11 | } 12 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | 5 | def remove_file(name): 6 | os.remove(name) 7 | 8 | 9 | if __name__ == '__main__': 10 | if '{{ cookiecutter.use_mypy|lower }}' == 'do not use': 11 | remove_file('mypy.ini') 12 | if '{{ cookiecutter.use_pipenv|lower }}' == 'n': 13 | remove_file('.gitattributes') 14 | remove_file('Pipfile') 15 | if '{{ cookiecutter.use_travis|lower }}' != 'y': 16 | remove_file('.travis.yml') 17 | if '{{ cookiecutter.use_docker|lower }}' != 'y': 18 | remove_file('Dockerfile') 19 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==4.6.7 2 | pytest-cookies==0.4.0 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banksalad/python/a6220453cb3d550d5c4a6a9297781dbb250e4b69/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def context(): 6 | def builder( 7 | version='3.7', 8 | mypy='do not use', 9 | black='n', 10 | pipenv='n', 11 | travis='y', 12 | docker='y', 13 | ): 14 | return { 15 | 'project_name': 'Rainist', 16 | 'project_slug': 'rainist', 17 | 'package_name': 'cookie', 18 | 'python_version': version, 19 | 'use_mypy': mypy, 20 | 'use_black': black, 21 | 'use_pipenv': pipenv, 22 | 'use_travis': travis, 23 | 'use_docker': docker, 24 | } 25 | 26 | return builder 27 | -------------------------------------------------------------------------------- /tests/test_makefile.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('black', ['y', 'n']) 7 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 8 | @pytest.mark.parametrize('mypy', ['do not use', 'beginner', 'expert']) 9 | def test_makefile_total_lines(cookies, context, black, pipenv, mypy): 10 | """ 11 | We expect Makefile has below content 12 | ``` 13 | 1 .PHONY: check 14 | 2 ## check: check if everything's okay 15 | 3 check: 16 | 4 isort 17 | 5 black 18 | 6 19 | 7 .PHONY: format 20 | 8 ## format: format files 21 | 9 format: 22 | 10 isort 23 | 11 black 24 | ``` 25 | """ 26 | ctx = context(black=black, pipenv=pipenv, mypy=mypy) 27 | result = cookies.bake(extra_context=ctx) 28 | 29 | makefile = result.project.join('Makefile') 30 | lines = makefile.readlines(cr=False) 31 | 32 | expected = 43 33 | expected -= 2 if black == 'n' else 0 34 | expected -= 1 if mypy == 'do not use' else 0 35 | expected += 6 if pipenv == 'y' else 0 36 | assert len(lines) == expected 37 | 38 | 39 | @pytest.mark.parametrize('black', ['y', 'n']) 40 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 41 | @pytest.mark.parametrize('mypy', ['do not use', 'beginner', 'expert']) 42 | def test_makefile_total_section(cookies, context, black, pipenv, mypy): 43 | ctx = context(black=black, pipenv=pipenv, mypy=mypy) 44 | result = cookies.bake(extra_context=ctx) 45 | 46 | makefile = result.project.join('Makefile') 47 | content = makefile.read() 48 | sections = content.strip().split('\n\n') 49 | 50 | expected = 8 # init,check,format,test,coverage,htmlcov,requirements,help 51 | expected -= 1 if pipenv == 'n' else 0 # requirements 52 | assert len(sections) == expected 53 | 54 | 55 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 56 | def test_makefile_phony(cookies, context, pipenv): 57 | ctx = context(pipenv=pipenv) 58 | result = cookies.bake(extra_context=ctx) 59 | 60 | makefile = result.project.join('Makefile') 61 | 62 | phonies = re.findall(r'\.PHONY: (\w+)', makefile.read()) 63 | 64 | expected = 8 # init,check,format,test,coverage,htmlcov,requirements,help 65 | expected -= 1 if pipenv == 'n' else 0 # requirements 66 | assert len(set(phonies)) == expected 67 | -------------------------------------------------------------------------------- /tests/test_mypy.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize('mypy', ['beginner', 'expert']) 5 | def test_makefile_total_lines(cookies, context, mypy): 6 | """ 7 | We expect mypy.ini has below content 8 | ``` 9 | 1 [mypy] 10 | 2 python_version = 3.7 11 | 3 ignore_missing_imports = True 12 | 4 check_untyped_defs = False 13 | 5 disallow_untyped_defs = False 14 | 6 disallow_any_generics = True 15 | 7 warn_no_return = True 16 | 8 no_implicit_optional = True 17 | 9 18 | ``` 19 | """ 20 | ctx = context(mypy=mypy) 21 | result = cookies.bake(extra_context=ctx) 22 | 23 | ini = result.project.join('mypy.ini') 24 | lines = ini.readlines(cr=False) 25 | 26 | expected = 9 27 | assert len(lines) == expected 28 | -------------------------------------------------------------------------------- /tests/test_package.py: -------------------------------------------------------------------------------- 1 | from typing import List, Iterator 2 | 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('black', ['y', 'n']) 7 | @pytest.mark.parametrize('mypy', ['do not use', 'beginner', 'expert']) 8 | @pytest.mark.parametrize('requirements', ['requirements-dev.txt']) 9 | def test_requirements_include_specifiers( 10 | cookies, context, black, mypy, requirements 11 | ): 12 | """ 13 | We expect each package has `==` specifier 14 | ``` 15 | 1 black==19.3b0 16 | 2 mypy==0.701 17 | ``` 18 | """ 19 | ctx = context(black=black, mypy=mypy) 20 | result = cookies.bake(extra_context=ctx) 21 | 22 | packages = result.project.join(requirements) 23 | content = packages.read() 24 | lines = content.strip().split('\n') 25 | 26 | assert all('==' in l for l in lines) 27 | 28 | 29 | @pytest.mark.parametrize('black', ['y', 'n']) 30 | @pytest.mark.parametrize('mypy', ['do not use', 'beginner', 'expert']) 31 | @pytest.mark.parametrize('packages', ['[dev-packages]']) 32 | def test_pipfile_include_specifiers(cookies, context, black, mypy, packages): 33 | """ 34 | We expect each package has `==` specifier 35 | ``` 36 | 1 [dev-packages] 37 | 2 isort = "==4.3.20" 38 | 3 black = "==19.3b0" 39 | 4 40 | 5 [packages] 41 | 6 sanic = "==19.6.0" 42 | ``` 43 | """ 44 | ctx = context(black=black, mypy=mypy, pipenv='y') 45 | result = cookies.bake(extra_context=ctx) 46 | 47 | pipfile = result.project.join('Pipfile') 48 | content = pipfile.read() 49 | sections = content.strip().split('\n\n') 50 | lines = filter( 51 | is_pipfile_requirement, split_section_to_lines(sections, packages) 52 | ) 53 | 54 | assert all('==' in l for l in lines) 55 | 56 | 57 | def split_section_to_lines(sections: List[str], title: str) -> Iterator[str]: 58 | for s in sections: 59 | if title in s: 60 | yield from s.split('\n') 61 | 62 | 63 | def is_pipfile_requirement(line: str) -> bool: 64 | """ 65 | >>> is_pipfile_requirement('isort = "==4.3.20"') 66 | True 67 | >>> is_pipfile_requirement('[dev-packages]') 68 | False 69 | """ 70 | return len(line.split(' ')) == 3 and '=' in line 71 | -------------------------------------------------------------------------------- /tests/test_readme.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_bake_project(cookies): 5 | result = cookies.bake() 6 | 7 | assert result.exit_code == 0 8 | assert result.exception is None 9 | assert result.project.basename == 'python-project' 10 | assert result.project.isdir() 11 | 12 | 13 | @pytest.mark.parametrize('version', ['3.7', '3.6']) 14 | @pytest.mark.parametrize('black', ['y', 'n']) 15 | @pytest.mark.parametrize('travis', ['y', 'n']) 16 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 17 | def test_readme_total_lines(cookies, context, version, black, travis, pipenv): 18 | """ 19 | We expect README.md has below content 20 | ``` 21 | 1 # Python Project 22 | 2 23 | 3 [python][black][travis] ... 24 | 4 25 | 5 ## Header 26 | 6 27 | 7 content 28 | ``` 29 | """ 30 | ctx = context(version=version, black=black, travis=travis, pipenv=pipenv) 31 | result = cookies.bake(extra_context=ctx) 32 | 33 | readme = result.project.join('README.md') 34 | lines = readme.readlines(cr=False) 35 | 36 | expected = 36 37 | expected -= 0 if travis == 'y' else 1 38 | expected -= 1 if pipenv == 'n' else 0 39 | assert len(lines) == expected 40 | -------------------------------------------------------------------------------- /tests/test_travis.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.mark.parametrize('version', ['3.7', '3.6']) 5 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 6 | def test_travis_total_lines(cookies, context, version, pipenv): 7 | """ 8 | We expect .travis.yml has below content 9 | ``` 10 | 1 sudo: required 11 | 2 12 | 3 language: python 13 | 4 14 | 5 install: 15 | 6 - make 16 | ``` 17 | """ 18 | ctx = context(version=version, pipenv=pipenv) 19 | result = cookies.bake(extra_context=ctx) 20 | 21 | makefile = result.project.join('.travis.yml') 22 | lines = makefile.readlines(cr=False) 23 | 24 | expected = 32 25 | expected -= 2 if version == '3.6' else 0 26 | expected -= 6 if pipenv == 'n' else 0 27 | assert len(lines) == expected 28 | 29 | 30 | @pytest.mark.parametrize('version', ['3.7', '3.6']) 31 | @pytest.mark.parametrize('pipenv', ['y', 'n']) 32 | def test_travis_total_section(cookies, context, version, pipenv): 33 | ctx = context(version=version, pipenv=pipenv) 34 | result = cookies.bake(extra_context=ctx) 35 | 36 | makefile = result.project.join('.travis.yml') 37 | content = makefile.read() 38 | sections = content.strip().split('\n\n') 39 | 40 | expected = 10 41 | expected -= 1 if version == '3.6' else 0 42 | expected -= 1 if pipenv == 'n' else 0 43 | assert len(sections) == expected 44 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # Python settings with isort 10 | [*.py] 11 | charset = utf-8 12 | include_trailing_comma = true 13 | indent_style = space 14 | known_standard_library = contextvars, dataclasses 15 | indent_size = 4 16 | line_length = 79 17 | multi_line_output = 3 18 | not_skip = __init__.py 19 | 20 | [Makefile] 21 | indent_style = tab 22 | 23 | [.travis.yml] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitattributes: -------------------------------------------------------------------------------- 1 | Pipfile.lock binary 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#*\# 3 | .\#* 4 | .idea/ 5 | .DS_Store 6 | ._* 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | build/ 11 | *.egg-info/ 12 | target/ 13 | htmlcov/ 14 | .coverage 15 | .coverage.* 16 | .cache 17 | coverage.xml 18 | *.cover 19 | .pytest_cache/ 20 | {%- if cookiecutter.use_mypy|lower != "do not use" %} 21 | .mypy_cache/ 22 | {%- endif %} 23 | .env 24 | venv/ 25 | env/ 26 | [._]*.s[a-v][a-z] 27 | [._]*.sw[a-p] 28 | [._]s[a-rt-v][a-z] 29 | [._]ss[a-gi-z] 30 | [._]sw[a-p] 31 | .netrwhist 32 | .vscode/* 33 | .history 34 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore= 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Use multiple processes to speed up Pylint. 17 | jobs=0 18 | 19 | # List of plugins (as comma separated values of python modules names) to load, 20 | # usually to register additional checkers. 21 | load-plugins= 22 | 23 | # Pickle collected data for later comparisons. 24 | persistent=yes 25 | 26 | # Specify a configuration file. 27 | #rcfile= 28 | 29 | # Allow loading of arbitrary C extensions. Extensions are imported into the 30 | # active Python interpreter and may run arbitrary code. 31 | unsafe-load-any-extension=no 32 | 33 | 34 | [MESSAGES CONTROL] 35 | 36 | # Only show warnings with the listed confidence levels. Leave empty to show 37 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 38 | confidence= 39 | 40 | # Disable the message, report, category or checker with the given id(s). You 41 | # can either give multiple identifiers separated by comma (,) or put this 42 | # option multiple times (only on the command line, not in the configuration 43 | # file where it should appear only once).You can also use "--disable=all" to 44 | # disable everything first and then reenable specific checks. For example, if 45 | # you want to run only the similarities checker, you can use "--disable=all 46 | # --enable=similarities". If you want to run only the classes checker, but have 47 | # no Warning level messages displayed, use"--disable=all --enable=classes 48 | # --disable=W" 49 | disable= 50 | missing-docstring, 51 | too-few-public-methods, 52 | bad-continuation, 53 | logging-fstring-interpolation, 54 | fixme, 55 | 56 | 57 | # Enable the message, report, category or checker with the given id(s). You can 58 | # either give multiple identifier separated by comma (,) or put this option 59 | # multiple time (only on the command line, not in the configuration file where 60 | # it should appear only once). See also the "--disable" option for examples. 61 | enable= 62 | 63 | 64 | [REPORTS] 65 | 66 | # Python expression which should return a note less than 10 (10 is the highest 67 | # note). You have access to the variables errors warning, statement which 68 | # respectively contain the number of errors / warnings messages and the total 69 | # number of statements analyzed. This is used by the global evaluation report 70 | # (RP0004). 71 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 72 | 73 | # Template used to display messages. This is a python new-style format string 74 | # used to format the message information. See doc for all details 75 | #msg-template= 76 | 77 | # Set the output format. Available formats are text, parseable, colorized, json 78 | # and msvs (visual studio).You can also give a reporter class, eg 79 | # mypackage.mymodule.MyReporterClass. 80 | output-format=colorized 81 | 82 | # Tells whether to display a full report or only the messages 83 | reports=no 84 | 85 | # Activate the evaluation score. 86 | score=yes 87 | 88 | 89 | [REFACTORING] 90 | 91 | # Maximum number of nested blocks for function / method body 92 | max-nested-blocks=5 93 | 94 | 95 | [BASIC] 96 | 97 | # Naming hint for argument names 98 | argument-name-hint=(([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ 99 | 100 | # Regular expression matching correct argument names 101 | argument-rgx=(([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ 102 | 103 | # Naming hint for attribute names 104 | attr-name-hint=(([a-z][a-z0-9_]{1,30})|(_[a-z0-9_]*))$ 105 | 106 | # Regular expression matching correct attribute names 107 | attr-rgx=(([a-z][a-z0-9_]{1,30})|(_[a-z0-9_]*))$ 108 | 109 | # Bad variable names which should always be refused, separated by a comma 110 | bad-names=foo,bar,baz,toto,tutu,tata 111 | 112 | # Naming hint for class attribute names 113 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$ 114 | 115 | # Regular expression matching correct class attribute names 116 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$ 117 | 118 | # Naming hint for class names 119 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 120 | 121 | # Regular expression matching correct class names 122 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 123 | 124 | # Naming hint for constant names 125 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 126 | 127 | # Regular expression matching correct constant names 128 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 129 | 130 | # Minimum line length for functions/classes that require docstrings, shorter 131 | # ones are exempt. 132 | docstring-min-length=-1 133 | 134 | # Naming hint for function names 135 | function-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 136 | 137 | # Regular expression matching correct function names 138 | function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 139 | 140 | # Good variable names which should always be accepted, separated by a comma 141 | good-names=i,j,k,ex,Run,_ 142 | 143 | # Include a hint for the correct naming format with invalid-name 144 | include-naming-hint=no 145 | 146 | # Naming hint for inline iteration names 147 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 148 | 149 | # Regular expression matching correct inline iteration names 150 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 151 | 152 | # Naming hint for method names 153 | method-name-hint=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 154 | 155 | # Regular expression matching correct method names 156 | method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ 157 | 158 | # Naming hint for module names 159 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 160 | 161 | # Regular expression matching correct module names 162 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 163 | 164 | # Colon-delimited sets of names that determine each other's naming style when 165 | # the name regexes allow several styles. 166 | name-group= 167 | 168 | # Regular expression which should only match function or class names that do 169 | # not require a docstring. 170 | no-docstring-rgx=^_ 171 | 172 | # List of decorators that produce properties, such as abc.abstractproperty. Add 173 | # to this list to register other decorators that produce valid properties. 174 | property-classes=abc.abstractproperty 175 | 176 | # Naming hint for variable names 177 | variable-name-hint=(([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ 178 | 179 | # Regular expression matching correct variable names 180 | variable-rgx=(([a-z][a-z0-9_]{0,30})|(_[a-z0-9_]*))$ 181 | 182 | 183 | [FORMAT] 184 | 185 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 186 | expected-line-ending-format=LF 187 | 188 | # Regexp for a line that is allowed to be longer than the limit. 189 | ignore-long-lines=^\s*(# )??$ 190 | 191 | # Number of spaces of indent required inside a hanging or continued line. 192 | indent-after-paren=4 193 | 194 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 195 | # tab). 196 | indent-string=' ' 197 | 198 | # Maximum number of characters on a single line. 199 | max-line-length=79 200 | 201 | # Maximum number of lines in a module 202 | max-module-lines=800 203 | 204 | # List of optional constructs for which whitespace checking is disabled. `dict- 205 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 206 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 207 | # `empty-line` allows space-only lines. 208 | no-space-check= 209 | trailing-comma, 210 | dict-separator, 211 | 212 | # Allow the body of a class to be on the same line as the declaration if body 213 | # contains single statement. 214 | single-line-class-stmt=no 215 | 216 | # Allow the body of an if to be on the same line as the test if there is no 217 | # else. 218 | single-line-if-stmt=no 219 | 220 | 221 | [LOGGING] 222 | 223 | # Logging modules to check that the string format arguments are in logging 224 | # function parameter format 225 | logging-modules=logging 226 | 227 | 228 | [MISCELLANEOUS] 229 | 230 | # List of note tags to take in consideration, separated by a comma. 231 | notes=FIXME,XXX,TODO 232 | 233 | 234 | [SIMILARITIES] 235 | 236 | # Ignore comments when computing similarities. 237 | ignore-comments=yes 238 | 239 | # Ignore docstrings when computing similarities. 240 | ignore-docstrings=yes 241 | 242 | # Ignore imports when computing similarities. 243 | ignore-imports=no 244 | 245 | # Minimum lines number of a similarity. 246 | min-similarity-lines=4 247 | 248 | 249 | [SPELLING] 250 | 251 | # Spelling dictionary name. Available dictionaries: none. To make it working 252 | # install python-enchant package. 253 | spelling-dict= 254 | 255 | # List of comma separated words that should not be checked. 256 | spelling-ignore-words= 257 | 258 | # A path to a file that contains private dictionary; one word per line. 259 | spelling-private-dict-file= 260 | 261 | # Tells whether to store unknown words to indicated private dictionary in 262 | # --spelling-private-dict-file option instead of raising a message. 263 | spelling-store-unknown-words=no 264 | 265 | 266 | [TYPECHECK] 267 | 268 | # List of decorators that produce context managers, such as 269 | # contextlib.contextmanager. Add to this list to register other decorators that 270 | # produce valid context managers. 271 | contextmanager-decorators=contextlib.contextmanager 272 | 273 | # List of members which are set dynamically and missed by pylint inference 274 | # system, and so shouldn't trigger E1101 when accessed. Python regular 275 | # expressions are accepted. 276 | generated-members= 277 | 278 | # Tells whether missing members accessed in mixin class should be ignored. A 279 | # mixin class is detected if its name ends with "mixin" (case insensitive). 280 | ignore-mixin-members=yes 281 | 282 | # This flag controls whether pylint should warn about no-member and similar 283 | # checks whenever an opaque object is returned when inferring. The inference 284 | # can return multiple potential results while evaluating a Python object, but 285 | # some branches might not be evaluated, which results in partial inference. In 286 | # that case, it might be useful to still emit no-member and other checks for 287 | # the rest of the inferred objects. 288 | ignore-on-opaque-inference=yes 289 | 290 | # List of class names for which member attributes should not be checked (useful 291 | # for classes with dynamically set attributes). This supports the use of 292 | # qualified names. 293 | ignored-classes=optparse.Values,thread._local,_thread._local,Enum 294 | 295 | # List of module names for which member attributes should not be checked 296 | # (useful for modules/projects where namespaces are manipulated during runtime 297 | # and thus existing member attributes cannot be deduced by static analysis. It 298 | # supports qualified module names, as well as Unix pattern matching. 299 | ignored-modules=setup 300 | 301 | # Show a hint with possible names when a member name was not found. The aspect 302 | # of finding the hint is based on edit distance. 303 | missing-member-hint=yes 304 | 305 | # The minimum edit distance a name should have in order to be considered a 306 | # similar match for a missing member name. 307 | missing-member-hint-distance=1 308 | 309 | # The total number of similar names that should be taken in consideration when 310 | # showing a hint for a missing member. 311 | missing-member-max-choices=1 312 | 313 | 314 | [VARIABLES] 315 | 316 | # List of additional names supposed to be defined in builtins. Remember that 317 | # you should avoid to define new builtins when possible. 318 | additional-builtins= 319 | 320 | # Tells whether unused global variables should be treated as a violation. 321 | allow-global-unused-variables=yes 322 | 323 | # List of strings which can identify a callback function by name. A callback 324 | # name must start or end with one of those strings. 325 | callbacks=cb_,_cb 326 | 327 | # A regular expression matching the name of dummy variables (i.e. expectedly 328 | # not used). 329 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 330 | 331 | # Argument names that match this expression will be ignored. Default to name 332 | # with leading underscore 333 | ignored-argument-names=_.*|^ignored_|^unused_ 334 | 335 | # Tells whether we should check for unused import in __init__ files. 336 | init-import=yes 337 | 338 | # List of qualified module names which can have objects that can redefine 339 | # builtins. 340 | redefining-builtins-modules=six.moves,future.builtins 341 | 342 | 343 | [CLASSES] 344 | 345 | # List of method names used to declare (i.e. assign) instance attributes. 346 | defining-attr-methods=__init__,__new__,set_up 347 | 348 | # List of member names, which should be excluded from the protected access 349 | # warning. 350 | exclude-protected=_asdict,_fields,_replace,_source,_make 351 | 352 | # List of valid names for the first argument in a class method. 353 | valid-classmethod-first-arg=cls 354 | 355 | # List of valid names for the first argument in a metaclass class method. 356 | valid-metaclass-classmethod-first-arg=cls 357 | 358 | 359 | [DESIGN] 360 | 361 | # Maximum number of arguments for function / method 362 | max-args=5 363 | 364 | # Maximum number of attributes for a class (see R0902). 365 | max-attributes=7 366 | 367 | # Maximum number of boolean expressions in a if statement 368 | max-bool-expr=5 369 | 370 | # Maximum number of branch for function / method body 371 | max-branches=10 372 | 373 | # Maximum number of locals for function / method body 374 | max-locals=15 375 | 376 | # Maximum number of parents for a class (see R0901). 377 | max-parents=7 378 | 379 | # Maximum number of public methods for a class (see R0904). 380 | max-public-methods=20 381 | 382 | # Maximum number of return / yield for function / method body 383 | max-returns=6 384 | 385 | # Maximum number of statements in function / method body 386 | max-statements=50 387 | 388 | # Minimum number of public methods for a class (see R0903). 389 | min-public-methods=2 390 | 391 | 392 | [IMPORTS] 393 | 394 | # Allow wildcard imports from modules that define __all__. 395 | allow-wildcard-with-all=no 396 | 397 | # Analyse import fallback blocks. This can be used to support both Python 2 and 398 | # 3 compatible code, which means that the block might have code that exists 399 | # only in one or another interpreter, leading to false positives when analysed. 400 | analyse-fallback-blocks=no 401 | 402 | # Deprecated modules which should not be used, separated by a comma 403 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 404 | 405 | # Create a graph of external dependencies in the given file (report RP0402 must 406 | # not be disabled) 407 | ext-import-graph= 408 | 409 | # Create a graph of every (i.e. internal and external) dependencies in the 410 | # given file (report RP0402 must not be disabled) 411 | import-graph= 412 | 413 | # Create a graph of internal dependencies in the given file (report RP0402 must 414 | # not be disabled) 415 | int-import-graph= 416 | 417 | # Force import order to recognize a module as part of the standard 418 | # compatibility libraries. 419 | known-standard-library= 420 | 421 | # Force import order to recognize a module as part of a third party library. 422 | known-third-party= 423 | 424 | 425 | [EXCEPTIONS] 426 | 427 | # Exceptions that will emit a warning when being caught. Defaults to 428 | # "Exception" 429 | overgeneral-exceptions=Exception 430 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: python 4 | 5 | {%- if cookiecutter.python_version == "3.7" %} 6 | 7 | dist: xenial 8 | {%- endif %} 9 | 10 | python: 11 | - "{{ cookiecutter.python_version }}" 12 | 13 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 14 | 15 | env: 16 | global: 17 | - PIPENV_VENV_IN_PROJECT=1 18 | - PIPENV_IGNORE_VIRTUALENVS=1 19 | - PIPENV_DONT_LOAD_ENV=1 20 | {%- endif %} 21 | 22 | cache: 23 | - pip 24 | 25 | before_install: 26 | - pip install -U pip 27 | 28 | install: 29 | - make 30 | 31 | script: 32 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 33 | - pipenv run make check 34 | - pipenv run make test 35 | {%- else %} 36 | - make check 37 | - make test 38 | {%- endif %} 39 | 40 | after_success: 41 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 42 | - pipenv run make coverage 43 | {%- else %} 44 | - make coverage 45 | {%- endif %} 46 | - bash < (curl -s https://codecov.io/bash) 47 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Dockerfile: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.python_version == "3.7" -%} 2 | FROM python:3.7.3-alpine3.9 AS base 3 | {%- else -%} 4 | FROM python:3.6.8-alpine3.9 AS base 5 | {%- endif %} 6 | 7 | RUN pip install -U pip 8 | 9 | # Build Stage 10 | FROM base AS build 11 | 12 | RUN apk update && apk add --no-cache --upgrade \ 13 | build-base \ 14 | gcc 15 | 16 | WORKDIR /wheels 17 | COPY requirements.txt . 18 | 19 | RUN pip wheel -r requirements.txt 20 | 21 | # Execution Stage 22 | FROM base 23 | 24 | ENV PYTHONUNBUFFERED=1 25 | 26 | COPY --from=build /wheels /wheels 27 | 28 | RUN pip install -r /wheels/requirements.txt -f /wheels && \ 29 | rm -rf /wheels && \ 30 | rm -rf /root/.cache/pip/* 31 | 32 | WORKDIR /app 33 | 34 | COPY {{ cookiecutter.package_name }} {{ cookiecutter.package_name }} 35 | 36 | ENTRYPOINT ["python"] 37 | CMD ["-m", "{{ cookiecutter.package_name }}"] 38 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: init 2 | ## init: install requirements 3 | init: 4 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 5 | pip install pipenv 6 | pipenv install --dev 7 | {%- else %} 8 | pip install -r requirements.txt 9 | pip install -r requirements-dev.txt 10 | {%- endif %} 11 | 12 | .PHONY: check 13 | ## check: check if everything's okay 14 | check: 15 | isort --recursive --check-only {{ cookiecutter.package_name }} tests 16 | {%- if cookiecutter.use_black|lower != 'n' %} 17 | black -S -l 79 --check {{ cookiecutter.package_name }} tests 18 | {%- endif %} 19 | pylint {{ cookiecutter.package_name }} 20 | {%- if cookiecutter.use_mypy|lower != 'do not use' %} 21 | mypy {{ cookiecutter.package_name }} 22 | {%- endif %} 23 | 24 | .PHONY: format 25 | ## format: format files 26 | format: 27 | isort -rc -y {{ cookiecutter.package_name }} tests 28 | {%- if cookiecutter.use_black|lower != 'n' %} 29 | black -S -l 79 {{ cookiecutter.package_name }} tests 30 | {%- endif %} 31 | 32 | .PHONY: test 33 | ## test: run tests 34 | test: 35 | python -m pytest 36 | 37 | .PHONY: coverage 38 | ## coverage: run tests with coverage 39 | coverage: 40 | python -m pytest --cov {{ cookiecutter.package_name }} --cov-report term --cov-report xml 41 | 42 | .PHONY: htmlcov 43 | ## htmlcov: run tests with coverage and create coverage report HTML files 44 | htmlcov: 45 | python -m pytest --cov {{ cookiecutter.package_name }} --cov-report html 46 | rm -rf /tmp/htmlcov && mv htmlcov /tmp/ 47 | open /tmp/htmlcov/index.html 48 | 49 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 50 | 51 | .PHONY: requirements 52 | ## requirements: update requirements.txt 53 | requirements: 54 | pipenv lock -r > requirements.txt 55 | pipenv lock -dr > requirements-dev.txt 56 | {%- endif %} 57 | 58 | .PHONY: help 59 | ## help: prints this help message 60 | help: 61 | @echo "Usage: \n" 62 | @sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' 63 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | isort = "==4.3.20" 8 | {%- if cookiecutter.use_black|lower != 'n' %} 9 | black = "==19.3b0" 10 | {%- endif %} 11 | pylint = "==2.3.1" 12 | {%- if cookiecutter.use_mypy|lower != 'do not use' %} 13 | mypy = "==0.730" 14 | {%- endif %} 15 | pytest = "==4.6.3" 16 | pytest-cov = "==2.7.1" 17 | 18 | [packages] 19 | 20 | [requires] 21 | python_version = "{{ cookiecutter.python_version }}" 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.md: -------------------------------------------------------------------------------- 1 | # {{ cookiecutter.project_name }} 2 | 3 | [![Python Version: {{ cookiecutter.python_version }}](https://badgen.net/badge/python/{{ cookiecutter.python_version }}/blue)](https://docs.python.org/{{ cookiecutter.python_version }}/) 4 | 5 | {%- if cookiecutter.use_black|lower != 'n' -%} 6 |  [![Code Style: Black](https://badgen.net/badge/code%20style/black/black)](https://github.com/ambv/black) 7 | {%- endif %} 8 | 9 | {%- if cookiecutter.use_travis|lower == 'y' -%} 10 |  [![Build Status](https://badgen.net/badge/travis/passing/green)](https://travis-ci.com/) [![codecov](https://badgen.net/badge/coverage/100%25/green)](https://codecov.io/) 11 | 12 | {%- endif %} 13 | 14 | ## Getting Started 15 | 16 | 17 | 18 | ### Installation 19 | 20 | ```sh 21 | $ make 22 | $ ./bin/install_hooks.sh 23 | ``` 24 | 25 | ## Test 26 | 27 | ```sh 28 | $ make check 29 | $ make test 30 | ``` 31 | 32 | ## Requirements 33 | 34 | 35 | 36 | {%- if cookiecutter.use_pipenv|lower != 'n' %} 37 | * [Pipenv](https://github.com/pypa/pipenv) - 의존성 관리 38 | {%- endif %} 39 | 40 | ## Related Documents 41 | 42 | 43 | 44 | ## License 45 | 46 | 47 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/bin/install_hooks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PROJECT_DIR=$(git rev-parse --show-toplevel) 3 | 4 | ln -s $PROJECT_DIR/bin/pre-push $PROJECT_DIR/.git/hooks/pre-push 5 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/bin/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | isort --recursive --check-only {{ cookiecutter.package_name }} tests 4 | if [ $? -ne 0 ]; then 5 | echo "[!] isort failed! Run 'isort -rc -y {{ cookiecutter.package_name }} tests'" 6 | exit 1 7 | fi 8 | echo "[+] isort success!" 9 | 10 | {% if cookiecutter.use_black|lower == 'y' -%} 11 | black -S -l 79 --check {{ cookiecutter.package_name }} tests 12 | if [ $? -ne 0 ]; then 13 | echo "[!] black failed! Run 'black -S -l 79 {{ cookiecutter.package_name }} tests'" 14 | exit 1 15 | fi 16 | echo "[+] black success!" 17 | {%- endif %} 18 | 19 | pylint {{ cookiecutter.package_name }} 20 | if [ $? -ne 0 ]; then 21 | echo "[!] pylint failed! Please fix code before push" 22 | exit 1 23 | fi 24 | echo "[+] pylint success!" 25 | 26 | {% if cookiecutter.use_mypy|lower != 'do not use' -%} 27 | mypy {{ cookiecutter.package_name }} 28 | if [ $? -ne 0 ]; then 29 | echo "[!] mypy failed! Please fix code before push" 30 | exit 1 31 | fi 32 | echo "[+] mypy success!" 33 | {%- endif %} 34 | 35 | exit 0 36 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | python_version = {{ cookiecutter.python_version }} 3 | ignore_missing_imports = True 4 | {%- if cookiecutter.use_mypy|lower == "beginner" %} 5 | check_untyped_defs = False 6 | disallow_untyped_defs = False 7 | {%- else %} 8 | check_untyped_defs = True 9 | disallow_untyped_defs = True 10 | {%- endif %} 11 | disallow_any_generics = True 12 | warn_no_return = True 13 | no_implicit_optional = True 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.use_black|lower == 'y' -%} 2 | black==19.3b0 3 | {% endif -%} 4 | {% if cookiecutter.use_mypy|lower != 'do not use' -%} 5 | mypy==0.730 6 | {% endif -%} 7 | 8 | codecov==2.0.15 9 | isort==4.3.20 10 | pylint==2.3.1 11 | pytest==4.6.3 12 | pytest-cov==2.7.1 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banksalad/python/a6220453cb3d550d5c4a6a9297781dbb250e4b69/{{cookiecutter.project_slug}}/requirements.txt -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banksalad/python/a6220453cb3d550d5c4a6a9297781dbb250e4b69/{{cookiecutter.project_slug}}/tests/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.package_name}}/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banksalad/python/a6220453cb3d550d5c4a6a9297781dbb250e4b69/{{cookiecutter.project_slug}}/{{cookiecutter.package_name}}/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.package_name}}/__main__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banksalad/python/a6220453cb3d550d5c4a6a9297781dbb250e4b69/{{cookiecutter.project_slug}}/{{cookiecutter.package_name}}/__main__.py --------------------------------------------------------------------------------