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
48 changes: 26 additions & 22 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@


name: tests
permissions:
contents: read
Expand All @@ -14,11 +13,12 @@ on:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.14", "3.13", "3.12", "3.11"]
python-version: ["3.14", "3.13", "3.12"]
os: [ubuntu-latest, macos-latest] # TODO: windows-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
run: |
Expand All @@ -28,43 +28,45 @@ jobs:
git checkout $GITHUB_SHA || (git fetch && git checkout $GITHUB_SHA)

- name: 'Set up Python ${{ matrix.python-version }}'
uses: actions/setup-python@v5
uses: actions/setup-python@main
# https://github.com/marketplace/actions/setup-python
with:
python-version: '${{ matrix.python-version }}'
cache: 'pip' # caching pip dependencies
cache-dependency-path: '**/requirements.*.txt'

- name: Cache ROMs
id: cache-roms
uses: actions/cache@v3
# https://github.com/marketplace/actions/cache
- name: Install uv and set the Python version
# https://docs.astral.sh/uv/guides/integration/github/#multiple-python-versions
uses: astral-sh/setup-uv@main
with:
path: roms
key: cache-roms
python-version: ${{ matrix.python-version }}

- name: 'Bootstrap dev CLI'
# The first CLI call will create the ".venv"
run: |
./dev-cli.py version

- name: 'dev CLI help'
run: |
./dev-cli.py --help

- name: 'Bootstrap app CLI'
# The first CLI call will create the ".venv-app"
- name: 'Bootstrap app venv'
# The first CLI call will create the .venv
run: |
./cli.py version

- name: 'app CLI help'
run: |
./cli.py --help

- name: Cache ROMs
id: cache-roms
uses: actions/cache@main
# https://github.com/marketplace/actions/cache
with:
path: roms
key: cache-roms

- name: 'Download ROMs'
run: |
./cli.py download-roms

- name: 'Bootstrap dev venv'
# The first CLI call will create the .venv
run: |
./dev-cli.py version

- name: 'dev CLI help'
run: |
./dev-cli.py --help
Expand All @@ -81,9 +83,11 @@ jobs:
./dev-cli.py coverage

- name: 'Upload coverage report'
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@main
# https://github.com/marketplace/actions/codecov
with:
# https://docs.codecov.com/docs/codecov-tokens#example-github-actions--official-codecov-action
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
verbose: true

2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
# See https://pre-commit.com for more information
repos:
- repo: https://github.com/jedie/cli-base-utilities
rev: v0.25.0
rev: v0.27.2
hooks:
- id: update-readme-history
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,19 @@ usage: ./dev-cli.py [-h] {coverage,install,lint,mypy,nox,pip-audit,publish,test,
│ • lint Check/fix code style by run: "ruff check --fix" │
│ • mypy Run Mypy (configured in pyproject.toml) │
│ • nox Run nox │
│ • pip-audit Run pip-audit check against current requirements files │
│ • pip-audit │
│ Run pip-audit check against current requirements files │
│ • publish Build and upload this project to PyPi │
│ • test Run unittests │
│ • update Update dependencies (uv.lock) and git pre-commit hooks │
│ • update-readme-history │
│ Update project history base on git commits/tags in README.md Will be exited with 1 if the README.md was updated
otherwise with 0.
Update project history base on git commits/tags in README.md Will be exited with 1 if the README.md │
was updated otherwise with 0.
│ │
Also, callable via e.g.:
python -m cli_base update-readme-history -v
Also, callable via e.g.:
python -m cli_base update-readme-history -v
│ • update-test-snapshot-files │
Update all test snapshot files (by remove and recreate all snapshot files)
Update all test snapshot files (by remove and recreate all snapshot files)
│ • version Print version and exit │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Expand Down Expand Up @@ -476,6 +477,7 @@ To make a new release, do this:
[comment]: <> (✂✂✂ auto generated history start ✂✂✂)

* [v0.10.0](https://github.com/jedie/DragonPy/compare/v0.9.3...v0.10.0)
* 2026-01-26 - Apply manageprojects updates
* 2025-12-09 - Update via manageprojects
* 2025-07-28 - update requirements + remove obsolete requirements.txt
* 2025-04-22 - replace click with tyro
Expand Down
122 changes: 35 additions & 87 deletions cli.py
Original file line number Diff line number Diff line change
@@ -1,117 +1,65 @@
#!/usr/bin/env python3

"""
bootstrap CLI
~~~~~~~~~~~~~
`./cli.py` is for normal usage.
Just call this file, and the magic happens ;)

Just call this file, and the magic happens ;)
The `uv` tool is required to run the development CLI.

e.g.: Install `uv` via `pipx`
apt-get install pipx
pipx install uv
"""

import hashlib
import os
import shlex
import shutil
import subprocess
import sys
import venv
from pathlib import Path


def print_no_pip_error():
print('Error: Pip not available!')
print('Hint: "apt-get install python3-venv"\n')


try:
from ensurepip import version
except ModuleNotFoundError as err:
print(err)
print('-' * 100)
print_no_pip_error()
raise
else:
if not version():
print_no_pip_error()
sys.exit(-1)


assert sys.version_info >= (3, 11), f'Python version {sys.version_info} is too old!'


if sys.platform == 'win32': # wtf
# Files under Windows, e.g.: .../.venv/Scripts/python.exe
BIN_NAME = 'Scripts'
FILE_EXT = '.exe'
else:
# Files under Linux/Mac and all other than Windows, e.g.: .../.venv/bin/python3
BIN_NAME = 'bin'
FILE_EXT = ''

BASE_PATH = Path(__file__).parent
VENV_PATH = BASE_PATH / '.venv-app'
BIN_PATH = VENV_PATH / BIN_NAME
PYTHON_PATH = BIN_PATH / f'python3{FILE_EXT}'
PIP_PATH = BIN_PATH / f'pip{FILE_EXT}'
UV_PATH = BIN_PATH / f'uv{FILE_EXT}'

DEP_LOCK_PATH = BASE_PATH / 'uv.lock'
DEP_HASH_PATH = VENV_PATH / '.dep_hash'
assert sys.version_info >= (3, 12), f'Python version {sys.version_info} is too old!'

# script file defined in pyproject.toml as [console_scripts]
# (Under Windows: ".exe" not added!)
PROJECT_SHELL_SCRIPT = BIN_PATH / 'dragonpy_app'

# Create and use "/.venv-app/" for virtualenv:
VIRTUAL_ENV = '.venv-app'

def get_dep_hash():
"""Get SHA512 hash from lock file content."""
return hashlib.sha512(DEP_LOCK_PATH.read_bytes()).hexdigest()

def print_uv_error_and_exit():
print('\nError: "uv" command not found in PATH. Please install "uv" first!\n')
print('Hint:')
print('\tapt-get install pipx\n')
print('\tpipx install uv\n')
sys.exit(1)

def store_dep_hash():
"""Generate .venv/.dep_hash"""
DEP_HASH_PATH.write_text(get_dep_hash())


def venv_up2date():
"""Is existing .venv is up-to-date?"""
if DEP_HASH_PATH.is_file():
return DEP_HASH_PATH.read_text() == get_dep_hash()
return False


def verbose_check_call(*popen_args):
def verbose_check_call(*popen_args, **extra_env):
print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}\n')
return subprocess.check_call(popen_args)
env = {
'VIRTUAL_ENV': VIRTUAL_ENV,
'UV_VENV': VIRTUAL_ENV,
**os.environ,
**extra_env,
}
return subprocess.check_call(
popen_args,
env=env,
cwd=Path(__file__).parent, # Needed if called from other working directory
)


def main(argv):
assert DEP_LOCK_PATH.is_file(), f'File not found: "{DEP_LOCK_PATH}" !'

# Create virtual env in ".venv/":
if not PYTHON_PATH.is_file():
print(f'Create virtual env here: {VENV_PATH.absolute()}')
builder = venv.EnvBuilder(symlinks=True, upgrade=True, with_pip=True)
builder.create(env_dir=VENV_PATH)

# Set environment variable for uv to use '.venv-app' as project environment:
os.environ['UV_PROJECT_ENVIRONMENT'] = str(VENV_PATH.absolute())

if not PROJECT_SHELL_SCRIPT.is_file() or not venv_up2date():
# Update pip
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'pip')

# Install uv
verbose_check_call(PYTHON_PATH, '-m', 'pip', 'install', '-U', 'uv')

# install requirements
verbose_check_call(UV_PATH, 'sync', '--frozen', '--no-dev')
uv_bin = shutil.which('uv') # Ensure 'uv' is available in PATH
if not uv_bin:
print_uv_error_and_exit()

# install project
verbose_check_call(PIP_PATH, 'install', '--no-deps', '-e', '.')
store_dep_hash()
if not Path(VIRTUAL_ENV).is_dir():
verbose_check_call(uv_bin, 'venv', VIRTUAL_ENV)

# Call our entry point CLI:
try:
verbose_check_call(PROJECT_SHELL_SCRIPT, *argv[1:])
verbose_check_call(uv_bin, 'run', '--active', '-m', 'dragonpy', *argv[1:])
except subprocess.CalledProcessError as err:
sys.exit(err.returncode)
except KeyboardInterrupt:
Expand Down
Loading