lms.model.base

  1import typing
  2
  3import edq.util.json
  4import edq.util.time
  5
  6import lms.model.constants
  7import lms.util.string
  8
  9TEXT_SEPARATOR: str = ': '
 10TEXT_EMPTY_VALUE: str = ''
 11
 12T = typing.TypeVar('T', bound = 'BaseType')
 13
 14class BaseType(edq.util.json.DictConverter):
 15    """
 16    The base class for all core LMS types.
 17    This class ensures that all children have the core functionality necessary for this package.
 18
 19    The typical structure of types in this package is that types in the model package extend this class.
 20    Then, backends may declare their own types that extend the other classes from the model package.
 21    For example: lms.model.base.BaseType -> lms.model.assignments.Assignment -> lms.backend.canvas.model.assignments.Assignment
 22
 23    General (but less efficient) implementations of core functions will be provided.
 24    """
 25
 26    CORE_FIELDS: typing.List[str] = []
 27    """
 28    The common fields shared across backends for this type that are used for comparison and other operations.
 29    Child classes should set this to define how comparisons are made.
 30    """
 31
 32    INT_COMPARISON_FIELDS: typing.Set[str] = {'id'}
 33    """
 34    Fields that should be compared like ints (even if they are strings).
 35    By default, this set will include 'id'.
 36    """
 37
 38    def __init__(self,
 39            **kwargs: typing.Any) -> None:
 40        self.extra_fields: typing.Dict[str, typing.Any] = kwargs.copy()
 41        """ Additional fields not common to all backends or explicitly used by the creating child backend. """
 42
 43    def __eq__(self, other: object) -> bool:
 44        if (not isinstance(other, BaseType)):
 45            return False
 46
 47        # Check the specified fields only.
 48        for field_name in self.CORE_FIELDS:
 49            if (not hasattr(other, field_name)):
 50                return False
 51
 52            value_self = getattr(self, field_name)
 53            value_other = getattr(other, field_name)
 54
 55            if (field_name in self.INT_COMPARISON_FIELDS):
 56                comparison = lms.util.string.compare_maybe_ints(value_self, value_other)
 57                if (comparison != 0):
 58                    return False
 59            elif (value_self != value_other):
 60                return False
 61
 62        return True
 63
 64    def __hash__(self) -> int:
 65        values = tuple(getattr(self, field_name) for field_name in self.CORE_FIELDS)
 66        return hash(values)
 67
 68    def __lt__(self, other: 'BaseType') -> bool:  # type: ignore[override]
 69        if (not isinstance(other, BaseType)):
 70            return False
 71
 72        # Check the specified fields only.
 73        for field_name in self.CORE_FIELDS:
 74            if (not hasattr(other, field_name)):
 75                return False
 76
 77            value_self = getattr(self, field_name)
 78            value_other = getattr(other, field_name)
 79
 80            if (field_name in self.INT_COMPARISON_FIELDS):
 81                comparison = lms.util.string.compare_maybe_ints(value_self, value_other)
 82                if (comparison == 0):
 83                    continue
 84
 85                return (comparison < 0)
 86
 87            if (value_self == value_other):
 88                continue
 89
 90            return bool(value_self < value_other)
 91
 92        return False
 93
 94    def as_text_rows(self,
 95            skip_headers: bool = False,
 96            pretty_headers: bool = False,
 97            **kwargs: typing.Any) -> typing.List[str]:
 98        """
 99        Create a representation of this object in the "text" style of this project meant for display.
100        A list of rows will be returned.
101        """
102
103        rows = []
104
105        kwargs['pretty_timestamps'] = True
106
107        for (field_name, row) in self._get_fields(**kwargs).items():
108            if (not skip_headers):
109                header = field_name
110                if (pretty_headers):
111                    header = header.replace('_', ' ').title()
112
113                row = f"{header}{TEXT_SEPARATOR}{row}"
114
115            rows.append(row)
116
117        return rows
118
119    def get_headers(self,
120            pretty_headers: bool = False,
121            **kwargs: typing.Any) -> typing.List[str]:
122        """
123        Get a list of headers to label the values represented by this object meant for display.
124        This method is a companion to as_table_rows(),
125        given the same options these two methods will produce rows with the same length and ordering.
126        """
127
128        headers = []
129
130        for field_name in self._get_fields(**kwargs):
131            header = field_name
132            if (pretty_headers):
133                header = header.replace('_', ' ').title()
134
135            headers.append(header)
136
137        return headers
138
139    def as_table_rows(self,
140            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
141        """
142        Get a list of the values by this object meant for display.
143        This method is a companion to get_headers(),
144        given the same options these two methods will produce rows with the same length and ordering.
145
146        Note that the default implementation for this method always return a single row,
147        but children may override and return multiple rows per object.
148        """
149
150        return [list(self._get_fields(**kwargs).values())]
151
152    def as_json_dict(self,
153            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
154        """
155        Get a dict representation of this object meant for display as JSON.
156        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
157        Calling this method differs from passing this object to json.dumps() (or any sibling),
158        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
159        """
160
161        return {field_name: self._get_field_value(field_name) for field_name in self._get_fields(**kwargs)}
162
163    def _get_fields(self,
164            include_extra_fields: bool = False,
165            **kwargs: typing.Any) -> typing.Dict[str, str]:
166        """
167        Get a dictionary representing the "target" fields of this object meant for display.
168        Keys (field names) will not be modified, but values will be sent to self._value_to_text().
169        Keys are placed in the dictionary in a consistent ordering.
170        """
171
172        field_names = self.CORE_FIELDS.copy()
173
174        # Append any extra fields after the core fields.
175        if (include_extra_fields):
176            # First, include any fields that are not in self.extra_fields.
177            for extra_name in (list(vars(self).keys()) + list(self.extra_fields.keys())):
178                if (extra_name == 'extra_fields'):
179                    continue
180
181                if (extra_name not in field_names):
182                    field_names.append(extra_name)
183
184        fields = {}
185        for field_name in field_names:
186            fields[field_name] = self._value_to_text(self._get_field_value(field_name), **kwargs)
187
188        return fields
189
190    def _get_field_value(self, name: str, default: typing.Any = None) -> typing.Any:
191        """
192        Get the value for a field.
193        This is similar to `getattr(self, name, default)`,
194        but this will also check `extra_fields` if the field is not found at the top level.
195        """
196
197        if (hasattr(self, name)):
198            return getattr(self, name)
199
200        if (name in self.extra_fields):
201            return self.extra_fields[name]
202
203        return default
204
205    def _value_to_text(self,
206            value: typing.Any,
207            indent: typing.Union[int, None] = None,
208            pretty_timestamps: bool = False,
209            **kwargs: typing.Any) -> str:
210        """
211        Convert some arbitrary value (usually found within a BaseType) to a string.
212        None values will be returned as `TEXT_EMPTY_VALUE`.
213        """
214
215        if (value is None):
216            return TEXT_EMPTY_VALUE
217
218        if (hasattr(value, '_to_text')):
219            return str(value._to_text())
220
221        if (isinstance(value, (edq.util.json.DictConverter, dict, list, tuple))):
222            return str(edq.util.json.dumps(value, indent = indent))
223
224        if (pretty_timestamps and isinstance(value, edq.util.time.Timestamp)):
225            return value.pretty(short = True)
226
227        return str(value)
228
229    @classmethod
230    def from_json_dict(cls: typing.Type[T],
231            data: typing.Dict[str, typing.Any],
232            **kwargs: typing.Any) -> T:
233        """
234        Create an object from a dict that can be used for JSON.
235        This is the inverse of as_json_dict().
236        """
237
238        return typing.cast(T, cls.from_dict(data))
239
240def base_list_to_output_format(values: typing.Sequence[BaseType], output_format: str,
241        sort: bool = True,
242        skip_headers: bool = False,
243        pretty_headers: bool = False,
244        include_extra_fields: bool = False,
245        **kwargs: typing.Any) -> str:
246    """
247    Convert a list of base types to a string representation.
248    The returned string will not include a trailing newline.
249
250    The given list may be modified by this call.
251    """
252
253    values = list(values)
254
255    if (sort):
256        values.sort()
257
258    output = ''
259
260    if (output_format == lms.model.constants.OUTPUT_FORMAT_JSON):
261        output = base_list_to_json(values,
262                include_extra_fields = include_extra_fields,
263                **kwargs)
264    elif (output_format == lms.model.constants.OUTPUT_FORMAT_TABLE):
265        output = base_list_to_table(values,
266                skip_headers = skip_headers, pretty_headers = pretty_headers,
267                include_extra_fields = include_extra_fields,
268                **kwargs)
269    elif (output_format == lms.model.constants.OUTPUT_FORMAT_TEXT):
270        output = base_list_to_text(values,
271                skip_headers = skip_headers, pretty_headers = pretty_headers,
272                include_extra_fields = include_extra_fields,
273                **kwargs)
274    else:
275        raise ValueError(f"Unknown output format: '{output_format}'.")
276
277    return output
278
279def base_list_to_json(values: typing.Sequence[BaseType],
280        indent: int = 4,
281        extract_single_list: bool = False,
282        **kwargs: typing.Any) -> str:
283    """ Convert a list of base types to a JSON string representation. """
284
285    output_values = [value.as_json_dict(**kwargs) for value in values]
286    if (extract_single_list and (len(output_values) == 1)):
287        output_values = output_values[0]  # type: ignore[assignment]
288
289    return str(edq.util.json.dumps(output_values, indent = indent, sort_keys = False))
290
291def base_list_to_table(values: typing.Sequence[BaseType],
292        skip_headers: bool = False,
293        delim: str = "\t",
294        **kwargs: typing.Any) -> str:
295    """ Convert a list of base types to a table string representation. """
296
297    rows = []
298
299    if ((len(values) > 0) and (not skip_headers)):
300        rows.append(values[0].get_headers(**kwargs))
301
302    for value in values:
303        rows += value.as_table_rows(**kwargs)
304
305    return "\n".join([delim.join(row) for row in rows])
306
307def base_list_to_text(values: typing.Sequence[BaseType],
308        **kwargs: typing.Any) -> str:
309    """ Convert a list of base types to a text string representation. """
310
311    output = []
312
313    for value in values:
314        rows = value.as_text_rows(**kwargs)
315        output.append("\n".join(rows))
316
317    return "\n\n".join(output)
TEXT_SEPARATOR: str = ': '
TEXT_EMPTY_VALUE: str = ''
class BaseType(edq.util.json.DictConverter):
 15class BaseType(edq.util.json.DictConverter):
 16    """
 17    The base class for all core LMS types.
 18    This class ensures that all children have the core functionality necessary for this package.
 19
 20    The typical structure of types in this package is that types in the model package extend this class.
 21    Then, backends may declare their own types that extend the other classes from the model package.
 22    For example: lms.model.base.BaseType -> lms.model.assignments.Assignment -> lms.backend.canvas.model.assignments.Assignment
 23
 24    General (but less efficient) implementations of core functions will be provided.
 25    """
 26
 27    CORE_FIELDS: typing.List[str] = []
 28    """
 29    The common fields shared across backends for this type that are used for comparison and other operations.
 30    Child classes should set this to define how comparisons are made.
 31    """
 32
 33    INT_COMPARISON_FIELDS: typing.Set[str] = {'id'}
 34    """
 35    Fields that should be compared like ints (even if they are strings).
 36    By default, this set will include 'id'.
 37    """
 38
 39    def __init__(self,
 40            **kwargs: typing.Any) -> None:
 41        self.extra_fields: typing.Dict[str, typing.Any] = kwargs.copy()
 42        """ Additional fields not common to all backends or explicitly used by the creating child backend. """
 43
 44    def __eq__(self, other: object) -> bool:
 45        if (not isinstance(other, BaseType)):
 46            return False
 47
 48        # Check the specified fields only.
 49        for field_name in self.CORE_FIELDS:
 50            if (not hasattr(other, field_name)):
 51                return False
 52
 53            value_self = getattr(self, field_name)
 54            value_other = getattr(other, field_name)
 55
 56            if (field_name in self.INT_COMPARISON_FIELDS):
 57                comparison = lms.util.string.compare_maybe_ints(value_self, value_other)
 58                if (comparison != 0):
 59                    return False
 60            elif (value_self != value_other):
 61                return False
 62
 63        return True
 64
 65    def __hash__(self) -> int:
 66        values = tuple(getattr(self, field_name) for field_name in self.CORE_FIELDS)
 67        return hash(values)
 68
 69    def __lt__(self, other: 'BaseType') -> bool:  # type: ignore[override]
 70        if (not isinstance(other, BaseType)):
 71            return False
 72
 73        # Check the specified fields only.
 74        for field_name in self.CORE_FIELDS:
 75            if (not hasattr(other, field_name)):
 76                return False
 77
 78            value_self = getattr(self, field_name)
 79            value_other = getattr(other, field_name)
 80
 81            if (field_name in self.INT_COMPARISON_FIELDS):
 82                comparison = lms.util.string.compare_maybe_ints(value_self, value_other)
 83                if (comparison == 0):
 84                    continue
 85
 86                return (comparison < 0)
 87
 88            if (value_self == value_other):
 89                continue
 90
 91            return bool(value_self < value_other)
 92
 93        return False
 94
 95    def as_text_rows(self,
 96            skip_headers: bool = False,
 97            pretty_headers: bool = False,
 98            **kwargs: typing.Any) -> typing.List[str]:
 99        """
100        Create a representation of this object in the "text" style of this project meant for display.
101        A list of rows will be returned.
102        """
103
104        rows = []
105
106        kwargs['pretty_timestamps'] = True
107
108        for (field_name, row) in self._get_fields(**kwargs).items():
109            if (not skip_headers):
110                header = field_name
111                if (pretty_headers):
112                    header = header.replace('_', ' ').title()
113
114                row = f"{header}{TEXT_SEPARATOR}{row}"
115
116            rows.append(row)
117
118        return rows
119
120    def get_headers(self,
121            pretty_headers: bool = False,
122            **kwargs: typing.Any) -> typing.List[str]:
123        """
124        Get a list of headers to label the values represented by this object meant for display.
125        This method is a companion to as_table_rows(),
126        given the same options these two methods will produce rows with the same length and ordering.
127        """
128
129        headers = []
130
131        for field_name in self._get_fields(**kwargs):
132            header = field_name
133            if (pretty_headers):
134                header = header.replace('_', ' ').title()
135
136            headers.append(header)
137
138        return headers
139
140    def as_table_rows(self,
141            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
142        """
143        Get a list of the values by this object meant for display.
144        This method is a companion to get_headers(),
145        given the same options these two methods will produce rows with the same length and ordering.
146
147        Note that the default implementation for this method always return a single row,
148        but children may override and return multiple rows per object.
149        """
150
151        return [list(self._get_fields(**kwargs).values())]
152
153    def as_json_dict(self,
154            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
155        """
156        Get a dict representation of this object meant for display as JSON.
157        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
158        Calling this method differs from passing this object to json.dumps() (or any sibling),
159        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
160        """
161
162        return {field_name: self._get_field_value(field_name) for field_name in self._get_fields(**kwargs)}
163
164    def _get_fields(self,
165            include_extra_fields: bool = False,
166            **kwargs: typing.Any) -> typing.Dict[str, str]:
167        """
168        Get a dictionary representing the "target" fields of this object meant for display.
169        Keys (field names) will not be modified, but values will be sent to self._value_to_text().
170        Keys are placed in the dictionary in a consistent ordering.
171        """
172
173        field_names = self.CORE_FIELDS.copy()
174
175        # Append any extra fields after the core fields.
176        if (include_extra_fields):
177            # First, include any fields that are not in self.extra_fields.
178            for extra_name in (list(vars(self).keys()) + list(self.extra_fields.keys())):
179                if (extra_name == 'extra_fields'):
180                    continue
181
182                if (extra_name not in field_names):
183                    field_names.append(extra_name)
184
185        fields = {}
186        for field_name in field_names:
187            fields[field_name] = self._value_to_text(self._get_field_value(field_name), **kwargs)
188
189        return fields
190
191    def _get_field_value(self, name: str, default: typing.Any = None) -> typing.Any:
192        """
193        Get the value for a field.
194        This is similar to `getattr(self, name, default)`,
195        but this will also check `extra_fields` if the field is not found at the top level.
196        """
197
198        if (hasattr(self, name)):
199            return getattr(self, name)
200
201        if (name in self.extra_fields):
202            return self.extra_fields[name]
203
204        return default
205
206    def _value_to_text(self,
207            value: typing.Any,
208            indent: typing.Union[int, None] = None,
209            pretty_timestamps: bool = False,
210            **kwargs: typing.Any) -> str:
211        """
212        Convert some arbitrary value (usually found within a BaseType) to a string.
213        None values will be returned as `TEXT_EMPTY_VALUE`.
214        """
215
216        if (value is None):
217            return TEXT_EMPTY_VALUE
218
219        if (hasattr(value, '_to_text')):
220            return str(value._to_text())
221
222        if (isinstance(value, (edq.util.json.DictConverter, dict, list, tuple))):
223            return str(edq.util.json.dumps(value, indent = indent))
224
225        if (pretty_timestamps and isinstance(value, edq.util.time.Timestamp)):
226            return value.pretty(short = True)
227
228        return str(value)
229
230    @classmethod
231    def from_json_dict(cls: typing.Type[T],
232            data: typing.Dict[str, typing.Any],
233            **kwargs: typing.Any) -> T:
234        """
235        Create an object from a dict that can be used for JSON.
236        This is the inverse of as_json_dict().
237        """
238
239        return typing.cast(T, cls.from_dict(data))

The base class for all core LMS types. This class ensures that all children have the core functionality necessary for this package.

The typical structure of types in this package is that types in the model package extend this class. Then, backends may declare their own types that extend the other classes from the model package. For example: lms.model.base.BaseType -> lms.model.assignments.Assignment -> lms.backend.canvas.model.assignments.Assignment

General (but less efficient) implementations of core functions will be provided.

BaseType(**kwargs: Any)
39    def __init__(self,
40            **kwargs: typing.Any) -> None:
41        self.extra_fields: typing.Dict[str, typing.Any] = kwargs.copy()
42        """ Additional fields not common to all backends or explicitly used by the creating child backend. """
CORE_FIELDS: List[str] = []

The common fields shared across backends for this type that are used for comparison and other operations. Child classes should set this to define how comparisons are made.

INT_COMPARISON_FIELDS: Set[str] = {'id'}

Fields that should be compared like ints (even if they are strings). By default, this set will include 'id'.

extra_fields: Dict[str, Any]

Additional fields not common to all backends or explicitly used by the creating child backend.

def as_text_rows( self, skip_headers: bool = False, pretty_headers: bool = False, **kwargs: Any) -> List[str]:
 95    def as_text_rows(self,
 96            skip_headers: bool = False,
 97            pretty_headers: bool = False,
 98            **kwargs: typing.Any) -> typing.List[str]:
 99        """
100        Create a representation of this object in the "text" style of this project meant for display.
101        A list of rows will be returned.
102        """
103
104        rows = []
105
106        kwargs['pretty_timestamps'] = True
107
108        for (field_name, row) in self._get_fields(**kwargs).items():
109            if (not skip_headers):
110                header = field_name
111                if (pretty_headers):
112                    header = header.replace('_', ' ').title()
113
114                row = f"{header}{TEXT_SEPARATOR}{row}"
115
116            rows.append(row)
117
118        return rows

Create a representation of this object in the "text" style of this project meant for display. A list of rows will be returned.

def get_headers(self, pretty_headers: bool = False, **kwargs: Any) -> List[str]:
120    def get_headers(self,
121            pretty_headers: bool = False,
122            **kwargs: typing.Any) -> typing.List[str]:
123        """
124        Get a list of headers to label the values represented by this object meant for display.
125        This method is a companion to as_table_rows(),
126        given the same options these two methods will produce rows with the same length and ordering.
127        """
128
129        headers = []
130
131        for field_name in self._get_fields(**kwargs):
132            header = field_name
133            if (pretty_headers):
134                header = header.replace('_', ' ').title()
135
136            headers.append(header)
137
138        return headers

Get a list of headers to label the values represented by this object meant for display. This method is a companion to as_table_rows(), given the same options these two methods will produce rows with the same length and ordering.

def as_table_rows(self, **kwargs: Any) -> List[List[str]]:
140    def as_table_rows(self,
141            **kwargs: typing.Any) -> typing.List[typing.List[str]]:
142        """
143        Get a list of the values by this object meant for display.
144        This method is a companion to get_headers(),
145        given the same options these two methods will produce rows with the same length and ordering.
146
147        Note that the default implementation for this method always return a single row,
148        but children may override and return multiple rows per object.
149        """
150
151        return [list(self._get_fields(**kwargs).values())]

Get a list of the values by this object meant for display. This method is a companion to get_headers(), given the same options these two methods will produce rows with the same length and ordering.

Note that the default implementation for this method always return a single row, but children may override and return multiple rows per object.

def as_json_dict(self, **kwargs: Any) -> Dict[str, Any]:
153    def as_json_dict(self,
154            **kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
155        """
156        Get a dict representation of this object meant for display as JSON.
157        (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.)
158        Calling this method differs from passing this object to json.dumps() (or any sibling),
159        because this method may not include all fields, may flatten or alter fields, and will order fields differently.
160        """
161
162        return {field_name: self._get_field_value(field_name) for field_name in self._get_fields(**kwargs)}

Get a dict representation of this object meant for display as JSON. (Note that we are not returning JSON, just a dict that is ready to be converted to JSON.) Calling this method differs from passing this object to json.dumps() (or any sibling), because this method may not include all fields, may flatten or alter fields, and will order fields differently.

@classmethod
def from_json_dict(cls: Type[~T], data: Dict[str, Any], **kwargs: Any) -> ~T:
230    @classmethod
231    def from_json_dict(cls: typing.Type[T],
232            data: typing.Dict[str, typing.Any],
233            **kwargs: typing.Any) -> T:
234        """
235        Create an object from a dict that can be used for JSON.
236        This is the inverse of as_json_dict().
237        """
238
239        return typing.cast(T, cls.from_dict(data))

Create an object from a dict that can be used for JSON. This is the inverse of as_json_dict().

def base_list_to_output_format( values: Sequence[BaseType], output_format: str, sort: bool = True, skip_headers: bool = False, pretty_headers: bool = False, include_extra_fields: bool = False, **kwargs: Any) -> str:
241def base_list_to_output_format(values: typing.Sequence[BaseType], output_format: str,
242        sort: bool = True,
243        skip_headers: bool = False,
244        pretty_headers: bool = False,
245        include_extra_fields: bool = False,
246        **kwargs: typing.Any) -> str:
247    """
248    Convert a list of base types to a string representation.
249    The returned string will not include a trailing newline.
250
251    The given list may be modified by this call.
252    """
253
254    values = list(values)
255
256    if (sort):
257        values.sort()
258
259    output = ''
260
261    if (output_format == lms.model.constants.OUTPUT_FORMAT_JSON):
262        output = base_list_to_json(values,
263                include_extra_fields = include_extra_fields,
264                **kwargs)
265    elif (output_format == lms.model.constants.OUTPUT_FORMAT_TABLE):
266        output = base_list_to_table(values,
267                skip_headers = skip_headers, pretty_headers = pretty_headers,
268                include_extra_fields = include_extra_fields,
269                **kwargs)
270    elif (output_format == lms.model.constants.OUTPUT_FORMAT_TEXT):
271        output = base_list_to_text(values,
272                skip_headers = skip_headers, pretty_headers = pretty_headers,
273                include_extra_fields = include_extra_fields,
274                **kwargs)
275    else:
276        raise ValueError(f"Unknown output format: '{output_format}'.")
277
278    return output

Convert a list of base types to a string representation. The returned string will not include a trailing newline.

The given list may be modified by this call.

def base_list_to_json( values: Sequence[BaseType], indent: int = 4, extract_single_list: bool = False, **kwargs: Any) -> str:
280def base_list_to_json(values: typing.Sequence[BaseType],
281        indent: int = 4,
282        extract_single_list: bool = False,
283        **kwargs: typing.Any) -> str:
284    """ Convert a list of base types to a JSON string representation. """
285
286    output_values = [value.as_json_dict(**kwargs) for value in values]
287    if (extract_single_list and (len(output_values) == 1)):
288        output_values = output_values[0]  # type: ignore[assignment]
289
290    return str(edq.util.json.dumps(output_values, indent = indent, sort_keys = False))

Convert a list of base types to a JSON string representation.

def base_list_to_table( values: Sequence[BaseType], skip_headers: bool = False, delim: str = '\t', **kwargs: Any) -> str:
292def base_list_to_table(values: typing.Sequence[BaseType],
293        skip_headers: bool = False,
294        delim: str = "\t",
295        **kwargs: typing.Any) -> str:
296    """ Convert a list of base types to a table string representation. """
297
298    rows = []
299
300    if ((len(values) > 0) and (not skip_headers)):
301        rows.append(values[0].get_headers(**kwargs))
302
303    for value in values:
304        rows += value.as_table_rows(**kwargs)
305
306    return "\n".join([delim.join(row) for row in rows])

Convert a list of base types to a table string representation.

def base_list_to_text(values: Sequence[BaseType], **kwargs: Any) -> str:
308def base_list_to_text(values: typing.Sequence[BaseType],
309        **kwargs: typing.Any) -> str:
310    """ Convert a list of base types to a text string representation. """
311
312    output = []
313
314    for value in values:
315        rows = value.as_text_rows(**kwargs)
316        output.append("\n".join(rows))
317
318    return "\n\n".join(output)

Convert a list of base types to a text string representation.