Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support DestSurveyID parameter to RPC method copy_survey #1016

Merged
merged 1 commit into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/unreleased/Added-20231028-222932.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: Support `DestSurveyID` parameter to RPC method `copy_survey`
time: 2023-10-28T22:29:32.491728-06:00
custom:
Issue: "1016"
11 changes: 3 additions & 8 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,15 @@ repos:
- id: check-readthedocs

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.1
rev: v0.1.3
hooks:
- id: ruff
name: Ruff lint
args: [--fix, --exit-non-zero-on-fix, --show-fixes]
- id: ruff
name: Ruff format
entry: ruff format

- repo: https://github.com/psf/black
rev: 23.10.0
hooks:
- id: black
language_version: python3.11

- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
Expand Down Expand Up @@ -77,6 +72,6 @@ repos:
args: [--all]

- repo: https://github.com/tox-dev/pyproject-fmt
rev: "1.2.0"
rev: "1.3.0"
hooks:
- id: pyproject-fmt
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ Python.
<!-- start integration status -->
| | **PostgreSQL** | **MySQL** |
| - |:--: | :-: |
| 6.3.0 | ✅ | ✅ |
| 6.2.11 | ✅ | ✅ |
| 6.2.9 | ✅ | ✅ |
| 6.2.8 | ✅ | ✅ |
| 6.2.7 | ✅ | ✅ |
| 5.6.41 | ✅ | ✅ |
| 5.6.40 | ✅ | ✅ |
| 5.6.39 | ✅ | ✅ |
| 5.6.38 | ✅ | ✅ |
| 5.6.37 | ✅ | ✅ |
<!-- end integration status -->

## Installation
Expand Down
39 changes: 35 additions & 4 deletions docs/_ext/limesurvey_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ class UnreleasedFeature(Directive):
"""

required_arguments = 1
message = (
"This method is only supported in LimeSurvey >= {next_version} "
"(currently in development)."
)
message = "This method is only supported in LimeSurvey >= {next_version}."
admonition_type = nodes.warning

def run(self) -> list[nodes.Node]:
Expand All @@ -33,6 +30,25 @@ def run(self) -> list[nodes.Node]:
return [self.admonition_type("", nodes.paragraph(text=text))]


class UnreleasedParameter(Directive):
"""A directive for development-only parameters.

Adds a warning to method parameters that are only available in the next minor
release of LimeSurvey.
"""

required_arguments = 2
message = (
"The parameter {parameter} is only supported in LimeSurvey >= {next_version}."
)
admonition_type = nodes.warning

def run(self) -> list[nodes.Node]:
next_version, parameter = self.arguments[:2]
text = self.message.format(next_version=next_version, parameter=parameter)
return [self.admonition_type("", nodes.paragraph(text=text))]


class ReleasedFeature(UnreleasedFeature):
"""A directive for released features.

Expand All @@ -43,9 +59,24 @@ class ReleasedFeature(UnreleasedFeature):
admonition_type = nodes.note


class ReleasedParameter(UnreleasedParameter):
"""A directive for released parameters.

Adds a note to method parameters that are only available after some release of
LimeSurvey.
"""

message = (
"The parameter {parameter} is only supported in LimeSurvey >= {next_version}."
)
admonition_type = nodes.note


def setup(app: Sphinx) -> dict[str, t.Any]:
app.add_directive("future", UnreleasedFeature)
app.add_directive("futureparam", UnreleasedParameter)
app.add_directive("minlimesurvey", ReleasedFeature)
app.add_directive("minlimesurveyparam", ReleasedParameter)

return {
"version": "0.1",
Expand Down
38 changes: 23 additions & 15 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,20 @@ docs = [
line-length = 88

[tool.ruff]
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]
line-length = 88
src = ["src", "tests", "docs"]
target-version = "py38"

[tool.ruff.lint]
explicit-preview-rules = false
ignore = [
"ANN101", # missing-type-self
"DJ", # flake8-django
"FIX002", # line-contains-todo
"COM812", # missing-trailing-comma
"ISC001", # single-line-implicit-string-concatenation
]
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]
line-length = 88
preview = true
select = [
"F", # Pyflakes
Expand Down Expand Up @@ -138,8 +144,6 @@ select = [
"LOG", # flake8-logging
"RUF", # Ruff-specific rules
]
src = ["src", "tests", "docs"]
target-version = "py38"
unfixable = [
"ERA", # Don't remove commented out code
]
Expand Down Expand Up @@ -170,44 +174,48 @@ unfixable = [
# Enable preview style formatting.
preview = true

[tool.ruff.flake8-quotes]
[tool.ruff.lint.flake8-quotes]
docstring-quotes = "double"
inline-quotes = "double"
multiline-quotes = "double"

[tool.ruff.flake8-annotations]
[tool.ruff.lint.flake8-annotations]
allow-star-arg-any = true
mypy-init-return = true
suppress-dummy-args = true

[tool.ruff.flake8-errmsg]
[tool.ruff.lint.flake8-errmsg]
max-string-length = 30

[tool.ruff.flake8-import-conventions]
[tool.ruff.lint.flake8-import-conventions]
banned-from = ["typing"]

[tool.ruff.flake8-import-conventions.extend-aliases]
[tool.ruff.lint.flake8-import-conventions.extend-aliases]
typing = "t"

[tool.ruff.flake8-pytest-style]
[tool.ruff.lint.flake8-pytest-style]
fixture-parentheses = false
mark-parentheses = false

[tool.ruff.isort]
[tool.ruff.lint.isort]
known-first-party = ["citric"]
required-imports = ["from __future__ import annotations"]

[tool.ruff.mccabe]
[tool.ruff.lint.mccabe]
max-complexity = 5

[tool.ruff.pydocstyle]
[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.pylint]
[tool.ruff.lint.pylint]
max-args = 10

[tool.pytest.ini_options]
addopts = ["-vvv", "-W error"]
addopts = [
"-vvv",
"-W error",
"-W default::citric._compat.FutureVersionWarning",
]
markers = [
"integration_test: Integration and end-to-end tests",
"xfail_mysql: Mark a test as expected to fail on MySQL",
Expand Down
19 changes: 13 additions & 6 deletions scripts/docker_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,30 @@
import requests
import requests_cache

PATTERN_VERSION = re.compile(r"(\d+\.\d+\.\d+)-\d{6}-apache")
PATTERN_5x = re.compile(r"5\.\d+.\d+-\d{6}-apache")
PATTERN_6x = re.compile(r"6\.\d+.\d+-\d{6}-apache")

requests_cache.install_cache("docker_tags")


def _extract_version(tag: dict) -> tuple[int, ...]:
"""Extract version from tag."""
name = tag["name"]
return (
tuple(int(part) for part in match.group(1).split("."))
if (match := PATTERN_VERSION.match(name))
else (999,)
)


def get_tags() -> t.Generator[dict, None, None]:
"""Get all tags from the Docker Hub."""
url = (
"https://hub.docker.com/v2/namespaces/martialblog/repositories/limesurvey/tags"
)
while True:
data = requests.get(url, timeout=5).json()
data = requests.get(url, timeout=30).json()
yield from data["results"]

url = data.get("next")
Expand All @@ -31,11 +42,7 @@ def get_tags() -> t.Generator[dict, None, None]:

def sort_tags(tags: t.Iterable[dict]) -> list[dict]:
"""Sort tags."""
return sorted(
tags,
key=lambda tag: tag["name"],
reverse=True,
)
return sorted(tags, key=_extract_version, reverse=True)


def filter_tags(tags: t.Iterable[dict]) -> t.Generator[str, None, None]:
Expand Down
30 changes: 30 additions & 0 deletions src/citric/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,33 @@ def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Callable:
return wrapper

return decorate


def future_parameter(version: str, parameter: str) -> t.Callable:
"""Mark a function as only available in the current development build of LimeSurvey.

Args:
version: The earliest version of LimeSurvey that this parameter is
available in.
parameter: The parameter that is only available in the current development
build of LimeSurvey.

Returns:
The wrapped function.
"""
message = _warning_message(version)

def decorate(fn: t.Callable) -> t.Callable:
@wraps(fn)
def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Callable:
if parameter in kwargs:
warnings.warn(
f"Parameter {parameter} {''.join(message)}",
FutureVersionWarning,
stacklevel=2,
)
return fn(*args, **kwargs)

return wrapper

return decorate
17 changes: 15 additions & 2 deletions src/citric/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import requests

from citric import enums
from citric._compat import future_parameter
from citric.exceptions import LimeSurveyStatusError
from citric.session import Session

Expand Down Expand Up @@ -488,21 +489,33 @@ def update_response(self, survey_id: int, response_data: dict[str, t.Any]) -> bo
data = self._map_response_keys(response_data, questions)
return self.session.update_response(survey_id, data)

def copy_survey(self, survey_id: int, name: str) -> dict[str, t.Any]:
@future_parameter("6.4.0", "destination_survey_id")
def copy_survey(
self,
survey_id: int,
name: str,
*,
destination_survey_id: int | None = None,
) -> dict[str, t.Any]:
"""Copy a survey.

Calls :rpc_method:`copy_survey`.

Args:
survey_id: ID of the source survey.
name: Name of the new survey.
destination_survey_id: ID of the new survey. If already used a, random one
will be generated.

Returns:
Dictionary of status message and the new survey ID.

.. versionadded:: 0.0.10
.. versionchanged:: NEXT_VERSION
The ``destination_survey_id`` optional parameter was added.
.. futureparam:: 6.4.0 destination_survey_id
"""
return self.session.copy_survey(survey_id, name)
return self.session.copy_survey(survey_id, name, destination_survey_id)

def import_cpdb_participants(
self,
Expand Down
8 changes: 7 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,13 @@ def test_add_survey(client: MockClient):

def test_copy_survey(client: MockClient):
"""Test copy_survey client method."""
assert_client_session_call(client, "copy_survey", 1, NEW_SURVEY_NAME)
assert_client_session_call(
client,
"copy_survey",
1,
NEW_SURVEY_NAME,
destination_survey_id=None,
)


def test_delete_group(client: MockClient):
Expand Down
21 changes: 19 additions & 2 deletions tests/test_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,35 @@

import pytest

from citric._compat import FutureVersionWarning, future
from citric._compat import FutureVersionWarning, future, future_parameter


@future("4.0.0")
def my_function() -> None:
"""A simple function."""


@future_parameter("4.0.0", "new_param")
def function_new_param(new_param: str | None = None) -> None:
"""A simple function."""


def test_dev_only():
"""Test that dev_only raises a warning."""
"""Test that calling a dev-only functions raise a warning."""
with pytest.warns(
FutureVersionWarning,
match="Method my_function is only supported .* 4.0.0",
):
my_function()


def test_dev_only_param():
"""Test that calling a dev-only function parameters raise a warning."""
# Calling with the default value should not raise a warning
function_new_param()

with pytest.warns(
FutureVersionWarning,
match="Parameter new_param is only supported .* 4.0.0",
):
function_new_param(new_param="test")
Loading