From 2834058d0d1450e9346717da70cbecf2dbf4a96c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 22:34:37 +0000 Subject: [PATCH 1/2] ci: only run the release workflow on the upstream repository The Release workflow triggers on every push to master. On a fork that means semantic-release rewrites the version in pyproject.toml, commits the change to the default branch, tags it and then tries to publish the result to PyPI under the upstream package name. Gate the job on the repository name so forks keep a clean master. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PjZRpyoEfSCLjTx5ctzQmJ --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8df3ca7f..2119bfad 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,6 +11,10 @@ permissions: jobs: release: + # Releases are cut from the upstream repository only. Without this guard the + # workflow also runs on forks, where semantic-release rewrites the version in + # pyproject.toml, commits to the default branch and attempts a PyPI upload. + if: github.repository == 'appium/python-client' runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 From 4921cabb5b1689fa049e17d3b2f1863c2c00be04 Mon Sep 17 00:00:00 2001 From: Dor-bl <59066376+Dor-bl@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:01:26 +0000 Subject: [PATCH 2/2] perf: remove unnecessary deepcopy in AppiumOptions.as_w3c --- appium/options/common/base.py | 2 +- test/unit/options/options_test.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/unit/options/options_test.py diff --git a/appium/options/common/base.py b/appium/options/common/base.py index 5bd4f1a9..2de5a5f9 100644 --- a/appium/options/common/base.py +++ b/appium/options/common/base.py @@ -109,7 +109,7 @@ def process_key(k: str) -> str: return key return key if ':' in key else f'{APPIUM_PREFIX}{key}' - processed_caps = {process_key(k): v for k, v in copy.deepcopy(capabilities).items()} + processed_caps = {process_key(k): v for k, v in capabilities.items()} return {'capabilities': {'firstMatch': [{}], 'alwaysMatch': processed_caps}} def to_w3c(self) -> dict: diff --git a/test/unit/options/options_test.py b/test/unit/options/options_test.py new file mode 100644 index 00000000..6692eb9a --- /dev/null +++ b/test/unit/options/options_test.py @@ -0,0 +1,27 @@ +from appium.options.common.base import AppiumOptions + + +def test_as_w3c(): + caps = { + 'platformName': 'Android', + 'platformVersion': '11.0', + 'deviceName': 'Android Emulator', + 'automationName': 'UiAutomator2', + 'app': '/path/to/app.apk', + 'customCap': 'customValue', + } + res = AppiumOptions.as_w3c(caps) + expected = { + 'capabilities': { + 'firstMatch': [{}], + 'alwaysMatch': { + 'platformName': 'Android', + 'appium:platformVersion': '11.0', + 'appium:deviceName': 'Android Emulator', + 'appium:automationName': 'UiAutomator2', + 'appium:app': '/path/to/app.apk', + 'appium:customCap': 'customValue', + }, + } + } + assert res == expected