From be00942078aeb2e90cb8867b14d3ab100c23d4e4 Mon Sep 17 00:00:00 2001 From: lindsay stevens Date: Fri, 3 May 2024 20:30:43 +1000 Subject: [PATCH] add: example script to create or update a form - fix: template token copypasta in tests (token from MD template). - chg: use create_ignore_duplicate_error in tests, because that would avoid ignoring issues like the above (invalid form). --- docs/examples/README.md | 6 +- .../create_or_update_form.py | 60 +++++++++++++++++++ .../create_or_update_form/requirements.txt | 1 + tests/resources/forms_data.py | 6 +- tests/utils/forms.py | 30 +++++++--- 5 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 docs/examples/create_or_update_form/create_or_update_form.py create mode 100644 docs/examples/create_or_update_form/requirements.txt diff --git a/docs/examples/README.md b/docs/examples/README.md index 72c38f2..3e86544 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -26,4 +26,8 @@ A script that uses mail merge to create personalized Word documents with data fr ## [October 2022 webinar materials](2022-10-pyodk-webinar.ipynb) -A Jupyter notebook companion to an October 2022 webinar by Hélène Martin introducing `pyodk`. Includes link to the session recording. \ No newline at end of file +A Jupyter notebook companion to an October 2022 webinar by Hélène Martin introducing `pyodk`. Includes link to the session recording. + +## [Create or Update Form script](create_or_update_form/create_or_update_form.py) + +A script to create or update a form, optionally with attachments. diff --git a/docs/examples/create_or_update_form/create_or_update_form.py b/docs/examples/create_or_update_form/create_or_update_form.py new file mode 100644 index 0000000..dbda43a --- /dev/null +++ b/docs/examples/create_or_update_form/create_or_update_form.py @@ -0,0 +1,60 @@ +import sys +from os import PathLike +from pathlib import Path + +from pyodk.client import Client +from pyodk.errors import PyODKError +from requests import Response + +""" +A script to create or update a form, optionally with attachments. + +Either use as a CLI script, or import create_or_update into another module. + +If provided, all files in the [attachments_dir] path will be uploaded with the form. +""" + + +def create_ignore_duplicate_error(client: Client, definition: PathLike | str | bytes): + """Create the form; ignore the error raised if it exists (409.3).""" + try: + client.forms.create(definition=definition) + except PyODKError as err: + if len(err.args) >= 2 and isinstance(err.args[1], Response): + err_detail = err.args[1].json() + err_code = err_detail.get("code") + if err_code is not None and str(err_code) == "409.3": + return + raise + + +def create_or_update(form_id: str, definition: str, attachments: str | None): + """Create (and publish) the form, optionally with attachments.""" + with Client() as client: + create_ignore_duplicate_error(client=client, definition=definition) + attach = None + if attachments is not None: + attach = Path(attachments).iterdir() + client.forms.update( + definition=definition, + form_id=form_id, + attachments=attach, + ) + + +if __name__ == "__main__": + usage = """ +Usage: + +python create_or_update_form.py form_id definition.xlsx +python create_or_update_form.py form_id definition.xlsx attachments_dir + """ + if len(sys.argv) < 3: + print(usage) + sys.exit(1) + fid = sys.argv[1] + def_path = sys.argv[2] + attach_path = None + if len(sys.argv) == 4: + attach_path = sys.argv[3] + create_or_update(form_id=fid, definition=def_path, attachments=attach_path) diff --git a/docs/examples/create_or_update_form/requirements.txt b/docs/examples/create_or_update_form/requirements.txt new file mode 100644 index 0000000..9485a46 --- /dev/null +++ b/docs/examples/create_or_update_form/requirements.txt @@ -0,0 +1 @@ +pyodk==1.0.0 diff --git a/tests/resources/forms_data.py b/tests/resources/forms_data.py index 87293a5..1be4596 100644 --- a/tests/resources/forms_data.py +++ b/tests/resources/forms_data.py @@ -87,7 +87,7 @@ def get_xml__range_draft( def get_md__pull_data(version: str | None = None) -> str: if version is None: - version = datetime.now().isoformat() + version = datetime.now(UTC).isoformat() return f""" | settings | | | version | @@ -106,7 +106,7 @@ def get_md__pull_data(version: str | None = None) -> str: | survey | | | | | | | type | name | label | calculation | | | calculate | fruit | | pulldata('fruits', 'name', 'name_key', 'mango') | -| | note | note_fruit | The fruit ${{fruit}} pulled from csv | | +| | note | note_fruit | The fruit ${fruit} pulled from csv | | """ md__dingbat = """ | settings | @@ -115,5 +115,5 @@ def get_md__pull_data(version: str | None = None) -> str: | survey | | | | | | | type | name | label | calculation | | | calculate | fruit | | pulldata('fruits', 'name', 'name_key', 'mango') | -| | note | note_fruit | The fruit ${{fruit}} pulled from csv | | +| | note | note_fruit | The fruit ${fruit} pulled from csv | | """ diff --git a/tests/utils/forms.py b/tests/utils/forms.py index 9957d6a..9df3e1a 100644 --- a/tests/utils/forms.py +++ b/tests/utils/forms.py @@ -1,10 +1,30 @@ +from os import PathLike + from pyodk.client import Client from pyodk.errors import PyODKError +from requests import Response from tests.utils import utils from tests.utils.md_table import md_table_to_temp_dir +def create_ignore_duplicate_error( + client: Client, + definition: PathLike | str | bytes, + form_id: str, +): + """Create the form; ignore the error raised if it exists (409.3).""" + try: + client.forms.create(definition=definition, form_id=form_id) + except PyODKError as err: + if len(err.args) >= 2 and isinstance(err.args[1], Response): + err_detail = err.args[1].json() + err_code = err_detail.get("code") + if err_code is not None and str(err_code) == "409.3": + return + raise + + def create_new_form__md(client: Client, form_id: str, form_def: str): """ Create a new form from a MarkDown string. @@ -16,10 +36,7 @@ def create_new_form__md(client: Client, form_id: str, form_def: str): with ( md_table_to_temp_dir(form_id=form_id, mdstr=form_def) as fp, ): - try: - client.forms.create(definition=fp, form_id=form_id) - except PyODKError: - pass + create_ignore_duplicate_error(client=client, definition=fp, form_id=form_id) def create_new_form__xml(client: Client, form_id: str, form_def: str): @@ -32,10 +49,7 @@ def create_new_form__xml(client: Client, form_id: str, form_def: str): """ with utils.get_temp_file(suffix=".xml") as fp: fp.write_text(form_def) - try: - client.forms.create(definition=fp, form_id=form_id) - except PyODKError: - pass + create_ignore_duplicate_error(client=client, definition=fp, form_id=form_id) def get_latest_form_version(client: Client, form_id: str) -> str: