diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..f5036b7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,13 @@ +# CODEOWNERS +# +# This file specifies which people or teams own code in this repository. +# Directives are specified using one of the following forms: +# +# * @org/team-name # entire repo +# /path/to/file @user-name # specific file +# /docs/** @team-name # all files in docs folder +# *.js @js-owner # all JS files +# +# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +# +# Order is important; the last matching pattern takes precedence. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..8580266 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,28 @@ +name: "Bug Report" +description: Report a bug +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: textarea + id: bug + attributes: + label: Bug description + description: Expected vs. actual behavior. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Minimal steps. + placeholder: | + 1. ... + 2. ... + See error. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment (optional) + description: OS, Python, toolchain. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..0e4dcee --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,20 @@ +name: "Feature Request" +description: Suggest a feature +title: "[Feature]: " +labels: ["feature", "triage"] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What problem does it solve? + placeholder: I'm frustrated when... + validations: + required: true + - type: textarea + id: solution + attributes: + label: Solution + description: Describe the feature. + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e51e585 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,46 @@ +# Dependabot configuration file +# Enables automated dependency updates for pip (individual PRs) and GitHub Actions (grouped PR). +# Full documentation: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 + +updates: + # Pip dependencies from pyproject.toml ([project].dependencies and [dependency-groups]) + - package-ecosystem: pip + directory: "/" + target-branch: "main" + schedule: + interval: "weekly" + # day: "monday" # Optional: Restrict to specific weekday + open-pull-requests-limit: 5 # Limit number of open PRs + rebase-strategy: "auto" # Options: auto, safe, noop + labels: + - "dependencies" + versioning-strategy: "increase" # lockfile-only, increase, increase-if-necessary + # Useful: Ignore outdated/unwanted packages + # ignore: + # - dependency-name: "legacy-package" + # Useful: Periodically update lockfile even without dep changes (requires lockfile) + # lockfile-maintenance: + # enabled: true + + # GitHub Actions in .github/workflows/*.yml + - package-ecosystem: "github-actions" + directory: "/" # Root; scans .github/**/workflow yml files + target-branch: "main" + schedule: + interval: "monthly" + # day: "monday" + open-pull-requests-limit: 5 + rebase-strategy: "auto" + labels: + - "CI" + groups: + gha-updates: + patterns: + - "*" # Groups ALL GitHub Actions updates into single PR + +# Additional notes: +# - Pip updates: Individual PRs per package (no group). +# - Reviews: Uses .github/CODEOWNERS automatically. +# - Automerge: Enable via branch protection rules or 'automerge: true' (experimental). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..53e2625 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +# PR Checklist + +- [ ] Tests pass +- [ ] Docs if needed +- [ ] pre-commit passes + +## Changes + +Brief description. + +## Related + +Closes # diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 17e1fc2..0f66c57 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,31 +2,30 @@ name: Publish WorkFlow on: release: - types: [created] + types: [published] jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.8] steps: - name: ๐Ÿ›Ž๏ธ Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: ๐Ÿ Set up Python + uses: actions/setup-python@v5 with: - ref: ${{ github.head_ref }} - - name: ๐Ÿ Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: ๐Ÿฆพ Install dependencies - run: | - python -m pip install --upgrade pip - pip install ".[dev]" - - name: ๐Ÿš€ Publish to PyPi - env: - PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} - PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - PYPI_TEST_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }} + python-version: "3.10" + cache: "pip" + - name: ๐Ÿฆพ Install build dependencies run: | - make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD \ No newline at end of file + python -m pip install --upgrade pip build + - name: ๐Ÿ“ฆ Build package + run: python -m build + - name: Upload to release + uses: AButler/upload-release-assets@v3.0 + with: + files: "dist/*" + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: ๐Ÿš€ Publish to PyPI + uses: pypa/gh-action-pypi-publish@v1.1.0 + with: + password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fd76a1..2b92709 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,22 +9,17 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7, 3.8, 3.9] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: ๐Ÿ›Ž๏ธ Checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.head_ref }} + uses: actions/checkout@v4 - name: ๐Ÿ Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: ๐Ÿ”ง Set up uv + uses: astral-sh/setup-uv@v5 - name: ๐Ÿฆพ Install dependencies - run: | - python -m pip install --upgrade pip - pip install ".[dev]" - - name: ๐Ÿงน Lint with flake8 - run: | - make check_code_quality + run: uv sync --dev - name: ๐Ÿงช Test - run: "python -m pytest ./test" \ No newline at end of file + run: uv run pytest test/ src/ diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml deleted file mode 100644 index c9bb3f0..0000000 --- a/.github/workflows/welcome.yml +++ /dev/null @@ -1,16 +0,0 @@ -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - -jobs: - build: - name: ๐Ÿ‘‹ Welcome - runs-on: ubuntu-latest - steps: - - uses: actions/first-interaction@v1.1.1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: "Hello there, thank you for opening an Issue ! ๐Ÿ™๐Ÿป The team was notified and they will get back to you asap." - pr-message: "Hello there, thank you for opening an PR ! ๐Ÿ™๐Ÿป The team was notified and they will get back to you asap." \ No newline at end of file diff --git a/.gitignore b/.gitignore index b6e4761..23a9e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# JetBrain IDE +.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cd63a7d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,57 @@ +# .pre-commit-config.yaml +# +# Pre-commit configuration for: +# - Local git hooks: `pre-commit install` & `pre-commit run --all-files` +# - pre-commit.ci bot: PR checks, auto hook updates, etc. +# +# Docs: https://pre-commit.ci/ +# +ci: + autoupdate_schedule: monthly # Monthly bot PRs to update hook revs + submodules: true # Include git submodules in checks + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit.com hooks + for more information, see https://pre-commit.ci # Custom commit message for bot autofixes + skip: [~draft, ~WIP] # Skip bot on PRs with these labels (regex prefix match) + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + # Basic file checks/fixes: whitespace, EOF, YAML/TOML validation, large files, security, symlinks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-toml + - id: check-added-large-files + - id: check-ast + - id: check-docstring-first + - id: detect-private-key + - id: check-symlinks + - repo: https://github.com/astral-sh/ruff-pre-commit + # Python linter (ruff --fix) & formatter (ruff-format) + rev: v0.14.10 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + - repo: https://github.com/hukkin/mdformat + # Markdown formatter (mdformat) + rev: 1.0.0 + hooks: + - id: mdformat + - repo: https://github.com/abravalheri/validate-pyproject + # Validate pyproject.toml schema/content + rev: v0.23 + hooks: + - id: validate-pyproject + - repo: https://github.com/tox-dev/pyproject-fmt + # Format pyproject.toml + rev: v2.9.0 + hooks: + - id: pyproject-fmt + - repo: https://github.com/codespell-project/codespell + # Spell checker for code, comments, docs + rev: v2.4.1 + hooks: + - id: codespell diff --git a/Makefile b/Makefile deleted file mode 100644 index a99809c..0000000 --- a/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -.PHONY: style check_code_quality - -export PYTHONPATH = . -check_dirs := src - -style: - black $(check_dirs) - isort --profile black $(check_dirs) - -check_code_quality: - black --check $(check_dirs) - isort --check-only --profile black $(check_dirs) - # stop the build if there are Python syntax errors or undefined names - flake8 $(check_dirs) --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. E203 for black, E501 for docstring, W503 for line breaks before logical operators - flake8 $(check_dirs) --count --max-line-length=88 --exit-zero --ignore=D --extend-ignore=E203,E501,W503 --statistics - -publish: - python setup.py sdist bdist_wheel - twine upload -r testpypi dist/* -u ${PYPI_USERNAME} -p ${PYPI_TEST_PASSWORD} --verbose - twine check dist/* - twine upload dist/* -u ${PYPI_USERNAME} -p ${PYPI_PASSWORD} --verbose \ No newline at end of file diff --git a/README.md b/README.md index dd78b7b..f35fefb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Python Template ๐Ÿ -A template repo holding our common setup for a python project. + +A template repo holding Roboflow's common setup for a python project. ## Installation @@ -22,54 +23,61 @@ The project has the following structure ``` โ”œโ”€โ”€ .github โ”‚ โ””โ”€โ”€ workflows -โ”‚ โ””โ”€โ”€ test.yml # holds our github action config +โ”‚ โ””โ”€โ”€ test.yml # holds our github action config โ”œโ”€โ”€ .gitignore -โ”œโ”€โ”€ Makefile โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ setup.py +โ”œโ”€โ”€ pyproject.toml โ”œโ”€โ”€ src -โ”‚ โ”œโ”€โ”€ __init__.py -โ”‚ โ”œโ”€โ”€ hello.py -โ””โ”€โ”€ test +โ”‚ โ””โ”€โ”€ sandbox +โ”‚ โ”œโ”€โ”€ __init__.py +โ”‚ โ””โ”€โ”€ hello.py +โ””โ”€โ”€ test โ””โ”€โ”€ test_hello.py ``` ### Code Quality ๐Ÿงน -We provide two handy commands inside the `Makefile`, namely: +We use `pre-commit` to ensure code quality. You can install it using: + +```bash +pre-commit install +``` + +You can run the checks manually on all files: -- `make style` to format the code -- `make check_code_quality` to check code quality (PEP8 basically) +```bash +pre-commit run --all-files +``` -So far, **there is no types checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4). +So far, **there is no types checking with mypy**. See [issue](https://github.com/roboflow-ai/template-python/issues/4). ### Tests ๐Ÿงช -[`pytests`](https://docs.pytest.org/en/7.1.x/) is used to run our tests. +[`pytest`](https://docs.pytest.org/en/7.1.x/) is used to run our tests. + +```bash +pytest . -v +``` ### Publish on PyPi ๐Ÿš€ -**Important**: Before publishing, edit `__version__` in [src/__init__](/src/__init__.py) to match the wanted new version. +**Important**: Before publishing, edit `__version__` in [src/sandbox/__init__.py](/src/sandbox/__init__.py) to match the wanted new version. -We use [`twine`](https://twine.readthedocs.io/en/stable/) to make our life easier. You can publish by using +We use [`twine`](https://twine.readthedocs.io/en/stable/) to upload our package. -``` -export PYPI_USERNAME="you_username" -export PYPI_PASSWORD="your_password" -export PYPI_TEST_PASSWORD="your_password_for_test_pypi" -make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD -``` +```bash +# Build the package +python3 -m build -You can also use token for auth, see [pypi doc](https://pypi.org/help/#apitoken). In that case, +# Upload to TestPyPI (to verify everything is correct) +# Note: For TestPyPI you need to use your TestPyPI credentials +twine upload -r testpypi dist/* --verbose -``` -export PYPI_USERNAME="__token__" -export PYPI_PASSWORD="your_token" -export PYPI_TEST_PASSWORD="your_token_for_test_pypi" -make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD +# Upload to PyPI +twine upload dist/* --verbose ``` -**Note**: We will try to push to [test pypi](https://test.pypi.org/) before pushing to pypi, to assert everything will work +**Note**: For authentication, we recommend using [API tokens](https://pypi.org/help/#apitoken). Set `TWINE_USERNAME` to `__token__` and `TWINE_PASSWORD` to your token value. ### CI/CD ๐Ÿค– @@ -77,10 +85,11 @@ We use [GitHub actions](https://github.com/features/actions) to automatically ru On any pull request, we will check the code quality and tests. -When a new release is created, we will try to push the new code to PyPi. We use [`twine`](https://twine.readthedocs.io/en/stable/) to make our life easier. +When a new release is created, we will try to push the new code to PyPi. We use [`twine`](https://twine.readthedocs.io/en/stable/) to make our life easier. -The **correct steps** to create a new realease are the following: -- edit `__version__` in [src/__init__](/src/__init__.py) to match the wanted new version. +The **correct steps** to create a new release are the following: + +- edit `__version__` in [src/sandbox/__init__.py](/src/sandbox/__init__.py) to match the wanted new version. - create a new [`tag`](https://git-scm.com/docs/git-tag) with the release name, e.g. `git tag v0.0.1 && git push origin v0.0.1` or from the GitHub UI. - create a new release from GitHub UI @@ -89,8 +98,9 @@ The CI will run when you create the new release. # Q&A ## Why no cookiecutter? + This is a template repo, it's meant to be used inside GitHub upon repo creation. ## Why reinvent the wheel? -There are several very good templates on GitHub, but I prefer to use code we wrote instead of blinding taking the most starred template and having features we don't need. From experience, it's better to keep it simple and general enough for our specific use cases. +There are several very good templates on GitHub, I prefer to use code we wrote instead of blinding taking the most starred template and having features we don't need. From experience, it's better to keep it simple and general enough for our specific use cases. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d22c330 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,69 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ "setuptools>=61" ] + +[project] +name = "sandbox-template" +description = "Roboflow Python Sandbox Template" +readme = "README.md" +license = "MIT" +authors = [ + { name = "Roboflow", email = "develop@roboflow.com" }, +] +requires-python = ">=3.9" +classifiers = [ + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dynamic = [ "version" ] +dependencies = [ ] + +urls."Homepage" = "https://roboflow.com" + +[dependency-groups] +dev = [ + "pre-commit", + "pytest", + "twine", + "wheel", +] + +[tool.setuptools] +package-dir = { "" = "src" } + +[tool.setuptools.dynamic] +version = { attr = "sandbox.__version__" } + +[tool.setuptools.packages.find] +where = [ "src" ] + +[tool.ruff] +target-version = "py39" + +line-length = 120 +format.indent-style = "space" +format.quote-style = "double" +lint.select = [ + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "D", # pydocstyle - https://docs.astral.sh/ruff/rules/#pydocstyle-d + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#pycodestyle-e-w + "F", # pyflakes - https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # isort - https://docs.astral.sh/ruff/rules/#isort-i + "N", # pep8-naming - https://docs.astral.sh/ruff/rules/#pep8-naming-n + "SIM", # flake8-simplify - https://docs.astral.sh/ruff/rules/#flake8-simplify-sim + "UP", # pyupgrade - https://docs.astral.sh/ruff/rules/#pyupgrade-up + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#pycodestyle-e-w +] +lint.isort.known-first-party = [ "sandbox" ] + +[tool.pytest.ini_options] +addopts = [ + "--color=yes", + "--doctest-modules", +] diff --git a/setup.py b/setup.py deleted file mode 100644 index 3f61437..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -import setuptools -from setuptools import find_packages -import re - -with open("./src/__init__.py", 'r') as f: - content = f.read() - # from https://www.py4u.net/discuss/139845 - version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', content).group(1) - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="template-python-zuppif#1", - version=version, - author="zuppif", - author_email="francesco.zuppichini@gmail.com", - description="", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", - install_requires=[ - # list your requires - ], - packages=find_packages(exclude=("tests",)), - extras_require={ - "dev": ["flake8", "black==22.3.0", "isort", "twine", "pytest", "wheel"], - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires=">=3.7", -) diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index b1a19e3..0000000 --- a/src/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.5" diff --git a/src/hello.py b/src/hello.py deleted file mode 100644 index 3676faf..0000000 --- a/src/hello.py +++ /dev/null @@ -1,2 +0,0 @@ -def hello() -> str: - return "World" diff --git a/src/sandbox/__init__.py b/src/sandbox/__init__.py new file mode 100644 index 0000000..929ae6a --- /dev/null +++ b/src/sandbox/__init__.py @@ -0,0 +1,3 @@ +"""Sandbox package.""" + +__version__ = "0.0.0.dev0" diff --git a/src/sandbox/hello.py b/src/sandbox/hello.py new file mode 100644 index 0000000..807b8f4 --- /dev/null +++ b/src/sandbox/hello.py @@ -0,0 +1,10 @@ +"""Module containing a hello world function.""" + + +def hello() -> str: + """Return the string "World". + + >>> hello() + 'World' + """ + return "World" diff --git a/test/test_hello.py b/test/test_hello.py index 985ff07..39aab3d 100644 --- a/test/test_hello.py +++ b/test/test_hello.py @@ -1,6 +1,9 @@ -from src.hello import hello +"""Test the hello module.""" + +from sandbox.hello import hello def test_hello(): + """Test the hello function.""" res = hello() - assert res == "World" \ No newline at end of file + assert res == "World"