diff --git a/.github/actions/setup-nix/action.yaml b/.github/actions/setup-nix/action.yaml index ad05d66..9c735d6 100644 --- a/.github/actions/setup-nix/action.yaml +++ b/.github/actions/setup-nix/action.yaml @@ -1,26 +1,19 @@ name: "Setup Nix" -description: "Install Nix and configure cache" -inputs: - python-version: - description: "Python version for cache key (e.g., python311, python313)" - required: false - default: "python311" +description: "Install Nix and configure Cachix" runs: using: "composite" steps: - name: Install Nix - uses: cachix/install-nix-action@4e002c8ec80594ecd40e759629461e26c8abed15 # v31.9.0 + uses: cachix/install-nix-action@0b0e072294b088b73964f1d72dfdac0951439dbd # v31.8.4 with: github_access_token: ${{ github.token }} - - name: Cache Nix store - uses: nix-community/cache-nix-action@b426b118b6dc86d6952988d396aa7c6b09776d08 # v7 + - name: Setup Cachix (numtide) + uses: cachix/cachix-action@0fc020193b5a1fa3ac4575aa3a7d3aa6a35435ad # v16 with: - primary-key: nix-${{ runner.os }}-${{ inputs.python-version }}-${{ hashFiles('flake.lock', 'uv.lock', 'pyproject.toml', 'src/**/*.py') }} - restore-prefixes-first-match: | - nix-${{ runner.os }}-${{ inputs.python-version }}- - nix-${{ runner.os }}- + name: numtide + authToken: "" - - name: Verify Nix installation + - name: Load Nix development environment shell: bash run: nix develop --command true diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 40d7c21..c8c219c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -34,7 +34,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["python311", "python313"] + python-version: ["3.11", "3.13"] + include: + - python-version: "3.11" + sync-extras: "--all-extras" + - python-version: "3.13" + sync-extras: "--all-extras" steps: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 @@ -43,17 +48,18 @@ jobs: - name: Setup Nix uses: ./.github/actions/setup-nix - with: - python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: nix develop --command just install ${{ matrix.sync-extras }} - name: Run Lint - run: nix develop .#${{ matrix.python-version }} --command just lint + run: nix develop --command just lint - name: Run Ty - run: nix develop .#${{ matrix.python-version }} --command just ty + run: nix develop --command just ty - name: Run Tests - run: nix develop .#${{ matrix.python-version }} --command just test + run: nix develop --command just test coverage: runs-on: ubuntu-latest @@ -67,6 +73,9 @@ jobs: - name: Setup Nix uses: ./.github/actions/setup-nix + - name: Install dependencies + run: nix develop --command just install --all-extras + - name: Run Tests with Coverage run: nix develop --command just coverage diff --git a/flake.lock b/flake.lock index 8248742..d16a84c 100644 --- a/flake.lock +++ b/flake.lock @@ -3,15 +3,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1767039857, - "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", - "owner": "NixOS", + "lastModified": 1761588595, + "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", + "owner": "edolstra", "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", "type": "github" }, "original": { - "owner": "NixOS", + "owner": "edolstra", "repo": "flake-compat", "type": "github" } @@ -41,11 +41,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1767281941, - "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=", + "lastModified": 1765911976, + "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa", + "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", "type": "github" }, "original": { @@ -108,11 +108,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1767364772, - "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", + "lastModified": 1766870016, + "narHash": "sha256-fHmxAesa6XNqnIkcS6+nIHuEmgd/iZSP/VXxweiEuQw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", + "rev": "5c2bc52fb9f8c264ed6c93bd20afa2ff5e763dce", "type": "github" }, "original": { @@ -138,61 +138,12 @@ "type": "github" } }, - "pyproject-build-systems": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "pyproject-nix": [ - "pyproject-nix" - ], - "uv2nix": [ - "uv2nix" - ] - }, - "locked": { - "lastModified": 1763662255, - "narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=", - "owner": "pyproject-nix", - "repo": "build-system-pkgs", - "rev": "042904167604c681a090c07eb6967b4dd4dae88c", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "build-system-pkgs", - "type": "github" - } - }, - "pyproject-nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1764134915, - "narHash": "sha256-xaKvtPx6YAnA3HQVp5LwyYG1MaN4LLehpQI8xEdBvBY=", - "owner": "pyproject-nix", - "repo": "pyproject.nix", - "rev": "2c8df1383b32e5443c921f61224b198a2282a657", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "pyproject.nix", - "type": "github" - } - }, "root": { "inputs": { "flake-parts": "flake-parts", "git-hooks": "git-hooks", "nixpkgs": "nixpkgs_2", - "pyproject-build-systems": "pyproject-build-systems", - "pyproject-nix": "pyproject-nix", - "treefmt-nix": "treefmt-nix", - "uv2nix": "uv2nix" + "treefmt-nix": "treefmt-nix" } }, "treefmt-nix": { @@ -200,11 +151,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1767122417, - "narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=", + "lastModified": 1766000401, + "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d", + "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", "type": "github" }, "original": { @@ -212,29 +163,6 @@ "repo": "treefmt-nix", "type": "github" } - }, - "uv2nix": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ], - "pyproject-nix": [ - "pyproject-nix" - ] - }, - "locked": { - "lastModified": 1767152253, - "narHash": "sha256-xxuRsew0pedwptVnhfru01xbe+dDhI+OY1kCFDMxBUs=", - "owner": "pyproject-nix", - "repo": "uv2nix", - "rev": "7a3eb140416318349ec58d2d4e81afe071bc9f03", - "type": "github" - }, - "original": { - "owner": "pyproject-nix", - "repo": "uv2nix", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index f93d60f..509a98f 100644 --- a/flake.nix +++ b/flake.nix @@ -6,25 +6,6 @@ flake-parts.url = "github:hercules-ci/flake-parts"; git-hooks.url = "github:cachix/git-hooks.nix"; treefmt-nix.url = "github:numtide/treefmt-nix"; - - # uv2nix inputs - pyproject-nix = { - url = "github:pyproject-nix/pyproject.nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - uv2nix = { - url = "github:pyproject-nix/uv2nix"; - inputs.pyproject-nix.follows = "pyproject-nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - - pyproject-build-systems = { - url = "github:pyproject-nix/build-system-pkgs"; - inputs.pyproject-nix.follows = "pyproject-nix"; - inputs.uv2nix.follows = "uv2nix"; - inputs.nixpkgs.follows = "nixpkgs"; - }; }; outputs = @@ -32,26 +13,8 @@ flake-parts, git-hooks, treefmt-nix, - nixpkgs, - pyproject-nix, - uv2nix, - pyproject-build-systems, ... }: - let - # Load uv2nix workspace - workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; - - # Create overlay from uv.lock - overlay = workspace.mkPyprojectOverlay { - sourcePreference = "wheel"; - }; - - # Editable overlay for development - editableOverlay = workspace.mkEditablePyprojectOverlay { - root = "$REPO_ROOT"; - }; - in flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" @@ -68,112 +31,8 @@ { config, pkgs, - system, ... }: - let - # Supported Python versions - pythonVersions = { - python311 = pkgs.python311; - python313 = pkgs.python313; - }; - - # Override for packages that need additional build dependencies - buildSystemOverrides = final: prev: { - pypika = prev.pypika.overrideAttrs (old: { - nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ - final.setuptools - ]; - }); - # stackone-ai needs editables for editable install - stackone-ai = prev.stackone-ai.overrideAttrs (old: { - nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ - final.editables - ]; - }); - }; - - # Helper function to create a Python environment for a given version - mkPythonEnv = - python: - let - pythonSet = - (pkgs.callPackage pyproject-nix.build.packages { - inherit python; - }).overrideScope - ( - nixpkgs.lib.composeManyExtensions [ - pyproject-build-systems.overlays.wheel - overlay - buildSystemOverrides - editableOverlay - ] - ); - in - pythonSet.mkVirtualEnv "stackone-ai-${python.pythonVersion}-env" workspace.deps.all; - - # Create virtualenvs for each Python version - virtualenvs = builtins.mapAttrs (_name: python: mkPythonEnv python) pythonVersions; - - # Default Python version (3.11) - defaultPython = pythonVersions.python311; - defaultVirtualenv = virtualenvs.python311; - - # Helper function to create a devShell for a given Python version - mkDevShell = - python: virtualenv: - pkgs.mkShell { - packages = [ - virtualenv - pkgs.uv - pkgs.just - pkgs.nixfmt-rfc-style - pkgs.basedpyright - - # security - pkgs.gitleaks - - # Node.js for MCP mock server - pkgs.bun - pkgs.pnpm_10 - pkgs.typescript-go - ]; - - env = { - # Prevent uv from managing Python - Nix handles it - UV_NO_SYNC = "1"; - UV_PYTHON = "${python}/bin/python"; - UV_PYTHON_DOWNLOADS = "never"; - # Set VIRTUAL_ENV for tools like ty that look for site-packages - VIRTUAL_ENV = "${virtualenv}"; - }; - - shellHook = '' - echo "StackOne AI Python SDK development environment (Python ${python.pythonVersion})" - - # Set repo root for editable installs - export REPO_ROOT=$(git rev-parse --show-toplevel) - - # Unset PYTHONPATH to avoid conflicts - unset PYTHONPATH - - # Initialize git submodules if not already done - if [ -f .gitmodules ] && [ ! -f vendor/stackone-ai-node/package.json ]; then - echo "Initializing git submodules..." - git submodule update --init --recursive - fi - - # Install Node.js dependencies for MCP mock server (used in tests) - if [ -f vendor/stackone-ai-node/package.json ]; then - if [ ! -f vendor/stackone-ai-node/node_modules/.pnpm/lock.yaml ] || \ - [ vendor/stackone-ai-node/pnpm-lock.yaml -nt vendor/stackone-ai-node/node_modules/.pnpm/lock.yaml ]; then - echo "Installing MCP mock server dependencies..." - (cd vendor/stackone-ai-node && pnpm install --frozen-lockfile) - fi - fi - ''; - }; - in { # Treefmt configuration for formatting treefmt = { @@ -222,7 +81,7 @@ ty = { enable = true; name = "ty"; - entry = "${defaultVirtualenv}/bin/ty check"; + entry = "${pkgs.uv}/bin/uv run ty check"; files = "^stackone_ai/"; language = "system"; types = [ "python" ]; @@ -230,18 +89,49 @@ }; }; - # Development shells for each Python version - devShells = { - default = mkDevShell defaultPython defaultVirtualenv; - python311 = mkDevShell pythonVersions.python311 virtualenvs.python311; - python313 = mkDevShell pythonVersions.python313 virtualenvs.python313; - }; + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + uv + just + nixfmt-rfc-style + basedpyright + + # security + gitleaks + + # Node.js for MCP mock server + bun + pnpm_10 + typescript-go + ]; + + shellHook = '' + echo "StackOne AI Python SDK development environment" + + # Initialize git submodules if not already done + if [ -f .gitmodules ] && [ ! -f vendor/stackone-ai-node/package.json ]; then + echo "📦 Initializing git submodules..." + git submodule update --init --recursive + fi + + # Install Python dependencies only if .venv is missing or uv.lock is newer + if [ ! -d .venv ] || [ uv.lock -nt .venv ]; then + echo "📦 Installing Python dependencies..." + uv sync --all-extras + fi + + # Install Node.js dependencies for MCP mock server (used in tests) + if [ -f vendor/stackone-ai-node/package.json ]; then + if [ ! -f vendor/stackone-ai-node/node_modules/.pnpm/lock.yaml ] || \ + [ vendor/stackone-ai-node/pnpm-lock.yaml -nt vendor/stackone-ai-node/node_modules/.pnpm/lock.yaml ]; then + echo "📦 Installing MCP mock server dependencies..." + (cd vendor/stackone-ai-node && pnpm install --frozen-lockfile) + fi + fi - # Package outputs - packages = { - default = defaultVirtualenv; - python311 = virtualenvs.python311; - python313 = virtualenvs.python313; + # Install git hooks + ${config.pre-commit.installationScript} + ''; }; }; }; diff --git a/justfile b/justfile index 5d47b76..f0e40c5 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,3 @@ -# Helper to run Python commands (uses uv run if not in Nix environment) -_run := if env("VIRTUAL_ENV", "") != "" { "" } else { "uv run " } - # Install dependencies and pre-commit hooks install *extras: uv sync {{ extras }} @@ -13,28 +10,25 @@ lint: format: nix fmt -# Alias for format -lint-fix: format - # Run all tests test: - {{ _run }}pytest + uv run pytest # Run tests with coverage coverage: - {{ _run }}pytest --cov --cov-report=term --cov-report=json --cov-report=html + uv run pytest --cov --cov-report=term --cov-report=json --cov-report=html # Run tool-specific tests test-tools: - {{ _run }}pytest tests + uv run pytest tests # Run example tests test-examples: - {{ _run }}pytest examples + uv run pytest examples # Run type checking ty: - {{ _run }}ty check stackone_ai + uv run ty check stackone_ai # Run gitleaks secret detection gitleaks: @@ -42,7 +36,7 @@ gitleaks: # Update version in __init__.py update-version: - {{ _run }}python scripts/update_version.py + uv run scripts/update_version.py # Build package build: