Skip to content

Commit

Permalink
Add python project
Browse files Browse the repository at this point in the history
  • Loading branch information
code-yeongyu committed Mar 5, 2023
1 parent 6e04f10 commit 3ee1ef2
Show file tree
Hide file tree
Showing 10 changed files with 1,880 additions and 0 deletions.
Empty file added .type_stubs/.gitkeep
Empty file.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Python Poetry Template

This template is designed to make it easy to set up a Python project that is well-structured, organized, and easy to maintain. It comes pre-configured with several tools that will help you develop your project more efficiently, including:

- Visual Studio Code integration: with the `RunOnSave`, `even-better-toml` and `ruff` extensions installed, you can format, lint, and type-check your code automatically every time you save a file.
- Type checking: this template is configured to use `mypy` and `pyright` to automatically infer types when possible, without imposing strict typing requirements on your code.
- Linting: `ruff`, an extremely fast Python linter, written in Rust is configured well to help you catch and fix code style issues.
- Formatting: `yapf`, `ruff`, and `unify` are configured to help you keep your code clean and well-organized.
- Testing: `pytest` is configured to make it easy to run tests, and `pytest-cov` is configured to help you measure code coverage.
- Dependency management: `poetry` is configured to help you manage your project's dependencies.
- Toolkits: `invoke` is configured to provide a range of useful tasks, such as running your code, running tests, formatting your code, and checking your code style and types. These tasks are fully configurable in the [tasks.py](tasks.py) file.

## Installation

To install this template, simply follow these steps:

```sh
git clone [email protected]:code-yeongyu/Python-Poetry-Template.git
cd Python-Poetry-Template
poetry install
code --install-extension emeraldwalk.RunOnSave
code --install-extension tamasfe.even-better-toml
code --install-extension charliermarsh.ruff
```

## Usage

To use this template, you can follow these steps:

### Open Shell

To open a shell in the project directory, use the following command:

```sh
poetry shell
```

### Name your project

```sh
invoke rename-project <your-project-name>
```

### Run Code

To run your code, use the following command:

```sh
poetry run invoke run
```

### Run Tests

To run your tests, use the following command:

```sh
poetry run invoke test
```

### Run Formatters

To run the code formatters, use the following command:

```sh
poetry run invoke format_code
```

### Run Checking Code Style & Type hint

To check your code style and type hints, use the following command:

```sh
poetry run invoke check
```
43 changes: 43 additions & 0 deletions monkey_patch_invoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import inspect
import types
from typing import Any, Callable, Dict, List, Tuple, Union

from invoke.context import Context
from invoke.tasks import NO_DEFAULT, Task


def monkey_patch_invoke() -> None:

def _patched_argspec(
self: Any, # pylint: disable=unused-argument
body: Union[Callable[[Context], None], Context],
) -> Tuple[List[str], Dict[str, object]]:
"""
A monkey patching code for supporting python3
from: https://github.com/pyinvoke/invoke/issues/357#issuecomment-1250744013
"""
signature: inspect.Signature = inspect.Signature()
if isinstance(body, types.FunctionType):
signature = inspect.signature(body)
elif isinstance(body, types.MethodType):
signature = inspect.signature(body.__call__)

parameter_names = [name for name, _ in signature.parameters.items()]
argument_specs: dict[str, object] = {}
for key, value in signature.parameters.items():
value = value.default if not value.default == signature.empty else NO_DEFAULT
argument_specs[key] = value

# Pop context argument
try:
context_arg = parameter_names.pop(0)
except IndexError as error:
raise TypeError('Tasks must have an initial Context argument!') from error

del argument_specs[context_arg]
return parameter_names, argument_specs

Task.argspec = _patched_argspec


monkey_patch_invoke()
1,573 changes: 1,573 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions poetry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[virtualenvs]
create = true
in-project = true
66 changes: 66 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "ygka"
version = "0.0.0"
description = ""
authors = []
readme = "README.md"

[tool.pyright]
typeCheckingMode = "strict"

# Python environment settings
pythonPlatform = "All"
venvPath = "./.venv"
stubPath = "./.type_stubs"
include = ["./*"]
exclude = ["**/node_modules", "**/__pycache__"]

# For untyped modules
useLibraryCodeForTypes = true # Attempt to read and infer types from third-party modules if no stub files are present
reportMissingTypeStubs = false # Ignore errors from modules without type stubs
reportUnknownMemberType = false # Ignore errors from untyped function calls in third-party modules
reportUnknownVariableType = false # Ignore errors from untyped function calls in third-party modules

pythonVersion = "3.9"

[tool.yapf]
based_on_style = "pep8"
spaces_before_comment = 2
split_before_logical_operator = true
column_limit = 119
allow_split_before_dict_value = false

[tool.ruff]
line-length = 119
select = ["PLE", "PLR", "PLW", "E", "W", "F", "I", "Q", "C", "B"]

[tool.pytest.ini_options]
addopts = ["--cov=ygka"]


[tool.poetry.dependencies]
python = "^3.9"
poetry = "^1.4.0"

[tool.poetry.scripts]
ygka = "ygka:main"

[tool.ruff.flake8-quotes]
inline-quotes = "single"
docstring-quotes = "single"
multiline-quotes = "single"

[tool.poetry.group.dev.dependencies]
toml = "^0.10.2"
yapf = "^0.32.0"
pytest = "^7.2.2"
pytest-cov = "^4.0.0"
invoke = "^1.7.3"
ruff = "^0.0.199"
types-invoke = "^1.7.3.16"
types-toml = "^0.10.8.5"
pyright = "^1.1.296"
115 changes: 115 additions & 0 deletions tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# type: ignore
import shutil

import toml
from invoke import Context, task
from invoke.exceptions import UnexpectedExit

import monkey_patch_invoke as _ # noqa: F401


def get_pep8_compliant_name(project_name: str) -> str:
return project_name.replace('-', '_')


def get_project_path():
with open('pyproject.toml', encoding='utf-8') as file:
data = toml.load(file)
project_name = get_pep8_compliant_name(data['tool']['poetry']['name'])
return project_name


@task
def run(context: Context):
context.run(f'python -m {get_project_path()}', pty=True)


@task
def test(context: Context):
context.run('pytest . --cov=. --cov-report=xml', pty=True)


@task
def format_code(context: Context, verbose: bool = False):
commands = [
f'pautoflake {get_project_path()}',
f'ruff --fix {get_project_path()}',
f'yapf --in-place --recursive --parallel {get_project_path()}',
]

for command in commands:
context.run(command, pty=True)


@task
def check(context: Context):
check_code_style(context)
check_types(context)


@task
def check_code_style(context: Context):
commands = [
f'ruff {get_project_path()}',
f'yapf --diff --recursive --parallel {get_project_path()}',
]

for command in commands:
context.run(command, pty=True)


@task
def check_types(context: Context):
context.run(f'pyright {get_project_path()}', pty=True)


@task
def rename_project(_context: Context, project_name: str):
# Get the current project path
current_project_path = get_project_path()

# Rename the directory
shutil.move(get_pep8_compliant_name(current_project_path), get_pep8_compliant_name(project_name))

# Update the project name in pyproject.toml
with open('pyproject.toml', 'r', encoding='utf-8') as file:
data = toml.load(file)

data['tool']['poetry']['name'] = project_name

with open('pyproject.toml', 'w', encoding='utf-8') as file:
toml.dump(data, file)


@task
def release(context: Context, version: str) -> None:
'''Build & Publish to PyPI.'''

# load pyproject
pyproject_path = 'pyproject.toml'
pyproject_string = ''
with open(pyproject_path, 'r', encoding='utf-8') as pyproject_file:
pyproject_string = pyproject_file.read()
pyproject = toml.loads(pyproject_string)
# change version to today datetime
pyproject['tool']['poetry']['version'] = version
with open(pyproject_path, 'w', encoding='utf-8') as pyproject_file:
toml.dump(pyproject, pyproject_file)

# build & publish
try:
context.run(
'''
poetry build --no-interaction
poetry publish --no-interaction
''',
pty=True,
)
except UnexpectedExit as exception:
with open(pyproject_path, 'w', encoding='utf-8') as pyproject_file:
pyproject_file.write(pyproject_string)
raise exception from exception

# recover to original
with open(pyproject_path, 'w', encoding='utf-8') as pyproject_file:
pyproject_file.write(pyproject_string)
2 changes: 2 additions & 0 deletions ygka/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def main():
print('Hello, World!')
4 changes: 4 additions & 0 deletions ygka/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import ygka

if __name__ == '__main__': # to make the project executable by `python3 -m ygka <query>`
ygka.main()
Empty file added ygka/tests/__init__.py
Empty file.

0 comments on commit 3ee1ef2

Please sign in to comment.