From ddeb514b5bb39cdd8f32bbda850d17165e34eea1 Mon Sep 17 00:00:00 2001 From: daquinteroflex Date: Mon, 1 Dec 2025 17:04:07 +0100 Subject: [PATCH] ci: tidy3d-extras integration tests & docs --- ...extras-python-client-integration-tests.yml | 145 +++ .../workflows/tidy3d-python-client-tests.yml | 46 +- dev.Dockerfile | 3 + docs/development/docker.rst | 939 +++++++++++++++++- tests/tidy3d_extras_license_test.py | 111 +++ 5 files changed, 1200 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/tidy3d-extras-python-client-integration-tests.yml create mode 100644 tests/tidy3d_extras_license_test.py diff --git a/.github/workflows/tidy3d-extras-python-client-integration-tests.yml b/.github/workflows/tidy3d-extras-python-client-integration-tests.yml new file mode 100644 index 0000000000..a6ad2e0621 --- /dev/null +++ b/.github/workflows/tidy3d-extras-python-client-integration-tests.yml @@ -0,0 +1,145 @@ +name: "public/tidy3d-extras/python-client-integration-tests" + +on: + workflow_dispatch: + inputs: + release_tag: + description: 'Release Tag (v2.10.0, v2.10.0rc1)' + required: false + type: string + default: '' + + workflow_call: + inputs: + release_tag: + description: 'Release Tag (v2.10.0, v2.10.0rc1)' + required: false + type: string + default: '' + outputs: + workflow_success: + description: 'Overall integration test workflow success status' + value: ${{ jobs.integration-tests.result == 'success' }} + +permissions: + contents: read + +jobs: + integration-tests: + name: python-${{ matrix.python-version }}-${{ matrix.platform }}-extras + runs-on: ${{ matrix.platform }} + permissions: + contents: read + concurrency: + group: tidy3d-extras-integration-${{ github.event.pull_request.number || github.sha }}-${{ matrix.platform }}-${{ matrix.python-version }} + cancel-in-progress: true + strategy: + fail-fast: false + matrix: + python-version: ['3.10', '3.13'] + platform: [windows-latest, ubuntu-latest, macos-latest] + defaults: + run: + shell: bash + env: + PIP_ONLY_BINARY: gdstk + MPLBACKEND: agg + RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }} + + steps: + - name: checkout-head + if: ${{ !env.RELEASE_TAG }} + uses: actions/checkout@v4 + with: + fetch-depth: 1 + submodules: false + persist-credentials: false + + - name: checkout-tag + if: ${{ env.RELEASE_TAG }} + uses: actions/checkout@v4 + with: + ref: refs/tags/${{ env.RELEASE_TAG }} + fetch-depth: 1 + submodules: false + persist-credentials: false + + - name: set-python-${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: install-poetry + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1.4.1 + with: + version: 2.1.1 + virtualenvs-create: true + virtualenvs-in-project: true + + - name: configure-aws-credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-access-key-id: ${{ secrets.AWS_CODEARTIFACT_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_CODEARTIFACT_ACCESS_SECRET }} + aws-region: us-east-1 + + - name: configure-codeartifact-authentication + run: | + set -e + echo "Getting CodeArtifact token..." + CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \ + --domain flexcompute \ + --domain-owner 625554095313 \ + --query authorizationToken \ + --output text) + + echo "Configuring Poetry with CodeArtifact credentials..." + poetry config http-basic.codeartifact aws $CODEARTIFACT_AUTH_TOKEN + echo "✅ CodeArtifact authentication configured" + + - name: install-project + shell: bash + run: | + poetry --version + python --version + python -m venv .venv + if [[ "${{ runner.os }}" == "Windows" ]]; then + source .venv/Scripts/activate + python --version + else + source .venv/bin/activate + which python + fi + poetry env use python + poetry env info + poetry run pip install --upgrade pip wheel setuptools + poetry run pip install gdstk --only-binary gdstk + poetry install -E extras -E dev + + - name: verify-extras-installation + run: | + export SIMCLOUD_APIKEY=${{ secrets.TIDY3D_API_KEY }} + poetry run tidy3d configure --apikey ${{ secrets.TIDY3D_API_KEY }} + poetry run python -c "import tidy3d; print(f'tidy3d version: {tidy3d.__version__}')" + poetry run python -c "import tidy3d_extras; print(f'tidy3d-extras version: {tidy3d_extras.__version__}')" + cd tests/ + poetry run pytest tidy3d_extras_license_test.py + + - name: run-doctests + run: | + export SIMCLOUD_APIKEY=${{ secrets.TIDY3D_API_KEY }} + poetry run tidy3d configure --apikey ${{ secrets.TIDY3D_API_KEY }} + poetry run pytest -rF --tb=short tidy3d + + - name: run-tests-coverage + env: + PYTHONUNBUFFERED: "1" + run: | + export SIMCLOUD_APIKEY=${{ secrets.TIDY3D_API_KEY }} + poetry run tidy3d configure --apikey ${{ secrets.TIDY3D_API_KEY }} + poetry run pytest --cov=tidy3d -rF --tb=short tests/_test_data/_test_datasets_no_vtk.py + poetry run pytest --cov=tidy3d -rF --tb=short tests + poetry run coverage report -m + TOTAL_COVERAGE=$(poetry run coverage report --format=total) + echo "total=$TOTAL_COVERAGE" >> "$GITHUB_ENV" + echo "### Total coverage: ${TOTAL_COVERAGE}%" diff --git a/.github/workflows/tidy3d-python-client-tests.yml b/.github/workflows/tidy3d-python-client-tests.yml index c5c0566b4a..40d1d0e77f 100644 --- a/.github/workflows/tidy3d-python-client-tests.yml +++ b/.github/workflows/tidy3d-python-client-tests.yml @@ -14,15 +14,19 @@ on: type: boolean default: false cli_tests: - description: 'Run develop-cli tests' + description: 'develop-cli' type: boolean default: false submodule_tests: - description: 'Run submodule tests' + description: 'submodule-tests' type: boolean default: false version_match_tests: - description: 'Run version consistency checks' + description: 'version-consistency-checks' + type: boolean + default: false + extras_integration_tests: + description: 'integration-tidy3d-extras' type: boolean default: false release_tag: @@ -30,7 +34,7 @@ on: required: false type: string default: '' - + workflow_call: inputs: remote_tests: @@ -58,6 +62,11 @@ on: type: boolean required: false default: false + extras_integration_tests: + description: 'Run tidy3d-extras integration tests' + type: boolean + required: false + default: false release_tag: description: 'Release Tag (v2.10.0, v2.10.0rc1)' required: false @@ -95,6 +104,7 @@ jobs: cli_tests: ${{ steps.determine-test-type.outputs.cli_tests }} submodule_tests: ${{ steps.determine-test-type.outputs.submodule_tests }} version_match_tests: ${{ steps.determine-test-type.outputs.version_match_tests }} + extras_integration_tests: ${{ steps.determine-test-type.outputs.extras_integration_tests }} steps: - name: determine-test-type id: determine-test-type @@ -108,6 +118,7 @@ jobs: INPUT_CLI: ${{ github.event.inputs.cli_tests || inputs.cli_tests }} INPUT_SUBMODULE: ${{ github.event.inputs.submodule_tests || inputs.submodule_tests }} INPUT_VERSION_MATCH: ${{ github.event.inputs.version_match_tests || inputs.version_match_tests }} + INPUT_EXTRAS_INTEGRATION: ${{ github.event.inputs.extras_integration_tests || inputs.extras_integration_tests }} run: | echo "Event: $EVENT_NAME" echo "Draft: $DRAFT_STATE" @@ -118,6 +129,7 @@ jobs: echo "Input cli: $INPUT_CLI" echo "Input submodule: $INPUT_SUBMODULE" echo "Input version_match: $INPUT_VERSION_MATCH" + echo "Input extras_integration: $INPUT_EXTRAS_INTEGRATION" remote_tests=false local_tests=false @@ -126,6 +138,7 @@ jobs: version_match_tests=false code_quality_tests=false pr_review_tests=false + extras_integration_tests=false # Workflow_dispatch input override if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then @@ -150,6 +163,10 @@ jobs: if [[ "$INPUT_VERSION_MATCH" == "true" ]]; then version_match_tests=true fi + + if [[ "$INPUT_EXTRAS_INTEGRATION" == "true" ]]; then + extras_integration_tests=true + fi fi # All PRs that have been triggered need local tests (remote reserved for merge queue/manual) @@ -163,6 +180,7 @@ jobs: local_tests=true remote_tests=true code_quality_tests=true + extras_integration_tests=true fi echo "local_tests=$local_tests" >> $GITHUB_OUTPUT @@ -172,6 +190,7 @@ jobs: echo "version_match_tests=$version_match_tests" >> $GITHUB_OUTPUT echo "code_quality_tests=$code_quality_tests" >> $GITHUB_OUTPUT echo "pr_review_tests=$pr_review_tests" >> $GITHUB_OUTPUT + echo "extras_integration_tests=$extras_integration_tests" >> $GITHUB_OUTPUT echo "code_quality_tests=$code_quality_tests" echo "pr_review_tests=$pr_review_tests" echo "local_tests=$local_tests" @@ -179,6 +198,7 @@ jobs: echo "cli_tests=$cli_tests" echo "submodule_tests=$submodule_tests" echo "version_match_tests=$version_match_tests" + echo "extras_integration_tests=$extras_integration_tests" lint: needs: determine-test-scope @@ -919,7 +939,16 @@ jobs: echo "" echo "=== Submodule Checks Passed ===" - + + extras-integration-tests: + name: extras-integration-tests + needs: determine-test-scope + if: needs.determine-test-scope.outputs.extras_integration_tests == 'true' + uses: ./.github/workflows/tidy3d-extras-python-client-tests-integration.yml + with: + release_tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }} + secrets: inherit # zizmor: ignore[secrets-inherit] + workflow-validation: name: workflow-validation if: always() @@ -936,6 +965,7 @@ jobs: - develop-cli-tests - verify-version-consistency - test-submodules + - extras-integration-tests runs-on: ubuntu-latest steps: - name: check-linting-result @@ -1004,6 +1034,12 @@ jobs: echo "❌ Submodule tests failed." exit 1 + - name: check-extras-integration-tests-result + if: ${{ needs.determine-test-scope.outputs.extras_integration_tests == 'true' && needs.extras-integration-tests.result != 'success' && needs.extras-integration-tests.result != 'skipped' }} + run: | + echo "❌ tidy3d-extras integration tests failed." + exit 1 + - name: all-checks-passed if: ${{ success() }} run: echo "✅ All required jobs passed!" diff --git a/dev.Dockerfile b/dev.Dockerfile index de7dce7c78..18b0c4f36a 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -8,6 +8,8 @@ RUN apt-get update && \ git \ pandoc \ xsel \ + groff \ + mandoc \ xclip RUN apt-get update && apt-get install -y zip unzip curl \ @@ -20,6 +22,7 @@ RUN apt-get update && apt-get install -y zip unzip curl \ ENV POETRY_HOME=/opt/poetry RUN curl -sSL https://install.python-poetry.org | python3 - ENV PATH="/root/.local/bin:${POETRY_HOME}/bin:${PATH}" +RUN poetry self add poetry-codeartifact-login RUN apt-get update && DEBIAN_FRONTEND="noninteractive" TZ="America/New_York" apt-get install -y curl \ && curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz \ diff --git a/docs/development/docker.rst b/docs/development/docker.rst index b56e48f7ef..fbab5856f0 100644 --- a/docs/development/docker.rst +++ b/docs/development/docker.rst @@ -1,88 +1,949 @@ -Docker Development Image Installation -===================================== +Docker Development Environment +============================= +This guide will get you up and running with Tidy3D Python client development using Docker. The Docker workflow provides an isolated, reproducible development environment without affecting your system packages. -Quick Start Guide ------------------ +.. contents:: Quick Links + :local: + :depth: 2 + +Overview +-------- + +The Docker development environment provides: + +* **Isolation**: Clean environment separate from your system +* **Reproducibility**: Everyone uses the same base environment +* **Simplicity**: No need to manage system dependencies +* **Flexibility**: Easy to reset and start fresh + +**What's included:** -This guide is for anyone setting up the development environment for the first time or migrating from a version after ``2.9.0`` that the dev.Dockerfile image was enabled. +* Python 3.11 with ``uv`` package manager +* Poetry for dependency management +* AWS CLI for CodeArtifact access +* Neovim, git, pandoc for documentation +* Jupyter Lab for notebook development Prerequisites -^^^^^^^^^^^^^^^ +------------- + +System Requirements +^^^^^^^^^^^^^^^^^^^ -1. Install **Docker Engine**. You can find the official instructions here: `https://docs.docker.com/engine/install/ `_. - - *Note for Ubuntu users*: It's recommended to avoid installing Docker from the Snap store. -2. Follow the official `Linux post-installation steps `_ to run Docker commands without ``sudo`` and to enable the Docker daemon to start on boot. -3. **Reboot** your system for all group changes to take effect. +* **Operating System**: Ubuntu 22.04+, macOS, or Windows with WSL2 +* **Docker Engine**: Installed and running +* **Git**: With SSH keys configured for GitHub +* **Disk Space**: ~2GB for Docker image + container + +Quick Check +~~~~~~~~~~~ + +.. code-block:: bash -Setup and Usage + # Check Docker is installed and running + docker --version + docker ps + + # Check Git SSH access + ssh -T git@github.com + +Installing Docker ^^^^^^^^^^^^^^^^^ -Follow these steps in your terminal to clone the repository, build the Docker image, and launch a development session. +If you don't have Docker installed: + +**Ubuntu** + +.. code-block:: bash + + # Install Docker Engine (NOT from snap) + curl -fsSL https://get.docker.com -o get-docker.sh + sudo sh get-docker.sh + + # Add your user to docker group + sudo usermod -aG docker $USER + + # Reboot to apply group changes + sudo reboot + + # Verify installation + docker run hello-world + +See: `Docker Ubuntu Install Guide `_ + +**macOS** + +Download and install Docker Desktop from `docker.com `_ + +**Windows** + +Install Docker Desktop with WSL2 backend from `docker.com `_ + +Access Requirements +^^^^^^^^^^^^^^^^^^^ + +* **GitHub**: Access to `flexcompute/tidy3d `_ +* **Tidy3D Account**: Sign up at `tidy3d.simulation.cloud `_ +* **API Key**: Get yours from `account page `_ + +Quick Start Guide +----------------- + +This guide is for anyone setting up the development environment for the first time or migrating from a version after ``2.9.0`` when the dev.Dockerfile image was enabled. + +Step 1: Clone the Repository +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash - # 1. Clone the repository and navigate into it + # Clone the repository and navigate into it git clone https://github.com/flexcompute/tidy3d.git cd tidy3d - # 2. Build the development Docker image + # Checkout develop branch or your feature branch + git checkout develop + +Step 2: Build the Docker Image +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Build the development Docker image (~5 minutes): + +.. code-block:: bash + + # Build the development Docker image docker build -t tidy3d-python-client-dev -f dev.Dockerfile . - # 3. Create a persistent container with your local code mounted +The image includes: + +* Debian base with Python 3.11 +* ``uv`` for fast package installation +* Poetry for dependency management +* AWS CLI, git, pandoc, neovim +* ``flexdaemon`` user (UID 1000, GID 1000) + +Step 3: Create the Development Container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Create a persistent container with your local code mounted: + +.. code-block:: bash + + # Create a persistent container with your local code mounted # This also maps port 8888 for tools like Jupyter Lab docker container create --name=tidy3d_python_client_dev \ - --userns=host \ - -p 8888:8888 \ - -v .:/home/flexdaemon/tidy3d \ - tidy3d-python-client-dev + --userns=host \ + -p 8888:8888 \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev + +**Flags explained:** - # 4. Start the container +* ``--name``: Container name for easy reference +* ``--userns=host``: Use host user namespace (prevents permission issues) +* ``-p 8888:8888``: Expose port 8888 for Jupyter Lab +* ``-v .:/home/flexdaemon/tidy3d``: Mount current directory into container + +Step 4: Start the Container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + # Start the container docker start tidy3d_python_client_dev - # 5. Open an interactive shell inside the running container +Step 5: Open an Interactive Shell +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + # Open an interactive shell inside the running container docker exec -it tidy3d_python_client_dev /bin/bash -You are now inside the container's shell. From here, you can set up the Python environment. +You're now inside the container at ``/home/flexdaemon``! + +Step 6: Set Up Python Environment +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Inside the container, create a virtual environment and install the package: .. code-block:: bash - # (Inside Container) 6. Create a virtual environment and activate it + # (Inside Container) Navigate to the mounted tidy3d directory + cd tidy3d + + # (Inside Container) Create a virtual environment and activate it uv venv -p 3.11 source .venv/bin/activate - # (Inside Container) 7. Install the project in editable mode with dev dependencies + # (Inside Container) Install the project in editable mode with dev dependencies uv pip install -e .[dev] -With the environment ready, you can run tests, format code, or start a Jupyter Lab session. +.. note:: + The virtual environment is created inside the mounted directory, so it persists between container restarts. + +Step 7: Configure API Key and Run Tests +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Configure your API key and verify everything is working: + +.. code-block:: bash + + # (Inside Container) Configure your API key + tidy3d configure --apikey=YOUR_API_KEY_HERE + + # (Inside Container) Test the configuration + python -c "import tidy3d.web as web; web.test()" + + # (Inside Container) Run tests + pytest tests/ -v + +If tests pass, you're ready to develop! + +Development Workflow +-------------------- + +Daily Workflow +^^^^^^^^^^^^^^ + +1. **Start the container** (if not running): + + .. code-block:: bash + + # On host + docker start tidy3d_python_client_dev + docker exec -it tidy3d_python_client_dev bash + +2. **Activate virtual environment** (inside container): + + .. code-block:: bash + + cd tidy3d + source .venv/bin/activate + +3. **Make changes** to the code on your host machine using your favorite editor (VSCode, PyCharm, etc.) + + Changes are immediately reflected in the container since the directory is mounted. + +4. **Run tests** (inside container): + + .. code-block:: bash + + # Run all tests + pytest + + # Run specific test file + pytest tests/test_simulation.py + + # Run with verbose output + pytest -v -s + +5. **Commit changes** (on host or in container): + + .. code-block:: bash + + git add . + git commit -m "Your descriptive message" + git push origin your-feature-branch + +Multiple Terminal Sessions +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can open multiple terminals into the same container: + +.. code-block:: bash + + # Terminal 1 (host) + docker exec -it tidy3d_python_client_dev bash + + # Terminal 2 (host) + docker exec -it tidy3d_python_client_dev bash + + # Terminal 3 (host) + docker exec -it tidy3d_python_client_dev bash + +Each terminal shares the same filesystem and running processes. + +Running Tests +^^^^^^^^^^^^^ -Running Github Actions Locally -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. code-block:: bash + + # Inside container with venv activated + + # Run all tests + pytest + + # Run tests in parallel (faster) + pytest -n auto + + # Run specific test module + pytest tests/test_components.py + + # Run tests matching pattern + pytest -k "test_simulation" + + # Run with coverage + pytest --cov=tidy3d --cov-report=html + # View coverage: python -m http.server 8000 -d htmlcov + +Code Quality Checks +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + # Inside container with venv activated + + # Format code with ruff + ruff format . + + # Check for linting issues + ruff check . + + # Auto-fix linting issues + ruff check --fix . + + # Run type checking + mypy tidy3d + +Building Documentation +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + # Inside container with venv activated + cd docs + + # Build HTML documentation + make clean + make html + + # View the docs + cd _build/html + python -m http.server 8000 + # Open http://localhost:8000 in your host browser + +Managing the Container +---------------------- -To run github actions locally install the -[act](https://github.com/nektos/gh-act) extension: -```bash -gh extension install nektos/gh-act -``` +Start and Stop +^^^^^^^^^^^^^^ -You can then run remote-tests locally: +.. code-block:: bash + + # Start container + docker start tidy3d_python_client_dev + + # Stop container (preserves state) + docker stop tidy3d_python_client_dev + + # Restart container + docker restart tidy3d_python_client_dev + +Checking Status +^^^^^^^^^^^^^^^ .. code-block:: bash - gh act --pull=false -W .github/workflows/tidy3d-python-client-tests.yml -P "ubuntu-latest=tidy3d_python_client_dev:latest" --input remote_tests=true "workflow_dispatch" + # Check if container is running + docker ps + + # Check all containers (including stopped) + docker ps -a + + # View container logs + docker logs tidy3d_python_client_dev + + # Follow container logs (live) + docker logs -f tidy3d_python_client_dev + +Removing and Recreating +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + # Stop and remove container + docker stop tidy3d_python_client_dev + docker container rm tidy3d_python_client_dev + + # Recreate container (preserves code on host) + docker container create --name=tidy3d_python_client_dev \ + --userns=host \ + -p 8888:8888 \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev + +.. note:: + Your code changes are safe! They're stored on your host in the mounted directory. + +Rebuilding the Image +^^^^^^^^^^^^^^^^^^^^ + +If ``dev.Dockerfile`` changes or you want to update base dependencies: + +.. code-block:: bash + + # Rebuild the image + docker build -t tidy3d-python-client-dev -f dev.Dockerfile . + + # Remove old container and create new one + docker stop tidy3d_python_client_dev + docker container rm tidy3d_python_client_dev + docker container create --name=tidy3d_python_client_dev \ + --userns=host \ + -p 8888:8888 \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev +Advanced Usage +-------------- Running Jupyter Lab ^^^^^^^^^^^^^^^^^^^ -To run a Jupyter Lab instance that's accessible from your host machine's web browser, execute the following command from within the container's shell: +Start Jupyter Lab inside the container to work with notebooks: .. code-block:: bash - # (Inside Container) Make sure your virtual environment is activated! - jupyter lab . --ip=0.0.0.0 + # Inside container with venv activated + cd tidy3d + jupyter lab . --ip=0.0.0.0 --port=8888 + +Jupyter Lab will print a URL like: + +.. code-block:: -After it starts, Jupyter will print a URL to the terminal containing a security token. It will look like this: -``http://127.0.0.1:8888/lab?token=...`` + http://127.0.0.1:8888/lab?token=abc123def456... Copy this complete URL and paste it into your web browser (like Firefox) on your host machine to begin your session. + +.. note:: + Port 8888 is forwarded to your host via the ``-p 8888:8888`` flag used when creating the container. + +Running GitHub Actions Locally +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To run GitHub Actions locally, install the `act `_ extension: + +.. code-block:: bash + + gh extension install nektos/gh-act + +You can then run remote-tests locally using your dev container image: + +.. code-block:: bash + + gh act --pull=false \ + -W .github/workflows/tidy3d-python-client-tests.yml \ + -P "ubuntu-latest=tidy3d-python-client-dev:latest" \ + --input remote_tests=true \ + "workflow_dispatch" + +.. note:: + The image name must match the one you built: ``tidy3d-python-client-dev:latest`` + +VSCode Integration +^^^^^^^^^^^^^^^^^^ + +Attach VSCode to the running container for a full IDE experience: + +**Install Extensions** + +In VSCode, install: + +* **Remote - Containers** (ms-vscode-remote.remote-containers) +* **Docker** (ms-azuretools.vscode-docker) + +**Attach to Container** + +1. Start your container: + + .. code-block:: bash + + docker start tidy3d_python_client_dev + +2. In VSCode: + + * Open Command Palette (Cmd/Ctrl+Shift+P) + * Type "Remote-Containers: Attach to Running Container" + * Select ``tidy3d_python_client_dev`` + +3. VSCode will open a new window connected to the container + +4. Open folder: ``/home/flexdaemon/tidy3d`` + +5. Select Python interpreter: ``.venv/bin/python`` + +Now you can: + +* Edit code with full IntelliSense +* Run tests from VSCode test explorer +* Debug Python code with breakpoints +* Use integrated terminal (already inside container) + +Optional: AWS CodeArtifact Access (Internal Developers Only) +------------------------------------------------------------- + +.. note:: + **Who needs this?** + + CodeArtifact hosts internal packages like ``tidy3d-extras`` needed for: + + * Integration tests in CI/CD + * Backend-dependent development work + * Full-stack Tidy3D development + + **External contributors**: You can skip this entire section. The open-source ``tidy3d`` client works without CodeArtifact. + +.. warning:: + **Security Policy**: We use AWS SSO for authentication, which requires daily login for security compliance. + This is a simple 2-step process per development session: + + 1. ``aws sso login`` - Opens browser for authentication (~30 seconds) + 2. ``poetry aws-login`` - Automatic token management + +Why CodeArtifact? +^^^^^^^^^^^^^^^^^ + +**Q: Why can't we use PyPI?** + +A: Internal packages like ``tidy3d-extras`` contain proprietary code that cannot be published publicly. + +**Q: Why SSO with daily expiration?** + +A: Flexcompute security policy requires time-limited credentials. Long-lived tokens pose security risks. + +**Q: What if I don't want to deal with this?** + +A: Use the Docker development environment, which matches our CI setup exactly. Or if you're only working on the open-source client, you don't need CodeArtifact at all. + +Supported Path: Poetry with AWS Login Plugin +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The development container includes the ``poetry-codeartifact-login`` plugin for simplified authentication. + +.. note:: + **For non-Docker users**: If you're not using the Docker container, you'll need to install the plugin first: + + .. code-block:: bash + + # Install the Poetry plugin (one-time, outside Docker) + poetry self add poetry-codeartifact-login + + # Also ensure AWS CLI v2 is installed on your system + aws --version # Should show version 2.x + + Docker users can skip this step - the plugin is pre-installed in the dev container. + +**One-Time Setup** + +Inside the container (or on your local machine if not using Docker), configure AWS SSO (only needed once): + +.. code-block:: bash + + # Configure AWS SSO + aws configure sso + +Enter the following when prompted: + +.. list-table:: + :widths: 40 60 + :header-rows: 1 + + * - Prompt + - Value + * - SSO session name + - ``flexcompute-pypi`` + * - SSO start URL + - ``https://d-9067bfae6e.awsapps.com/start/#`` + * - SSO region + - ``us-east-1`` + * - Account + - ``625554095313`` + * - Role + - ``codeartifact-readonly`` + * - Profile name + - ``flexcompute-pypi`` + +**Daily Authentication** (each development session) + +Each day when you start development, run these two commands: + +.. code-block:: bash + + # Step 1: Login to AWS SSO (opens browser on host for authentication) + aws sso login --profile flexcompute-pypi + + # Step 2: Configure Poetry with CodeArtifact + poetry aws-login codeartifact --profile flexcompute-pypi + + # That's it! Now you can install internal packages + poetry install -E extras + +.. note:: + **How it works**: The ``poetry aws-login`` command automatically: + + * Generates a short-lived CodeArtifact authentication token + * Configures Poetry to use the token for the ``codeartifact`` source + * Stores credentials securely (no manual token passing required) + + The plugin handles all token management internally, so you never see or copy tokens manually. + +Persisting AWS Credentials +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To avoid re-configuring AWS SSO each time you recreate the container, mount your AWS credentials: + +.. code-block:: bash + + # Recreate container with AWS credentials mounted + docker container create --name=tidy3d_python_client_dev \ + --userns=host \ + -p 8888:8888 \ + -v .:/home/flexdaemon/tidy3d \ + -v ~/.aws:/home/flexdaemon/.aws \ + tidy3d-python-client-dev + +This way, you only need to run ``aws configure sso`` once on your host machine. + +Alternative Package Managers (Self-Support) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you prefer ``pip``, ``uv``, or other package managers instead of Poetry, you'll need to configure +CodeArtifact authentication yourself. + +**The officially supported development path uses Poetry.** For other package managers: + +* See `AWS CodeArtifact pip documentation `_ +* Use ``aws codeartifact login --tool pip`` for manual setup +* You are responsible for configuration and troubleshooting + +Alternatively, use the Docker development environment which has everything pre-configured. + +Quick Reference +--------------- + +Command Cheatsheet +^^^^^^^^^^^^^^^^^^ + +**Container Management:** + +.. code-block:: bash + + # Build image + docker build -t tidy3d-python-client-dev -f dev.Dockerfile . + + # Create container + docker container create --name=tidy3d_python_client_dev \ + --userns=host -p 8888:8888 \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev + + # Start/stop container + docker start tidy3d_python_client_dev + docker stop tidy3d_python_client_dev + + # Enter container + docker exec -it tidy3d_python_client_dev bash + +**Inside Container:** + +.. code-block:: bash + + # Setup environment + cd tidy3d + uv venv -p 3.11 + source .venv/bin/activate + uv pip install -e .[dev] + + # Testing + pytest # All tests + pytest -n auto # Parallel tests + pytest tests/test_file.py # Specific file + + # Code quality + ruff format . # Format code + ruff check . # Lint code + mypy tidy3d # Type check + + # Documentation + cd docs && make html # Build docs + + # Jupyter + jupyter lab . --ip=0.0.0.0 # Start Jupyter Lab + + # CodeArtifact (internal developers only) + aws sso login --profile flexcompute-pypi # Daily: Login to AWS + poetry aws-login codeartifact --profile flexcompute-pypi # Daily: Configure Poetry + +Directory Structure +^^^^^^^^^^^^^^^^^^^ + +.. code-block:: + + Host: + ~/flexcompute/tidy3d/ + ├── tidy3d/ # Source code (mounted to container) + ├── tests/ # Test suite (mounted) + ├── docs/ # Documentation (mounted) + ├── .venv/ # Virtual env (created inside, persists) + ├── pyproject.toml # Poetry config (mounted) + └── dev.Dockerfile # Docker image definition + + Container: + /home/flexdaemon/ + ├── tidy3d/ # Mounted from host + │ ├── .venv/ # Your virtual environment + │ ├── tidy3d/ # Package source + │ └── tests/ # Tests + └── .aws/ # AWS credentials (if mounted) + +Troubleshooting +--------------- + +Container Issues +^^^^^^^^^^^^^^^^ + +**Problem**: ``docker: permission denied`` + +**Solution**: Add your user to the docker group: + +.. code-block:: bash + + sudo usermod -aG docker $USER + # Logout and login (or reboot) + +**Problem**: Container exits immediately + +**Solution**: Check logs for errors: + +.. code-block:: bash + + docker logs tidy3d_python_client_dev + + # Try running interactively to debug + docker run --rm -it tidy3d-python-client-dev bash + +**Problem**: Port 8888 already in use + +**Solution**: Use a different port: + +.. code-block:: bash + + docker container create --name=tidy3d_python_client_dev \ + --userns=host \ + -p 8889:8888 \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev + + # Access Jupyter at http://localhost:8889 + +Permission Issues +^^^^^^^^^^^^^^^^^ + +**Problem**: Cannot edit files, permission denied + +**Solution**: Ensure container uses host user namespace: + +.. code-block:: bash + + # Container must be created with --userns=host flag + docker container create --name=tidy3d_python_client_dev \ + --userns=host \ + -v .:/home/flexdaemon/tidy3d \ + tidy3d-python-client-dev + +**Problem**: ``flexdaemon`` user has wrong UID + +**Solution**: The ``dev.Dockerfile`` creates user with UID 1000. If your host user has a different UID, files may have permission issues. Check your host UID: + +.. code-block:: bash + + # On host + id -u # Should output 1000 for best compatibility + + # If different, you can modify the Dockerfile or change file ownership + +Virtual Environment Issues +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Problem**: ``uv venv`` command not found + +**Solution**: ``uv`` is pre-installed in the image. Verify: + +.. code-block:: bash + + which uv + uv --version + + # If missing, rebuild the image + +**Problem**: Virtual environment activation fails + +**Solution**: Make sure you're in the right directory: + +.. code-block:: bash + + cd /home/flexdaemon/tidy3d + ls .venv # Should exist after 'uv venv' + source .venv/bin/activate + +Test Failures +^^^^^^^^^^^^^ + +**Problem**: Tests fail with ``ModuleNotFoundError`` + +**Solution**: Reinstall in editable mode: + +.. code-block:: bash + + # Inside container with venv activated + cd /home/flexdaemon/tidy3d + uv pip install -e .[dev] + +**Problem**: Import errors for optional dependencies + +**Solution**: Install all extras: + +.. code-block:: bash + + uv pip install -e .[dev,jax,vtk,trimesh,gdstk] + +CodeArtifact Authentication Issues +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +**Problem**: ``poetry install -E extras`` fails with "Unable to find tidy3d-extras" or authentication error + +**Solution**: Verify SSO session is active and re-authenticate: + +.. code-block:: bash + + # Check if SSO session is valid + aws sts get-caller-identity --profile flexcompute-pypi + + # If expired or fails, re-authenticate (2-step process) + aws sso login --profile flexcompute-pypi + poetry aws-login codeartifact --profile flexcompute-pypi + + # Retry installation + poetry install -E extras + +**Problem**: ``aws sso login`` doesn't work, times out, or fails + +**Solution**: This is an AWS account access issue. Contact IT support or your team lead to verify: + +* Your AWS account is active +* You have access to the correct AWS organization +* Your SSO credentials are correct +* You have the ``codeartifact-readonly`` role assigned + +**Problem**: ``poetry aws-login`` command not found + +**Solution**: The plugin is pre-installed in the dev container. If missing: + +.. code-block:: bash + + # Check if plugin is installed + poetry self show plugins + + # Reinstall if needed + poetry self add poetry-codeartifact-login + +**Problem**: I use pip/uv, not Poetry. How do I authenticate? + +**Solution**: The officially supported path is Poetry with the ``poetry aws-login`` command. + +For pip/uv users, you must self-support using AWS documentation. + +**Option 1 (for pip users):** + +.. code-block:: bash + + aws sso login --profile flexcompute-pypi + aws codeartifact login --tool pip \ + --repository pypi-releases \ + --domain flexcompute \ + --domain-owner 625554095313 \ + --region us-east-1 \ + --profile flexcompute-pypi + +**Option 2 (not recommended):** + +.. code-block:: bash + + CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token \ + --domain flexcompute \ + --domain-owner 625554095313 \ + --query authorizationToken \ + --output text \ + --profile flexcompute-pypi` + + pip config set site.extra-index-url \ + https://aws:$CODEARTIFACT_AUTH_TOKEN@flexcompute-625554095313.d.codeartifact.us-east-1.amazonaws.com/pypi/pypi-releases/simple/ + +Note: Tokens expire after 12 hours, requiring daily re-authentication. + +Alternatively, use the Docker development environment. + +**Problem**: ``poetry lock`` or ``poetry update`` hangs + +**Solution**: Clear Poetry cache and retry: + +.. code-block:: bash + + # Clear Poetry cache + poetry cache clear pypi --all + poetry cache clear codeartifact --all + + # Retry operation + poetry update --lock + +Jupyter Issues +^^^^^^^^^^^^^^ + +**Problem**: Cannot access Jupyter Lab from host browser + +**Solution**: Ensure Jupyter is binding to 0.0.0.0: + +.. code-block:: bash + + # Inside container + jupyter lab . --ip=0.0.0.0 --port=8888 + + # Port must be forwarded when creating container + # Check with: docker inspect tidy3d_python_client_dev | grep PortBindings + +**Problem**: Jupyter kernel dies or doesn't start + +**Solution**: Check virtual environment has ipykernel: + +.. code-block:: bash + + uv pip install ipykernel + python -m ipykernel install --user --name=tidy3d + +Next Steps +---------- + +After completing this guide, you should have: + +✅ Docker development container running +✅ Python environment set up with dependencies +✅ Tests passing successfully +✅ (Optional) Jupyter Lab accessible +✅ (Optional) VSCode attached to container +✅ (Optional) AWS CodeArtifact configured + +**Continue learning:** + +* :doc:`usage` - Advanced development workflows +* :doc:`documentation` - Building and writing documentation +* :doc:`recommendations` - Best practices and code style +* :doc:`release/index` - Release process and versioning + +**More Docker resources:** + +* `Docker Documentation `_ +* `Dev Containers in VSCode `_ + +Welcome to Docker-based Tidy3D development! diff --git a/tests/tidy3d_extras_license_test.py b/tests/tidy3d_extras_license_test.py new file mode 100644 index 0000000000..f94b333340 --- /dev/null +++ b/tests/tidy3d_extras_license_test.py @@ -0,0 +1,111 @@ +"""Test that the license check raises an error if the api key is invalid.""" + +from __future__ import annotations + +import os +import subprocess +import sys + +import pytest + +import tidy3d as td + + +def _extension_can_load() -> bool: + """Return True if the tidy3d_extras extension can be loaded on this platform.""" + try: + import tidy3d_extras + + # Check if the extension module is actually available + return hasattr(tidy3d_extras, "extension") + except Exception: + return False + + +def _local_subpixel_works_with_bad_key() -> bool: + """Return True if local subpixel succeeds even with a bad API key.""" + sim = td.Simulation( + size=(1, 0, 0), + grid_spec=td.GridSpec.auto(wavelength=1), + boundary_spec=td.BoundarySpec.all_sides(td.Periodic()), + run_time=1e-30, + ) + prev_pref = td.config.simulation.use_local_subpixel + td.config.simulation.use_local_subpixel = True + try: + _ = sim.epsilon_on_grid( + grid=sim.discretize(sim.geometry), + freq=td.C_0 / 1.55, + ) + return True + except td.exceptions.Tidy3dImportError: + return False + finally: + td.config.simulation.use_local_subpixel = prev_pref + + +def test_license_check(monkeypatch, caplog): + monkeypatch.setenv("SIMCLOUD_APIKEY", "BADKEY") + + # package should still import successfully, just without .extension + result = subprocess.run( + [sys.executable, "-c", "import tidy3d_extras"], + capture_output=True, + text=True, + check=True, + cwd=os.path.dirname(__file__), + ) + assert result.returncode == 0 + print(result.stdout) + + # calling local_subpixel should fail with a clear error message when the API key is bad + with pytest.raises(subprocess.CalledProcessError) as excinfo: + subprocess.run( + [ + sys.executable, + "-c", + "import tidy3d_extras_license_test; tidy3d_extras_license_test.subpixel()", + ], + capture_output=True, + text=True, + check=True, + cwd=os.path.dirname(__file__), + ) + print(result.stdout) + print(excinfo.value.stdout) + print(excinfo.value.stderr) + + # Check if the extension can actually load on this platform + # On some platforms (e.g., macOS with certain Python versions), the extension + # may fail to load due to ABI compatibility issues before license checks can run + extension_loads = _extension_can_load() + + combined_output = excinfo.value.stdout + excinfo.value.stderr + + # Core license / auth failure - only check if extension loads properly + if extension_loads: + assert "Incorrect API Key" in combined_output, ( + "Expected 'Incorrect API Key' error when extension loads but API key is invalid" + ) + + # tidy3d-extras initialization and feature error messages should always be present + assert ( + "invalid API key" in combined_output or "did not initialize correctly" in combined_output + ), "Expected tidy3d-extras initialization error message" + assert "local_subpixel" in combined_output, ( + "Expected 'local_subpixel' to be mentioned in error message" + ) + + +def subpixel(): + sim = td.Simulation( + size=(1, 0, 0), + grid_spec=td.GridSpec.auto(wavelength=1), + boundary_spec=td.BoundarySpec.all_sides(td.Periodic()), + run_time=1e-30, + ) + td.config.simulation.use_local_subpixel = True + _ = sim.epsilon_on_grid( + grid=sim.discretize(sim.geometry), + freq=td.C_0 / 1.55, + )