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
51 changes: 51 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
fail-fast: false

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true

- name: Install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH

- name: Install dependencies
run: |
uv pip install --system -e ".[test]"
uv pip install --system ruff isort

- name: Run ruff check
run: ruff check src/

- name: Run isort check
run: isort --check src/

- name: Run tests with coverage
run: pytest --cov=mxrepo --cov-report=term-missing --cov-report=xml

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.13'
with:
file: ./coverage.xml
fail_ci_if_error: false
continue-on-error: true
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
__pycache__
/.mxmake/
/requirements-mxdev.txt
/.claude/
CLAUDE.md
142 changes: 123 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
#
# DOMAINS:
#: core.base
#: core.help
#: core.mxenv
#: core.mxfiles
#: core.packages
#: qa.isort
#: qa.ruff
#: qa.test
#
# SETTINGS (ALL CHANGES MADE BELOW SETTINGS WILL BE LOST)
##############################################################################
Expand Down Expand Up @@ -41,26 +43,30 @@ EXTRA_PATH?=

# Primary Python interpreter to use. It is used to create the
# virtual environment if `VENV_ENABLED` and `VENV_CREATE` are set to `true`.
# If global `uv` is used, this value is passed as `--python VALUE` to the venv creation.
# uv then downloads the Python interpreter if it is not available.
# for more on this feature read the [uv python documentation](https://docs.astral.sh/uv/concepts/python-versions/)
# Default: python3
PRIMARY_PYTHON?=python3

# Minimum required Python version.
# Default: 3.9
# Default: 3.10
PYTHON_MIN_VERSION?=3.9

# Install packages using the given package installer method.
# Supported are `pip` and `uv`. If uv is used, its global availability is
# checked. Otherwise, it is installed, either in the virtual environment or
# using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If
# `VENV_ENABLED` and uv is selected, uv is used to create the virtual
# environment.
# Supported are `pip` and `uv`. When `uv` is selected, a global installation
# is auto-detected and used if available. Otherwise, uv is installed in the
# virtual environment or using `PRIMARY_PYTHON`, depending on the
# `VENV_ENABLED` setting.
# Default: pip
PYTHON_PACKAGE_INSTALLER?=uv

# Flag whether to use a global installed 'uv' or install
# it in the virtual environment.
# Default: false
MXENV_UV_GLOBAL?=true
# Python version for UV to install/use when creating virtual
# environments with global UV. Passed to `uv venv -p VALUE`. Supports version
# specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value
# for backward compatibility.
# Default: $(PRIMARY_PYTHON)
UV_PYTHON?=$(PRIMARY_PYTHON)

# Flag whether to use virtual environment. If `false`, the
# interpreter according to `PRIMARY_PYTHON` found in `PATH` is used.
Expand Down Expand Up @@ -114,6 +120,28 @@ PROJECT_CONFIG?=mx.ini
# Default: false
PACKAGES_ALLOW_PRERELEASES?=false

## qa.test

# The command which gets executed. Defaults to the location the
# :ref:`run-tests` template gets rendered to if configured.
# Default: .mxmake/files/run-tests.sh
TEST_COMMAND?=.mxmake/files/run-tests.sh

# Additional Python requirements for running tests to be
# installed (via pip).
# Default: pytest
TEST_REQUIREMENTS?=pytest

# Additional make targets the test target depends on.
# No default value.
TEST_DEPENDENCY_TARGETS?=

## core.help

# Request to show all targets, descriptions and arguments for a given domain.
# No default value.
HELP_DOMAIN?=

##############################################################################
# END SETTINGS - DO NOT EDIT BELOW THIS LINE
##############################################################################
Expand Down Expand Up @@ -152,7 +180,7 @@ $(SENTINEL): $(firstword $(MAKEFILE_LIST))
# mxenv
##############################################################################

export OS:=$(OS)
OS?=

# Determine the executable path
ifeq ("$(VENV_ENABLED)", "true")
Expand All @@ -168,26 +196,61 @@ else
MXENV_PYTHON=$(PRIMARY_PYTHON)
endif

# Determine the package installer
# Determine the package installer with non-interactive flags
ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
PYTHON_PACKAGE_COMMAND=uv pip
PYTHON_PACKAGE_COMMAND=uv pip --no-progress
else
PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip
endif

# Auto-detect global uv availability (simple existence check)
ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false")
else
UV_AVAILABLE:=false
endif

# Determine installation strategy
# depending on the PYTHON_PACKAGE_INSTALLER and UV_AVAILABLE
# - both vars can be false or
# - one of them can be true,
# - but never boths.
USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false")
USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false")

# Check if global UV is outdated (non-blocking warning)
ifeq ("$(USE_GLOBAL_UV)","true")
UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false")
else
UV_OUTDATED:=false
endif

MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel
$(MXENV_TARGET): $(SENTINEL)
# Validation: Check Python version if not using global uv
ifneq ("$(USE_GLOBAL_UV)","true")
@$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \
&& echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || :
else
@echo "Using global uv for Python $(UV_PYTHON)"
endif
# Validation: Check VENV_FOLDER is set if venv enabled
@[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \
&& echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || :
@[[ "$(VENV_ENABLED)$(PYTHON_PACKAGE_INSTALLER)" == "falseuv" ]] \
# Validation: Check uv not used with system Python
@[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \
&& echo "Package installer uv does not work with a global Python interpreter." && exit 1 || :
# Warning: Notify if global UV is outdated
ifeq ("$(UV_OUTDATED)","true")
@echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade."
endif

# Create virtual environment
ifeq ("$(VENV_ENABLED)", "true")
ifeq ("$(VENV_CREATE)", "true")
ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue")
@echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'"
@uv venv -p $(PRIMARY_PYTHON) --seed $(VENV_FOLDER)
ifeq ("$(USE_GLOBAL_UV)","true")
@echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'"
@uv venv --allow-existing --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER)
else
@echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'"
@$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)
Expand All @@ -197,10 +260,14 @@ endif
else
@echo "Using system Python interpreter"
endif
ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse")
@echo "Install uv"

# Install uv locally if needed
ifeq ("$(USE_LOCAL_UV)","true")
@echo "Install uv in virtual environment"
@$(MXENV_PYTHON) -m pip install uv
endif

# Install/upgrade core packages
@$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel
@echo "Install/Update MXStack Python packages"
@$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE)
Expand Down Expand Up @@ -397,6 +464,43 @@ INSTALL_TARGETS+=packages
DIRTY_TARGETS+=packages-dirty
CLEAN_TARGETS+=packages-clean

##############################################################################
# test
##############################################################################

TEST_TARGET:=$(SENTINEL_FOLDER)/test.sentinel
$(TEST_TARGET): $(MXENV_TARGET)
@echo "Install $(TEST_REQUIREMENTS)"
@$(PYTHON_PACKAGE_COMMAND) install $(TEST_REQUIREMENTS)
@touch $(TEST_TARGET)

.PHONY: test
test: $(FILES_TARGET) $(SOURCES_TARGET) $(PACKAGES_TARGET) $(TEST_TARGET) $(TEST_DEPENDENCY_TARGETS)
@test -z "$(TEST_COMMAND)" && echo "No test command defined" && exit 1 || :
@echo "Run tests using $(TEST_COMMAND)"
@/usr/bin/env bash -c "$(TEST_COMMAND)"

.PHONY: test-dirty
test-dirty:
@rm -f $(TEST_TARGET)

.PHONY: test-clean
test-clean: test-dirty
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y $(TEST_REQUIREMENTS) || :
@rm -rf .pytest_cache

INSTALL_TARGETS+=$(TEST_TARGET)
CLEAN_TARGETS+=test-clean
DIRTY_TARGETS+=test-dirty

##############################################################################
# help
##############################################################################

.PHONY: help
help: $(MXENV_TARGET)
@mxmake help-generator

##############################################################################
# Custom includes
##############################################################################
Expand Down
12 changes: 11 additions & 1 deletion mx.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
[settings]
main-package = -e .
main-package = -e .[test]

mxmake-templates =
run-tests

mxmake-test-path = tests
mxmake-test-runner = pytest
mxmake-test-runner-args = --junitxml=reports/pyjunit.xml --cov=mxrepo --cov-report=term --cov-report=xml:reports/pycoverage.xml --cov-report=html:reports/pycoverage/html
mxmake-source-path = src/heidi/cloud

[mxmake-run-tests]
40 changes: 37 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ classifiers = [
dynamic = ["readme"]

[project.optional-dependencies]
test = ["pytest"]
test = [
"pytest",
"pytest-cov",
"pytest-mock",
]

[project.urls]
Homepage = "https://github.com/mxstack/mxrepo"
Expand Down Expand Up @@ -54,5 +58,35 @@ ignore_missing_imports = true
python_version = "3.9"

[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = []
line-length = 88
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]

[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"F", # pyflakes
"B", # flake8-bugbear
"UP", # pyupgrade
"S", # bandit (security)
]
ignore = []

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"] # Allow assert in tests

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"-ra",
]
Loading