Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/actions/setup-python-env/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Setup Python env
description: Setup Python and dependencies
inputs:
python-version:
description: Python version to use
required: true
runs:
using: composite
steps:
- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: uv.lock
version: "0.10.11"

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python-version }}

- name: Install dependencies
shell: bash
run: uv sync --all-extras --dev
94 changes: 94 additions & 0 deletions .github/workflows/code-checkers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: Code checkers

on: [push, pull_request]

env:
PYTHON_VERSIONS: '["3.8", "3.14"]'

jobs:
setup:
name: Setup
runs-on: ubuntu-latest
outputs:
python-versions: ${{ env.PYTHON_VERSIONS }}
python-latest: ${{ steps.set-vars.outputs.python-latest }}
steps:
- id: set-vars
run: |
LATEST=$(echo '${{ env.PYTHON_VERSIONS }}' | jq -c '.[-1:]')
echo "python-latest=$LATEST" >> $GITHUB_OUTPUT

linting:
name: Linting
runs-on: ubuntu-latest
needs: setup
strategy:
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.python-versions) }}
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}

- name: Ruff check
run: uv run ruff check

static-type-checking:
name: Static type checking
runs-on: ubuntu-latest
needs: setup
strategy:
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.python-latest) }}
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}

- name: Mypy check
run: uv run mypy

- name: Pyrefly check
run: uv run pyrefly check

semgrep:
name: Semgrep
runs-on: ubuntu-latest
needs: setup
strategy:
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.python-latest) }}
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}

- name: Semgrep scan
# Python Flask is excluded due to false positive errors in the JSON-RPC utilities.
run: |
uv run semgrep scan \
--config p/default \
--config p/security-audit \
--config semgrep.yml \
--error \
--exclude-rule python.flask.security.audit.directly-returned-format-string.directly-returned-format-string \
--exclude-rule yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag

tests:
name: Tests
runs-on: ubuntu-latest
needs: setup
strategy:
matrix:
python-version: ${{ fromJSON(needs.setup.outputs.python-versions) }}
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-python-env
with:
python-version: ${{ matrix.python-version }}

- name: Pytest check
run: uv run pytest
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
__pycache__
.mypy_cache
.pytest_cache
.ruff_cache
*.egg-info
*.pyc
1 change: 1 addition & 0 deletions .semgrepignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stubs/
153 changes: 153 additions & 0 deletions pyproject.toml
Comment thread
Wescoeur marked this conversation as resolved.
Comment thread
Millefeuille42 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
[project]
name = "xcp-storage"
dynamic = [
"authors",
"description",
"license",
"version"
]
requires-python = ">=3.7" # TODO(XCPNG-3032): Change it for modern python.

[build-system]
requires = ["setuptools>=40"]
build-backend = "setuptools.build_meta"

[dependency-groups]
dev = [
"mypy>=1.4.1",
"pyrefly>=0.14.0",
"pytest>=7.4.4",
"ruff>=0.15.6",
"semgrep>=1.46.0",
]

[tool.mypy]
python_version = "3.14"
mypy_path = "stubs"
files = [
"src",
"tests"
]

disallow_untyped_defs = true
enable_error_code = ["explicit-override"]
ignore_missing_imports = false
implicit_optional = false

[[tool.mypy.overrides]]
module = "setuptools.*"
ignore_missing_imports = true

# To generate LINSTOR stubs, run the following commands in the LINSTOR source folder:
# ./setup.py build
# stubgen --include-private build/lib/linstor/ -o <XCP_STORAGE_PATH>/stubs/
# echo "from . import sharedconsts as consts" >> <XCP_STORAGE_PATH>/stubs/linstor/__init__.pyi
[[tool.mypy.overrides]]
module = "linstor.*"

disable_error_code = [
"arg-type",
"attr-defined",
"explicit-override",
"name-defined",
"no-redef",
"override"
]
disallow_untyped_defs = false

[tool.pyrefly]
python-version = "3.14"
search-path = ["stubs"]
project-includes = [
"src",
"tests"
]

[tool.ruff]
include = [
Comment thread
klmp200 marked this conversation as resolved.
"src/**/*.py",
"tests/**/*.py"
]
line-length = 120

[tool.ruff.lint]
exclude = ["stubs/*"]
ignore = [
"B019", # flake8-bugbear: cached-instance-method
"EM101", # flake8-errmsg: raw-string-in-exception
"EM102", # flake8-errmsg: f-string-in-exception
"S101", # flake8-bandit: assert
"S603", # flake8-bandit: subprocess-without-shell-equals-true
"TD003", # flake8-todos: missing-todo-link
"UP006", # pyupgrade: non-pep585-annotation
"UP007", # pyupgrade: non-pep604-annotation-union
"UP045", # pyupgrade: non-pep604-annotation-optional
]
select = [
"A", # flake8-builtins
"ANN", # flake8-annotations
"ARG", # flake8-unused-arguments
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"C90", # mccabe
"DTZ", # flake8-datetimez
"E", # pycodestyle: error
"EM", # flake8-errmsg
"ERA", # eradicate
"EXE", # flake8-executable
"F", # Pyflakes
"FBT", # flake8-boolean-trap
"FLY", # flynt
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"LOG", # flake8-logging
"N", # pep8-naming
"PERF", # perflint
"PIE", # flake8-pie
"PLW", # pylint: warning
"PTH", # flake8-use-pathlib
"Q", # flake8-quotes
"RET", # flake8-return
"RUF013", # ruff: implicit-optional
"RUF059", # ruff: unused-unpacked-variable
"S", # flake8-bandit
"SIM", # flake8-simplify
"SLF", # flake8-self
"T100", # flake8-debugger
"T20", # flake8-print
"TD", # flake8-todos
"TID", # flake8-tidy-imports
"UP", # pyupgrade
"W", # pycodestyle: warning
]
typing-modules = ["xcp_storage.typing"]

[tool.ruff.lint.isort.sections]
"typing" = ["xcp_storage.typing"]

[tool.ruff.lint.isort]
case-sensitive = false
combine-as-imports = true
force-sort-within-sections = true
known-first-party = ["xcp_storage"]
known-third-party = ["linstor"]
lines-after-imports = 1
order-by-type = false
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"local-folder",
"typing"
]

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"

[tool.pytest.ini_options]
pythonpath = ["src", "stubs"]
testpaths = ["tests"]
33 changes: 33 additions & 0 deletions semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
rules:
- id: python-missing-final-on-constants
languages: [python]
severity: WARNING
message: "Constant `$CONST` must be annotated with `Final`. Fix: `$CONST: Final = $VAL`"
patterns:
- pattern-either:
- pattern: "$CONST = $VAL"
- pattern: "$CONST: $TYPE = $VAL"

- metavariable-regex:
metavariable: $CONST
regex: "^[A-Z_][A-Z0-9_]*$"

- pattern-not-inside: "$_: Final = $_"
- pattern-not-inside: "$_: Final[...] = $_"
- pattern-not-inside: "$CONST = TypeVar(...)"
- pattern-not-inside: "$CONST = ParamSpec(...)"
- pattern-not-inside: |
class $ENUM(..., Enum, ...):
...
- pattern-not-inside: |
class $ENUM(..., IntEnum, ...):
...
- pattern-not-inside: |
class $ENUM(..., StrEnum, ...):
...
- pattern-not-inside: |
class $FLAG(..., Flag, ...):
...
- pattern-not-inside: |
class $FLAG(..., IntFlag, ...):
...
18 changes: 18 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[metadata]
name = xcp-storage
version = 1.0.0
description = XCP storage layer, scripts and drivers
author = Ronan Abhamon <ronan.abhamon@vates.tech>
author_email = ronan.abhamon@vates.tech
url = https://vates.tech
license = GPLv3

[options]
packages = find:
package_dir =
=src
# TODO(XCPNG-3032): Change it for modern python.
python_requires = >=3.6

[options.packages.find]
where = src
21 changes: 21 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
#
# Copyright (C) 2026 Vates SAS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License

from setuptools import setup

# ==============================================================================

if __name__ == "__main__":
setup()
Empty file added src/xcp_storage/__init__.py
Empty file.
Loading
Loading