diff --git a/docs/assets/images/img-excel2xml-module-docstring.png b/docs/assets/images/img-excel2xml-module-docstring.png new file mode 100644 index 000000000..fd37d2810 Binary files /dev/null and b/docs/assets/images/img-excel2xml-module-docstring.png differ diff --git a/docs/assets/templates/excel2xml_sample_script.py b/docs/assets/templates/excel2xml_sample_script.py index 25429c600..6ea630929 100644 --- a/docs/assets/templates/excel2xml_sample_script.py +++ b/docs/assets/templates/excel2xml_sample_script.py @@ -47,9 +47,9 @@ # and if it's not there, look in "category_dict_fallback" category_values = [category_dict.get(x.strip(), category_dict_fallback[x.strip()]) for x in row["Category"].split(",")] - resource.append(excel2xml.make_list_prop("category", ":hasCategory", values=category_values)) + resource.append(excel2xml.make_list_prop("category", ":hasCategory", category_values)) if excel2xml.check_notna(row["Complete?"]): - resource.append(excel2xml.make_boolean_prop(name=":isComplete", value=row["Complete?"])) + resource.append(excel2xml.make_boolean_prop(":isComplete", row["Complete?"])) if excel2xml.check_notna(row["Color"]): resource.append(excel2xml.make_color_prop(":colorprop", row["Color"])) if pd.notna(row["Date discovered"]): @@ -93,7 +93,7 @@ link = excel2xml.make_link("Link between Resource 0 and 1", "link_res_0_res_1") link.append(excel2xml.make_text_prop("hasComment", "This is a comment")) -link.append(excel2xml.make_resptr_prop("hasLinkTo", values=["res_0", "res_1"])) +link.append(excel2xml.make_resptr_prop("hasLinkTo", ["res_0", "res_1"])) root.append(link) # write file diff --git a/docs/dsp-tools-excel2xml.md b/docs/dsp-tools-excel2xml.md index 5cc21d200..7b5527a00 100644 --- a/docs/dsp-tools-excel2xml.md +++ b/docs/dsp-tools-excel2xml.md @@ -5,30 +5,72 @@ dsp-tools assists you in converting a data source in CSV/XLS(X) format to an XML transformation from Excel/CSV to XML: - The CLI command `dsp-tools excel2xml` creates an XML file from an Excel/CSV file which is already structured - according to the DSP specifications. This is mostly used for DaSCH-interal data migration. The CLI command is - documented [here](dsp-tools-excel.md#cli-command-excel2xml). + according to the DSP specifications. This is mostly used for DaSCH-interal data migration. **The CLI command is + documented [here](dsp-tools-excel.md#cli-command-excel2xml).** - The module `excel2xml` can be imported into a custom Python script that transforms any tabular data into an XML. This - use case is more frequent, because data from research projects have a variety of formats/structures. **This document - only treats the `excel2xml` module.** + use case is more frequent, because data from research projects have a variety of formats/structures. **The + `excel2xml` module is documented on this page.** +
+**In the following, an example is given how to use the module `excel2xml`:** -## How to use the module excel2xml -At the end of this document, you find a sample Python script. In the following, it is explained how to use it. +Save the following files into a directory, and run the Python script: + - sample data: [excel2xml_sample_data.csv](./assets/templates/excel2xml_sample_data.csv) + - sample ontology: [excel2xml_sample_onto.json](./assets/templates/excel2xml_sample_onto.json) + - sample script: [excel2xml_sample_script.py](./assets/templates/excel2xml_sample_script.py) -### General preparation -Insert your ontology name, project shortcode, and the path to your data source. If necessary, activate one of the lines -that are commented out. -Then, the `root` element is created, which represents the `` tag of the XML document. As first children of -``, some standard permissions are added. At the end, please carefully check the permissions of the finished XML -file if they meet your requirements, and adapt them if necessary. -The standard permission of a resource is "res-default", and of a property "prop-default". If you don't specify it -otherwise, all resources and properties get these permissions. With excel2xml, it is not possible to create resources/ -properties that don't have permissions, because they would be invisible for all users except project admins and system -admins. Read more about permissions [here](./dsp-tools-xmlupload.md#how-to-use-the-permissions-attribute-in-resourcesproperties). +This is the simplified pattern how the Python script works: +``` +1 main_df = pd.read_csv("excel2xml_sample_data.csv", dtype="str", sep=",") +2 root = excel2xml.make_root(...) +3 root = excel2xml.append_permissions(root) +4 # if necessary: create list mappings, according to explanation below +5 for index, row in main_df.iterrows(): +6 resource = excel2xml.make_resource(...) +7 resource.append(excel2xml.make_text_prop(...)) +8 root.append(resource) +9 excel2xml.write_xml(root, "data.xml") +``` +``` +1 read in your data source with the pandas library (https://pandas.pydata.org/) +2 create the root element `` +3 append the permissions +4 if necessary: create list mappings (see below) +5 iterate through the rows of your data source: +6 create the `` tag +7 append properties to it +8 append the resource to the root tag `` +9 save the finished XML file +``` -### Create list mappings +
+These steps are now explained in-depth: + + +## 1. Read in your data source +In the first paragraph of the sample script, insert your ontology name, project shortcode, and the path to your data +source. If necessary, activate one of the lines that are commented out. + + +## 2. Create root element `` +Then, the root element is created, which represents the `` tag of the XML document. + + +## 3. Append the permissions +As first children of ``, some standard permissions are added. At the end, please carefully check the permissions +of the finished XML file to ensure that they meet your requirements, and adapt them if necessary. + +The standard permission of a resource is `res-default`, and of a property `prop-default`. If you don't specify it +otherwise, all resources and properties get these permissions. + +With `excel2xml`, it is not possible to create resources/properties that don't have permissions, because they would be +invisible for all users except project admins and system admins. [Read more about permissions +here](./dsp-tools-xmlupload.md#how-to-use-the-permissions-attribute-in-resourcesproperties). + + +## 4. Create list mappings Let's assume that your data source has a column containing list values named after the "label" of the JSON project list, instead of the "name" which is needed for the `dsp-tools xmlupload`. You need a way to get the names from the labels. If your data source uses the labels correctly, this is an easy task: The method `create_json_list_mapping()` creates a @@ -39,38 +81,117 @@ correct JSON project node name. This happens based on string similarity. Please no false matches! -### Create all resources -With the help of the [Python pandas library](https://pandas.pydata.org/), you can then iterate through the rows of your -Excel/CSV, and create resources and properties. Some examples of useful helper methods are: +## 5. Iterate through the rows of your data source +With the help of Pandas, you can then iterate through the rows of your Excel/CSV, and create resources and properties. + + +### 6. Create the `` tag +There are four kind of resources that can be created: + +| super | tag | method | +|--------------|----------------|---------------------| +| `Resource` | `` | `make_resource()` | +| `Annotation` | `` | `make_annotation()` | +| `Region` | `` | `make_region()` | +| `LinkObj` | `` | `make_link()` | + +`` is the most frequent of them. The other three are [explained +here](./dsp-tools-xmlupload.md#dsp-base-resources--base-properties-to-be-used-directly-in-the-xml-file). + +Special care is needed when the ID of a resource is created. Every resource must have an ID that is unique in the file, +and it must meet the constraints of xsd:ID. You can simply achieve this if you use the method `make_xsd_id_compatible()`. + + +### 7. Append the properties +For every property, there is a helper function that explains itself when you hover over it. So you don't need to worry +any more how to construct a certain XML value for a certain property. + +Here's how the Docstrings assist you: + + - method signature: names of the parameters and accepted types + - short explanation how the method behaves + - usage examples + - link to the dsp-tools documentation of this property + - a short description for every parameter + - short description of the returned object. + - Note: `etree._Element` is a type annotation of an underlying library. You don't have to care about it, as long as + you proceed as described (append the returned object to the parent resource). + +![docstring example](./assets/images/img-excel2xml-module-docstring.png) + + +#### Fine-tuning with `PropertyElement` +There are two possibilities how to create a property: The value can be passed as it is, or as `PropertyElement`. If it +is passed as it is, the `permissions` are assumed to be `prop-default`, texts are assumed to be encoded as `utf8`, and +the value won't have a comment: +``` +make_text_prop(":testproperty", "first text") +``` +``` + + first text + +``` + +If you want to change these defaults, you have to use a `PropertyElement` instead: +``` +make_text_prop( + ":testproperty", + PropertyElement( + value="first text", + permissions="prop-restricted", + encoding="xml", + comment="some comment" + ) +) +``` +``` + + first text + +``` + + +#### Supported boolean formats +For `make_boolean_prop(cell)`, the following formats are supported: + - true: True, "true", "True", "1", 1, "yes", "Yes" + - false: False, "false", "False", "0", 0, "no", "No" -#### Create an ID for a resource -The method `make_xsd_id_compatible(string)` makes a string compatible with the constraints of xsd:ID, so that it can be -used as ID of a resource. +N/A-like values will raise an Error. So if your cell is empty, this method will not count it as false, but will raise an +Error. If you want N/A-like values to be counted as false, you may use a construct like this: +```python +if excel2xml.check_notna(cell): + # the cell contains usable content + excel2xml.make_boolean_prop(":hasBoolean", cell) +else: + # the cell is empty: you can decide to count this as "False" + excel2xml.make_boolean_prop(":hasBoolean", False) +``` -#### Create a property -For every property, there is a helper function that explains itself when you hover over it. It also has a link to -the dsp-tools documentation of this property. So you don't need to worry how to construct a certain XML value for a -certain property. -For `make_boolean_prop(cell)`, the following formats are supported: +### 8. Append the resource to root +At the end of the for-loop, it is important not to forget to append the finished resource to the root. - - true: True, "true", "True", "1", 1, "yes", "Yes" - - false: False, "false", "False", "0", 0, "no", "No" + +## 9. Save the file +At the very end, save the file under a name that you can choose yourself. -#### Check if a cell contains a usable value +## Other helper methods +### Check if a cell contains a usable value The method `check_notna(cell)` checks a value if it is usable in the context of data archiving. A value is considered usable if it is - a number (integer or float, but not numpy.nan) - a boolean - - a string with at least one Unicode letter, underscore, or number, but not "None", "", "N/A", or "-" + - a string with at least one Unicode letter (matching the regex `\p{L}`), underscore, ?, !, or number, but not "None", + "", "N/A", or "-" - a PropertyElement whose "value" fulfills the above criteria -#### Calendar date parsing +### Calendar date parsing The method `find_date_in_string(string)` tries to find a calendar date in a string. If successful, it returns the DSP-formatted date string. @@ -99,12 +220,3 @@ Currently supported date formats: | 1849/1850 | GREGORIAN:CE:1849:CE:1850 | | 1849/50 | GREGORIAN:CE:1849:CE:1850 | | 1845-50 | GREGORIAN:CE:1845:CE:1850 | - - -## Complete example -Save the following files into a directory, and run the Python script. The features discussed in this document are -contained therein. - - - sample data: [excel2xml_sample_data.csv](assets/templates/excel2xml_sample_data.csv) - - sample ontology: [excel2xml_sample_onto.json](assets/templates/excel2xml_sample_onto.json) - - sample script: [excel2xml_sample_script.py](assets/templates/excel2xml_sample_script.py) diff --git a/knora/dsplib/models/propertyelement.py b/knora/dsplib/models/propertyelement.py index eb3291a42..ffa2e981d 100644 --- a/knora/dsplib/models/propertyelement.py +++ b/knora/dsplib/models/propertyelement.py @@ -1,6 +1,4 @@ from typing import Union, Optional -import pandas as pd -import regex from dataclasses import dataclass from knora.dsplib.models.helpers import BaseError @@ -10,13 +8,13 @@ class PropertyElement: """ A PropertyElement object carries more information about a property value than the value itself. The "value" is the value that could be passed to a method as plain string/int/float/bool. Use a PropertyElement - instead to define more precisely what attributes your tag (for example) will have. + instead to define more precisely what attributes your value tag (e.g. , , ...) will have. Args: - value: This is the content that will be written between the tags (for example) + value: This is the content that will be written into the value tag (e.g. , , ...) permissions: This is the permissions that your tag (for example) will have comment: This is the comment that your tag (for example) will have - encoding: For tags only. Can be "xml" or "utf8". + encoding: For tags only. If provided, it must be "xml" or "utf8". Examples: See the difference between the first and the second example: @@ -40,15 +38,5 @@ class PropertyElement: encoding: Optional[str] = None def __post_init__(self) -> None: - if not any([ - isinstance(self.value, int), - isinstance(self.value, float) and pd.notna(self.value), # necessary because isinstance(np.nan, float) - isinstance(self.value, bool), - isinstance(self.value, str) and all([ - regex.search(r"\p{L}|\d|_", self.value, flags=regex.UNICODE), - not bool(regex.search(r"^(none||-|n/a)$", self.value, flags=regex.IGNORECASE)) - ]) - ]): - raise BaseError(f"'{self.value}' is not a valid value for a PropertyElement") if self.encoding not in ["utf8", "xml", None]: raise BaseError(f"'{self.encoding}' is not a valid encoding for a PropertyElement") diff --git a/knora/dsplib/utils/shared.py b/knora/dsplib/utils/shared.py index a99fd86ce..7c7f74811 100644 --- a/knora/dsplib/utils/shared.py +++ b/knora/dsplib/utils/shared.py @@ -174,7 +174,8 @@ def check_notna(value: Optional[Any]) -> bool: Check a value if it is usable in the context of data archiving. A value is considered usable if it is - a number (integer or float, but not np.nan) - a boolean - - a string with at least one Unicode letter, underscore, or number, but not "None", "", "N/A", or "-" + - a string with at least one Unicode letter (matching the regex ``\\p{L}``), underscore, !, ?, or number, but not + "None", "", "N/A", or "-" - a PropertyElement whose "value" fulfills the above criteria Args: @@ -195,7 +196,7 @@ def check_notna(value: Optional[Any]) -> bool: return True elif isinstance(value, str): return all([ - regex.search(r"\p{L}|\d|_", value, flags=regex.UNICODE), + regex.search(r"[\p{L}\d_!?]", value, flags=regex.UNICODE), not bool(regex.search(r"^(none||-|n/a)$", value, flags=regex.IGNORECASE)) ]) else: diff --git a/knora/excel2xml.py b/knora/excel2xml.py index af6e79a0e..aa009174a 100644 --- a/knora/excel2xml.py +++ b/knora/excel2xml.py @@ -18,37 +18,32 @@ from knora.dsplib.models.propertyelement import PropertyElement from knora.dsplib.utils.shared import simplify_name, check_notna -############################## -# global variables and classes -############################## + xml_namespace_map = { None: "https://dasch.swiss/schema", "xsi": "http://www.w3.org/2001/XMLSchema-instance" } -########### -# functions -########### def make_xsd_id_compatible(string: str) -> str: """ - Make a string compatible with the constraints of xsd:ID as defined in http://www.datypic.com/sc/xsd/t-xsd_ID.html. - An xsd:ID cannot contain special characters, and it must be unique in the document. + Make a string compatible with the constraints of xsd:ID, so that it can be used as "id" attribute of a + tag. An xsd:ID must not contain special characters, and it must be unique in the document. This method replaces the illegal characters by "_" and appends a random number to the string to make it unique. - The string must contain at least one word-character (regex [A-Za-z0-9_]), but must not be "None", "", "N/A", or - "-". In such cases, a BaseError is thrown. + The string must contain at least one Unicode letter (matching the regex ``\\p{L}``), underscore, !, ?, or number, + but must not be "None", "", "N/A", or "-". Otherwise, a BaseError will be raised. Args: - string: string which to make the xsd:ID from + string: input string Returns: - an `xsd:ID` based on string + an xsd:ID based on the input string """ if not isinstance(string, str) or not check_notna(string): - raise BaseError(f"The string {string} cannot be made an xsd:ID") + raise BaseError(f"The input '{string}' cannot be transformed to an xsd:ID") # if start of string is neither letter nor underscore, add an underscore res = re.sub(r"^(?=[^A-Za-z_])", "_", string) @@ -69,7 +64,8 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str DSP-formatted string. Returns None if no date was found. Notes: - - Assumes Christian era (no BC dates) and Gregorian calendar. + - All dates are interpreted in the Christian era and the Gregorian calendar. There is no support for BC dates or + non-Gregorian calendars. - The years 0000-2999 are supported, in 4-digit form. - Dates written with slashes are always interpreted in a European manner: 5/11/2021 is the 5th of November. @@ -165,8 +161,6 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str startdate = datetime.date(year, month, day) enddate = startdate except ValueError: - warnings.warn(f"Date parsing error in resource {calling_resource}: '{iso_date.group(0)}' is not a valid " - f"date", stacklevel=2) return None elif eur_date_range: @@ -182,8 +176,6 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str if enddate < startdate: raise ValueError except ValueError: - warnings.warn(f"Date parsing error in resource {calling_resource}: '{eur_date_range.group(0)}' is not a " - f"valid date", stacklevel=2) return None elif eur_date: @@ -194,8 +186,6 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str startdate = datetime.date(startyear, startmonth, startday) enddate = startdate except ValueError: - warnings.warn(f"Date parsing error in resource {calling_resource}: '{eur_date.group(0)}' is not a valid " - f"date", stacklevel=2) return None elif monthname_date: @@ -206,8 +196,6 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str startdate = datetime.date(year, month, day) enddate = startdate except ValueError: - warnings.warn(f"Date parsing error in resource {calling_resource}: '{monthname_date.group(0)}' is not a " - f"valid date", stacklevel=2) return None elif year_range: @@ -229,72 +217,28 @@ def find_date_in_string(string: str, calling_resource: str = "") -> Optional[str return None -def _check_and_prepare_values( - value: Optional[Union[PropertyElement, str, int, float, bool]], - values: Optional[Iterable[Union[PropertyElement, str, int, float, bool]]], - name: str, - calling_resource: str = "" +def prepare_value( + value: Union[PropertyElement, str, int, float, bool, Iterable[Union[PropertyElement, str, int, float, bool]]] ) -> list[PropertyElement]: """ - There is a variety of possibilities how to call a make_*_prop() method. Before such a method can do its job, the - parameters need to be checked and prepared, which is done by this helper method. The parameters "value" and "values" - are passed to it as they were received. This method will then perform the following checks, and throw a BaseError in - case of failure: - - check that exactly one of them contains data, but not both. - - check that the values are usable, and not N/A - - Then, all values are transformed to PropertyElements and returned as a list. In case of a single "value", the - resulting list contains the PropertyElement of this value. + This method transforms the parameter "value" from a make_*_prop() method into a list of PropertyElements. "value" is + passed on to this method as it was received. Args: value: "value" as received from the caller - values: "values" as received from the caller - name: name of the property (for better error messages) - calling_resource: name of the resource (for better error messages) Returns: a list of PropertyElements """ + # make sure that "value" is list-like + if not isinstance(value, Iterable) or isinstance(value, str): + value = [value] - # reduce 'value' to None if it is not usable - if not check_notna(value): - value = None - - # reduce 'values' to None if it is not usable - if values and not any([check_notna(val) for val in values]): - values = None - - # assert that either "value" or "values" is usable, but not both at the same time - if not value and not values: - raise BaseError(f"ERROR in resource '{calling_resource}', property '{name}': 'value' and 'values' cannot both " - f"be empty") - if value and values: - raise BaseError(f"ERROR in resource '{calling_resource}', property '{name}': You cannot provide a 'value' and " - f"a 'values' at the same time!") - - # construct the resulting list - result: list[PropertyElement] = list() - - if value: - # make a PropertyElement out of it, if necessary - if isinstance(value, PropertyElement): - result.append(value) - else: - result.append(PropertyElement(value)) - elif values: - # if "values" contains unusable elements, remove them - multiple_values = [val for val in values if check_notna(val)] - # make a PropertyElement out of them, if necessary - for elem in multiple_values: - if isinstance(elem, PropertyElement): - result.append(elem) - else: - result.append(PropertyElement(elem)) - - return result + # make a PropertyElement out of its elements, if necessary. + return [x if isinstance(x, PropertyElement) else PropertyElement(x) for x in value] -def make_root(shortcode: str, default_ontology: str) -> etree.Element: +def make_root(shortcode: str, default_ontology: str) -> etree._Element: """ Start building your XML document by creating the root element . @@ -326,7 +270,7 @@ def make_root(shortcode: str, default_ontology: str) -> etree.Element: return root -def append_permissions(root_element: etree.Element) -> etree.Element: +def append_permissions(root_element: etree.Element) -> etree._Element: """ After having created a root element, call this method to append the four permissions "res-default", "res-restricted", "prop-default", and "prop-restricted" to it. These four permissions are a good basis to @@ -388,7 +332,7 @@ def make_resource( permissions: str = "res-default", ark: Optional[str] = None, iri: Optional[str] = None -) -> etree.Element: +) -> etree._Element: """ Creates an empty resource element, with the attributes as specified by the arguments @@ -433,9 +377,10 @@ def make_bitstream_prop( path: str, permissions: str = "prop-default", calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ - Creates a bitstream element that points to path. + Creates a bitstream element that points to "path". If "path" doesn't point to a valid file, a warning will be + printed to the console, but the script will continue. Args: path: path to a valid file that will be uploaded @@ -443,7 +388,7 @@ def make_bitstream_prop( calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> resource = make_resource(...) @@ -454,7 +399,8 @@ def make_bitstream_prop( """ if not os.path.isfile(path): - warnings.warn(f"The following is not a valid path: {path} (resource '{calling_resource}')", + warnings.warn(f"Failed validation in bitstream tag of resource '{calling_resource}': The following path " + f"doesn't point to a file: {path}", stacklevel=2) prop_ = etree.Element("{%s}bitstream" % (xml_namespace_map[None]), permissions=permissions, nsmap=xml_namespace_map) @@ -470,29 +416,32 @@ def _format_bool(unformatted: Union[bool, str, int], name: str, calling_resource elif unformatted in (True, "true", "1", 1, "yes"): return "true" else: - raise BaseError(f"Invalid boolean format for prop '{name}' in resource '{calling_resource}': '{unformatted}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{unformatted}' is not a valid boolean.") def make_boolean_prop( name: str, value: Union[PropertyElement, str, int, bool], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from a boolean value. The value can be provided directly or inside a PropertyElement. The following formats are supported: - true: (True, "true", "True", "1", 1, "yes", "Yes") - false: (False, "false", "False", "0", 0, "no", "No") - Unless provided as PropertyElement, the permission for every value is "prop-default". + If the value is not a valid boolean, a BaseError will be raised. + + Unless provided as PropertyElement, the permissions of the value default to "prop-default". Args: name: the name of this property as defined in the onto - value: a str/bool/int itself or inside a PropertyElement + value: a boolean value as str/bool/int, or as str/bool/int inside a PropertyElement calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_boolean_prop(":testproperty", "no") @@ -513,7 +462,8 @@ def make_boolean_prop( elif isinstance(value, str) or isinstance(value, bool) or isinstance(value, int): value_new = PropertyElement(_format_bool(value, name, calling_resource)) else: - raise BaseError(f"Invalid boolean format for prop '{name}' in resource '{calling_resource}': '{value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{value}' is not a valid boolean.") # make xml structure of the value prop_ = etree.Element( @@ -537,25 +487,22 @@ def make_boolean_prop( def make_color_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more colors. The color(s) can be provided as string or as PropertyElement with a - string inside. If provided as string, the permission for every value is "prop-default". + string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid color, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more DSP color(s), as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_color_prop(":testproperty", "#00ff66") @@ -566,7 +513,7 @@ def make_color_prop( #00ff66 - >>> make_color_prop(":testproperty", values=["#00ff66", "#000000"]) + >>> make_color_prop(":testproperty", ["#00ff66", "#000000"]) #00ff66 #000000 @@ -576,25 +523,21 @@ def make_color_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.search(r"^#[0-9a-f]{6}$", str(val.value).strip(), flags=re.IGNORECASE): - raise BaseError(f"Invalid color format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid color.") - # make xml structure of the value + # make xml structure of the valid values prop_ = etree.Element( "{%s}color-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -611,24 +554,22 @@ def make_color_prop( def make_date_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more dates/date ranges. The date(s) can be provided as string or as PropertyElement - with a string inside. If provided as string, the permission for every value is "prop-default". + with a string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use ``values``. + If the value is not a valid DSP date, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more DSP dates, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_date_prop(":testproperty", "GREGORIAN:CE:2014-01-31") @@ -641,7 +582,7 @@ def make_date_prop( GREGORIAN:CE:2014-01-31 - >>> make_date_prop(":testproperty", values=["GREGORIAN:CE:1930-09-02:CE:1930-09-03", "GREGORIAN:CE:1930-09-02:CE:1930-09-03"]) + >>> make_date_prop(":testproperty", ["GREGORIAN:CE:1930-09-02:CE:1930-09-03", "GREGORIAN:CE:1930-09-02:CE:1930-09-03"]) GREGORIAN:CE:1930-09-02:CE:1930-09-03 @@ -655,26 +596,22 @@ def make_date_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.search(r"^(GREGORIAN:|JULIAN:)?(CE:|BCE:)?(\d{4})(-\d{1,2})?(-\d{1,2})?" r"((:CE|:BCE)?(:\d{4})(-\d{1,2})?(-\d{1,2})?)?$", str(val.value).strip()): - raise BaseError(f"Invalid date format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid DSP date.") - # make xml structure of the value + # make xml structure of the valid values prop_ = etree.Element( "{%s}date-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -691,26 +628,23 @@ def make_date_prop( def make_decimal_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more decimal numbers. The decimal(s) can be provided as string, float, or as - PropertyElement with a string/float inside. If provided as string/float, the permission for every value is + PropertyElement with a string/float inside. If provided as string/float, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid decimal number, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/float/PropertyElement - values: an iterable of distinct strings/PropertyElements + value: one or more decimal numbers, as string/float/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_decimal_prop(":testproperty", "3.14159") @@ -721,7 +655,7 @@ def make_decimal_prop( 3.14159 - >>> make_decimal_prop(":testproperty", values=["3.14159", "2.718"]) + >>> make_decimal_prop(":testproperty", ["3.14159", "2.718"]) 3.14159 2.718 @@ -731,25 +665,21 @@ def make_decimal_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.search(r"^\d+\.\d+$", str(val.value).strip()): - raise BaseError(f"Invalid decimal format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid decimal number.") - # make xml structure of the value + # make xml structure of the valid values prop_ = etree.Element( "{%s}decimal-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -766,25 +696,22 @@ def make_decimal_prop( def make_geometry_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more areas of an image. The area(s) can be provided as JSON-string or as - PropertyElement with the JSON-string inside. If provided as string, the permission for every value is "prop-default". + PropertyElement with the JSON-string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid JSON geometry object, a BaseError is raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more JSON geometry objects, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_geometry_prop(":testproperty", json_string) @@ -795,7 +722,7 @@ def make_geometry_prop( {JSON} - >>> make_geometry_prop(":testproperty", values=[json_string1, json_string2]) + >>> make_geometry_prop(":testproperty", [json_string1, json_string2]) {JSON} {JSON} @@ -805,30 +732,25 @@ def make_geometry_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: try: value_as_dict = json.loads(val.value) - assert value_as_dict["type"] in ["rectangle", "circle"] + assert value_as_dict["type"] in ["rectangle", "circle", "polygon"] assert isinstance(value_as_dict["points"], list) except (json.JSONDecodeError, TypeError, IndexError, KeyError, AssertionError): - raise BaseError(f"Invalid geometry format for prop '{name}' in resource '{calling_resource}': " - f"'{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid JSON geometry object.") - # make xml structure of the value + # make xml structure of the valid values prop_ = etree.Element( "{%s}geometry-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -844,26 +766,23 @@ def make_geometry_prop( def make_geoname_prop( name: str, - value: Optional[Union[PropertyElement, str, int]] = None, - values: Optional[Iterable[Union[PropertyElement, str, int]]] = None, + value: Union[PropertyElement, str, int, Iterable[Union[PropertyElement, str, int]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more geonames.org IDs. The ID(s) can be provided as string, integer, or as - PropertyElement with a string/integer inside. If provided as string/integer, the permission for every value is + PropertyElement with a string/integer inside. If provided as string/integer, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid geonames.org identifier, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/int/PropertyElement - values: an iterable of (usually distinct) strings/ints/PropertyElements + value: one or more geonames.org IDs, as string/int/PropertyElement, or as iterable of strings/ints/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_geoname_prop(":testproperty", "2761369") @@ -874,7 +793,7 @@ def make_geoname_prop( 2761369 - >>> make_geoname_prop(":testproperty", values=["2761369", "1010101"]) + >>> make_geoname_prop(":testproperty", ["2761369", "1010101"]) 2761369 1010101 @@ -884,24 +803,21 @@ def make_geoname_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.search(r"^[0-9]+$", str(val.value)): - raise BaseError(f"Invalid geoname format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a geonames.org identifier.") + # make xml structure of the valid values prop_ = etree.Element( "{%s}geoname-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -918,26 +834,23 @@ def make_geoname_prop( def make_integer_prop( name: str, - value: Optional[Union[PropertyElement, str, int]] = None, - values: Optional[Iterable[Union[PropertyElement, str, int]]] = None, + value: Union[PropertyElement, str, int, Iterable[Union[PropertyElement, str, int]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more integers. The integers can be provided as string, integer, or as - PropertyElement with a string/integer inside. If provided as string/integer, the permission for every value is + PropertyElement with a string/integer inside. If provided as string/integer, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid integer, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/int/PropertyElement - values: an iterable of (usually distinct) strings/ints/PropertyElements + value: one or more integers, as string/int/PropertyElement, or as iterable of strings/ints/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_integer_prop(":testproperty", "2761369") @@ -948,7 +861,7 @@ def make_integer_prop( 2761369 - >>> make_integer_prop(":testproperty", values=["2761369", "1010101"]) + >>> make_integer_prop(":testproperty", ["2761369", "1010101"]) 2761369 1010101 @@ -958,24 +871,21 @@ def make_integer_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.search(r"^\d+$", str(val.value).strip()): - raise BaseError(f"Invalid integer format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid integer.") + # make xml structure of the valid values prop_ = etree.Element( "{%s}integer-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -992,25 +902,22 @@ def make_integer_prop( def make_interval_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ - Make a from one or more intervals. The interval(s) can be provided as string or as PropertyElement - with a string inside. If provided as string, the permission for every value is "prop-default". + Make a from one or more DSP intervals. The interval(s) can be provided as string or as + PropertyElement with a string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the value is not a valid DSP interval, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more DSP intervals, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_interval_prop(":testproperty", "61:3600") @@ -1021,7 +928,7 @@ def make_interval_prop( 61:3600 - >>> make_interval_prop(":testproperty", values=["61:3600", "60.5:120.5"]) + >>> make_interval_prop(":testproperty", ["61:3600", "60.5:120.5"]) 61:3600 60.5:120.5 @@ -1031,25 +938,21 @@ def make_interval_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not re.match(r"([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)):([+-]?([0-9]+([.][0-9]*)?|[.][0-9]+))", str(val.value)): - raise BaseError(f"Invalid integer format for prop '{name}' in resource '{calling_resource}': '{val.value}'") - + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid DSP interval.") + # make xml structure of the valid values prop_ = etree.Element( "{%s}interval-prop" % (xml_namespace_map[None]), name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -1067,25 +970,23 @@ def make_interval_prop( def make_list_prop( list_name: str, name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ - Make a from one or more list items. The list item(s) can be provided as string or as PropertyElement - with a string inside. If provided as string, the permission for every value is "prop-default". + Make a from one or more list nodes. The name(s) of the list node(s) can be provided as string or as + PropertyElement with a string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use ``values``. + If the name of one of the list nodes is not a valid string, a BaseError will be raised. Args: list_name: the name of the list as defined in the onto name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more node names, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_list_prop("mylist", ":testproperty", "first_node") @@ -1096,7 +997,7 @@ def make_list_prop( first_node - >>> make_list_prop("mylist", ":testproperty", values=["first_node", "second_node"]) + >>> make_list_prop("mylist", ":testproperty", ["first_node", "second_node"]) first_node second_node @@ -1106,17 +1007,13 @@ def make_list_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not isinstance(val.value, str) or not check_notna(val.value): - raise BaseError(f"Invalid list format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid name of a list node.") # make xml structure of the valid values prop_ = etree.Element( @@ -1125,7 +1022,7 @@ def make_list_prop( name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -1142,25 +1039,22 @@ def make_list_prop( def make_resptr_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ - Make a from one or more links to other resources. The links(s) can be provided as string or as - PropertyElement with a string inside. If provided as string, the permission for every value is "prop-default". + Make a from one or more IDs of other resources. The ID(s) can be provided as string or as + PropertyElement with a string inside. If provided as string, the permissions default to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` children, use - ``values``. + If the ID of one of the target resources is not a valid string, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more resource identifiers, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_resptr_prop(":testproperty", "resource_1") @@ -1171,7 +1065,7 @@ def make_resptr_prop( resource_1 - >>> make_resptr_prop(":testproperty", values=["resource_1", "resource_2"]) + >>> make_resptr_prop(":testproperty", ["resource_1", "resource_2"]) resource_1 resource_2 @@ -1181,17 +1075,13 @@ def make_resptr_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not isinstance(val.value, str) or not check_notna(val.value): - raise BaseError(f"Invalid resptr format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Validation Error in resource '{calling_resource}', property '{name}': " + f"The following doesn't seem to be a valid ID of a target resource: '{val.value}'") # make xml structure of the valid values prop_ = etree.Element( @@ -1199,7 +1089,7 @@ def make_resptr_prop( name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -1216,25 +1106,22 @@ def make_resptr_prop( def make_text_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ - Make a from one or more texts. The text(s) can be provided as string or as PropertyElement with a string - inside. The default encoding is utf8. The default permission for every value is "prop-default". + Make a from one or more strings. The string(s) can be provided as string or as PropertyElement with a + string inside. If provided as string, the encoding defaults to utf8, and the permissions to "prop-default". - To create one ```` child, use the param ``value``, to create more than one ```` - children, use ``values``. + If the value is not a valid string, a BaseError will be raised. Args: name: the name of this property as defined in the onto - value: a string/PropertyElement - values: an iterable of (usually distinct) strings/PropertyElements + value: one or more strings, as string/PropertyElement, or as iterable of strings/PropertyElements calling_resource: the name of the parent resource (for better error messages) Returns: - an etree.Element that can be appended to the parent resource with resource.append(make_*_prop(...)) + an etree._Element that can be appended to the parent resource with resource.append(make_*_prop(...)) Examples: >>> make_text_prop(":testproperty", "first text") @@ -1245,7 +1132,7 @@ def make_text_prop( first text - >>> make_text_prop(":testproperty", values=["first text", "second text"]) + >>> make_text_prop(":testproperty", ["first text", "second text"]) first text second text @@ -1255,17 +1142,13 @@ def make_text_prop( """ # check the input: prepare a list with valid values - values_new = _check_and_prepare_values( - value=value, - values=values, - name=name, - calling_resource=calling_resource - ) + values = prepare_value(value) # check value type - for val in values_new: + for val in values: if not isinstance(val.value, str) or not check_notna(val.value): - raise BaseError(f"Invalid text format for prop '{name}' in resource '{calling_resource}': '{val.value}'") + raise BaseError(f"Failed validation in resource '{calling_resource}', property '{name}': " + f"'{val.value}' is not a valid string.") # make xml structure of the valid values prop_ = etree.Element( @@ -1273,7 +1156,7 @@ def make_text_prop( name=name, nsmap=xml_namespace_map ) - for val in values_new: + for val in values: kwargs = {"permissions": val.permissions} if check_notna(val.comment): kwargs["comment"] = val.comment @@ -1294,26 +1177,23 @@ def make_text_prop( def make_time_prop( name: str, - value: Optional[Union[PropertyElement, str]] = None, - values: Optional[Iterable[Union[PropertyElement, str]]] = None, + value: Union[PropertyElement, str, Iterable[Union[PropertyElement, str]]], calling_resource: str = "" -) -> etree.Element: +) -> etree._Element: """ Make a from one or more datetime values of the form "2009-10-10T12:00:00-05:00". The time(s) can be - provided as string or as PropertyElement with a string inside. If provided as string, the permission for every - value is "prop-default". + provided as string or as PropertyElement with a string inside. If provided as string, the permissions default to + "prop-default". - To create one `` - >>> make_time_prop(":testproperty", values=["2009-10-10T12:00:00-05:00", "1901-01-01T01:00:00-00:00"]) + >>> make_time_prop(":testproperty", ["2009-10-10T12:00:00-05:00", "1901-01-01T01:00:00-00:00"])