Skip to content

Commit 858f427

Browse files
authored
Merge pull request #1308 from Sage-Bionetworks/v4.11.0-rc-dev
[SYNPY-1735] Release python client v4.11.0
2 parents 4af27a4 + 9532827 commit 858f427

249 files changed

Lines changed: 64059 additions & 3553 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 119 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@ concurrency:
3434
cancel-in-progress: true
3535

3636
jobs:
37-
3837
pre-commit:
3938
runs-on: ubuntu-latest
4039
steps:
41-
- uses: actions/checkout@v4
42-
- uses: actions/setup-python@v5
43-
with:
44-
python-version: '3.13'
45-
- uses: pre-commit/action@v3.0.1
40+
- uses: actions/checkout@v4
41+
- uses: actions/setup-python@v5
42+
with:
43+
python-version: '3.13'
44+
- uses: pre-commit/action@v3.0.1
4645

4746
# run unit (and integration tests if account secrets available) on our build matrix
4847
test:
@@ -51,11 +50,11 @@ jobs:
5150
strategy:
5251
fail-fast: false
5352
matrix:
54-
os: [ubuntu-22.04, macos-13, windows-2022]
53+
os: [ubuntu-22.04, macos-15-intel, windows-2022]
5554

5655
# if changing the below change the run-integration-tests versions and the check-deploy versions
5756
# Make sure that we are running the integration tests on the first and last versions of the matrix
58-
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
57+
python: ['3.10', '3.11', '3.12', '3.13', '3.14']
5958

6059
runs-on: ${{ matrix.os }}
6160

@@ -84,15 +83,15 @@ jobs:
8483
path: |
8584
${{ steps.get-dependencies.outputs.site_packages_loc }}
8685
${{ steps.get-dependencies.outputs.site_bin_dir }}
87-
key: ${{ runner.os }}-${{ matrix.python }}-build-${{ env.cache-name }}-${{ hashFiles('setup.py') }}-v27
86+
key: ${{ runner.os }}-${{ matrix.python }}-build-${{ env.cache-name }}-${{ hashFiles('setup.py') }}-v31
8887

8988
- name: Install py-dependencies
9089
if: steps.cache-dependencies.outputs.cache-hit != 'true'
9190
shell: bash
9291
run: |
9392
python -m pip install --upgrade pip
9493
95-
pip install -e ".[boto3,pandas,pysftp,tests]"
94+
pip install -e ".[boto3,pandas,pysftp,tests,curator]"
9695
9796
# ensure that numpy c extensions are installed on windows
9897
# https://stackoverflow.com/a/59346525
@@ -109,7 +108,7 @@ jobs:
109108
pytest -sv --cov-append --cov=. --cov-report xml tests/unit
110109
- name: Check for Secret availability
111110
id: secret-check
112-
if: ${{ contains(fromJSON('["3.9"]'), matrix.python) || contains(fromJSON('["3.13"]'), matrix.python) }}
111+
if: ${{ contains(fromJSON('["3.10"]'), matrix.python) || contains(fromJSON('["3.14"]'), matrix.python) }}
113112
# perform secret check & put boolean result as an output
114113
shell: bash
115114
run: |
@@ -125,34 +124,42 @@ jobs:
125124
echo "synapse_pat_available=true" >> $GITHUB_OUTPUT;
126125
fi
127126
127+
- name: Download Failed Tests from Previous Attempt
128+
if: ${{ github.run_attempt > 1 && (contains(fromJSON('["3.10"]'), matrix.python) || contains(fromJSON('["3.14"]'), matrix.python)) && steps.secret-check.outputs.secrets_available == 'true'}}
129+
uses: actions/download-artifact@v4
130+
with:
131+
name: failed-tests-${{ matrix.os }}-${{ matrix.python }}
132+
continue-on-error: true
133+
128134
# run integration tests iff the decryption keys for the test configuration are available.
129135
# they will not be available in pull requests from forks.
130136
# run integration tests on the oldest and newest supported versions of python.
131137
# we don't run on the entire matrix to avoid a 3xN set of concurrent tests against
132138
# the target server where N is the number of supported python versions.
133139
- name: run-integration-tests
140+
id: integration_tests
134141
shell: bash
135142

136143
# keep versions consistent with the first and last from the strategy matrix
137-
if: ${{ (contains(fromJSON('["3.9"]'), matrix.python) || contains(fromJSON('["3.13"]'), matrix.python)) && steps.secret-check.outputs.secrets_available == 'true'}}
144+
if: ${{ (contains(fromJSON('["3.10"]'), matrix.python) || contains(fromJSON('["3.14"]'), matrix.python)) && steps.secret-check.outputs.secrets_available == 'true' && github.event_name != 'release'}}
138145
run: |
139146
# Set SYNAPSE_PROFILE based on OS and Python version
140147
if [ "${{ startsWith(matrix.os, 'ubuntu') }}" == "true" ]; then
141-
if [ "${{ matrix.python }}" == "3.9" ]; then
148+
if [ "${{ matrix.python }}" == "3.10" ]; then
142149
export SYNAPSE_PROFILE="TestUbuntuMinimumPython"
143-
elif [ "${{ matrix.python }}" == "3.13" ]; then
150+
elif [ "${{ matrix.python }}" == "3.14" ]; then
144151
export SYNAPSE_PROFILE="TestUbuntuMaximumPython"
145152
fi
146153
elif [ "${{ startsWith(matrix.os, 'windows') }}" == "true" ]; then
147-
if [ "${{ matrix.python }}" == "3.9" ]; then
154+
if [ "${{ matrix.python }}" == "3.10" ]; then
148155
export SYNAPSE_PROFILE="TestWindowsMinimumPython"
149-
elif [ "${{ matrix.python }}" == "3.13" ]; then
156+
elif [ "${{ matrix.python }}" == "3.14" ]; then
150157
export SYNAPSE_PROFILE="TestWindowsMaximumPython"
151158
fi
152159
elif [ "${{ startsWith(matrix.os, 'macos') }}" == "true" ]; then
153-
if [ "${{ matrix.python }}" == "3.9" ]; then
160+
if [ "${{ matrix.python }}" == "3.10" ]; then
154161
export SYNAPSE_PROFILE="TestMacosMinimumPython"
155-
elif [ "${{ matrix.python }}" == "3.13" ]; then
162+
elif [ "${{ matrix.python }}" == "3.14" ]; then
156163
export SYNAPSE_PROFILE="TestMacosMaximumPython"
157164
fi
158165
fi
@@ -194,21 +201,96 @@ jobs:
194201
# Setup ignore patterns based on Python version
195202
IGNORE_FLAGS="--ignore=tests/integration/synapseclient/test_command_line_client.py"
196203
197-
if [ "${{ matrix.python }}" == "3.9" ]; then
198-
# For min Python version, ignore async tests
199-
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=tests/integration/synapseclient/models/async/"
200-
echo "Running integration tests for Min Python version (3.9) - ignoring async tests"
201-
elif [ "${{ matrix.python }}" == "3.13" ]; then
202-
# For max Python version, ignore synchronous tests
203-
IGNORE_FLAGS="$IGNORE_FLAGS --ignore=tests/integration/synapseclient/models/synchronous/"
204-
echo "Running integration tests for Max Python version (3.13) - ignoring synchronous tests"
205-
fi
204+
# Check if we should run only failed tests from previous attempt
205+
if [[ -f failed_tests.txt && -s failed_tests.txt ]]; then
206+
echo "::notice::Retry attempt ${{ github.run_attempt }} detected - running only previously failed tests"
207+
cat failed_tests.txt
206208
207-
# use loadscope to avoid issues running tests concurrently that share scoped fixtures
208-
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml tests/integration -n 8 $IGNORE_FLAGS --dist loadscope
209+
# Run only the failed tests
210+
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml \
211+
--junit-xml=test-results.xml \
212+
-n 8 --dist loadscope \
213+
$(cat failed_tests.txt | tr '\n' ' ')
214+
else
215+
echo "::notice::First attempt or no previous failures - running full integration test suite"
216+
217+
# use loadscope to avoid issues running tests concurrently that share scoped fixtures
218+
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml \
219+
--junit-xml=test-results.xml \
220+
tests/integration -n 8 $IGNORE_FLAGS --dist loadscope
221+
fi
209222
210223
# Execute the CLI tests in a non-dist way because they were causing some test instability when being run concurrently
211224
pytest -sv --reruns 3 --cov-append --cov=. --cov-report xml tests/integration/synapseclient/test_command_line_client.py
225+
226+
- name: Extract Failed Tests
227+
if: always() && steps.integration_tests.outcome == 'failure'
228+
shell: bash
229+
run: |
230+
python -c "
231+
import xml.etree.ElementTree as ET
232+
import os
233+
tree = ET.parse('test-results.xml')
234+
root = tree.getroot()
235+
failed = []
236+
for testcase in root.iter('testcase'):
237+
if testcase.find('failure') is not None or testcase.find('error') is not None:
238+
classname = testcase.get('classname')
239+
name = testcase.get('name')
240+
file_attr = testcase.get('file')
241+
242+
# Use the file attribute if available, otherwise convert classname
243+
if file_attr:
244+
# file attribute is already in the correct format
245+
test_path = f'{file_attr}::{classname.split(\".\")[-1]}::{name}'
246+
else:
247+
# Convert classname from dot notation to file path
248+
# e.g., 'tests.integration.foo.test_bar.TestClass' -> 'tests/integration/foo/test_bar.py::TestClass'
249+
parts = classname.split('.')
250+
# Find the test file (usually starts with 'test_')
251+
module_parts = []
252+
class_parts = []
253+
found_test_file = False
254+
for part in parts:
255+
if not found_test_file:
256+
module_parts.append(part)
257+
if part.startswith('test_'):
258+
found_test_file = True
259+
else:
260+
class_parts.append(part)
261+
262+
file_path = '/'.join(module_parts) + '.py'
263+
if class_parts:
264+
test_path = f'{file_path}::{\"::\".join(class_parts)}::{name}'
265+
else:
266+
test_path = f'{file_path}::{name}'
267+
268+
failed.append(test_path)
269+
270+
with open('failed_tests.txt', 'w') as f:
271+
f.write('\n'.join(failed))
272+
print(f'Found {len(failed)} failed tests')
273+
for test in failed:
274+
print(f' - {test}')
275+
print(f'Current attempt: ${{ github.run_attempt }}')
276+
"
277+
278+
- name: Upload Failed Tests for Next Attempt
279+
if: always() && steps.integration_tests.outcome == 'failure' && github.run_attempt < 3
280+
uses: actions/upload-artifact@v4
281+
with:
282+
name: failed-tests-${{ matrix.os }}-${{ matrix.python }}
283+
path: failed_tests.txt
284+
retention-days: 2
285+
overwrite: true
286+
287+
- name: Fail job if integration tests failed after all retries
288+
if: always() && steps.integration_tests.outcome == 'failure'
289+
shell: bash
290+
run: |
291+
echo "::error::Integration tests failed after ${{ github.run_attempt }} attempt(s)"
292+
exit 1
293+
212294
- name: Upload coverage report
213295
id: upload_coverage_report
214296
uses: actions/upload-artifact@v4
@@ -225,12 +307,12 @@ jobs:
225307
steps:
226308
- uses: actions/checkout@v4
227309
with:
228-
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
310+
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
229311
- name: Check coverage-report artifact existence
230312
id: check_coverage_report
231313
uses: LIT-Protocol/artifact-exists-action@v0
232314
with:
233-
name: "coverage-report"
315+
name: 'coverage-report'
234316
- name: Download coverage report
235317
uses: actions/download-artifact@v4
236318
if: steps.check_coverage_report.outputs.exists == 'true'
@@ -240,21 +322,21 @@ jobs:
240322
id: check_coverage_xml
241323
uses: andstor/file-existence-action@v3
242324
with:
243-
files: "coverage.xml"
325+
files: 'coverage.xml'
244326
# This is a workaround described in https://community.sonarsource.com/t/sonar-on-github-actions-with-python-coverage-source-issue/36057
245327
- name: Override Coverage Source Path for Sonar
246328
if: steps.check_coverage_xml.outputs.files_exists == 'true'
247329
run: sed -i "s/<source>\/home\/runner\/work\/synapsePythonClient<\/source>/<source>\/github\/workspace<\/source>/g" coverage.xml
248330
- name: SonarCloud Scan
249-
uses: SonarSource/sonarqube-scan-action@v5.3.1
331+
uses: SonarSource/sonarqube-scan-action@v6.0.0
250332
if: ${{ always() }}
251333
env:
252334
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
253335
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
254336

255337
# on a GitHub release, build the pip package and upload it as a GitHub release asset
256338
package:
257-
needs: [test,pre-commit]
339+
needs: [test, pre-commit]
258340

259341
runs-on: ubuntu-22.04
260342

@@ -269,7 +351,7 @@ jobs:
269351

270352
- uses: actions/setup-python@v5
271353
with:
272-
python-version: 3.9
354+
python-version: '3.10'
273355

274356
- name: set-release-env
275357
shell: bash
@@ -366,7 +448,6 @@ jobs:
366448
# asset_path: dist/${{ steps.build-package.outputs.bdist-package-name }}
367449
# asset_content_type: application/zip
368450

369-
370451
# re-download the built package to the appropriate pypi server.
371452
# we upload prereleases to test.pypi.org and releases to pypi.org.
372453
deploy:
@@ -404,10 +485,10 @@ jobs:
404485

405486
strategy:
406487
matrix:
407-
os: [ubuntu-24.04, macos-13, windows-2022]
488+
os: [ubuntu-24.04, macos-15-intel, windows-2022]
408489

409490
# python versions should be consistent with the strategy matrix and the runs-integration-tests versions
410-
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
491+
python: ['3.10', '3.11', '3.12', '3.13', '3.14']
411492

412493
runs-on: ${{ matrix.os }}
413494

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ virtualenvs
66
examples_temp
77
.DS_Store
88
deploy.sh
9+
.venv/
910

1011
junk/
1112
nose.cfg

CONTRIBUTING.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ copied to forks).
9393
#### Installing the Python Client in a virtual environment with pipenv
9494
Perform the following one-time steps to set up your local environment.
9595
96-
1. This package uses Python, if you have not already, please install [pyenv](https://github.com/pyenv/pyenv#installation) to manage your Python versions. Versions supported by this package are all versions >=3.9 and <=3.13. If you do not install `pyenv` make sure that Python and `pip` are installed correctly and have been added to your PATH by running `python3 --version` and `pip3 --version`. If your installation was successful, your terminal will return the versions of Python and `pip` that you installed. **Note**: If you have `pyenv` it will install a specific version of Python for you.
96+
1. This package uses Python, if you have not already, please install [pyenv](https://github.com/pyenv/pyenv#installation) to manage your Python versions. Versions supported by this package are all versions >=3.10 and <=3.14. If you do not install `pyenv` make sure that Python and `pip` are installed correctly and have been added to your PATH by running `python3 --version` and `pip3 --version`. If your installation was successful, your terminal will return the versions of Python and `pip` that you installed. **Note**: If you have `pyenv` it will install a specific version of Python for you.
9797
9898
2. Install `pipenv` by running `pip install pipenv`.
9999
- If you already have `pipenv` installed, ensure that the version is >=2023.9.8 to avoid compatibility issues.
@@ -223,6 +223,64 @@ When integration tests are ran in the Github CI/CD pipeline it will upload the t
223223
#### Integration testing for external collaborators
224224
As an external collaborator you will not have access to a development account and environment to run the integration tests against. Either request that a Sage Bionetworks staff member run your integration tests via a pull request, or, contact us via the [Service Desk](https://sagebionetworks.jira.com/servicedesk/customer/portal/9) to requisition a development account for integration testing only.
225225
226+
### Managing Python version changes
227+
228+
When adding support for a new Python version or dropping support for an old version, several files across the codebase and CI/CD pipelines must be updated to ensure consistency and proper testing coverage.
229+
230+
#### Adding a new Python version
231+
232+
When adding support for a new Python version (e.g., adding Python 3.15), update the following:
233+
234+
**Code configuration files:**
235+
1. **`setup.cfg`**:
236+
- Add the new version to the `classifiers` list under `[metadata]` (e.g., `Programming Language :: Python :: 3.15`)
237+
- Update the `python_requires` constraint under `[options]` to include the new version (e.g., `>=3.10, <3.16`)
238+
239+
2. **`pyproject.toml`**:
240+
- Update the `target-version` list in the `[tool.black]` section to include the new version if needed
241+
242+
3. **`Dockerfile`**:
243+
- Update the base image to use the new Python version (e.g., `FROM python:3.15-slim`)
244+
245+
**CI/CD configuration files:**
246+
1. **`.github/workflows/build.yml`**:
247+
- Add the new version to the `python` matrix under the `test` job strategy
248+
- Ensure the new version is included in integration test runs (typically the latest version should be tested)
249+
- Update any Python version comments or documentation within the workflow
250+
251+
**Testing:**
252+
- Run the full test suite (both unit and integration tests) on the new Python version locally before submitting a PR
253+
- Verify that all CI/CD pipelines pass with the new version included
254+
255+
#### Dropping an old Python version
256+
257+
When dropping support for an old Python version (e.g., removing Python 3.10), update the following:
258+
259+
**Code configuration files:**
260+
1. **`setup.cfg`**:
261+
- Remove the old version from the `classifiers` list under `[metadata]`
262+
- Update the `python_requires` constraint under `[options]` to reflect the new minimum version (e.g., `>=3.11, <3.15`)
263+
264+
2. **`pyproject.toml`**:
265+
- Update the `target-version` list in the `[tool.black]` section to remove the old version
266+
267+
3. **`Dockerfile`**:
268+
- Ensure the base image uses a supported Python version
269+
270+
**CI/CD configuration files:**
271+
1. **`.github/workflows/build.yml`**:
272+
- Remove the old version from the `python` matrix under the `test` job strategy
273+
- Update the cache key version (e.g., increment `v28` to `v29`) to invalidate old caches
274+
275+
**Documentation:**
276+
- Update the README.md and any getting started documentation to reflect the new supported Python version range
277+
- Update CONTRIBUTING.md (this file) if it mentions specific Python versions in examples
278+
279+
**Important considerations:**
280+
- Python version changes should be coordinated with a release and clearly communicated in release notes
281+
- Breaking compatibility with a Python version is a significant change and should typically coincide with a major or minor version bump
282+
- Always test thoroughly on the minimum and maximum supported Python versions before release
283+
226284
### Asynchronous methods
227285
[Asyncio](https://docs.python.org/3/library/asyncio.html) is the future of the Synapse
228286
Python Client. As such, the expectation is that all future methods that rely on async

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.9-slim
1+
FROM python:3.14-slim
22
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
33

44
RUN apt-get update \

Pipfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ synapseclient = {file = ".", path = "."}
1010
python_version = "3.12.6"
1111

1212
[dev-packages]
13-
synapseclient = {file = ".", editable = true, path = ".", extras = ["dev", "tests", "pandas", "pysftp", "boto3", "docs"]}
13+
synapseclient = {file = ".", editable = true, path = ".", extras = ["dev", "tests", "pandas", "pysftp", "boto3", "docs", "curator"]}

0 commit comments

Comments
 (0)