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
2 changes: 1 addition & 1 deletion .copier-answers.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier
_commit: v0.0.50
_commit: v0.0.52
_src_path: gh:LabAutomationAndScreening/copier-python-package-template.git
create_docs: true
description: Generating programs for Vialab to control an Integra Assist Plus liquid
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,5 @@
"initializeCommand": "sh .devcontainer/initialize-command.sh",
"onCreateCommand": "sh .devcontainer/on-create-command.sh",
"postStartCommand": "sh .devcontainer/post-start-command.sh"
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 04ec2046 # spellchecker:disable-line
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 9be2b25c # spellchecker:disable-line
}
6 changes: 3 additions & 3 deletions .devcontainer/install-ci-tooling.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import tempfile
from pathlib import Path

UV_VERSION = "0.8.17"
PNPM_VERSION = "10.16.1"
UV_VERSION = "0.8.22"
PNPM_VERSION = "10.17.1"
COPIER_VERSION = "9.10.2"
COPIER_TEMPLATE_EXTENSIONS_VERSION = "0.3.3"
PRE_COMMIT_VERSION = "4.3.0"
Expand Down Expand Up @@ -65,7 +65,7 @@ def main():
)
else:
_ = subprocess.run(
f"curl -fsSL https://astral.sh/uv/{UV_VERSION}/install.sh | sh",
f"curl -fsSL --connect-timeout 20 --max-time 40 --retry 3 --retry-delay 5 --retry-connrefused --proto '=https' https://astral.sh/uv/{UV_VERSION}/install.sh | sh",
check=True,
shell=True,
env=uv_env,
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/get-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ jobs:
dependabot-commit-created: ${{ steps.update-hash.outputs.commit-created }}
pr-short-num: ${{ steps.find-pr-num.outputs.number }}
steps:
- name: Display full GitHub context
run: |
jq . <<'JSON'
${{ toJSON(github) }}
JSON

- name: Checkout code
uses: actions/checkout@v5.0.0
with:
persist-credentials: false

- name: Update Devcontainer Hash
if: ${{ github.actor == 'dependabot[bot]' && github.event_name == 'push' }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ jobs:
uses: actions/checkout@v5.0.0
with:
ref: ${{ github.ref_name }} # explicitly get the head of the branch, which will include any new commits pushed if this is a dependabot branch
persist-credentials: false

- name: Checkout code not during push
if: ${{ github.event_name != 'push' }}
uses: actions/checkout@v5.0.0
with:
persist-credentials: false

- name: Install latest versions of packages
uses: ./.github/actions/install_deps
Expand Down
19 changes: 15 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ repos:
.*pyrightconfig\.json|
)$

- repo: https://github.com/pre-commit/mirrors-prettier # TODO: switch to a different approach...this was archived in 2024
rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8
- repo: https://github.com/rbubley/mirrors-prettier
rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2
hooks:
- id: prettier
# TODO: get template YAML and MD files more in line with prettier expectations so we can start using prettier on those too
Expand All @@ -125,6 +125,7 @@ repos:
.*/vendor_files/.*|
.*/schema.graphql|
.*generated/graphql.ts|
template/.*|
)$
files: (.*.json)|(.*.ts)|(.*.jsx)|(.*.tsx)|(.*.yaml)|(.*.yml)|(.*.md)|(.*.html)|(.*.css)|(.*.scss)|(.*.less)|(.*.vue)|(.*.graphql)|(.*.gql)

Expand Down Expand Up @@ -178,6 +179,11 @@ repos:
- id: check-merge-conflict
- id: check-case-conflict

- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 83987cd6ad8943c7f029b500b14aaf82c00a01fa # frozen: 0.34.0
hooks:
- id: check-github-workflows

- repo: https://github.com/maresb/check-json5
rev: 893a2b5a0a27c3540bd8fcafe2968ccc05237179 # 1.0
hooks:
Expand Down Expand Up @@ -205,6 +211,11 @@ repos:
hooks:
- id: detect-private-key

# - repo: https://github.com/woodruffw/zizmor-pre-commit # TODO: implement this: https://github.com/LabAutomationAndScreening/copier-base-template/issues/95
# rev: b933184438555436e38621f46ceb0c417cbed400 # frozen: v1.13.0
# hooks:
# - id: zizmor

# Linting

- repo: https://github.com/Lucas-C/pre-commit-hooks-markup
Expand All @@ -215,15 +226,15 @@ repos:
exclude: docs/.*\.rst$

- repo: https://github.com/hadolint/hadolint
rev: 87de847754330ad47ae16bdfe2d1a757ccb4b4d4 # frozen: v2.13.1
rev: 4e697ba704fd23b2409b947a319c19c3ee54d24f # frozen: v2.14.0
hooks:
- id: hadolint-docker
name: Lint Dockerfiles
exclude: .*\.jinja$
description: Runs hadolint to lint Dockerfiles

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 13a6bda8ea7612b3aec844ded16569d424b9a1ab # frozen: v0.13.0
rev: a113f03edeabb71305f025e6e14bd2cd68660e29 # frozen: v0.13.1
hooks:
- id: ruff
name: ruff-src
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Deprecated


## [0.2.6] - 2025-10-07

### Fixed
- Include the vendor files directory in the packaged library

---

## [0.2.5] - 2025-09-08
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pyalab"
version = "0.2.5"
version = "0.2.6"
description = "Generating programs for Vialab to control an Integra Assist Plus liquid handling robot"
authors = [
{name = "Eli Fine"},
Expand Down Expand Up @@ -28,9 +28,9 @@ dev = [
# Specific to this repository

"lxml-stubs>=0.5.1",
"syrupy>=4.9.1",
"syrupy>=5.0.0",
"pytest-mock>=3.15.0",
"faker>=37.5.3",
"faker>=37.8.0",
"autodoc_pydantic==2.2.0",

# Managed by upstream template
Expand All @@ -44,6 +44,7 @@ dev = [

[tool.setuptools]
license-files = [] # kludge until this bug is fixed https://github.com/pypa/setuptools/issues/4759
package-data = {"pyalab" = ["vendor_files/**/*.xml"]}

[tool.uv]
package = true
Expand Down
4 changes: 4 additions & 0 deletions ruff-test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ ignore = [
"TRY003", # tests dont need to create a custom exception classes, generally you want to throw an AssertionError with a message anyway
]

unfixable = [
"SIM117", # pytest.raises context managers should generally be separate from others so they can be very specific about where the error is being raised from, so don't autofix this
]

[lint.flake8-pytest-style]
raises-require-match-for = ["*"] # ensures we always have a match for our raises statements so that we validate tests fail for the right reason
11 changes: 6 additions & 5 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ ignore = [
"D102", # Docstrings are not always necessary for public methods
"D103", # Docstrings are not always necessary for public functions
"D104", # Docstrings are not always necessary for public packages
"D105", # Docstrings are not always necessary for magic methods
"D106", # Nested classes are usually library-specific and don't always require its own docstring
"D107", # Init shouldn't need its own docstring, those arguments can be captured in the class level docstring
"D203", # Ignore D203 because it's a bug https://github.com/PyCQA/pydocstyle/issues/141
Expand All @@ -68,10 +69,10 @@ ignore = [
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = [
"T201", "T203", # don't automatically delete print statements, just warn about them
"RUF100", # don't automatically remove unnecessary suppressions, because that can leave behind a now-stale comment about why the suppression was originally added
"FURB136", "PLR1730", # don't automatically replace if statements with min/max functions. Sometimes the if statement is more readable or better for code coverage checking
]
"T201", "T203", # don't automatically delete print statements, just warn about them
"RUF100", # don't automatically remove unnecessary suppressions, because that can leave behind a now-stale comment about why the suppression was originally added
"FURB136", "PLR1730", # don't automatically replace if statements with min/max functions. Sometimes the if statement is more readable or better for code coverage checking
]

# Allow unused variables if it's an underscore or double underscore.
dummy-variable-rgx = "^_$|^__$"
Expand All @@ -86,7 +87,7 @@ dummy-variable-rgx = "^_$|^__$"
force-single-line = true

[lint.flake8-builtins]
builtins-ignorelist = ["id"] # We use the id() builtin little enough that it's okay to shadow it
builtins-ignorelist = ["id"] # We use the id() builtin rarely enough that it's okay to shadow it

[lint.flake8-annotations]
mypy-init-return = true # we don't care about annotating all __init__ methods as returning None
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class SingleFileXmlSnapshot(SingleFileSnapshotExtension):
_write_mode = (
WriteMode.TEXT
) # for some reason the default is binary, but it should be text to make diffs easier to read
_file_extension = "xml"
file_extension = "xml"
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing underscore prefix. The property should be _file_extension to match the naming convention of other private properties in this class like _write_mode.

Suggested change
file_extension = "xml"
_file_extension = "xml"

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify attribute name change aligns with syrupy 5.0.0 API.

This change from _file_extension to file_extension (removing the underscore prefix) contradicts a previous review comment that suggested it should be private to match the naming convention of _write_mode. This might be required by the syrupy 5.0.0 upgrade. Please verify that this change aligns with the syrupy 5.0.0 API requirements for SingleFileSnapshotExtension.

Search the web for syrupy 5.0.0 API documentation:

Additionally, check the syrupy source code:


🏁 Script executed:

#!/bin/bash
# Description: Check if file_extension needs to be public in syrupy 5.x

# Search for how file_extension is used in test files
echo "=== Checking file_extension usage ==="
rg -n --type=py 'file_extension' tests/

# Look for similar snapshot extension patterns in the codebase
echo "=== Checking for other snapshot extensions ==="
ast-grep --pattern $'class $_ (SingleFileSnapshotExtension):
  $$$
'

Length of output: 494


🌐 Web query:

Does syrupy 5.0.0 SingleFileSnapshotExtension require file_extension to be a public attribute instead of _file_extension?

💡 Result:

Short answer: No — SingleFileSnapshotExtension expects the (protected) _file_extension attribute (as used in the docs and examples). See the issue showing subclasses overriding _file_extension (and the subsequent fix for multi-part extensions). [1][2]

Sources:

  • README / built-in extensions listing (SingleFileSnapshotExtension). [1]
  • GitHub issue demonstrating subclassing by setting _file_extension and related fix. [2]

Override protected _file_extension instead of defining public file_extension
SingleFileSnapshotExtension subclasses must override the protected _file_extension attribute, not introduce a public file_extension.
Location: tests/unit/snapshot.py:14

🤖 Prompt for AI Agents
In tests/unit/snapshot.py around line 14, the subclass defines a public
attribute file_extension = "xml" but the framework expects subclasses to
override the protected attribute _file_extension; change/remove the public
file_extension and set the protected attribute _file_extension = "xml" instead
so SingleFileSnapshotExtension uses the intended protected override.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the correct syntax for the new v5 of syrupy

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🌐 Web query:

syrupy 5.0.0 breaking changes file_extension attribute SingleFileSnapshotExtension

💡 Result:

Short answer: there is no Syrupy 5.0.0 breaking change that removes the file‑extension attribute from SingleFileSnapshotExtension. The SingleFile extensions still expect/allow an internal _file_extension override (and historically used that private attribute), and support for multi‑part extensions (e.g. "png.zip") was added in an earlier fix. See: [CHANGELOG / v4.9.x], issue about multi‑part extensions (fixed/merged), and the SingleFile behavior discussion. [1][2][3]

Sources:

  • CHANGELOG (serializer/amber notes / v4.9.x showing serializer changes discussed for v5 milestone). [1]
  • Issue: "having a dot in the file extension causes unexpected behavior" (discusses _file_extension and shows fix for multi‑part extensions was merged). [2]
  • Issue: "Crash with SingleFileSnapshotExtension" (explains SingleFile uses a byte/string serializer and the private _file_extension usage/behavior). [3]

🌐 Web query:

syrupy 5.0.0 release notes changelog migration guide v4 to v5

💡 Result:

Summary of Syrupy 5.0.0 — key release / migration notes (v4 → v5)

  • Where to read full notes and docs: Syrupy repo README / v5 pre-release docs. [1]
  • Package release (PyPI 5.0.0 published). [2]

What changed (high‑level)

  • Major release (breaking changes). Requires Python > 3.9 and pytest >= 8 (v4 required Python >3.8.1 and pytest >=7,<9). [1][2]
  • Main branch/docsite moved to the v5 pre-release (readme/doc differences vs. v4 stable docs). [1]
  • Matchers: v5 improves matcher composition (compose_matchers) and changes matcher behavior/ APIs (issues and migration discussions exist in the v5 milestone). Example: easier composition for path_type/path_value and new behaviors for float handling. See issue threads for examples. [1][3]
  • General: many v5 changes were tracked as milestone issues (ordering of parameterized snapshots, matcher improvements, etc.). Review milestone/issue list for specific behavioral changes affecting snapshots. [3]

Migration checklist (v4 → v5)

  1. Upgrade runtime/test tooling:
    • Move to Python > 3.9 and pytest >= 8. [1][2]
  2. Update syrupy in your environment:
    • pip install --upgrade syrupy (or pin ==5.0.0). [2]
  3. Review snapshot matcher usage:
    • If you use custom matchers or path_type/path_value, check tests for compatibility; consider using compose_matchers where applicable. [1][3]
  4. Remove or adjust conflicting snapshot libraries:
    • If you previously used snapshottest, uninstall it and remove old snapshots (syrupy has guidance for migration from snapshottest). [1]
  5. Run test suite and update snapshots:
    • Run pytest; run pytest --snapshot-update to update snapshots where intended. Inspect any reordered or reformatted snapshots and adjust tests if necessary. [1]
  6. Check issues/milestone for edge cases:
    • Look through the v5 milestone and related issues for any behaviors that match your usage (parameterized ordering, float serialization, xdist limitations, etc.). [3]

If you want, I can:

  • fetch and paste the repository CHANGELOG or RELEASE notes (if present) and extract all breaking changes and code examples; or
  • scan your repo/tests for likely breaking matcher usages and suggest concrete changes to migrate.

Sources

  • Syrupy repository README / v5 notes (main branch). [1]
  • PyPI page for syrupy (release 5.0.0). [2]
  • Selected v5 milestone/issue discussion (matchers, float handling, ordering). [3]

🏁 Script executed:

#!/bin/bash
# Check the installed syrupy version and look for any examples or documentation
# in the repository about SingleFileSnapshotExtension usage

echo "=== Checking pyproject.toml for syrupy version ==="
rg -n "syrupy" pyproject.toml

echo -e "\n=== Looking for any syrupy documentation or examples in the repo ==="
fd -e md -e txt -e rst | xargs rg -l "syrupy" 2>/dev/null || echo "No documentation files found"

echo -e "\n=== Checking all Python files for SingleFileSnapshotExtension usage ==="
rg -n "SingleFileSnapshotExtension" --type=py

echo -e "\n=== Checking imports from syrupy ==="
rg -n "from syrupy" --type=py

Length of output: 779


@ejfine, thank you for the clarification! I appreciate you confirming that file_extension (public attribute) is the correct syntax for syrupy v5.0.0. I'll note this API change for future reference.


✏️ Learnings added
Learnt from: ejfine
PR: LabAutomationAndScreening/pyalab#52
File: tests/unit/snapshot.py:14-14
Timestamp: 2025-10-07T13:46:20.557Z
Learning: In syrupy 5.0.0, SingleFileSnapshotExtension subclasses should use the public attribute `file_extension` instead of the protected attribute `_file_extension` that was used in syrupy 4.x.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.



@pytest.fixture
Expand Down
24 changes: 12 additions & 12 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.