diff --git a/.github/workflows/lint-actions.yaml b/.github/workflows/lint-actions.yaml new file mode 100644 index 0000000..c5944c5 --- /dev/null +++ b/.github/workflows/lint-actions.yaml @@ -0,0 +1,18 @@ +name: Lint repository +on: [pull_request] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.x" + - uses: actions/setup-go@v5 + with: + go-version: "stable" + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files --show-diff-on-failure diff --git a/.gitignore b/.gitignore index 410574d..ef47788 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,218 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Integration test artifacts +integration-test/new_ver.json + +# Github actions act secrets +.secrets + +# Reforge specific models output logs diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c1fc2ba --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + args: [ --allow-multiple-documents ] + - id: check-added-large-files + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.1 + hooks: + - id: ruff-check + args: [ --fix ] + - id: ruff-format diff --git a/neo/Dockerfile b/neo/Dockerfile index 8c960cd..40f6c59 100644 --- a/neo/Dockerfile +++ b/neo/Dockerfile @@ -18,9 +18,6 @@ ENV REFORGE_PATH=/home/ubuntu/stable-diffusion-webui ENV UV_CACHE_DIR=${REFORGE_PATH}/venv/uvcache ENV DEBIAN_FRONTEND="noninteractive" -# Version of python to install with uv -ENV UV_PYTHON=3.11 - # Version variables controlled by https://github.com/snw35/dfupdate ENV UV_VERSION=0.10.0 ENV UV_URL=https://github.com/astral-sh/uv/releases/download/${UV_VERSION} @@ -51,6 +48,7 @@ RUN curl -L -O $REFORGE_URL/$REFORGE_FILENAME \ && mkdir ${REFORGE_PATH}/output COPY prepare_environment.py ${REFORGE_PATH}/prepare_environment.py +COPY get_python_version.py ${REFORGE_PATH}/get_python_version.py COPY entrypoint.sh ${REFORGE_PATH}/entrypoint.sh COPY webui-user.sh ${REFORGE_PATH}/webui-user.sh COPY webui.sh ${REFORGE_PATH}/webui.sh @@ -75,7 +73,7 @@ ENV SAGE_SHA256=32c92fe1032238f855f3b1498544b2a9fc25bd55dd4e6c310c2ba39410c5961a # # Construct full venv RUN curl -L -O ${SAGE_URL}/${SAGE_FILENAME} \ && echo "$SAGE_SHA256 ./${SAGE_FILENAME}" | sha256sum -c - \ - && uv python install ${UV_PYTHON} --default \ + && uv python install $(python3 ${REFORGE_PATH}/get_python_version.py) --default \ && uv venv ${REFORGE_PATH}/venv --clear --seed --allow-existing \ && . ${REFORGE_PATH}/venv/bin/activate \ && uv pip install insightface \ @@ -114,7 +112,6 @@ USER ubuntu WORKDIR /home/ubuntu ENV REFORGE_PATH=/home/ubuntu/stable-diffusion-webui ENV UV_CACHE_DIR=${REFORGE_PATH}/venv/uvcache -ENV UV_PYTHON=3.11 ENV PATH="$PATH:${REFORGE_PATH}:/home/ubuntu/.local/bin" WORKDIR ${REFORGE_PATH} diff --git a/neo/get_python_version.py b/neo/get_python_version.py new file mode 100644 index 0000000..0018ec7 --- /dev/null +++ b/neo/get_python_version.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +""" +Detect required Python major.minor for this repo. + +Priority: +1) pyproject.toml -> [project].requires-python (if present) +2) .python-version (if present) +3) modules/launch_utils.py -> check_python_version() AST parse + +Input: +- Environment variable: REFORGE_PATH (repo root) + +Output: +- Prints major.minor (e.g. 3.13) to stdout +- Exits non-zero on failure +""" + +from __future__ import annotations + +import ast +import os +import re +import sys +from pathlib import Path + + +def _first_major_minor(text: str) -> str | None: + m = re.search(r"(\d+)\.(\d+)", text) + if not m: + return None + return f"{m.group(1)}.{m.group(2)}" + + +def _from_pyproject(root: Path) -> str | None: + p = root / "pyproject.toml" + if not p.is_file(): + return None + + data = p.read_text(encoding="utf-8", errors="replace") + + # Try stdlib tomllib first (py3.11+) + try: + import tomllib # type: ignore + + obj = tomllib.loads(data) + spec = (obj.get("project") or {}).get("requires-python") + if isinstance(spec, str): + return _first_major_minor(spec) + except Exception: + pass + + # Fallback regex scan + m = re.search(r"(?m)^\s*requires-python\s*=\s*['\"]([^'\"]+)['\"]\s*$", data) + if m: + return _first_major_minor(m.group(1)) + + return None + + +def _from_python_version_file(root: Path) -> str | None: + p = root / ".python-version" + if not p.is_file(): + return None + + raw = p.read_text(encoding="utf-8", errors="replace").strip() + if not raw: + return None + + # Accept forms like "3.13" or "3.13.12" + return _first_major_minor(raw) + + +def _from_launch_utils(root: Path) -> str | None: + p = root / "modules" / "launch_utils.py" + if not p.is_file(): + return None + + src = p.read_text(encoding="utf-8", errors="replace") + + try: + mod = ast.parse(src) + except SyntaxError: + return None + + fn = None + for node in mod.body: + if isinstance(node, ast.FunctionDef) and node.name == "check_python_version": + fn = node + break + if fn is None: + return None + + major = None + minor = None + + for n in ast.walk(fn): + # Pattern used in this repo: if not (major == 3 and minor == 13): + if ( + isinstance(n, ast.Compare) + and len(n.ops) == 1 + and isinstance(n.ops[0], ast.Eq) + ): + if ( + isinstance(n.left, ast.Name) + and len(n.comparators) == 1 + and isinstance(n.comparators[0], ast.Constant) + ): + val = n.comparators[0].value + if isinstance(val, int): + if n.left.id == "major": + major = val + elif n.left.id == "minor": + minor = val + + if isinstance(major, int) and isinstance(minor, int): + return f"{major}.{minor}" + + return None + + +def main() -> int: + root_env = os.environ.get("REFORGE_PATH", "").strip() + if not root_env: + print("ERROR: REFORGE_PATH is not set", file=sys.stderr) + return 2 + + root = Path(root_env).expanduser().resolve() + if not root.exists() or not root.is_dir(): + print(f"ERROR: REFORGE_PATH is not a directory: {root}", file=sys.stderr) + return 2 + + for detector in (_from_pyproject, _from_python_version_file, _from_launch_utils): + ver = detector(root) + if ver: + print(ver) + return 0 + + print( + "ERROR: Could not determine required Python version from repository contents", + file=sys.stderr, + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/neo/prepare_environment.py b/neo/prepare_environment.py index bc02e17..78145c7 100644 --- a/neo/prepare_environment.py +++ b/neo/prepare_environment.py @@ -8,10 +8,12 @@ prepare_environment = launch_utils.prepare_environment + def main(): # We set this flag to allow buidling on non-GPU runners args.skip_torch_cuda_test = True prepare_environment() + if __name__ == "__main__": main()