diff --git a/.changes/0.0.10.md b/.changes/0.0.10.md index dc19126d..95c22450 100644 --- a/.changes/0.0.10.md +++ b/.changes/0.0.10.md @@ -1,10 +1,6 @@ ## 0.0.10 - 2022-02-17 ### Added -* [#155](https://github.com/edgarrmondragon/citric/pull/155) Experimental support for Python 3.11 -* [#165](https://github.com/edgarrmondragon/citric/pull/165) Test in Windows and MacOS - - * [#192](https://github.com/edgarrmondragon/citric/pull/192) Implement `copy_survey` in client * [#190](https://github.com/edgarrmondragon/citric/pull/190) Implement `import_group` in client * [#196](https://github.com/edgarrmondragon/citric/pull/196) Implement `list_groups` in client diff --git a/.changes/1.0.0.md b/.changes/1.0.0.md new file mode 100644 index 00000000..d6b6cfde --- /dev/null +++ b/.changes/1.0.0.md @@ -0,0 +1,9 @@ +## 1.0.0 - 2024-02-12 +### Added +* [#1079](https://github.com/edgarrmondragon/citric/issues/1079) Added `get_db_version` method to RPC client +* [#1092](https://github.com/edgarrmondragon/citric/issues/1092) Added a `Session.call` method to allow calling RPC methods without any error handling to get the raw response +### Removed +* [#1093](https://github.com/edgarrmondragon/citric/issues/1093) Removed the `Session._headers` attribute +### Documentation +* [#1057](https://github.com/edgarrmondragon/citric/issues/1057) Documented `conda install` option +* [#1092](https://github.com/edgarrmondragon/citric/issues/1092) Linked unimplemented methods to `Session.call` examples diff --git a/.changes/1.0.1.md b/.changes/1.0.1.md new file mode 100644 index 00000000..b60df14d --- /dev/null +++ b/.changes/1.0.1.md @@ -0,0 +1,5 @@ +## 1.0.1 - 2024-06-12 +### Fixed +* [#1101](https://github.com/edgarrmondragon/citric/issues/1101) fix: Bump min `requests` to `2.25.1` (released 2020-12-16) +### Documentation +* [#1152](https://github.com/edgarrmondragon/citric/issues/1152) Fixed links to LimeSurvey API docs \ No newline at end of file diff --git a/.changes/unreleased/Added-20240117-233210.yaml b/.changes/unreleased/Added-20240117-233210.yaml deleted file mode 100644 index bcdc682e..00000000 --- a/.changes/unreleased/Added-20240117-233210.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Added -body: Add `get_db_version` method to RPC client -time: 2024-01-17T23:32:10.890143-06:00 -custom: - Issue: "1079" diff --git a/.changes/unreleased/Documentation-20231217-085221.yaml b/.changes/unreleased/Documentation-20231217-085221.yaml deleted file mode 100644 index 82b65c32..00000000 --- a/.changes/unreleased/Documentation-20231217-085221.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: Documentation -body: 'docs: Document `conda install` option' -time: 2023-12-17T08:52:21.702919-06:00 -custom: - Issue: "1057" diff --git a/.circleci/config.yml b/.circleci/config.yml index 149e5f4e..6f733e21 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ version: 2.1 orbs: # The python orb contains a set of prepackaged CircleCI configuration you can use repeatedly in your configuration files # Orb commands and jobs help you with common scripting around a language/tool - # so you dont have to copy and paste it everywhere. + # so you don't have to copy and paste it everywhere. # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/python python: circleci/python@1.5.0 @@ -21,7 +21,8 @@ jobs: # The executor is the environment in which the steps below will be executed - below will use a python 3.10.2 container # Change the version below to your required version of python docker: - - image: cimg/python:3.11.3 + - image: cimg/python:3.11.7 + - image: cimg/python:3.12.1 # Checkout the code as the first step. This is a dedicated CircleCI step. # The python orb's install-packages step will install the dependencies from a Pipfile via Pipenv by default. # Here we're making sure we use just use the system-wide pip. By default it uses the project root's requirements.txt. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..1cf72b68 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global owner +* @edgarrmondragon diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..e963678a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +See the contributing guide in the [docs](https://citric.readthedocs.io/en/latest/contributing/code-of-conduct.html) for more information. diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index d71e1bec..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -github: [edgarrmondragon] -patreon: edgarrmondragon -ko_fi: edgarrmondragon diff --git a/.github/ISSUE_TEMPLATE/BUG.yml b/.github/ISSUE_TEMPLATE/BUG.yml index c3ae0149..8e1967e1 100644 --- a/.github/ISSUE_TEMPLATE/BUG.yml +++ b/.github/ISSUE_TEMPLATE/BUG.yml @@ -15,7 +15,7 @@ body: attributes: label: Citric Version description: Version of the package you are using - placeholder: "0.10.0" + placeholder: "1.0.1" validations: required: true - type: dropdown diff --git a/.github/actions/install-tools/action.yml b/.github/actions/install-tools/action.yml index 86034b4a..77bab159 100644 --- a/.github/actions/install-tools/action.yml +++ b/.github/actions/install-tools/action.yml @@ -3,7 +3,7 @@ description: Install tools for Python projects inputs: constraints: - default: ".github/workflows/constraints.txt" + default: "${{ github.workspace }}/.github/workflows/constraints.txt" description: "Path to pip constraints file" required: true os: @@ -31,18 +31,18 @@ runs: with open(os.environ["GITHUB_ENV"], mode="a") as io: print(f"VIRTUALENV_PIP={pip.__version__}", file=io) - - name: Install Hatch + - name: Install Nox shell: bash env: PIP_CONSTRAINT: ${{ inputs.constraints }} run: | - pipx install hatch --verbose - hatch --version + pipx install nox + nox --version - - name: Install Nox + - name: Install uv shell: bash env: PIP_CONSTRAINT: ${{ inputs.constraints }} run: | - pipx install nox - nox --version + pipx install uv + uv --version diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b6ca4979..7483a50d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,24 +21,6 @@ updates: update-types: - "patch" - "minor" - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: monthly - reviewers: - - "edgarrmondragon" - commit-message: - prefix: "ci: " - groups: - artifacts: - patterns: - - "actions/*-load-artifact" - ci-dependencies: - update-types: - - "patch" - - "minor" - exclude-patterns: - - "actions/*-load-artifact" - package-ecosystem: docker directory: "/" schedule: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..52ec75c3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +## Summary + + + +## Test Plan + + + +## Checklist + + + +- [ ] My pull request has a descriptive title. +- [ ] I have read the [CONTRIBUTING] guide. +- [ ] I have added tests that prove my fix is effective or that my feature works. +- [ ] If appropriate, I have added necessary documentation. +- [ ] For user-facing changes, refactorings, performance improvements or documentation updates, I have added a changelog entry using [Changie]. + +For both the title of the PR and the changelog entry, prefer simple past tense or constructions with "now". For example: + + - Added `Client.invite_participants()` + - `Client.user_activation_settings()` now accepts a `user_activation_settings` keyword argument + +[CONTRIBUTING]: https://citric.readthedocs.io/en/latest/contributing/code-of-conduct.html +[Changie]: https://changie.dev/ diff --git a/.github/workflows/api-changes.yml b/.github/workflows/api-changes.yml index c1676f09..8a361c07 100644 --- a/.github/workflows/api-changes.yml +++ b/.github/workflows/api-changes.yml @@ -22,22 +22,21 @@ jobs: NOXSESSION: api steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: 3.12 - name: Install tools env: - PIP_CONSTRAINT: .github/workflows/constraints.txt + PIP_CONSTRAINT: ${{ github.workspace }}/.github/workflows/constraints.txt run: | python -Im pip install -U pip - pipx install griffe - pipx install nox + pipx install griffe nox uv pipx list - name: Set REF diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cf83748..40b81a25 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,11 +20,31 @@ jobs: name: Build wheel and sdist runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 ref: ${{ github.event.inputs.tag || github.ref }} - - uses: hynek/build-and-inspect-python-package@c9fea028dc9c880c4d00d54727eff3fb1190d082 # v2.0.0 + - uses: hynek/build-and-inspect-python-package@2dbbf2b252d3a3c7cec7a810e3ed5983bd17b13a # v2.8.0 + + upload-to-release: + name: Upload to GitHub Release + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + needs: [build] + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + + steps: + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: Packages + path: dist + - uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 + with: + file: dist/** + tag: ${{ github.event.inputs.tag || github.ref }} + overwrite: false + file_glob: true publish: name: Publish to PyPI @@ -38,11 +58,11 @@ jobs: id-token: write # IMPORTANT: mandatory for trusted publishing steps: - - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: Packages path: dist - - uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # v1.8.11 + - uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 # v1.10.0 # Move this up when PyPI supports signing sign: @@ -51,22 +71,22 @@ jobs: runs-on: ubuntu-latest needs: [build] permissions: - contents: write # IMPORTANT: mandatory for making GitHub Releases - id-token: write # IMPORTANT: mandatory for sigstore + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for attestations + attestations: write # IMPORTANT: mandatory for attestations steps: - - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: Packages path: dist - - uses: sigstore/gh-action-sigstore-python@61f6a500bbfdd9a2a339cf033e5421951fbc1cd2 # v2.1.1 + - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + id: attest with: - inputs: >- - ./dist/*.tar.gz - ./dist/*.whl - - uses: svenstaro/upload-release-action@1beeb572c19a9242f4361f4cee78f8e0d9aec5df # v2 + subject-path: "./dist/citric*" + - uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # 2.9.0 with: - file: dist/** + file: ${{ steps.attest.outputs.bundle-path }} tag: ${{ github.event.inputs.tag || github.ref }} overwrite: false - file_glob: true + asset_name: attestations.intoto.jsonl diff --git a/.github/workflows/constraints.txt b/.github/workflows/constraints.txt index c0288fe6..c7f76f0e 100644 --- a/.github/workflows/constraints.txt +++ b/.github/workflows/constraints.txt @@ -1,5 +1,5 @@ -griffe==0.38.1 -hatch==1.9.1 -nox==2023.4.22 -pip==23.3.2 -pip-tools==7.3.0 +griffe==1.2.0 +nox==2024.4.15 +pip==24.2 +pip-tools==7.4.1 +uv==0.4.1 diff --git a/.github/workflows/gen-release-pr.yml b/.github/workflows/gen-release-pr.yml index 2d0d64cb..47a3fa40 100644 --- a/.github/workflows/gen-release-pr.yml +++ b/.github/workflows/gen-release-pr.yml @@ -23,8 +23,12 @@ permissions: read-all jobs: generate-pr: runs-on: ubuntu-latest + permissions: + contents: write # to create a github release + pull-requests: write # to create and update PRs + discussions: write # to create a discussion steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Batch changes uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 with: @@ -56,7 +60,7 @@ jobs: - name: Draft Release id: draft-release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 + uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 with: draft: true body_path: ".changes/${{ steps.latest.outputs.output }}.md" @@ -72,7 +76,7 @@ jobs: private_key: ${{ secrets.APP_PRIVATE_KEY }} - name: Create Pull Request - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0 with: token: ${{ steps.generate-token.outputs.token }} title: "chore: Release ${{ steps.latest.outputs.output }}" diff --git a/.github/workflows/gha-update.yml b/.github/workflows/gha-update.yml new file mode 100644 index 00000000..98627896 --- /dev/null +++ b/.github/workflows/gha-update.yml @@ -0,0 +1,43 @@ +name: Update GitHub Actions + +on: + workflow_dispatch: + schedule: + # Monthly + - cron: '0 0 1 * *' + +permissions: read-all + +jobs: + generate-pr: + runs-on: ubuntu-latest + permissions: + pull-requests: write # to create and update PRs + steps: + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + with: + python-version: '3.x' + - uses: hynek/setup-cached-uv@4b4bfa932036976749a9653b0fa4fa10b1a7092b # v2.1.0 + - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + id: generate-token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - run: | + uvx gha-update + - uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0 + with: + token: ${{ steps.generate-token.outputs.token }} + title: "chore: Update GitHub Actions" + branch: chore/update-gha + commit-message: "chore: Update GitHub Actions" + body: | + Update GitHub Actions to the latest versions. + + Uses https://github.com/davidism/gha-update. + reviewers: | + edgarrmondragon + assignees: | + edgarrmondragon + delete-branch: true + labels: Release diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 75fe8dc0..b4f148aa 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -33,12 +33,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -60,7 +60,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: SARIF file path: results.sarif @@ -68,6 +68,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@012739e5082ff0c22ca6d6ab32e07c36df03c4a4 # v3.22.12 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: results.sarif diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e64f420a..efa5c726 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,6 +11,7 @@ on: - pyproject.toml - .github/workflows/tests.yml - .github/workflows/constraints.txt + - .github/actions/install-tools/action.yml push: branches: - 'main' @@ -22,6 +23,7 @@ on: - pyproject.toml - .github/workflows/tests.yml - .github/workflows/constraints.txt + - .github/actions/install-tools/action.yml schedule: - cron: "25 7 */3 * *" workflow_dispatch: @@ -43,7 +45,7 @@ env: jobs: tests: - name: "Test ${{ matrix.python-version }}${{ matrix.nightly && ' (nightly) ' || ' ' }}${{ matrix.nogil && ' (nogil) ' || ' ' }}/ ${{ matrix.os }}" + name: "Test ${{ matrix.python-version }} ${{ matrix.nightly && '(nightly) ' || '' }}/ ${{ matrix.os }}" runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.experimental || false }} env: @@ -58,33 +60,16 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" - "pypy3.10" os: ["ubuntu-latest"] include: - - python-version: "3.13" - os: "ubuntu-latest" - session: "tests" - experimental: true - - - python-version: "3.13" + - python-version: "3.14" os: "ubuntu-latest" session: "tests" experimental: true nightly: true - # - python-version: "3.13" - # os: "ubuntu-latest" - # session: "tests" - # experimental: true - # nightly: true - # nogil: true - - # - python-version: "3.14" - # os: "ubuntu-latest" - # session: "tests" - # experimental: true - # nightly: true - - python-version: "3.12" os: "windows-latest" session: "tests" @@ -95,13 +80,13 @@ jobs: steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-tags: true - name: Setup Python ${{ matrix.python-version }} if: "${{ !matrix.nightly }}" - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -116,7 +101,6 @@ jobs: uses: deadsnakes/action@6c8b9b82fe0b4344f4b98f2775fcc395df45e494 # v3.1.0 with: python-version: "${{ matrix.python-version }}-dev" - # nogil: ${{ matrix.nogil }} - name: Install tools uses: ./.github/actions/install-tools @@ -126,8 +110,9 @@ jobs: nox --verbose -s tests - name: Upload coverage data - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: + include-hidden-files: true name: "coverage-unit-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.nightly && 'nightly' || 'stable' }}" path: ".coverage.*" @@ -150,18 +135,19 @@ jobs: tags: ${{ steps.tags.outputs.tags }} steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: - python-version: 3.12 + python-version: 3.x architecture: x64 allow-prereleases: true cache: pip cache-dependency-path: | pyproject.toml - .github/workflows/constraint.txt + requirements/*.txt + .github/workflows/constraints.txt - name: Install tools uses: ./.github/actions/install-tools @@ -188,7 +174,7 @@ jobs: engines: ${{ steps.engines.outputs.engines }} steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Test against all engines if: ${{ contains(github.event.pull_request.labels.*.name, 'Release') || inputs.all_integrations }} @@ -252,11 +238,6 @@ jobs: database: postgres # Test Limesurvey/LimeSurvey branches - - python-version: "3.12" - ref: refs/heads/5.x - context: https://github.com/martialblog/docker-limesurvey.git#master:5.0/apache - database: postgres - - python-version: "3.12" ref: refs/heads/develop context: https://github.com/martialblog/docker-limesurvey.git#master:6.0/apache @@ -269,12 +250,12 @@ jobs: steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-tags: true - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: ${{ matrix.python-version }} architecture: x64 @@ -294,7 +275,7 @@ jobs: echo "LS_CHECKSUM=$(shasum -a 256 ls.tar.gz | cut -d' ' -f1)" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Get Docker uses: actions-hub/docker/cli@f5fdbfc3f9d2a9265ead8962c1314108a7b7ec5d # v1.0.3 @@ -326,8 +307,9 @@ jobs: - name: Upload coverage data if: always() - uses: actions/upload-artifact@c7d193f32edcb7bfad88892161225aeda64e9392 # v4.0.0 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: + include-hidden-files: true name: "coverage-integration-${{ matrix.python-version }}-${{ matrix.image_tag || env.LS_CHECKSUM }}-${{ matrix.database }}" path: ".coverage.*" @@ -343,19 +325,19 @@ jobs: fail-fast: false steps: - name: Check out the repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: - python-version: "3.12" + python-version: "3.x" cache: pip - name: Install tools uses: ./.github/actions/install-tools - name: Download coverage data - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: "coverage-${{ matrix.flag }}-*" merge-multiple: true @@ -370,7 +352,7 @@ jobs: nox -- xml - name: Upload coverage report - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fd257a34..3b6c03d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-json @@ -21,38 +21,38 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.3 + rev: 0.29.2 hooks: - id: check-dependabot - id: check-github-workflows - id: check-readthedocs - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 + rev: v0.6.3 hooks: - id: ruff - name: Ruff lint args: [--fix, --exit-non-zero-on-fix, --show-fixes] - - id: ruff - name: Ruff format - entry: ruff format + - id: ruff-format - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell + # TODO: Use inline ignores, e.g. # codespell:ignore intoto + # https://github.com/codespell-project/codespell/issues/3387 + args: [-L, intoto] additional_dependencies: - tomli - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.1 hooks: - id: flake8 additional_dependencies: - - pydoclint==0.3.8 + - pydoclint==0.4.1 - repo: https://github.com/pre-commit/pre-commit - rev: v3.6.0 + rev: v3.8.0 hooks: - id: validate_manifest @@ -63,13 +63,17 @@ repos: args: [--all] - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.7.0" + rev: "2.2.1" hooks: - id: pyproject-fmt -- repo: https://github.com/jazzband/pip-tools - rev: 7.3.0 +- repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.4.2 hooks: - id: pip-compile files: ^pyproject\.toml$ - args: ["--extra", "docs", "-o", "docs/requirements.txt"] + args: ["pyproject.toml", "--universal", "--pre", "--python-version", "3.12", "--extra", "docs", "-o", "docs/requirements.txt"] + language_version: python3.12 + - id: pip-compile + files: ^pyproject\.toml$ + args: ["pyproject.toml", "--universal", "--resolution", "lowest-direct", "-o", "requirements/requirements-lowest-direct.txt"] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 859c2cf6..07bb6bc4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,9 +1,9 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: - python: "3.11" + python: "3.12" jobs: post_checkout: - git fetch --unshallow || true diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b97c1f4..3e4c08d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## 1.0.1 - 2024-06-12 +### Fixed +* [#1101](https://github.com/edgarrmondragon/citric/issues/1101) fix: Bump min `requests` to `2.25.1` (released 2020-12-16) +### Documentation +* [#1152](https://github.com/edgarrmondragon/citric/issues/1152) Fixed links to LimeSurvey API docs + +## 1.0.0 - 2024-02-12 +### Added +* [#1079](https://github.com/edgarrmondragon/citric/issues/1079) Added `get_db_version` method to RPC client +* [#1092](https://github.com/edgarrmondragon/citric/issues/1092) Added a `Session.call` method to allow calling RPC methods without any error handling to get the raw response +### Removed +* [#1093](https://github.com/edgarrmondragon/citric/issues/1093) Removed the `Session._headers` attribute +### Documentation +* [#1057](https://github.com/edgarrmondragon/citric/issues/1057) Documented `conda install` option +* [#1092](https://github.com/edgarrmondragon/citric/issues/1092) Linked unimplemented methods to `Session.call` examples + + ## 0.10.0 - 2023-11-29 ### Added * [#994](https://github.com/edgarrmondragon/citric/issues/994) Experimental support for the new REST API @@ -143,10 +160,6 @@ and is generated by [Changie](https://github.com/miniscruff/changie). ## 0.0.10 - 2022-02-17 ### Added -* [#155](https://github.com/edgarrmondragon/citric/pull/155) Experimental support for Python 3.11 -* [#165](https://github.com/edgarrmondragon/citric/pull/165) Test in Windows and MacOS - - * [#192](https://github.com/edgarrmondragon/citric/pull/192) Implement `copy_survey` in client * [#190](https://github.com/edgarrmondragon/citric/pull/190) Implement `import_group` in client * [#196](https://github.com/edgarrmondragon/citric/pull/196) Implement `list_groups` in client diff --git a/CITATION.cff b/CITATION.cff index b72266ac..7c58077e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -5,7 +5,7 @@ authors: given-names: "Edgar" orcid: "https://orcid.org/0000-0002-4182-0385" title: "Citric" -version: "0.10.0" +version: "1.0.1" doi: 10.5281/zenodo.10216279 date-released: 2021-11-11 url: "https://github.com/edgarrmondragon/citric" diff --git a/README.md b/README.md index fa90e5c1..a200c451 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,11 @@
Tests | +Project Health |
+
+
+
@@ -79,17 +82,6 @@
- Tested against LimeSurvey 6.0.0+ and 5.0.0+ versions.
- Experimental support for the new [REST API](https://manual.limesurvey.org/REST_API).
-### Integration tests
-
-Integration tests are run against a LimeSurvey instance, and both PostgreSQL and MySQL backends, using Docker Compose. The following versions of LimeSurvey were tested for this release:
-
-- [6.4.1](https://github.com/LimeSurvey/LimeSurvey/releases/tag/6.4.1+240108)
-- [6.4.0](https://github.com/LimeSurvey/LimeSurvey/releases/tag/6.4.0+231218)
-- [6.3.9](https://github.com/LimeSurvey/LimeSurvey/releases/tag/6.3.9+231211)
-- [5.6.50](https://github.com/LimeSurvey/LimeSurvey/releases/tag/5.6.50+240109)
-- [5.6.49](https://github.com/LimeSurvey/LimeSurvey/releases/tag/5.6.49+231212)
-- [5.6.48](https://github.com/LimeSurvey/LimeSurvey/releases/tag/5.6.48+231205)
-
## Installation
```sh
@@ -134,9 +126,11 @@ If you'd like to contribute to this project, please see the [contributing guide]
## Credits
+- The [LimeSurvey][limesurvey-site] team for providing a great survey platform.
- [Markus Opolka][martialblog] for maintaining a very robust set of [LimeSurvey Docker images](https://github.com/martialblog/docker-limesurvey/).
- [Claudio Jolowicz][claudio] and [his amazing blog post][hypermodern].
[claudio]: https://twitter.com/cjolowicz/
[hypermodern]: https://cjolowicz.github.io/posts/hypermodern-python-01-setup/
+[limesurvey-site]: https://www.limesurvey.org/
[martialblog]: https://github.com/martialblog/
diff --git a/code_samples/duckdb_sql.py b/code_samples/duckdb_sql.py
index 7ab1d7af..092bd733 100644
--- a/code_samples/duckdb_sql.py
+++ b/code_samples/duckdb_sql.py
@@ -2,9 +2,11 @@
from __future__ import annotations
-# ruff: noqa: I001, PTH123
+# ruff: noqa: I001, PTH123, FURB103
# start example
+from pathlib import Path
+
import citric
import duckdb
@@ -14,8 +16,7 @@
"secret",
)
-with open("responses.csv", "wb") as file:
- file.write(client.export_responses(12345, file_format="csv"))
+Path("responses.csv").write_bytes(client.export_responses(12345, file_format="csv"))
duckdb.execute("CREATE TABLE responses AS SELECT * FROM 'responses.csv'")
duckdb.sql("""
diff --git a/code_samples/ruff.toml b/code_samples/ruff.toml
index 8eaab689..a6c5fca3 100644
--- a/code_samples/ruff.toml
+++ b/code_samples/ruff.toml
@@ -1,4 +1,6 @@
extend = "../pyproject.toml"
+
+[lint]
ignore = [
"INP001", # implicit-namespace-package
]
diff --git a/code_samples/session_attr.py b/code_samples/session_attr.py
index 1cb19d59..21de4284 100644
--- a/code_samples/session_attr.py
+++ b/code_samples/session_attr.py
@@ -11,6 +11,9 @@
"secret",
)
-# Call the not_available_in_client method, not available in the RPC class
-new_survey_id = client.session.not_available_in_client(35239, "copied_survey")
+# Get the raw response from mail_registered_participants
+result = client.session.call("mail_registered_participants", 35239)
+
+# Get the raw response from remind_participants
+result = client.session.call("remind_participants", 35239)
# end example
diff --git a/docker-compose.yml b/docker-compose.yml
index 146fbd71..fb2bff36 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -26,10 +26,10 @@ services:
EMAIL_SMTPPASSWORD: ${LS_SMTP_PASSWORD:-secret}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/index.php/admin"]
- interval: 30s
+ interval: 15s
timeout: 10s
retries: 3
- start_period: 15s
+ start_period: 5s
db:
image: postgres:16
@@ -43,8 +43,8 @@ services:
- 5432:5432
healthcheck:
test: ["CMD", "pg_isready", "-U", "limesurvey"]
- interval: 30s
- timeout: 30s
+ interval: 15s
+ timeout: 10s
retries: 3
storage:
@@ -57,6 +57,7 @@ services:
command: server /data --console-address ":9001"
profiles:
- web
+ - notebook
mailhog:
image: mailhog/mailhog
diff --git a/docs/conf.py b/docs/conf.py
index e47d7dd7..e7ca5f8f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,18 +13,25 @@
if t.TYPE_CHECKING:
from sphinx.application import Sphinx
+# -- Project information ---------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
+
project = "citric"
+
author = "Edgar Ramírez Mondragón"
+project_copyright = f"2020, {author}"
version = citric.__version__
release = citric.__version__
-project_copyright = f"2020, {author}"
+
+# -- General configuration -------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
+
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.extlinks",
"sphinx.ext.intersphinx",
"sphinx.ext.linkcode",
"sphinx.ext.napoleon",
- "sphinx_autodoc_typehints",
"autoapi.extension",
"myst_parser",
"sphinx_copybutton",
@@ -33,30 +40,29 @@
"notfound.extension",
]
-myst_heading_anchors = 2
+source_suffix = {
+ ".rst": "restructuredtext",
+ ".md": "markdown",
+}
-autodoc_typehints = "description"
-autodoc_typehints_description_target = "documented"
+nitpicky = True
+nitpick_ignore = {
+ ("py:class", "citric.types.Result"),
+ ("py:class", "Result"),
+ ("py:class", "YesNo"),
+ ("py:class", "T"),
+ ("py:obj", "T"),
+}
-autoapi_type = "python"
-autoapi_root = "_api"
-autoapi_dirs = [
- Path("../src").resolve(),
-]
-autoapi_options = [
- "members",
- "undoc-members",
- "show-inheritance",
- "show-module-summary",
- "special-members",
- "imported-members",
- "private-members",
-]
+# -- Options for internationalization --------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-internationalization
+
+# -- Options for Math ------------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-math
+
+# -- Options for HTML output -----------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-html_extra_path = [
- "googled10b55fb460af091.html",
- "code.png",
-]
html_theme = "furo"
html_theme_options = {
"navigation_with_keys": True,
@@ -65,46 +71,47 @@
"source_directory": "docs/",
}
html_title = "Citric, a Python client for LimeSurvey"
-
-hoverxref_default_type = "tooltip"
-
-intersphinx_mapping = {
- "requests": ("https://requests.readthedocs.io/en/latest/", None),
- "requests-cache": ("https://requests-cache.readthedocs.io/en/stable/", None),
- "python": ("https://docs.python.org/3/", None),
-}
-
-hoverxref_intersphinx = [
- "requests",
+html_extra_path = [
+ "googled10b55fb460af091.html",
+ "code.png",
]
-hoverxref_domains = [
- "py",
-]
+# -- Options for Autodoc ---------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration
-hoverxref_role_types = {
- "hoverxref": "tooltip",
- "ref": "modal",
- "mod": "modal",
- "class": "tooltip",
-}
+autodoc_typehints = "description"
+autodoc_typehints_description_target = "documented"
+
+# -- Options for extlinks --------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
extlinks = {
"rpc_method": (
- "https://api.limesurvey.org/classes/remotecontrol_handle.html#method_%s",
+ "https://api.limesurvey.org/classes/remotecontrol-handle.html#method_%s",
"RPC method %s",
),
"ls_manual": (
"https://manual.limesurvey.org/%s",
"%s",
),
+ "ls_tag": (
+ "https://github.com/LimeSurvey/LimeSurvey/releases/tag/%s",
+ "%s",
+ ),
}
-source_suffix = {
- ".rst": "restructuredtext",
- ".md": "markdown",
+# -- Options for intersphinx -----------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
+
+intersphinx_mapping = {
+ "requests": ("https://requests.readthedocs.io/en/latest/", None),
+ "requests-cache": ("https://requests-cache.readthedocs.io/en/stable/", None),
+ "python": ("https://docs.python.org/3/", None),
}
+# -- Options for linkcode --------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/linkcode.html#configuration
+
def linkcode_resolve(domain: str, info: dict) -> str | None:
"""Get URL to source code.
@@ -124,6 +131,49 @@ def linkcode_resolve(domain: str, info: dict) -> str | None:
return f"https://github.com/edgarrmondragon/citric/tree/main/src/{filename}.py"
+# -- Options for Napoleon --------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#configuration
+
+# -- Options for AutoAPI ---------------------------------------------------------------
+# https://sphinx-autoapi.readthedocs.io/en/latest/reference/config.html
+
+autoapi_dirs = [
+ Path("../src").resolve(),
+]
+autoapi_options = [
+ "members",
+ "undoc-members",
+ "show-inheritance",
+ "show-module-summary",
+ "special-members",
+ "imported-members",
+ "private-members",
+]
+autoapi_root = "_api"
+
+# -- Options for Myst ------------------------------------------------------------------
+# https://myst-parser.readthedocs.io/en/latest/configuration.html
+
+myst_heading_anchors = 2
+
+# -- Options for hoverxref -------------------------------------------------------------
+# https://sphinx-hoverxref.readthedocs.io/en/latest/configuration.html
+
+hoverxref_role_types = {
+ "hoverxref": "tooltip",
+ "ref": "modal",
+ "mod": "modal",
+ "class": "tooltip",
+}
+hoverxref_default_type = "tooltip"
+hoverxref_domains = [
+ "py",
+]
+hoverxref_intersphinx = [
+ "requests",
+]
+
+
def skip_member_filter(
app: Sphinx, # noqa: ARG001
what: str, # noqa: ARG001
diff --git a/docs/contributing/environment.md b/docs/contributing/environment.md
index 7e431fe8..441a22f5 100644
--- a/docs/contributing/environment.md
+++ b/docs/contributing/environment.md
@@ -14,8 +14,6 @@ Ready to contribute? Here's how to set up `citric` for local development.
cd citric
```
-1. Install [`hatch`][hatch]:
-
1. Install [`nox`][nox] (used for automation):
```shell
@@ -54,7 +52,6 @@ Ready to contribute? Here's how to set up `citric` for local development.
changie new
```
-[hatch]: https://hatch.pypa.io/latest/install/
[nox]: https://nox.thea.codes/en/stable/
[pre-commit]: https://pre-commit.com/
[changie]: https://changie.dev/
diff --git a/docs/contributing/update-github-actions.md b/docs/contributing/update-github-actions.md
new file mode 100644
index 00000000..bac8f41f
--- /dev/null
+++ b/docs/contributing/update-github-actions.md
@@ -0,0 +1,25 @@
+# Update GitHub Actions pins
+
+There are a few ways to update the GitHub Actions pins.
+
+## Run workflow in GitHub
+
+Go to the [Actions tab](https://github.com/edgarrmondragon/citric/actions/workflows/gha-update.yml) and click on the `Run workflow` dropdown.
+
+## Run workflow locally
+
+1. Install the [GitHub CLI](https://cli.github.com/).
+2. Run the following command:
+
+ ```bash
+ gh workflow run gha-update.yml
+ ```
+
+## Run the `gha-update` tool locally
+
+1. Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/).
+2. Run the following command:
+
+ ```bash
+ uvx gha-update
+ ```
diff --git a/docs/how-to.md b/docs/how-to.md
index 50b35aa7..a3f95aaf 100644
--- a/docs/how-to.md
+++ b/docs/how-to.md
@@ -39,7 +39,7 @@ Otherwise, you can manually close the session with {meth}`client.close() |
ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
ResultSet
: to convert to pandas, call .DataFrame()
or to polars, call .PolarsDataFrame()
Text for second question
Text for first question
Please upload a text file
A file with .txt extension
\n", + " | submitdate | \n", + "lastpage | \n", + "startlanguage | \n", + "seed | \n", + "token | \n", + "startdate | \n", + "datestamp | \n", + "G01Q01 | \n", + "G01Q02 | \n", + "G02Q03 | \n", + "G02Q03[filecount] | \n", + "
---|---|---|---|---|---|---|---|---|---|---|---|
id | \n", + "\n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " | \n", + " |
1 | \n", + "2024-02-08 17:19:28 | \n", + "NaN | \n", + "en | \n", + "NaN | \n", + "53afb | \n", + "2024-02-08 17:19:28 | \n", + "2024-02-08 17:19:28 | \n", + "Score soldier network station edge. Degree mil... | \n", + "5 | \n", + "NaN | \n", + "NaN | \n", + "
2 | \n", + "2024-02-08 17:19:28 | \n", + "NaN | \n", + "en | \n", + "NaN | \n", + "c36b3 | \n", + "2024-02-08 17:19:28 | \n", + "2024-02-08 17:19:28 | \n", + "Half college hospital. Sell matter two phone r... | \n", + "3 | \n", + "NaN | \n", + "NaN | \n", + "
3 | \n", + "2024-02-08 17:19:28 | \n", + "NaN | \n", + "en | \n", + "NaN | \n", + "f99a1 | \n", + "2024-02-08 17:19:28 | \n", + "2024-02-08 17:19:28 | \n", + "Sense executive eye five fill. Technology hear... | \n", + "2 | \n", + "NaN | \n", + "NaN | \n", + "
4 | \n", + "2024-02-08 17:19:28 | \n", + "NaN | \n", + "en | \n", + "NaN | \n", + "f8311 | \n", + "2024-02-08 17:19:28 | \n", + "2024-02-08 17:19:28 | \n", + "Score people half. Only center team care radio... | \n", + "2 | \n", + "NaN | \n", + "NaN | \n", + "
5 | \n", + "2024-02-08 17:19:28 | \n", + "NaN | \n", + "en | \n", + "NaN | \n", + "676a2 | \n", + "2024-02-08 17:19:28 | \n", + "2024-02-08 17:19:28 | \n", + "Church clear of. Wear too way I. Expert everyt... | \n", + "5 | \n", + "NaN | \n", + "NaN | \n", + "
First question
" @@ -752,3 +775,85 @@ def test_users(client: citric.Client): def test_survey_groups(client: citric.Client): """Test survey group methods.""" assert len(client.list_survey_groups()) == 1 + + +@pytest.mark.integration_test +def test_mail_registered_participants( + client: citric.Client, + survey_id: int, + participants: list[dict[str, str]], + mailhog: MailHogClient, + subtests: SubTests, +): + """Test mail_registered_participants.""" + client.activate_survey(survey_id) + client.activate_tokens(survey_id, [1, 2]) + client.add_participants( + survey_id, + participant_data=participants, + create_tokens=False, + ) + + with subtests.test(msg="No initial emails"): + assert mailhog.get_all()["total"] == 0 + + # `mail_registered_participants` returns a non-error status messages even when + # emails are sent successfully and that violates assumptions made by this + # library about the meaning of `status` messages + with pytest.raises( + LimeSurveyStatusError, + match="0 left to send", + ): + client.session.mail_registered_participants(survey_id) + + with subtests.test(msg="2 emails sent"): + assert mailhog.get_all()["total"] == 2 + + mailhog.delete() + + with pytest.raises( + LimeSurveyStatusError, + match="Error: No candidate tokens", + ): + client.session.mail_registered_participants(survey_id) + + with subtests.test(msg="No more emails sent"): + assert mailhog.get_all()["total"] == 0 + + +@pytest.mark.integration_test +def test_remind_participants( + client: citric.Client, + survey_id: int, + participants: list[dict[str, str]], + mailhog: MailHogClient, + subtests: SubTests, +): + """Test remind_participants.""" + client.activate_survey(survey_id) + client.activate_tokens(survey_id, [1, 2]) + client.add_participants( + survey_id, + participant_data=participants, + create_tokens=False, + ) + + with subtests.test(msg="No initial emails"): + assert mailhog.get_all()["total"] == 0 + + # Use `call` to avoid error handling + client.session.call("mail_registered_participants", survey_id) + + with subtests.test(msg="2 emails sent"): + assert mailhog.get_all()["total"] == 2 + + mailhog.delete() + + # `remind_participants` returns a non-error status messages even when emails are + # sent successfully and that violates assumptions made by this library about the + # meaning of `status` messages" + with pytest.raises(LimeSurveyStatusError, match="0 left to send"): + client.session.remind_participants(survey_id) + + with subtests.test(msg="2 reminders sent"): + assert mailhog.get_all()["total"] == 2