You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+33-23Lines changed: 33 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -180,25 +180,28 @@ Now that you have chosen a JIRA ticket and have your own fork of this repository
180
180
181
181
All code added to the client must have tests. These might include unit tests (to test specific functionality of code that was added to support fixing the bug or feature), integration tests (to test that the feature is usable - e.g., it should have complete the expected behavior as reported in the feature request or bug report), or both.
182
182
183
-
The Python client uses [`pytest`](https://docs.pytest.org/en/latest/) to run tests. The test code is located in the [test](./test) subdirectory.
183
+
The Python client uses [`pytest`](https://docs.pytest.org/en/latest/) to run tests. The test code is located in the [tests](./tests) subdirectory.
184
184
185
185
Here's how to run the test suite:
186
186
187
-
> *Note:* The entire set of tests takes approximately 20 minutes to run.
#### Integration testing against the `dev` synapse server
@@ -244,8 +247,8 @@ When adding support for a new Python version (e.g., adding Python 3.15), update
244
247
245
248
**CI/CD configuration files:**
246
249
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)
250
+
- Add the new version to the `unit-tests` job matrix (all Python versions run on Ubuntu; only one version per macOS/Windows)
251
+
- Add the new version to the `integration-tests` job matrix if it should be integration-tested (typically the oldest and newest supported versions)
249
252
- Update any Python version comments or documentation within the workflow
250
253
251
254
**Testing:**
@@ -269,7 +272,7 @@ When dropping support for an old Python version (e.g., removing Python 3.10), up
269
272
270
273
**CI/CD configuration files:**
271
274
1. **`.github/workflows/build.yml`**:
272
-
- Remove the old version from the `python` matrix under the `test` job strategy
275
+
- Remove the old version from the `unit-tests` and `integration-tests` job matrices
273
276
- Update the cache key version (e.g., increment `v28` to `v29`) to invalidate old caches
274
277
275
278
**Documentation:**
@@ -315,10 +318,10 @@ available at runtime:
315
318
1. Update the examples in the docstring to remove the await or async function calls.
316
319
1. Import the protocol class you created and add it to the class constructor to
317
320
inherit the protocol class.
318
-
1. Write unit and integration tests for BOTH the async and non-async versions.
319
-
1. Write your tests once with async in mind.
320
-
1. Copy them to a non-async testing directory.
321
-
1. Remove the async-related keywords and imports.
321
+
1. Write unit and integration tests for the **async** version only.
322
+
1. The `@async_to_sync` decorator automatically generates the sync wrapper at runtime.
323
+
1. The sync wrapper is covered by dedicated unit tests in `tests/unit/synapseclient/core/unit_test_async_to_sync.py` and a smoke integration test in `tests/integration/synapseclient/models/synchronous/test_sync_wrapper_smoke.py`.
324
+
1. **Do not** create duplicate synchronous test files.
322
325
1. Add the method definitions to the appropriate markdown file for generated doc pages.
323
326
324
327
##### Creating a new async method to be called internally by the client
@@ -331,8 +334,9 @@ generation of non-async code.
331
334
332
335
##### Modifying an existing async method
333
336
When you make a modification to an async method please also copy any changes to the
334
-
definition of the method OR docstring into the non-async method defintion. It is
335
-
expected that you manually keep them in-sync.
337
+
definition of the method OR docstring into the non-async Protocol class definition. The
338
+
sync wrapper is generated automatically by the `@async_to_sync` decorator, so only the
339
+
Protocol class (used for static type checking) needs to be updated manually.
336
340
337
341
### Code style
338
342
@@ -428,12 +432,18 @@ April 2024 it was found that during a single run of all integration tests almost
428
432
connections were created and subsequently closed during the test run. As such the
429
433
following set of guidelines should be followed:
430
434
431
-
- All tests should use the `async` keyword. This allows any tests to share the
432
-
underlying HTTPX async client for requests.
433
-
- Any non `session` scoped fixtures should not execute an HTTP request. If the fixture
434
-
does need to execute a request it should not be scoped to `function`. This is because
435
-
each scope level runs it's own event loop; Connection pools cannot be shared between
436
-
each of the event loops.
435
+
- **Test function signatures:**
436
+
- Tests in `tests/integration/synapseclient/models/async/` should use `async def` to test async methods
437
+
- A single sync wrapper smoke test in `tests/integration/synapseclient/models/synchronous/test_sync_wrapper_smoke.py` covers the `@async_to_sync` decorator against the real API. **Do not** create additional per-model synchronous integration test files.
438
+
- **Important (Python 3.14+):** Synchronous tests must NOT use `async def`, as this creates an active event loop that prevents synchronous methods from working correctly. The sync smoke test is skipped on Python 3.14+.
439
+
- **Fixtures:** Can be `async def` if they need to call async methods, but should use regular `def` when calling synchronous methods.
440
+
- **Fixture scoping guidelines:**
441
+
- `session` scope: Use for the Synapse client (`syn`), shared project (`project_model`), and `schedule_for_cleanup`. These are created once per test session/worker.
442
+
- `class` scope: Use for expensive container entities (Projects, Evaluations, Tables, Folders with files) that are **not mutated** by tests — only used as parents or read-only containers. This avoids redundant entity creation across tests within a class.
443
+
- `function` scope: Use for entities that tests **mutate** (e.g., files with changed names, datasets with added/removed items, submission statuses being updated). Each test gets a fresh entity.
444
+
- All fixtures that create Synapse entities **must** call `schedule_for_cleanup()` to register them for cleanup at session end.
445
+
- **Polling and retries:** For eventual-consistency scenarios (e.g., waiting for permission propagation, schema binding, attachment preview generation), use `wait_for_condition()` from `tests/integration/helpers.py` instead of hardcoded `asyncio.sleep()` calls. This uses exponential backoff and returns as soon as the condition is met.
446
+
- **Parallel execution:** Tests run with `pytest -n 8 --dist loadscope`, which ensures all tests in a class execute on the same worker sequentially. Session-scoped fixtures are shared within each worker.
0 commit comments