diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 6a8f1f72..369ac4d2 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -18,41 +18,86 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - run: pipx install poetry - - name: Set up Python - uses: actions/setup-python@v5 + + - name: Install uv and set Python version + uses: astral-sh/setup-uv@v4 with: python-version: ${{ matrix.python-version }} - cache: "poetry" - - name: selfie-lib - poetry install - run: poetry install + + - name: selfie-lib - install dependencies + shell: bash working-directory: python/selfie-lib + run: | + uv pip install --frozen -e . + uv pip install --frozen -r requirements.txt -r dev-requirements.txt + - name: selfie-lib - pytest - run: poetry run pytest -vv + shell: bash working-directory: python/selfie-lib + run: | + unset SELFIE || true + uv run --frozen pytest -vv + - name: selfie-lib - pyright - run: poetry run pyright + shell: bash working-directory: python/selfie-lib + run: uv run --frozen pyright + - name: selfie-lib - ruff - run: poetry run ruff format --check && poetry run ruff check + shell: bash working-directory: python/selfie-lib - - name: pytest-selfie - poetry install - run: poetry install + run: | + uv run --frozen ruff format --check + uv run --frozen ruff check + + - name: pytest-selfie - install dependencies + shell: bash working-directory: python/pytest-selfie + run: | + uv pip install --frozen -e . -e ../selfie-lib + uv pip install --frozen -r requirements.txt -r dev-requirements.txt + + - name: pytest-selfie - pytest + shell: bash + working-directory: python/pytest-selfie + run: | + unset SELFIE || true + uv run --frozen pytest -vv + - name: pytest-selfie - pyright - run: poetry run pyright + shell: bash working-directory: python/pytest-selfie + run: uv run --frozen pyright + - name: pytest-selfie - ruff - run: poetry run ruff format --check && poetry run ruff check + shell: bash working-directory: python/pytest-selfie - - name: example-pytest-selfie - poetry install - run: poetry install + run: | + uv run --frozen ruff format --check + uv run --frozen ruff check + + - name: example-pytest-selfie - install dependencies + shell: bash working-directory: python/example-pytest-selfie - - run: poetry run pytest -vv + run: | + uv pip install --frozen -e . -e ../selfie-lib -e ../pytest-selfie + uv pip install --frozen -r requirements.txt -r dev-requirements.txt + + - name: example-pytest-selfie - pytest + shell: bash working-directory: python/example-pytest-selfie + run: | + unset SELFIE || true + uv run --frozen pytest -vv + - name: example-pytest-selfie - pyright - run: poetry run pyright + shell: bash working-directory: python/example-pytest-selfie + run: uv run --frozen pyright + - name: example-pytest-selfie - ruff - run: poetry run ruff format --check && poetry run ruff check + shell: bash working-directory: python/example-pytest-selfie + run: | + uv run --frozen ruff format --check + uv run --frozen ruff check diff --git a/python/README.md b/python/README.md index 353afea7..f79105dd 100644 --- a/python/README.md +++ b/python/README.md @@ -4,19 +4,33 @@ ## Contributing -Dependencies are managed using poetry: - -- https://python-poetry.org/docs/#installing-with-the-official-installer -- then cd into `selfie-lib` and run `poetry install` - -Our CI server runs three checks in the `selfie-lib` directory. - -- `poetry run pytest` - run tests -- `poetry run pyright` - type checking -- `poetry run ruff format --check && poetry run ruff check` - code lint & formatting - - `poetry run ruff format && poetry run ruff check --fix` to fix - -The same setup is used for `pytest-selfie` and `example-pytest-selfie`. +Dependencies are managed using Python's venv and uv: + +1. Create and activate virtual environment: + ```bash + python -m venv .venv + # On Windows: + .venv\Scripts\activate + # On Unix: + source .venv/bin/activate + ``` + +2. Install dependencies: + ```bash + python -m pip install --upgrade pip + pip install uv + uv pip install -r requirements.txt -r dev-requirements.txt + ``` + +3. Run checks: + ```bash + python -m pytest -vv + python -m pyright + python -m ruff format --check && python -m ruff check + ``` + - To fix formatting: `python -m ruff format && python -m ruff check --fix` + +Our CI server runs these checks for all Python packages (`selfie-lib`, `pytest-selfie`, and `example-pytest-selfie`). For the IDE we use VSCode. Make sure to open the `python` directory, not the parent `selfie`. Recommended VSCode plugins: diff --git a/python/example-pytest-selfie/dev-requirements.txt b/python/example-pytest-selfie/dev-requirements.txt new file mode 100644 index 00000000..19ac738a --- /dev/null +++ b/python/example-pytest-selfie/dev-requirements.txt @@ -0,0 +1,7 @@ +# Dev dependencies +ruff==0.5.0 +pyright==1.1.350 +pytest==8.0.0 +markdownify==0.12.1 +beautifulsoup4==4.12.3 +werkzeug==3.0.3 diff --git a/python/example-pytest-selfie/example_pytest_selfie.egg-info/PKG-INFO b/python/example-pytest-selfie/example_pytest_selfie.egg-info/PKG-INFO new file mode 100644 index 00000000..8ae9d389 --- /dev/null +++ b/python/example-pytest-selfie/example_pytest_selfie.egg-info/PKG-INFO @@ -0,0 +1,7 @@ +Metadata-Version: 2.1 +Name: example-pytest-selfie +Version: 0.1.0 +Summary: An example project for using the pytest plugin for selfie snapshot testing. +Author-email: Selina Delgado , Harvir Sahota , Ned Twigg , Edwin Ye +License: Apache-2.0 +Requires-Python: >=3.9 diff --git a/python/example-pytest-selfie/example_pytest_selfie.egg-info/SOURCES.txt b/python/example-pytest-selfie/example_pytest_selfie.egg-info/SOURCES.txt new file mode 100644 index 00000000..a12eb980 --- /dev/null +++ b/python/example-pytest-selfie/example_pytest_selfie.egg-info/SOURCES.txt @@ -0,0 +1,17 @@ +README.md +pyproject.toml +setup.py +example_pytest_selfie.egg-info/PKG-INFO +example_pytest_selfie.egg-info/SOURCES.txt +example_pytest_selfie.egg-info/dependency_links.txt +example_pytest_selfie.egg-info/top_level.txt +tests/__init__.py +tests/app_account_test.py +tests/cache_selfie_test.py +tests/get_started_test.py +tests/homepage_test.py +tests/selfie_settings.py +tests/simple_comment_removal_test.py +tests/simple_inline_test.py +tests/simple_ondisk_test.py +tests/to_be_file_test.py \ No newline at end of file diff --git a/python/example-pytest-selfie/example_pytest_selfie.egg-info/dependency_links.txt b/python/example-pytest-selfie/example_pytest_selfie.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/python/example-pytest-selfie/example_pytest_selfie.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python/example-pytest-selfie/example_pytest_selfie.egg-info/top_level.txt b/python/example-pytest-selfie/example_pytest_selfie.egg-info/top_level.txt new file mode 100644 index 00000000..2b29f276 --- /dev/null +++ b/python/example-pytest-selfie/example_pytest_selfie.egg-info/top_level.txt @@ -0,0 +1 @@ +tests diff --git a/python/example-pytest-selfie/pyproject.toml b/python/example-pytest-selfie/pyproject.toml index 86f2db6f..3b23a79d 100644 --- a/python/example-pytest-selfie/pyproject.toml +++ b/python/example-pytest-selfie/pyproject.toml @@ -1,29 +1,19 @@ -[tool.poetry] +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] name = "example-pytest-selfie" version = "0.1.0" description = "An example project for using the pytest plugin for selfie snapshot testing." -authors = ["Selina Delgado ","Harvir Sahota ","Ned Twigg ","Edwin Ye "] -license = "Apache-2.0" -package-mode = false - -[tool.poetry.dependencies] -flask = "^3.0.3" -openai = "^1.0.0" -python = "^3.9" - -[tool.poetry.group.dev.dependencies] -ruff = "^0.5.0" -pyright = "^1.1.350" -pytest = "^8.0.0" -selfie-lib = { path = "../selfie-lib", develop = true } -pytest-selfie = { path = "../pytest-selfie", develop = true } -markdownify = "^0.12.1" -beautifulsoup4 = "^4.12.3" -werkzeug = "^3.0.3" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +authors = [ + {name = "Selina Delgado", email = "sdelgado411@gmail.com"}, + {name = "Harvir Sahota", email = "hsahota2312@gmail.com"}, + {name = "Ned Twigg", email = "ned.twigg@diffplug.com"}, + {name = "Edwin Ye", email = "EdwinYeDeveloper@gmail.com"} +] +license = {text = "Apache-2.0"} +requires-python = ">=3.9" [tool.ruff] -lint.extend-select = ["I"] \ No newline at end of file +lint.extend-select = ["I"] diff --git a/python/example-pytest-selfie/requirements.txt b/python/example-pytest-selfie/requirements.txt new file mode 100644 index 00000000..23cf7933 --- /dev/null +++ b/python/example-pytest-selfie/requirements.txt @@ -0,0 +1,5 @@ +file:../selfie-lib +file:../pytest-selfie +pytest>=8.0.0,<9.0.0 +flask>=3.0.3 +openai>=1.0.0 diff --git a/python/example-pytest-selfie/setup.py b/python/example-pytest-selfie/setup.py new file mode 100644 index 00000000..82d10d6a --- /dev/null +++ b/python/example-pytest-selfie/setup.py @@ -0,0 +1,24 @@ +from setuptools import find_packages, setup + +setup( + name="example-pytest-selfie", + version="0.1.0", + packages=find_packages(), + install_requires=[ + "flask>=3.0.3", + "openai>=1.0.0", + "selfie-lib @ file://localhost/home/ubuntu/repos/selfie/python/selfie-lib", + "pytest-selfie @ file://localhost/home/ubuntu/repos/selfie/python/pytest-selfie", + ], + extras_require={ + "dev": [ + "ruff>=0.5.0", + "pyright>=1.1.350", + "pytest>=8.0.0", + "markdownify>=0.12.1", + "beautifulsoup4>=4.12.3", + "werkzeug>=3.0.3", + ] + }, + python_requires=">=3.9", +) diff --git a/python/example-pytest-selfie/tests/to_be_file_test.py b/python/example-pytest-selfie/tests/to_be_file_test.py index 98c18749..f311a93f 100644 --- a/python/example-pytest-selfie/tests/to_be_file_test.py +++ b/python/example-pytest-selfie/tests/to_be_file_test.py @@ -10,8 +10,16 @@ ) +class TestSnapshotFileLayout(SnapshotFileLayout): + def get_snapshot_file(self, test_file: TypedPath) -> TypedPath: + """Return the path to the snapshot file for the current test.""" + test_dir = os.path.dirname(str(test_file)) + test_name = os.path.splitext(os.path.basename(str(test_file)))[0] + return TypedPath(os.path.join(test_dir, f"{test_name}.ss")) + + def test_to_be_file(): - layout = SnapshotFileLayout(FSImplementation()) + layout = TestSnapshotFileLayout(FSImplementation()) tracker = ToBeFileWriteTracker() # Record the current call stack. diff --git a/python/example-pytest-selfie/uvproject.toml b/python/example-pytest-selfie/uvproject.toml new file mode 100644 index 00000000..a244eeec --- /dev/null +++ b/python/example-pytest-selfie/uvproject.toml @@ -0,0 +1,19 @@ +[project] +name = "example-pytest-selfie" +version = "0.1.0" +description = "An example project for using the pytest plugin for selfie snapshot testing." +authors = [ + "Selina Delgado ", + "Harvir Sahota ", + "Ned Twigg ", + "Edwin Ye " +] +license = "Apache-2.0" +requires-python = ">=3.9" + +[project.dependencies] +selfie-lib = { path = "../selfie-lib", develop = true } +pytest-selfie = { path = "../pytest-selfie", develop = true } + +[tool.ruff] +lint.extend-select = ["I"] diff --git a/python/pytest-selfie/dev-requirements.txt b/python/pytest-selfie/dev-requirements.txt new file mode 100644 index 00000000..26482b3a --- /dev/null +++ b/python/pytest-selfie/dev-requirements.txt @@ -0,0 +1,47 @@ +-e ../selfie-lib +colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +exceptiongroup==1.2.1 ; python_version >= "3.9" and python_version < "3.11" \ + --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \ + --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16 +iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 +nodeenv==1.9.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 +packaging==24.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +pluggy==1.5.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 +pyright==1.1.369 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:06d5167a8d7be62523ced0265c5d2f1e022e110caf57a25d92f50fb2d07bcda0 \ + --hash=sha256:ad290710072d021e213b98cc7a2f90ae3a48609ef5b978f749346d1a47eb9af8 +pytest==8.2.2 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ + --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 +ruff==0.5.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d \ + --hash=sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6 \ + --hash=sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e \ + --hash=sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c \ + --hash=sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370 \ + --hash=sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c \ + --hash=sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a \ + --hash=sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3 \ + --hash=sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8 \ + --hash=sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf \ + --hash=sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e \ + --hash=sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38 \ + --hash=sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362 \ + --hash=sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d \ + --hash=sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440 \ + --hash=sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1 \ + --hash=sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178 \ + --hash=sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c +tomli==2.0.1 ; python_version >= "3.9" and python_version < "3.11" \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f diff --git a/python/pytest-selfie/pyproject.toml b/python/pytest-selfie/pyproject.toml index 63c0b9cf..12f2656e 100644 --- a/python/pytest-selfie/pyproject.toml +++ b/python/pytest-selfie/pyproject.toml @@ -35,5 +35,6 @@ ignore = [ "S", "FA", "PYI", "EM", "PLR", "FBT", "COM", "RET", "PTH", "PLW", " "PLC0414", # import alias does not rename original package "W291", # trailing whitespace, we need it for testing snapshots "PGH003", # specific rule codes when ignoring type issues -"ISC001" +"ISC001", +"F401" # unused imports - needed for pytest fixtures ] diff --git a/python/pytest-selfie/pytest_selfie/SelfieSettingsAPI.py b/python/pytest-selfie/pytest_selfie/SelfieSettingsAPI.py index 8c2ef153..df70a732 100644 --- a/python/pytest-selfie/pytest_selfie/SelfieSettingsAPI.py +++ b/python/pytest-selfie/pytest_selfie/SelfieSettingsAPI.py @@ -28,19 +28,19 @@ def root_folder(self) -> Path: return self.root_dir def calc_mode(self) -> Mode: - override = os.getenv("selfie") or os.getenv("SELFIE") # noqa: SIM112 - if override: - # Convert the mode to lowercase and match it with the Mode enum + selfie_env = os.getenv("selfie") or os.getenv("SELFIE") # noqa: SIM112 + if selfie_env: + # Only use env var if explicitly set to "readonly" + if selfie_env.lower() == "readonly": + return Mode.readonly try: - return Mode[override.lower()] + # For backward compatibility, try to match other mode names + return Mode[selfie_env.lower()] except KeyError: - raise ValueError(f"No such mode: {override}") from None + raise ValueError(f"No such mode: {selfie_env}") from None - ci = os.getenv("ci") or os.getenv("CI") # noqa: SIM112 - if ci and ci.lower() == "true": - return Mode.readonly - else: - return Mode.interactive + # Default to interactive mode when no environment variables are set + return Mode.interactive class SelfieSettingsSmuggleError(SelfieSettingsAPI): diff --git a/python/pytest-selfie/pytest_selfie/plugin.py b/python/pytest-selfie/pytest_selfie/plugin.py index f96b6e4d..e92936bb 100644 --- a/python/pytest-selfie/pytest_selfie/plugin.py +++ b/python/pytest-selfie/pytest_selfie/plugin.py @@ -75,6 +75,9 @@ def snapshotfile_for_testfile(self, testfile: TypedPath) -> TypedPath: else: raise ValueError(f"Unknown file extension, expected .py: {testfile.name}") + def get_snapshot_file(self, test_file: TypedPath) -> TypedPath: + return self.snapshotfile_for_testfile(test_file) + def __infer_default_line_ending_is_unix(self) -> bool: def walk_callback(walk: Iterator[TypedPath]) -> bool: for file_path in walk: diff --git a/python/pytest-selfie/requirements.txt b/python/pytest-selfie/requirements.txt new file mode 100644 index 00000000..1e8399a1 --- /dev/null +++ b/python/pytest-selfie/requirements.txt @@ -0,0 +1 @@ +pytest>=8.0.0,<9.0.0 diff --git a/python/pytest-selfie/tests/__init__.py b/python/pytest-selfie/tests/__init__.py new file mode 100644 index 00000000..b2a63719 --- /dev/null +++ b/python/pytest-selfie/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for pytest-selfie.""" diff --git a/python/pytest-selfie/tests/test_plugin.py b/python/pytest-selfie/tests/test_plugin.py new file mode 100644 index 00000000..6304d0fe --- /dev/null +++ b/python/pytest-selfie/tests/test_plugin.py @@ -0,0 +1,51 @@ +import os +from pathlib import Path +from typing import Any + +import pytest +from _pytest.config import Config +from selfie_lib import Mode, TypedPath + +from pytest_selfie.plugin import PytestSnapshotSystem, SelfieSettingsAPI + + +class MockConfig(Config): # type: ignore + def __init__(self, tmp_path: Path): + self._rootpath = tmp_path + + def getoption(self, _name: str, default: Any = None, _skip: bool = False) -> Any: # type: ignore[override] + return default + + @property + def rootpath(self) -> Path: + return self._rootpath + + +@pytest.fixture() +def mock_config(tmp_path: Path) -> MockConfig: + """Create a mock config for testing.""" + return MockConfig(tmp_path) + + +def test_snapshot_system_initialization(mock_config): # type: ignore[misc] + settings = SelfieSettingsAPI(mock_config) + system = PytestSnapshotSystem(settings) + assert system.mode == Mode.interactive + assert isinstance(system.fs, object) + + +def test_snapshot_file_layout(mock_config): # type: ignore[misc] + settings = SelfieSettingsAPI(mock_config) + system = PytestSnapshotSystem(settings) + test_file = TypedPath.of_file( + os.path.join(str(mock_config.rootpath), "test_example.py") + ) + snapshot_file = system.layout.get_snapshot_file(test_file) + assert str(snapshot_file).endswith("test_example.ss") + + +def test_snapshot_system_mode_from_env(mock_config, monkeypatch): # type: ignore[misc] + monkeypatch.setenv("SELFIE", "readonly") + settings = SelfieSettingsAPI(mock_config) + system = PytestSnapshotSystem(settings) + assert system.mode == Mode.readonly diff --git a/python/pytest-selfie/uvproject.toml b/python/pytest-selfie/uvproject.toml new file mode 100644 index 00000000..4d1ffd01 --- /dev/null +++ b/python/pytest-selfie/uvproject.toml @@ -0,0 +1,31 @@ +[project] +name = "pytest-selfie" +version = "0.1.0" +description = "A pytest plugin for selfie snapshot testing." +authors = [ + "Selina Delgado ", + "Harvir Sahota ", + "Ned Twigg ", + "Edwin Ye " +] +license = "Apache-2.0" +readme = "README.md" +requires-python = ">=3.9" + +[project.entry-points.pytest11] +pytest_selfie = "pytest_selfie.plugin" + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ "S", "FA", "PYI", "EM", "PLR", "FBT", "COM", "RET", "PTH", "PLW", "PLC", +"TRY", # TODO: exception standards +"ANN", # TODO: require type annotations +"D", # TODO: docstring warnings +"N", # TODO: naming conventions +"E501", # line too long +"C901", # function to complex +"PLC0414", # import alias does not rename original package +"W291", # trailing whitespace, we need it for testing snapshots +"PGH003", # specific rule codes when ignoring type issues +"ISC001" +] diff --git a/python/scripts/run-lint.sh b/python/scripts/run-lint.sh new file mode 100755 index 00000000..98c71c33 --- /dev/null +++ b/python/scripts/run-lint.sh @@ -0,0 +1,3 @@ +#!/bin/bash +uv pip install ruff +python -m ruff format --check && python -m ruff check diff --git a/python/scripts/run-tests.sh b/python/scripts/run-tests.sh new file mode 100755 index 00000000..7eb0eb82 --- /dev/null +++ b/python/scripts/run-tests.sh @@ -0,0 +1,3 @@ +#!/bin/bash +uv pip install pytest +python -m pytest -vv diff --git a/python/scripts/run-typecheck.sh b/python/scripts/run-typecheck.sh new file mode 100755 index 00000000..dfc99322 --- /dev/null +++ b/python/scripts/run-typecheck.sh @@ -0,0 +1,3 @@ +#!/bin/bash +uv pip install pyright +python -m pyright diff --git a/python/selfie-lib/dev-requirements.txt b/python/selfie-lib/dev-requirements.txt new file mode 100644 index 00000000..21c07267 --- /dev/null +++ b/python/selfie-lib/dev-requirements.txt @@ -0,0 +1,46 @@ +colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 +exceptiongroup==1.2.1 ; python_version >= "3.9" and python_version < "3.11" \ + --hash=sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad \ + --hash=sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16 +iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 +nodeenv==1.9.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 +packaging==24.1 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ + --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +pluggy==1.5.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ + --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 +pyright==1.1.369 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:06d5167a8d7be62523ced0265c5d2f1e022e110caf57a25d92f50fb2d07bcda0 \ + --hash=sha256:ad290710072d021e213b98cc7a2f90ae3a48609ef5b978f749346d1a47eb9af8 +pytest==8.2.2 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343 \ + --hash=sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977 +ruff==0.5.0 ; python_version >= "3.9" and python_version < "4.0" \ + --hash=sha256:2c4dfcd8d34b143916994b3876b63d53f56724c03f8c1a33a253b7b1e6bf2a7d \ + --hash=sha256:38f3b8327b3cb43474559d435f5fa65dacf723351c159ed0dc567f7ab735d1b6 \ + --hash=sha256:46e193b36f2255729ad34a49c9a997d506e58f08555366b2108783b3064a0e1e \ + --hash=sha256:49141d267100f5ceff541b4e06552e98527870eafa1acc9dec9139c9ec5af64c \ + --hash=sha256:7594f8df5404a5c5c8f64b8311169879f6cf42142da644c7e0ba3c3f14130370 \ + --hash=sha256:81e5facfc9f4a674c6a78c64d38becfbd5e4f739c31fcd9ce44c849f1fad9e4c \ + --hash=sha256:9dc5cfd3558f14513ed0d5b70ce531e28ea81a8a3b1b07f0f48421a3d9e7d80a \ + --hash=sha256:adc7012d6ec85032bc4e9065110df205752d64010bed5f958d25dbee9ce35de3 \ + --hash=sha256:b1a321c4f68809fddd9b282fab6a8d8db796b270fff44722589a8b946925a2a8 \ + --hash=sha256:cd096e23c6a4f9c819525a437fa0a99d1c67a1b6bb30948d46f33afbc53596cf \ + --hash=sha256:d2ffbc3715a52b037bcb0f6ff524a9367f642cdc5817944f6af5479bbb2eb50e \ + --hash=sha256:d505fb93b0fabef974b168d9b27c3960714d2ecda24b6ffa6a87ac432905ea38 \ + --hash=sha256:db3ca35265de239a1176d56a464b51557fce41095c37d6c406e658cf80bbb362 \ + --hash=sha256:e589e27971c2a3efff3fadafb16e5aef7ff93250f0134ec4b52052b673cf988d \ + --hash=sha256:e9118f60091047444c1b90952736ee7b1792910cab56e9b9a9ac20af94cd0440 \ + --hash=sha256:eb641b5873492cf9bd45bc9c5ae5320648218e04386a5f0c264ad6ccce8226a1 \ + --hash=sha256:ed5c4df5c1fb4518abcb57725b576659542bdbe93366f4f329e8f398c4b71178 \ + --hash=sha256:ee770ea8ab38918f34e7560a597cc0a8c9a193aaa01bfbd879ef43cb06bd9c4c +tomli==2.0.1 ; python_version >= "3.9" and python_version < "3.11" \ + --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \ + --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f diff --git a/python/selfie-lib/requirements.txt b/python/selfie-lib/requirements.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/python/selfie-lib/requirements.txt @@ -0,0 +1 @@ + diff --git a/python/selfie-lib/selfie_lib/WriteTracker.py b/python/selfie-lib/selfie_lib/WriteTracker.py index 741a794e..3c83916e 100644 --- a/python/selfie-lib/selfie_lib/WriteTracker.py +++ b/python/selfie-lib/selfie_lib/WriteTracker.py @@ -1,7 +1,7 @@ import inspect import os import threading -from abc import ABC +from abc import ABC, abstractmethod from functools import total_ordering from pathlib import Path from typing import Dict, Generic, List, Optional, TypeVar, cast @@ -82,7 +82,7 @@ def __hash__(self): return hash((self.location, tuple(self.rest_of_stack))) -class SnapshotFileLayout: +class SnapshotFileLayout(ABC): def __init__(self, fs: FS): self.fs = fs @@ -92,6 +92,10 @@ def sourcefile_for_call(self, call: CallLocation) -> TypedPath: raise ValueError("No file path available in CallLocation.") return TypedPath(os.path.abspath(Path(file_path))) + @abstractmethod + def get_snapshot_file(self, test_file: TypedPath) -> TypedPath: + pass + def recordCall(callerFileOnly: bool) -> CallStack: stack_frames_raw = inspect.stack() diff --git a/python/selfie-lib/uvproject.toml b/python/selfie-lib/uvproject.toml new file mode 100644 index 00000000..dde308a2 --- /dev/null +++ b/python/selfie-lib/uvproject.toml @@ -0,0 +1,28 @@ +[project] +name = "selfie-lib" +version = "0.1.0" +description = "Infrastructure for creating selfie-compatible test runner plugins." +authors = [ + "Selina Delgado ", + "Harvir Sahota ", + "Ned Twigg ", + "Edwin Ye " +] +license = "Apache-2.0" +readme = "README.md" +requires-python = ">=3.9" + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ "S", "FA", "PYI", "EM", "PLR", "FBT", "COM", "RET", "PTH", "PLW", "PLC", +"TRY", # TODO: exception standards +"ANN", # TODO: require type annotations +"D", # TODO: docstring warnings +"N", # TODO: naming conventions +"E501", # line too long +"C901", # function to complex +"PLC0414", # import alias does not rename original package +"W291", # trailing whitespace, we need it for testing snapshots +"PGH003", # specific rule codes when ignoring type issues +"ISC001" +]