$30 off During Our Annual Pro Sale. View Details »

Python Table Manners- Cut the Cookie Gracefully @ Euro Python 2020

Python Table Manners- Cut the Cookie Gracefully @ Euro Python 2020

### Goals
I expect the audiences to gain knowledge of the tools I mention and the primary usage of them. The tools will cover various aspects of software engineering (e.g., dependencies, testing, security, etc.). Also, I'll purpose how I combine all these tools in my development workflow as a sample for how the audiences can integrate these tools into their workflow.

### Outline for 30 minutes
* Dependency Management (4 min)
* Testing - Don't let your customer debug for you (4 min)
* Style Check and auto-fix (4 min)
* Task Management - No more repetitive typing (3 min)
* pre-commit - Prevent committing bad code into codebase (3 min)
* commitizen-tool - How good commit message can help (4 min)
* Security (3 min)
* Cookiecutter - Wrap up all the tools (3 min)
* Q & A (2 min)

### Outline for 45 minutes
* Dependency Management (5 min)
* Testing - Don't let your customer debug for you (5 min)
* Style Check and auto-fix (5 min)
* Task Management - No more repetitive typing (5 min)
* pre-commit - Prevent committing bad code into codebase (5 min)
* commitizen-tool - How good commit message can help (5 min)
* Security (5 min)
* Continuous Integration - Assemble all the trivial steps (5 min)
* Cookiecutter - Wrap up all the tools (3 min)
* Q & A (2 min)

Lee Wei

July 21, 2020
Tweet

More Decks by Lee Wei

Other Decks in Programming

Transcript

  1. @clleew
    Python Table Manners:
    Cut the Cookie Gracefully

    A Guideline Toward Cleaner Code

    View Slide

  2. @clleew
    $ cat speaker.py
    __name__ = "Wei Lee / 李唯"
    __position__ = [
    "Software Engineer @ Rakuten Slice",
    "Volunteer @ PyCon Taiwan",
    "Maintainer of commitizen-tools",
    ]
    __twitter__ = " @clleew"
    __github__ = " Lee-W"
    __blog__ = " http://lee-w.github.io"

    View Slide

  3. @clleew
    Outline
    • Clean up the table - Dependency Management
    • Put the correct tablewares - Testing
    • Use the tablewares elegantly - Coding Style
    • Mnemonic phrase - Task management
    • Say please! - Check before git operation
    • Speak formally - Cultivate a git commit convention
    • Safety matters - Security Issues
    • Where's the Cookie? - Project template

    View Slide

  4. @clleew
    Dependency Management
    Clean up the table

    View Slide

  5. @clleew
    How we used to start
    a Python project

    View Slide

  6. @clleew
    Sometimes we just forgot to…
    • ... activate / deactivate virtual environment
    • ... add package into requirements.txt

    View Slide

  7. @clleew
    Pipenv
    Python Dev Workflow for Humans
    • manage virtual environment + package
    → update at once
    → No more manual sync up!
    • generate hashes from packages
    → ensure getting the same packages next time

    View Slide

  8. @clleew
    Initial Virtual Environment

    View Slide

  9. @clleew
    Empty Pipfile
    [[source]]
    name = "pypi"
    url = "https://pypi.org/simple"
    verify_ssl = true
    [dev-packages]
    [packages]
    [requires]
    python_version = "3.7"

    View Slide

  10. @clleew
    Install Package

    View Slide

  11. @clleew
    Pipfile with “requests”
    [[source]]
    name = "pypi"
    url = "https://pypi.org/simple"
    verify_ssl = true
    [dev-packages]
    [packages]
    requests = "*"
    [requires]
    python_version = "3.7"
    [packages]
    requests = "*"

    View Slide

  12. @clleew
    "requests": {
    "hashes": [
    "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
    "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
    ],
    "index": "pypi",
    "version": "==2.22.0"
    },
    {
    ......
    "default": {
    ......
    "requests": {
    "hashes": [
    "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
    "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
    ],
    "index": "pypi",
    "version": "==2.22.0"
    },
    "urllib3": {
    "hashes": [
    "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
    "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
    ],
    "version": "==1.25.6"
    }
    },
    ......
    }
    Pipfile.lock with “requests”

    View Slide

  13. @clleew
    "requests": {
    "hashes": [
    "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
    "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
    ],
    "index": "pypi",
    "version": "==2.22.0"
    },
    {
    ......
    "default": {
    ......
    "requests": {
    "hashes": [
    "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
    "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
    ],
    "index": "pypi",
    "version": "==2.22.0"
    },
    "urllib3": {
    "hashes": [
    "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
    "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
    ],
    "version": "==1.25.6"
    }
    },
    ......
    }
    Pipfile.lock with “requests”
    You’re guaranteed to install the
    exact same package next time.

    View Slide

  14. @clleew

    View Slide

  15. @clleew
    Install package only in dev

    View Slide

  16. @clleew
    Pipfile with “pytest"
    [[source]]
    name = "pypi"
    url = "https://pypi.org/simple"
    verify_ssl = true
    [dev-packages]
    pytest = "*"
    [packages]
    requests = "*"
    [requires]
    python_version = "3.7"
    [dev-packages]
    pytest = "*"

    View Slide

  17. @clleew
    Run in virtual environment

    View Slide

  18. @clleew
    Some people might say
    that pipenv ...
    • ... does not update frequently.

    • ... locks slowly.

    • ... does not syncs up with install_requires in setup.py

    View Slide

  19. @clleew
    Poetry

    View Slide

  20. @clleew
    Poetry commands

    View Slide

  21. @clleew
    Testing
    Put the correct tablewares

    View Slide

  22. @clleew
    unittest
    Unit testing framework in Python Standard Library

    View Slide

  23. @clleew
    pytest

    View Slide

  24. @clleew
    Why pytest
    • More Pythonic

    • Compatible with old unittest tests

    • No more assert.+ (e.g., assertEqual, assertTrue, etc.)

    → just assert

    • Better test discovery

    • Advance features: mark, parameterize, etc.

    • Plenty of plugins

    View Slide

  25. @clleew
    Run pytest

    View Slide

  26. @clleew
    Run pytest

    View Slide

  27. @clleew
    always run python inside a virtual environment

    View Slide

  28. @clleew
    unittest style tests
    import unittest
    from atta.partner import sponsor
    class TestSponsor(unittest.TestCase):
    def setUp(self):
    sponsors = sponsor.get_all_sponsors('./data/packages.yaml',
    './data/sponsors.yaml')
    self.sponsors = sponsors
    ......
    def test_sponsor_number(self):
    self.assertEqual(len(self.sponsors), 1)
    .......

    View Slide

  29. @clleew
    unittest style tests
    import unittest
    from atta.partner import sponsor
    class TestSponsor(unittest.TestCase):
    def setUp(self):
    sponsors = sponsor.get_all_sponsors('./data/packages.yaml',
    './data/sponsors.yaml')
    self.sponsors = sponsors
    ......
    def test_sponsor_number(self):
    self.assertEqual(len(self.sponsors), 1)
    .......
    Prepare all the data needed in test cases

    View Slide

  30. @clleew
    unittest style tests
    import unittest
    from atta.partner import sponsor
    class TestSponsor(unittest.TestCase):
    def setUp(self):
    sponsors = sponsor.get_all_sponsors('./data/packages.yaml',
    './data/sponsors.yaml')
    self.sponsors = sponsors
    ......
    def test_sponsor_number(self):
    self.assertEqual(len(self.sponsors), 1)
    .......

    View Slide

  31. @clleew
    unittest style tests
    import unittest
    from atta.partner import sponsor
    class TestSponsor(unittest.TestCase):
    def setUp(self):
    sponsors = sponsor.get_all_sponsors('./data/packages.yaml',
    './data/sponsors.yaml')
    self.sponsors = sponsors
    ......
    def test_sponsor_number(self):
    self.assertEqual(len(self.sponsors), 1)
    .......

    View Slide

  32. @clleew
    pytest style tests
    import pytest
    from report_generator.partner import sponsor
    class TestSponsor:
    @pytest.fixture(scope="class")
    def sponsors(self):
    return sponsor.get_all_sponsors("test/data/packages.yaml",
    “test/data/sponsors.yaml")
    ......
    def test_sponsor_number(self, sponsors):
    assert len(sponsors) == 1
    ......

    View Slide

  33. @clleew
    pytest style tests
    import pytest
    from report_generator.partner import sponsor
    class TestSponsor:
    @pytest.fixture(scope="class")
    def sponsors(self):
    return sponsor.get_all_sponsors("test/data/packages.yaml",
    “test/data/sponsors.yaml")
    ......
    def test_sponsor_number(self, sponsors):
    assert len(sponsors) == 1
    ......
    Prepare the data needed in separate fixtures

    View Slide

  34. @clleew
    pytest style tests
    import pytest
    from report_generator.partner import sponsor
    class TestSponsor:
    @pytest.fixture(scope="class")
    def sponsors(self):
    return sponsor.get_all_sponsors("test/data/packages.yaml",
    “test/data/sponsors.yaml")
    ......
    def test_sponsor_number(self, sponsors):
    assert len(sponsors) == 1
    ......

    View Slide

  35. @clleew
    pytest style tests
    import pytest
    from report_generator.partner import sponsor
    @pytest.fixture(scope="function")
    def sponsors():
    return sponsor.get_all_sponsors("test/data/packages.yaml",
    "test/data/sponsors.yaml")
    def test_sponsor_number(sponsors):
    assert len(sponsors) == 1

    View Slide

  36. @clleew
    pytest style tests
    import pytest
    from report_generator.partner import sponsor
    @pytest.fixture(scope="function")
    def sponsors():
    return sponsor.get_all_sponsors("test/data/packages.yaml",
    "test/data/sponsors.yaml")
    def test_sponsor_number(sponsors):
    assert len(sponsors) == 1

    View Slide

  37. @clleew
    Configuration - pytest.ini
    [pytest]
    addopts = --strict-markers
    norecursedirs = .* build dist CVS _darcs {arch} *.egg venv env
    virtualenv
    Configure through pyproject.toml will be when 6.0 released #7247

    View Slide

  38. @clleew
    Pytest - powerful plugins
    • pytest-mock: Replace objects that are hard to test with
    fake objects

    • pytest-cov: Generate test coverage report

    • pytest-xdist: Distributed testing

    • and etc.

    View Slide

  39. @clleew
    Coding Style
    Use the tablewares elegantly

    View Slide

  40. @clleew
    flake8
    • Enforcing style consistency across Python project

    • Check possible errors before running your program

    • Eliminate bad style

    View Slide

  41. @clleew
    import os
    os = "My Operating system"
    Example - bad_code.py

    View Slide

  42. @clleew
    import os
    os = "My Operating system"
    redefinition of imported library

    (possible error)
    Example - bad_code.py

    View Slide

  43. @clleew
    import os
    os = "My Operating system"
    additional space

    (bad coding style)
    redefinition of imported library

    (possible error)
    Example - bad_code.py

    View Slide

  44. @clleew
    Run flake8

    View Slide

  45. @clleew
    Configuration - setup.cfg
    [flake8]
    ignore =
    # F632: use ==/!= to compare str, bytes, and int literals
    F632,
    # W503: Line break occurred before a binary operator
    W503,
    # E501: Line too long
    E501,
    # E203: Whitespace before ':' (for black)
    E203
    exclude =
    .git,
    __pycache__,
    build,
    dist
    max-line-length = 88

    View Slide

  46. @clleew
    Pylint

    View Slide

  47. @clleew
    import os
    os = "My Operating system"
    additional space

    (bad coding style)
    redefinition of imported library

    (possible error)
    Example - bad_code.py

    View Slide

  48. @clleew
    Run pylint

    View Slide

  49. @clleew
    Run pylint with "-r" argument
    A bunch of reports that 

    you can compare 

    with your previous run

    View Slide

  50. Configuration - pyproject.toml
    [tool.pylint.messages_control]
    disable = [
    "bad-continuation",
    "missing-function-docstring",
    "missing-module-docstring",
    "invalid-name"
    ]
    [tool.pylint.format]
    max-line-length = 88

    View Slide

  51. @clleew
    mypy
    • Static type checker

    • Compile-time type checking

    → avoid possible runtime errors

    • Type annotation enhances readability

    → machine-checked documentation

    View Slide

  52. from typing import List
    def func(vals: List[str]):
    for val in vals:
    assert isinstance(val, str)
    func([1, 2, 3])
    Example - error_type.py
    List[str]
    [1, 2, 3]

    View Slide

  53. from typing import List
    def func(vals: List[str]):
    for val in vals:
    assert isinstance(val, str)
    func([1, 2, 3])
    Example - error_type.py
    List[str]
    [1, 2, 3]

    View Slide

  54. Run mypy

    View Slide

  55. Run mypy
    all the files with .py extension

    View Slide

  56. Run mypy
    ignore errors that libs that is not type annotated

    View Slide

  57. @clleew
    Configuration - setup.cfg
    [mypy]
    files = **/*.py
    ignore_missing_imports = true
    follow_imports = silent
    warn_redundant_casts = True
    warn_unused_ignores = True
    warn_unused_configs = True
    [mypy]
    files = **/*.py
    ignore_missing_imports = true

    View Slide

  58. @clleew
    Configuration - setup.cfg
    [mypy]
    files = **/*.py
    ignore_missing_imports = true
    follow_imports = silent
    warn_redundant_casts = True
    warn_unused_ignores = True
    warn_unused_configs = True
    [mypy]
    files = **/*.py
    ignore_missing_imports = true

    View Slide

  59. Run mypy with configuration

    View Slide

  60. @clleew
    One step forward
    Fix the style automatically

    View Slide

  61. Black

    View Slide

  62. Run Black

    View Slide

  63. How Black reformat

    View Slide

  64. How Black reformat

    View Slide

  65. Why Black?
    The black code style is not configurable.

    View Slide

  66. Why Black?
    The black code style is not configurable.
    No more arguing about which style is better

    View Slide

  67. –The Zen of Python, by Tim Peters
    “There should be one-- and preferably only
    one --obvious way to do it.”

    View Slide

  68. @clleew
    Configuration - pyproject.toml
    [tool.black]
    line-length = 88
    include = '\.pyi?$'
    exclude = '''
    /(
    \.eggs
    | \.git
    | \.hg
    | \.mypy_cache
    | \.tox
    | \.venv
    | _build
    | buck-out
    | build
    | dist
    )/
    '''

    View Slide

  69. Isort

    View Slide

  70. Example
    wrong_import_order.py
    import django
    import os
    import some_custom_package
    import flask
    import datetime
    ...

    View Slide

  71. According to PEP8
    • Imports should be grouped in the following order:
    1. Standard library imports.
    2. Related third party imports.
    3. Local application/library specific imports.
    • You should put a blank line between each group
    of imports.

    View Slide

  72. Run Isort

    View Slide

  73. Run Isort
    import datetime
    import os
    import django
    import flask
    import some_custom_package
    ...

    View Slide

  74. @clleew
    Configuration - pyproject.toml
    [tool.isort]
    line_length = 88
    multi_line_output = 3
    include_trailing_comma = true
    force_grid_wrap = 0
    use_parentheses = true

    View Slide

  75. Style formatting and linting

    View Slide

  76. View Slide

  77. @clleew
    Task Management
    Mnemonic phrase

    View Slide

  78. Invoke
    • Task execution tool that can manage your
    commands
    • It’s like a Makefile but in Python.

    View Slide

  79. Invoke in practice (rg-cli)

    View Slide

  80. Before invoke in rg-cli

    View Slide

  81. Before invoke in rg-cli

    View Slide

  82. After invoke in rg-cli

    View Slide

  83. List all commands

    View Slide

  84. How ? - tasks.py
    from invoke import task
    VENV_PREFIX = "pipenv run"
    ......
    @task
    def install(cmd):
    """Install script in pipenv environment"""
    cmd.run(f"{VENV_PREFIX} python setup.py install")

    View Slide

  85. How ? - tasks.py
    from invoke import task
    VENV_PREFIX = "pipenv run"
    ......
    @task
    def install(cmd):
    """Install script in pipenv environment"""
    cmd.run(f"{VENV_PREFIX} python setup.py install")

    View Slide

  86. How ? - tasks.py
    from invoke import task
    VENV_PREFIX = "pipenv run"
    ......
    @task
    def install(cmd):
    """Install script in pipenv environment"""
    cmd.run(f"{VENV_PREFIX} python setup.py install")

    View Slide

  87. Modulize - Namespace

    View Slide

  88. Autocomplete

    View Slide

  89. Autocomplete

    View Slide

  90. Autocomplete

    View Slide

  91. Why not use Makefile?
    1.We’re Python developers!
    2.Some tasks might not be easy to handle through
    shell script.
    3.Combining Python and shell script

    View Slide

  92. @clleew
    Check before git operation
    Say please!

    View Slide

  93. @clleew
    pre-commit
    • Why?

    • Sometimes we still forget to run the check commands.

    • Prevent committing bad code into codebase

    • How?

    • pre-commit runs these commands before we do git
    operations (e.g., git push, git commit)

    View Slide

  94. @clleew
    Config (customized hooks)
    .pre-commit-config.yaml
    repos:
    - repo: local
    hooks:
    - id: style-reformat
    name: style-reformat
    stages: [commit]
    language: system
    pass_filenames: false
    entry: inv style.reformat
    types: [python]
    - id: style-check
    name: style-check
    stages: [push]
    language: system
    pass_filenames: false
    entry: inv style
    types: [python]
    ......

    View Slide

  95. @clleew
    Config (customized hooks)
    .pre-commit-config.yaml
    repos:
    - repo: local
    hooks:
    - id: style-reformat
    name: style-reformat
    stages: [commit]
    language: system
    pass_filenames: false
    entry: inv style.reformat
    types: [python]
    - id: style-check
    name: style-check
    stages: [push]
    language: system
    pass_filenames: false
    entry: inv style
    types: [python]
    ......
    entry: inv style.reformat
    - id: style-reformat
    stages: [commit]

    View Slide

  96. @clleew
    Config (customized hooks)
    .pre-commit-config.yaml
    repos:
    - repo: local
    hooks:
    - id: style-reformat
    name: style-reformat
    stages: [commit]
    language: system
    pass_filenames: false
    entry: inv style.reformat
    types: [python]
    - id: style-check
    name: style-check
    stages: [push]
    language: system
    pass_filenames: false
    entry: inv style
    types: [python]
    ......
    stages: [push]
    - id: style-check

    View Slide

  97. @clleew
    Config (existing hooks)
    .pre-commit-config.yaml
    ......
    - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v3.1.0
    hooks:
    - id: end-of-file-fixer
    - id: trailing-whitespace
    args: [--markdown-linebreak-ext=md]

    View Slide

  98. @clleew
    Setup pre-commit

    View Slide

  99. @clleew
    Run pre-commit

    View Slide

  100. @clleew
    Run pre-commit

    View Slide

  101. @clleew
    Cultivate
    a git commit convention
    Speak formally

    View Slide

  102. View Slide

  103. Hard to find to the right version

    View Slide

  104. commitizen-tools

    View Slide

  105. Commit - cz commit

    View Slide

  106. Commit - cz commit

    View Slide

  107. Commit - cz commit

    View Slide

  108. Commit - cz commit

    View Slide

  109. Commit - cz commit

    View Slide

  110. Commit - cz commit

    View Slide

  111. Commit - cz commit

    View Slide

  112. Standardized git commits

    View Slide

  113. Standardized git commits

    View Slide

  114. Other Commitizen features
    • pre-commit hook prevents you from not using
    commitizen
    • customizable commit rules
    • auto bump project version (SemVar)
    • auto generate changelog (Keep a Changelog)

    View Slide

  115. Let’s Sprint!

    View Slide

  116. Let’s Sprint!

    View Slide

  117. @clleew
    Security Issues
    Safety matters

    View Slide

  118. View Slide

  119. View Slide

  120. Safety
    Dependency Checking

    View Slide

  121. Check vulnerability
    in current environment

    View Slide

  122. Check vulnerability
    in current environment

    View Slide

  123. But if you use pipenv …

    View Slide

  124. But if you use pipenv …

    View Slide

  125. How does Safety work?
    • Search the vulnerability in CVS
    (Common Vulnerabilities and Exposures)
    • Free DB: update monthly
    • Paid DB: update realtime

    View Slide

  126. Bandit
    Static Analysis

    View Slide

  127. Check Common Security
    Issue in Python Code

    View Slide

  128. Check Common Security
    Issue in Python Code

    View Slide

  129. Check Common Security
    Issue in Python Code

    View Slide

  130. Check Common Security
    Issue in Python Code

    View Slide

  131. Check Common Security
    Issue in Python Code

    View Slide

  132. Not all the warnings
    should be fixed
    • Add exclude into your bandit configuration
    • Add # nosec after the end of the code to skip
    the warning

    View Slide

  133. @clleew

    View Slide

  134. @clleew
    Project template
    Where's the Cookie?

    View Slide

  135. @clleew
    We want these manners in all our Python projects ❗

    View Slide

  136. @clleew
    We want these manners in all our Python projects ❗
    But... we don't want to configure it everytime ❗

    View Slide

  137. @clleew
    Create a project template once
    and initialize projects through it

    View Slide

  138. @clleew
    Lee-W / cookiecutter-python-template

    View Slide

  139. @clleew

    View Slide

  140. @clleew

    View Slide

  141. @clleew

    View Slide

  142. @clleew

    View Slide

  143. @clleew

    View Slide

  144. @clleew
    How to make a template?
    cookiecutter.json

    View Slide

  145. @clleew
    How to make a template?
    cookiecutter.json

    View Slide

  146. @clleew
    Template structure
    Cookiecutter configuration
    Template (Use Jinja)

    View Slide

  147. @clleew
    An example in template
    {{cookiecutter.project_slug}}/tasks/env.py
    from invoke import task
    from tasks.common import VENV_PREFIX
    @task
    def init(ctx):
    """Install production dependencies"""
    {% if cookiecutter.dependency_management_tool == 'pipenv' -%}
    ctx.run("pipenv install --deploy")
    {%- elif cookiecutter.dependency_management_tool == 'poetry' -%}
    ctx.run("poetry install --no-dev")
    {%- endif %}
    {% if cookiecutter.dependency_management_tool == 'pipenv' -%}
    ctx.run("pipenv install --deploy")

    View Slide

  148. @clleew
    An example in template
    {{cookiecutter.project_slug}}/tasks/env.py
    from invoke import task
    from tasks.common import VENV_PREFIX
    @task
    def init(ctx):
    """Install production dependencies"""
    {% if cookiecutter.dependency_management_tool == 'pipenv' -%}
    ctx.run("pipenv install --deploy")
    {%- elif cookiecutter.dependency_management_tool == 'poetry' -%}
    ctx.run("poetry install --no-dev")
    {%- endif %}
    {% if cookiecutter.dependency_management_tool == 'pipenv' -%}
    ctx.run("pipenv install --deploy")

    View Slide

  149. @clleew
    An example in template
    {{cookiecutter.project_slug}}/tasks/env.py
    from invoke import task
    from tasks.common import VENV_PREFIX
    @task
    def init(ctx):
    """Install production dependencies"""
    {% if cookiecutter.dependency_management_tool == 'pipenv' -%}
    ctx.run("pipenv install --deploy")
    {%- elif cookiecutter.dependency_management_tool == 'poetry' -%}
    ctx.run("poetry install --no-dev")
    {%- endif %}
    {%- elif cookiecutter.dependency_management_tool == 'poetry' -%}
    ctx.run("poetry install --no-dev")
    {%- endif %}

    View Slide

  150. @clleew
    import os
    def remove_pipfile():
    os.remove("Pipfile")
    def main():
    if "{{ cookiecutter.dependency_management_tool }}" != "pipenv":
    remove_pipfile()
    if __name__ == "__main__":
    main()
    Run before/after project is generated
    hooks/post_gen_project.py

    View Slide

  151. @clleew
    Lee-W / cookiecutter-python-template

    View Slide

  152. @clleew

    View Slide

  153. Other Tools
    • Testing
    • Hypothesis
    • nox
    • Documentation
    • Mkdocs
    • Sphinx

    View Slide

  154. @clleew
    Related Talks
    • Dependency Management
    • Tzu-ping Chung - 這樣的開發環境沒問題嗎? (PyCon TW 2018)
    • Kenneth Reitz - Pipenv: The Future of Python Dependency
    Management (PyCon US 2018)
    • Patrick Muehlbauer - Python Dependency Management (PyCon DE
    2018)
    • Test
    • Chun-Yu Tseng - 快快樂樂成為 Coding Ninja (by pytest) (PyCon APAC
    2015)
    • Florian Bruhin – Pytest: Rapid Simple Testing (Swiss Python Summit 16)

    View Slide

  155. @clleew
    Related Talks
    • Style Check
    • Kyle Knapp - Automating Code Quality (PyCon US 2018)
    • Łukasz Langa - Life Is Better Painted Black, or: How to Stop Worrying and Embrace Auto-Formatting
    (PyCon US 2019)
    • Raymond Hettinger - Beyond PEP 8 -- Best practices for beautiful intelligible code (PyCon 2015)
    • Static Typing
    • Dustin Ingram - Static Typing in Python (PyCon US 2020)
    • Task Managements
    • Thea Flowers - Break the Cycle: Three excellent Python tools to automate repetitive tasks (PyCon US
    2019)
    • Security
    • Terri Oda - Python Security Tools (PyCon US 2019)
    • Tennessee Leeuwenburg - Watch out for Safety Bandits! (PyCon AU 2018)

    View Slide

  156. @clleew
    References
    • pipenv

    • poetry

    • flake8

    • pylint

    • mypy

    • black

    • isort
    • invoke

    • pre-commit

    • commitizen-tools

    • safety

    • bandit

    • cookiecutter

    View Slide

  157. @clleew

    View Slide

  158. @clleew
    Let's chat on discord

    View Slide