diff options
| author | robot-piglet <[email protected]> | 2026-05-30 22:59:32 +0300 |
|---|---|---|
| committer | robot-piglet <[email protected]> | 2026-05-30 23:21:30 +0300 |
| commit | 7b185ac5cce1be8572664dafa59dc340a9b47187 (patch) | |
| tree | 9651cbc8e56226df41ab6fccc936b5b5f84a58d1 /contrib/python/prettytable | |
| parent | f325ea8b5f12fce1cc7b7d19286875163956a945 (diff) | |
Intermediate changes
commit_hash:92c3a2039e8a959ce895f918b5bf3ec01a415885
Diffstat (limited to 'contrib/python/prettytable')
16 files changed, 3042 insertions, 2120 deletions
diff --git a/contrib/python/prettytable/py3/.dist-info/METADATA b/contrib/python/prettytable/py3/.dist-info/METADATA index 16e1235b677..6c57a35f2bc 100644 --- a/contrib/python/prettytable/py3/.dist-info/METADATA +++ b/contrib/python/prettytable/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: prettytable -Version: 3.14.0 +Version: 3.17.0 Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format Project-URL: Changelog, https://github.com/prettytable/prettytable/releases Project-URL: Funding, https://tidelift.com/subscription/pkg/pypi-prettytable?utm_source=pypi-prettytable&utm_medium=pypi @@ -12,17 +12,17 @@ License-Expression: BSD-3-Clause License-File: LICENSE Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3.15 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Text Processing Classifier: Typing :: Typed -Requires-Python: >=3.9 +Requires-Python: >=3.10 Requires-Dist: wcwidth Provides-Extra: tests Requires-Dist: pytest; extra == 'tests' @@ -36,7 +36,8 @@ Description-Content-Type: text/markdown [](https://pypi.org/project/prettytable/) [](https://pypistats.org/packages/prettytable) [](https://github.com/prettytable/prettytable/actions) -[](https://codecov.io/gh/prettytable/prettytable) +[](https://codecov.io/gh/prettytable/prettytable) +[](LICENSE) [](https://github.com/psf/black) [](https://tidelift.com/subscription/pkg/pypi-prettytable?utm_source=pypi-prettytable&utm_medium=badge) @@ -244,9 +245,9 @@ This string is guaranteed to look exactly the same as what would be printed by d your table to a file or insert it into a GUI. The table can be displayed in several different formats using `get_formatted_string` by -changing the `out_format=<text|html|json|csv|latex>`. This function passes through -arguments to the functions that render the table, so additional arguments can be given. -This provides a way to let a user choose the output formatting. +changing the `out_format=<text|html|json|csv|latex|mediawiki>`. This function passes +through arguments to the functions that render the table, so additional arguments can be +given. This provides a way to let a user choose the output formatting. ```python def my_cli_function(table_format: str = 'text'): @@ -302,6 +303,36 @@ prints: +-----------+------+------------+-----------------+ ``` +#### Filtering your table + +You can make sure that your tables are filtered by giving `get_string` a `row_filter` +keyword argument, which must be a function with one argument `row` returning a Boolean +value. The `row` is the list of fields in a row. + +For example, to print the example table we built earlier of Australian capital city +data, so that cities with a population of at least 1,000,000, we can do this: + +```python +def filter_function(row: list[str]) -> bool: + return row[2] > 999999 + +print(table.get_string(row_filter=filter_function)) +``` + +to get: + +``` ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+------------+-----------------+ +``` + #### Changing the alignment of columns By default, all columns in a table are centre aligned. @@ -413,28 +444,33 @@ table.sortby = None ``` If you want to specify a custom sorting function, you can use the `sort_key` keyword -argument. Pass this a function which accepts two lists of values and returns a negative -or positive value depending on whether the first list should appear before or after the -second one. If your table has n columns, each list will have n+1 elements. Each list -corresponds to one row of the table. The first element will be whatever data is in the -relevant row, in the column specified by the `sort_by` argument. The remaining n -elements are the data in each of the table's columns, in order, including a repeated -instance of the data in the `sort_by` column. +argument as Pythons `sorted()` `key` parameter. The value of the `sort_key` parameter +should be a function (or other callable) that takes a single argument and returns a key +to use for sorting purposes. + +If your table has n columns, each list will have n+1 elements. Each list corresponds to +one row of the table. The first element will be whatever data is in the relevant row, in +the column specified by the `sort_by` argument. The remaining n elements are the data in +each of the table's columns, in order, including a repeated instance of the data in the +`sort_by` column. #### Adding sections to a table You can divide your table into different sections using the `add_divider` method or -`divider` argument . This will add a dividing line into the table under the row who has -this field set. So we can set up a table like this: +`divider` argument to `add_row()` or even to `add_rows()`. This will add a dividing line +into the table under the row who has this field set. So we can set up a table like this: ```python table = PrettyTable() table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] table.add_row(["Adelaide", 1295, 1158259, 600.5]) table.add_divider() -table.add_row(["Brisbane", 5905, 1857594, 1146.4]) -table.add_row(["Darwin", 112, 120900, 1714.7]) -table.add_row(["Hobart", 1357, 205556, 619.5], divider=True) +table.add_row(["Brisbane", 5905, 1857594, 1146.4], divider=True) +table.add_rows( + [["Darwin", 112, 120900, 1714.7], + ["Hobart", 1357, 205556, 619.5]], + divider=True +) table.add_row(["Melbourne", 1566, 3806092, 646.9]) table.add_row(["Perth", 5386, 1554769, 869.4]) table.add_row(["Sydney", 2058, 4336374, 1214.8]) @@ -449,6 +485,7 @@ to get a table like this: | Adelaide | 1295 | 1158259 | 600.5 | +-----------+------+------------+-----------------+ | Brisbane | 5905 | 1857594 | 1146.4 | ++-----------+------+------------+-----------------+ | Darwin | 112 | 120900 | 1714.7 | | Hobart | 1357 | 205556 | 619.5 | +-----------+------+------------+-----------------+ @@ -506,34 +543,53 @@ PrettyTable has a number of style options which control various aspects of how t are displayed. You have the freedom to set each of these options individually to whatever you prefer. The `set_style` method just does this automatically for you. -The options are: +Table-specific options are: + +| Option | Details | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `HEADER`, `ALL`, `NONE` | These are variables defined inside the `prettytable` module so make sure you import them or use `prettytable.FRAME` etc. | +| `_horizontal_align_char` | Single character string used to indicate column alignment in horizontal lines. Default: `:` for Markdown, otherwise `None`. | +| `border` | A Boolean option (must be `True` or `False`). Controls whether a border is drawn inside and around the table. | +| `bottom_junction_char` | single character string used to draw bottom line junctions. Default: `junction_char`. | +| `bottom_left_junction_char` | Single character string used to draw bottom-left line junctions. Default: `junction_char`. | +| `bottom_right_junction_char` | Single character string used to draw bottom-right line junctions. Default: `junction_char`. | +| `break_on_hyphens` | Whether long lines are wrapped on hyphens. Default: `True`. | +| `header` | A Boolean option (must be `True` or `False`). Controls whether the first row of the table is a header showing the names of all the fields. | +| `horizontal_char` | Single character string used to draw horizontal lines. Default: `-`. | +| `hrules` | Controls printing of horizontal rules after rows. Allowed values: `FRAME`, `HEADER`, `ALL`, `NONE`. | +| `junction_char` | Single character string used to draw line junctions. Default: `+`. | +| `left_junction_char` | Single character string used to draw left line junctions. Default: `junction_char`. | +| `left_padding_width` | Number of spaces on left-hand side of column data. | +| `max_table_width` | Number of characters used for the maximum total table width. | +| `min_table_width` | Number of characters used for the minimum total table width. | +| `padding_width` | Number of spaces on either side of column data (only used if left and right paddings are `None`). | +| `preserve_internal_border` | A Boolean option (must be `True` or `False`). Controls whether borders are still drawn within the table even when `border=False`. | +| `right_junction_char` | Single character string used to draw right line junctions. Default: `junction_char`. | +| `right_padding_width` | Number of spaces on right-hand side of column data. | +| `top_junction_char` | Single character string used to draw top line junctions. Default: `junction_char`. | +| `top_left_junction_char` | Single character string used to draw top-left line junctions. Default: `junction_char`. | +| `top_right_junction_char` | Single character string used to draw top-right line junctions. Default: `junction_char`. | +| `use_header_width` | A Boolean option (must be `True` or `False`). Controls whether the width of the header is used for computing column width. Default: `True`. | +| `vertical_char` | Single character string used to draw vertical lines. Default: `\|`. | +| `vrules` | Controls printing of vertical rules between columns. Allowed values: `FRAME`, `ALL`, `NONE`. | -| Option | Details | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `border` | A Boolean option (must be `True` or `False`). Controls whether a border is drawn inside and around the table. | -| `preserve_internal_border` | A Boolean option (must be `True` or `False`). Controls whether borders are still drawn within the table even when `border=False`. | -| `header` | A Boolean option (must be `True` or `False`). Controls whether the first row of the table is a header showing the names of all the fields. | -| `hrules` | Controls printing of horizontal rules after rows. Allowed values: `FRAME`, `HEADER`, `ALL`, `NONE`. | -| `HEADER`, `ALL`, `NONE` | These are variables defined inside the `prettytable` module so make sure you import them or use `prettytable.FRAME` etc. | -| `vrules` | Controls printing of vertical rules between columns. Allowed values: `FRAME`, `ALL`, `NONE`. | -| `int_format` | A string which controls the way integer data is printed. This works like: `print("%<int_format>d" % data)`. | -| `float_format` | A string which controls the way floating point data is printed. This works like: `print("%<float_format>f" % data)`. | -| `custom_format` | A dictionary of field and callable. This allows you to set any format you want `pf.custom_format["my_col_int"] = lambda f, v: f"{v:,}"`. The type of the callable is `Callable[[str, Any], str]` | -| `padding_width` | Number of spaces on either side of column data (only used if left and right paddings are `None`). | -| `left_padding_width` | Number of spaces on left-hand side of column data. | -| `right_padding_width` | Number of spaces on right-hand side of column data. | -| `vertical_char` | Single character string used to draw vertical lines. Default: `\|`. | -| `horizontal_char` | Single character string used to draw horizontal lines. Default: `-`. | -| `_horizontal_align_char` | Single character string used to indicate column alignment in horizontal lines. Default: `:` for Markdown, otherwise `None`. | -| `junction_char` | Single character string used to draw line junctions. Default: `+`. | -| `top_junction_char` | Single character string used to draw top line junctions. Default: `junction_char`. | -| `bottom_junction_char` | single character string used to draw bottom line junctions. Default: `junction_char`. | -| `right_junction_char` | Single character string used to draw right line junctions. Default: `junction_char`. | -| `left_junction_char` | Single character string used to draw left line junctions. Default: `junction_char`. | -| `top_right_junction_char` | Single character string used to draw top-right line junctions. Default: `junction_char`. | -| `top_left_junction_char` | Single character string used to draw top-left line junctions. Default: `junction_char`. | -| `bottom_right_junction_char` | Single character string used to draw bottom-right line junctions. Default: `junction_char`. | -| `bottom_left_junction_char` | Single character string used to draw bottom-left line junctions. Default: `junction_char`. | +For options that can be set individually for each column (`align`, `valign`, +`custom_format`, `max_width`, `min_width`, `int_format`, `float_format`, `none_format`) +you can either set a value, that applies to all columns or set a dict with column names +and individual values. + +Column-specific options are: + +| Option | Details | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `align` | Controls alignment of fields, one of "l", "c", or "r" or a dictionary with column and value. | +| `custom_format` | Set any format, by setting a function that gets the original value,the formatted representation and returns the new string. E.g. `pf.custom_format["my_col_int"] = lambda f, v: f"{v:,}"`. The type of the callable is `Callable[[str, Any], str]`. This also takes a dictionary with column and function. | +| `float_format` | A string which controls the way floating point data is printed or a dictionary with column and value. This works like: `print("%<float_format>f" % data)`. | +| `int_format` | A string which controls the way integer data is printed or a dictionary with column and value. This works like: `print("%<int_format>d" % data)`. | +| `max_width` | Number of characters used for maximum width of a column or a dictionary with column and value. | +| `min_width` | Number of characters used for minimum width of a column or a dictionary with column and value. | +| `none_format` | Representation of None values or a dictionary with column and value. | +| `valign` | Controls vertical alignment of fields, one of "t", "m", or "b" or a dictionary with column and value. | You can set the style options to your own settings in two ways: @@ -632,6 +688,12 @@ PrettyTable will also print your tables in JSON, as a list of fields and an arra rows. Just like in ASCII form, you can actually get a string representation - just use `get_json_string()`. +### Displaying your table in MediaWiki markup + +PrettyTable can also print your tables in MediaWiki table markup, making it easy to +format tables for wikis. Similar to the ASCII format, you can get a string +representation using `get_mediawiki_string()`. + ### Displaying your table in HTML form PrettyTable will also print your tables in HTML form, as `<table>`s. Just like in ASCII diff --git a/contrib/python/prettytable/py3/README.md b/contrib/python/prettytable/py3/README.md index fb67d377942..a06e6c35cf6 100644 --- a/contrib/python/prettytable/py3/README.md +++ b/contrib/python/prettytable/py3/README.md @@ -4,7 +4,8 @@ [](https://pypi.org/project/prettytable/) [](https://pypistats.org/packages/prettytable) [](https://github.com/prettytable/prettytable/actions) -[](https://codecov.io/gh/prettytable/prettytable) +[](https://codecov.io/gh/prettytable/prettytable) +[](LICENSE) [](https://github.com/psf/black) [](https://tidelift.com/subscription/pkg/pypi-prettytable?utm_source=pypi-prettytable&utm_medium=badge) @@ -212,9 +213,9 @@ This string is guaranteed to look exactly the same as what would be printed by d your table to a file or insert it into a GUI. The table can be displayed in several different formats using `get_formatted_string` by -changing the `out_format=<text|html|json|csv|latex>`. This function passes through -arguments to the functions that render the table, so additional arguments can be given. -This provides a way to let a user choose the output formatting. +changing the `out_format=<text|html|json|csv|latex|mediawiki>`. This function passes +through arguments to the functions that render the table, so additional arguments can be +given. This provides a way to let a user choose the output formatting. ```python def my_cli_function(table_format: str = 'text'): @@ -270,6 +271,36 @@ prints: +-----------+------+------------+-----------------+ ``` +#### Filtering your table + +You can make sure that your tables are filtered by giving `get_string` a `row_filter` +keyword argument, which must be a function with one argument `row` returning a Boolean +value. The `row` is the list of fields in a row. + +For example, to print the example table we built earlier of Australian capital city +data, so that cities with a population of at least 1,000,000, we can do this: + +```python +def filter_function(row: list[str]) -> bool: + return row[2] > 999999 + +print(table.get_string(row_filter=filter_function)) +``` + +to get: + +``` ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+------------+-----------------+ +``` + #### Changing the alignment of columns By default, all columns in a table are centre aligned. @@ -381,28 +412,33 @@ table.sortby = None ``` If you want to specify a custom sorting function, you can use the `sort_key` keyword -argument. Pass this a function which accepts two lists of values and returns a negative -or positive value depending on whether the first list should appear before or after the -second one. If your table has n columns, each list will have n+1 elements. Each list -corresponds to one row of the table. The first element will be whatever data is in the -relevant row, in the column specified by the `sort_by` argument. The remaining n -elements are the data in each of the table's columns, in order, including a repeated -instance of the data in the `sort_by` column. +argument as Pythons `sorted()` `key` parameter. The value of the `sort_key` parameter +should be a function (or other callable) that takes a single argument and returns a key +to use for sorting purposes. + +If your table has n columns, each list will have n+1 elements. Each list corresponds to +one row of the table. The first element will be whatever data is in the relevant row, in +the column specified by the `sort_by` argument. The remaining n elements are the data in +each of the table's columns, in order, including a repeated instance of the data in the +`sort_by` column. #### Adding sections to a table You can divide your table into different sections using the `add_divider` method or -`divider` argument . This will add a dividing line into the table under the row who has -this field set. So we can set up a table like this: +`divider` argument to `add_row()` or even to `add_rows()`. This will add a dividing line +into the table under the row who has this field set. So we can set up a table like this: ```python table = PrettyTable() table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] table.add_row(["Adelaide", 1295, 1158259, 600.5]) table.add_divider() -table.add_row(["Brisbane", 5905, 1857594, 1146.4]) -table.add_row(["Darwin", 112, 120900, 1714.7]) -table.add_row(["Hobart", 1357, 205556, 619.5], divider=True) +table.add_row(["Brisbane", 5905, 1857594, 1146.4], divider=True) +table.add_rows( + [["Darwin", 112, 120900, 1714.7], + ["Hobart", 1357, 205556, 619.5]], + divider=True +) table.add_row(["Melbourne", 1566, 3806092, 646.9]) table.add_row(["Perth", 5386, 1554769, 869.4]) table.add_row(["Sydney", 2058, 4336374, 1214.8]) @@ -417,6 +453,7 @@ to get a table like this: | Adelaide | 1295 | 1158259 | 600.5 | +-----------+------+------------+-----------------+ | Brisbane | 5905 | 1857594 | 1146.4 | ++-----------+------+------------+-----------------+ | Darwin | 112 | 120900 | 1714.7 | | Hobart | 1357 | 205556 | 619.5 | +-----------+------+------------+-----------------+ @@ -474,34 +511,53 @@ PrettyTable has a number of style options which control various aspects of how t are displayed. You have the freedom to set each of these options individually to whatever you prefer. The `set_style` method just does this automatically for you. -The options are: +Table-specific options are: + +| Option | Details | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `HEADER`, `ALL`, `NONE` | These are variables defined inside the `prettytable` module so make sure you import them or use `prettytable.FRAME` etc. | +| `_horizontal_align_char` | Single character string used to indicate column alignment in horizontal lines. Default: `:` for Markdown, otherwise `None`. | +| `border` | A Boolean option (must be `True` or `False`). Controls whether a border is drawn inside and around the table. | +| `bottom_junction_char` | single character string used to draw bottom line junctions. Default: `junction_char`. | +| `bottom_left_junction_char` | Single character string used to draw bottom-left line junctions. Default: `junction_char`. | +| `bottom_right_junction_char` | Single character string used to draw bottom-right line junctions. Default: `junction_char`. | +| `break_on_hyphens` | Whether long lines are wrapped on hyphens. Default: `True`. | +| `header` | A Boolean option (must be `True` or `False`). Controls whether the first row of the table is a header showing the names of all the fields. | +| `horizontal_char` | Single character string used to draw horizontal lines. Default: `-`. | +| `hrules` | Controls printing of horizontal rules after rows. Allowed values: `FRAME`, `HEADER`, `ALL`, `NONE`. | +| `junction_char` | Single character string used to draw line junctions. Default: `+`. | +| `left_junction_char` | Single character string used to draw left line junctions. Default: `junction_char`. | +| `left_padding_width` | Number of spaces on left-hand side of column data. | +| `max_table_width` | Number of characters used for the maximum total table width. | +| `min_table_width` | Number of characters used for the minimum total table width. | +| `padding_width` | Number of spaces on either side of column data (only used if left and right paddings are `None`). | +| `preserve_internal_border` | A Boolean option (must be `True` or `False`). Controls whether borders are still drawn within the table even when `border=False`. | +| `right_junction_char` | Single character string used to draw right line junctions. Default: `junction_char`. | +| `right_padding_width` | Number of spaces on right-hand side of column data. | +| `top_junction_char` | Single character string used to draw top line junctions. Default: `junction_char`. | +| `top_left_junction_char` | Single character string used to draw top-left line junctions. Default: `junction_char`. | +| `top_right_junction_char` | Single character string used to draw top-right line junctions. Default: `junction_char`. | +| `use_header_width` | A Boolean option (must be `True` or `False`). Controls whether the width of the header is used for computing column width. Default: `True`. | +| `vertical_char` | Single character string used to draw vertical lines. Default: `\|`. | +| `vrules` | Controls printing of vertical rules between columns. Allowed values: `FRAME`, `ALL`, `NONE`. | -| Option | Details | -| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `border` | A Boolean option (must be `True` or `False`). Controls whether a border is drawn inside and around the table. | -| `preserve_internal_border` | A Boolean option (must be `True` or `False`). Controls whether borders are still drawn within the table even when `border=False`. | -| `header` | A Boolean option (must be `True` or `False`). Controls whether the first row of the table is a header showing the names of all the fields. | -| `hrules` | Controls printing of horizontal rules after rows. Allowed values: `FRAME`, `HEADER`, `ALL`, `NONE`. | -| `HEADER`, `ALL`, `NONE` | These are variables defined inside the `prettytable` module so make sure you import them or use `prettytable.FRAME` etc. | -| `vrules` | Controls printing of vertical rules between columns. Allowed values: `FRAME`, `ALL`, `NONE`. | -| `int_format` | A string which controls the way integer data is printed. This works like: `print("%<int_format>d" % data)`. | -| `float_format` | A string which controls the way floating point data is printed. This works like: `print("%<float_format>f" % data)`. | -| `custom_format` | A dictionary of field and callable. This allows you to set any format you want `pf.custom_format["my_col_int"] = lambda f, v: f"{v:,}"`. The type of the callable is `Callable[[str, Any], str]` | -| `padding_width` | Number of spaces on either side of column data (only used if left and right paddings are `None`). | -| `left_padding_width` | Number of spaces on left-hand side of column data. | -| `right_padding_width` | Number of spaces on right-hand side of column data. | -| `vertical_char` | Single character string used to draw vertical lines. Default: `\|`. | -| `horizontal_char` | Single character string used to draw horizontal lines. Default: `-`. | -| `_horizontal_align_char` | Single character string used to indicate column alignment in horizontal lines. Default: `:` for Markdown, otherwise `None`. | -| `junction_char` | Single character string used to draw line junctions. Default: `+`. | -| `top_junction_char` | Single character string used to draw top line junctions. Default: `junction_char`. | -| `bottom_junction_char` | single character string used to draw bottom line junctions. Default: `junction_char`. | -| `right_junction_char` | Single character string used to draw right line junctions. Default: `junction_char`. | -| `left_junction_char` | Single character string used to draw left line junctions. Default: `junction_char`. | -| `top_right_junction_char` | Single character string used to draw top-right line junctions. Default: `junction_char`. | -| `top_left_junction_char` | Single character string used to draw top-left line junctions. Default: `junction_char`. | -| `bottom_right_junction_char` | Single character string used to draw bottom-right line junctions. Default: `junction_char`. | -| `bottom_left_junction_char` | Single character string used to draw bottom-left line junctions. Default: `junction_char`. | +For options that can be set individually for each column (`align`, `valign`, +`custom_format`, `max_width`, `min_width`, `int_format`, `float_format`, `none_format`) +you can either set a value, that applies to all columns or set a dict with column names +and individual values. + +Column-specific options are: + +| Option | Details | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `align` | Controls alignment of fields, one of "l", "c", or "r" or a dictionary with column and value. | +| `custom_format` | Set any format, by setting a function that gets the original value,the formatted representation and returns the new string. E.g. `pf.custom_format["my_col_int"] = lambda f, v: f"{v:,}"`. The type of the callable is `Callable[[str, Any], str]`. This also takes a dictionary with column and function. | +| `float_format` | A string which controls the way floating point data is printed or a dictionary with column and value. This works like: `print("%<float_format>f" % data)`. | +| `int_format` | A string which controls the way integer data is printed or a dictionary with column and value. This works like: `print("%<int_format>d" % data)`. | +| `max_width` | Number of characters used for maximum width of a column or a dictionary with column and value. | +| `min_width` | Number of characters used for minimum width of a column or a dictionary with column and value. | +| `none_format` | Representation of None values or a dictionary with column and value. | +| `valign` | Controls vertical alignment of fields, one of "t", "m", or "b" or a dictionary with column and value. | You can set the style options to your own settings in two ways: @@ -600,6 +656,12 @@ PrettyTable will also print your tables in JSON, as a list of fields and an arra rows. Just like in ASCII form, you can actually get a string representation - just use `get_json_string()`. +### Displaying your table in MediaWiki markup + +PrettyTable can also print your tables in MediaWiki table markup, making it easy to +format tables for wikis. Similar to the ASCII format, you can get a string +representation using `get_mediawiki_string()`. + ### Displaying your table in HTML form PrettyTable will also print your tables in HTML form, as `<table>`s. Just like in ASCII diff --git a/contrib/python/prettytable/py3/prettytable/__init__.py b/contrib/python/prettytable/py3/prettytable/__init__.py index 66be991cb8b..f28c03658ab 100644 --- a/contrib/python/prettytable/py3/prettytable/__init__.py +++ b/contrib/python/prettytable/py3/prettytable/__init__.py @@ -28,6 +28,7 @@ from .prettytable import ( # noqa: F401 from_html, from_html_one, from_json, + from_mediawiki, ) __all__ = [ @@ -55,6 +56,7 @@ __all__ = [ "from_html", "from_html_one", "from_json", + "from_mediawiki", ] diff --git a/contrib/python/prettytable/py3/prettytable/_version.py b/contrib/python/prettytable/py3/prettytable/_version.py index 431bd6d8195..b6f2ce2e738 100644 --- a/contrib/python/prettytable/py3/prettytable/_version.py +++ b/contrib/python/prettytable/py3/prettytable/_version.py @@ -1,16 +1,34 @@ -# file generated by setuptools_scm +# file generated by setuptools-scm # don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + TYPE_CHECKING = False if TYPE_CHECKING: - from typing import Tuple, Union + from typing import Tuple + from typing import Union + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object + COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID + +__version__ = version = '3.17.0' +__version_tuple__ = version_tuple = (3, 17, 0) -__version__ = version = '3.14.0' -__version_tuple__ = version_tuple = (3, 14, 0) +__commit_id__ = commit_id = None diff --git a/contrib/python/prettytable/py3/prettytable/prettytable.py b/contrib/python/prettytable/py3/prettytable/prettytable.py index 5b6c396b0d1..1b6c964652c 100644 --- a/contrib/python/prettytable/py3/prettytable/prettytable.py +++ b/contrib/python/prettytable/py3/prettytable/prettytable.py @@ -35,17 +35,19 @@ from __future__ import annotations import io import re -import warnings -from collections.abc import Callable, Iterable, Mapping, Sequence from enum import IntEnum +from functools import lru_cache from html.parser import HTMLParser -from typing import TYPE_CHECKING, Any, Final, Literal, TypedDict, cast +from typing import Any, Literal, TypedDict, cast +TYPE_CHECKING = False if TYPE_CHECKING: + from collections.abc import Callable, Mapping, Sequence from sqlite3 import Cursor + from typing import Final, TypeAlias from _typeshed import SupportsRichComparison - from typing_extensions import Self, TypeAlias + from typing_extensions import Self class HRuleStyle(IntEnum): @@ -92,7 +94,7 @@ BASE_ALIGN_VALUE: Final = "base_align_value" RowType: TypeAlias = list[Any] AlignType: TypeAlias = Literal["l", "c", "r"] VAlignType: TypeAlias = Literal["t", "m", "b"] -HeaderStyleType: TypeAlias = Literal["cap", "title", "upper", "lower", None] +HeaderStyleType: TypeAlias = Literal["cap", "title", "upper", "lower"] | None class OptionsType(TypedDict): @@ -101,11 +103,13 @@ class OptionsType(TypedDict): end: int | None fields: Sequence[str | None] | None header: bool + use_header_width: bool border: bool preserve_internal_border: bool sortby: str | None reversesort: bool sort_key: Callable[[RowType], SupportsRichComparison] + row_filter: Callable[[RowType], bool] attributes: dict[str, str] format: bool hrules: HRuleStyle @@ -143,11 +147,16 @@ class OptionsType(TypedDict): none_format: str | dict[str, str | None] | None escape_header: bool escape_data: bool + break_on_hyphens: bool +# ANSI colour codes _re = re.compile(r"\033\[[0-9;]*m|\033\(B") +# OSC 8 hyperlinks +_osc8_re = re.compile(r"\033\]8;;.*?\033\\(.*?)\033\]8;;\033\\") +@lru_cache def _get_size(text: str) -> tuple[int, int]: lines = text.split("\n") height = len(lines) @@ -170,7 +179,9 @@ class PrettyTable: _sortby: str | None _reversesort: bool _sort_key: Callable[[RowType], SupportsRichComparison] + _row_filter: Callable[[RowType], bool] _header: bool + _use_header_width: bool _header_style: HeaderStyleType _border: bool _preserve_internal_border: bool @@ -204,6 +215,7 @@ class PrettyTable: orgmode: bool _widths: list[int] _hrule: str + _break_on_hyphens: bool def __init__(self, field_names: Sequence[str] | None = None, **kwargs) -> None: """Return a new PrettyTable instance @@ -217,6 +229,7 @@ class PrettyTable: start - index of first data row to include in output end - index of last data row to include in output PLUS ONE (list slice style) header - print a header showing field names (True or False) + use_header_width - reflect width of header (True or False) header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None) border - print a border around the table (True or False) @@ -256,11 +269,13 @@ class PrettyTable: single character string used to draw bottom-left line junctions sortby - name of field to sort rows by sort_key - sorting key function, applied to data points before sorting + row_filter - filter function applied on rows align - default align for each column (None, "l", "c" or "r") valign - default valign for each row (None, "t", "m" or "b") reversesort - True or False to sort in descending or ascending order - oldsortslice - Slice rows before sorting in the "old style" """ - + oldsortslice - Slice rows before sorting in the "old style" + break_on_hyphens - Whether long lines are broken on hypens or not, default: True + """ self.encoding = kwargs.get("encoding", "UTF-8") # Data @@ -283,11 +298,13 @@ class PrettyTable: "end", "fields", "header", + "use_header_width", "border", "preserve_internal_border", "sortby", "reversesort", "sort_key", + "row_filter", "attributes", "format", "hrules", @@ -323,6 +340,7 @@ class PrettyTable: "none_format", "escape_header", "escape_data", + "break_on_hyphens", ] self._none_format: dict[str, str | None] = {} @@ -349,6 +367,10 @@ class PrettyTable: self._header = kwargs["header"] else: self._header = True + if kwargs["use_header_width"] in (True, False): + self._use_header_width = kwargs["use_header_width"] + else: + self._use_header_width = True self._header_style = kwargs["header_style"] or None if kwargs["border"] in (True, False): self._border = kwargs["border"] @@ -367,6 +389,7 @@ class PrettyTable: else: self._reversesort = False self._sort_key = kwargs["sort_key"] or (lambda x: x) + self._row_filter = kwargs["row_filter"] or (lambda x: True) if kwargs["escape_data"] in (True, False): self._escape_data = kwargs["escape_data"] @@ -412,8 +435,12 @@ class PrettyTable: self._format = kwargs["format"] or False self._xhtml = kwargs["xhtml"] or False self._attributes = kwargs["attributes"] or {} + if kwargs["break_on_hyphens"] in (True, False): + self._break_on_hyphens = kwargs["break_on_hyphens"] + else: + self._break_on_hyphens = True - def _column_specific_args(self): + def _column_specific_args(self) -> None: # Column specific arguments, use property.setters for attr in ( "align", @@ -524,7 +551,7 @@ class PrettyTable: self._validate_nonnegative_int(option, val) elif option == "sortby": self._validate_field_name(option, val) - elif option == "sort_key": + elif option in ("sort_key", "row_filter"): self._validate_function(option, val) elif option == "hrules": self._validate_hrules(option, val) @@ -534,6 +561,7 @@ class PrettyTable: self._validate_all_field_names(option, val) elif option in ( "header", + "use_header_width", "border", "preserve_internal_border", "reversesort", @@ -543,6 +571,7 @@ class PrettyTable: "oldsortslice", "escape_header", "escape_data", + "break_on_hyphens", ): self._validate_true_or_false(option, val) elif option == "header_style": @@ -742,23 +771,35 @@ class PrettyTable: self._xhtml = val @property - def none_format(self): + def none_format(self) -> dict[str, str | None]: return self._none_format @none_format.setter - def none_format(self, val): + def none_format(self, val: str | dict[str, str | None] | None): + """Representation of None values: + + Arguments: + + val - The alternative representation to be used for None values + """ if not self._field_names: self._none_format = {} - elif val is None or (isinstance(val, dict) and len(val) == 0): + elif isinstance(val, str): for field in self._field_names: self._none_format[field] = None - else: self._validate_none_format(val) for field in self._field_names: self._none_format[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_none_format(fval) + self._none_format[field] = fval + else: + for field in self._field_names: + self._none_format[field] = None @property - def field_names(self): + def field_names(self) -> list[str]: """List or tuple of field names When setting field_names, if there are already field names the new list @@ -767,8 +808,8 @@ class PrettyTable: return self._field_names @field_names.setter - def field_names(self, val) -> None: - val = [str(x) for x in val] + def field_names(self, val: Sequence[Any]) -> None: + val = cast("list[str]", [str(x) for x in val]) self._validate_option("field_names", val) old_names = None if self._field_names: @@ -798,7 +839,7 @@ class PrettyTable: self.valign = "t" @property - def align(self): + def align(self) -> dict[str, AlignType]: """Controls alignment of fields Arguments: @@ -806,23 +847,27 @@ class PrettyTable: return self._align @align.setter - def align(self, val) -> None: - if val is None or (isinstance(val, dict) and len(val) == 0): + def align(self, val: AlignType | dict[str, AlignType] | None) -> None: + if isinstance(val, str): + self._validate_align(val) if not self._field_names: - self._align = {BASE_ALIGN_VALUE: "c"} + self._align = {BASE_ALIGN_VALUE: val} else: for field in self._field_names: - self._align[field] = "c" + self._align[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_align(fval) + self._align[field] = fval else: - self._validate_align(val) if not self._field_names: - self._align = {BASE_ALIGN_VALUE: val} + self._align = {BASE_ALIGN_VALUE: "c"} else: for field in self._field_names: - self._align[field] = val + self._align[field] = "c" @property - def valign(self): + def valign(self) -> dict[str, VAlignType]: """Controls vertical alignment of fields Arguments: @@ -830,19 +875,23 @@ class PrettyTable: return self._valign @valign.setter - def valign(self, val) -> None: + def valign(self, val: VAlignType | dict[str, VAlignType] | None) -> None: if not self._field_names: self._valign = {} - elif val is None or (isinstance(val, dict) and len(val) == 0): - for field in self._field_names: - self._valign[field] = "t" - else: + if isinstance(val, str): self._validate_valign(val) for field in self._field_names: self._valign[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_valign(fval) + self._valign[field] = fval + else: + for field in self._field_names: + self._valign[field] = "t" @property - def max_width(self): + def max_width(self) -> dict[str, int]: """Controls maximum width of fields Arguments: @@ -850,16 +899,20 @@ class PrettyTable: return self._max_width @max_width.setter - def max_width(self, val) -> None: - if val is None or (isinstance(val, dict) and len(val) == 0): - self._max_width = {} - else: + def max_width(self, val: int | dict[str, int] | None) -> None: + if isinstance(val, int): self._validate_option("max_width", val) for field in self._field_names: self._max_width[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_option("max_width", fval) + self._max_width[field] = fval + else: + self._max_width = {} @property - def min_width(self): + def min_width(self) -> dict[str, int]: """Controls minimum width of fields Arguments: @@ -867,13 +920,17 @@ class PrettyTable: return self._min_width @min_width.setter - def min_width(self, val) -> None: - if val is None or (isinstance(val, dict) and len(val) == 0): - self._min_width = {} - else: + def min_width(self, val: int | dict[str, int] | None) -> None: + if isinstance(val, int): self._validate_option("min_width", val) for field in self._field_names: self._min_width[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_option("min_width", fval) + self._min_width[field] = fval + else: + self._min_width = {} @property def min_table_width(self) -> int | None: @@ -989,6 +1046,20 @@ class PrettyTable: self._sort_key = val @property + def row_filter(self) -> Callable[[RowType], bool]: + """Filter function, applied to data points + + Arguments: + + row_filter - a function which takes one argument and returns a Boolean""" + return self._row_filter + + @row_filter.setter + def row_filter(self, val: Callable[[RowType], bool]) -> None: + self._validate_option("row_filter", val) + self._row_filter = val + + @property def header(self) -> bool: """Controls printing of table header with field names @@ -1003,6 +1074,22 @@ class PrettyTable: self._header = val @property + def use_header_width(self) -> bool: + """Controls whether header is included in computing width + + Arguments: + + use_header_width - respect width of fieldname in header to calculate column + width (True or False) + """ + return self._use_header_width + + @use_header_width.setter + def use_header_width(self, val: bool) -> None: + self._validate_option("use_header_width", val) + self._use_header_width = val + + @property def header_style(self) -> HeaderStyleType: """Controls stylisation applied to field names in header @@ -1075,7 +1162,7 @@ class PrettyTable: self._vrules = val @property - def int_format(self): + def int_format(self) -> dict[str, str]: """Controls formatting of integer data Arguments: @@ -1083,16 +1170,20 @@ class PrettyTable: return self._int_format @int_format.setter - def int_format(self, val) -> None: - if val is None or (isinstance(val, dict) and len(val) == 0): - self._int_format = {} - else: + def int_format(self, val: str | dict[str, str] | None) -> None: + if isinstance(val, str): self._validate_option("int_format", val) for field in self._field_names: self._int_format[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_option("int_format", fval) + self._int_format[field] = fval + else: + self._int_format = {} @property - def float_format(self): + def float_format(self) -> dict[str, str]: """Controls formatting of floating point data Arguments: @@ -1100,16 +1191,20 @@ class PrettyTable: return self._float_format @float_format.setter - def float_format(self, val) -> None: - if val is None or (isinstance(val, dict) and len(val) == 0): - self._float_format = {} - else: + def float_format(self, val: str | dict[str, str] | None) -> None: + if isinstance(val, str): self._validate_option("float_format", val) for field in self._field_names: self._float_format[field] = val + elif isinstance(val, dict) and val: + for field, fval in val.items(): + self._validate_option("float_format", fval) + self._float_format[field] = fval + else: + self._float_format = {} @property - def custom_format(self): + def custom_format(self) -> dict[str, Callable[[str, Any], str]]: """Controls formatting of any column using callable Arguments: @@ -1117,7 +1212,10 @@ class PrettyTable: return self._custom_format @custom_format.setter - def custom_format(self, val): + def custom_format( + self, + val: Callable[[str, Any], str] | dict[str, Callable[[str, Any], str]] | None, + ): if val is None: self._custom_format = {} elif isinstance(val, dict): @@ -1438,6 +1536,16 @@ class PrettyTable: self._validate_option("escape_data", val) self._escape_data = val + @property + def break_on_hyphens(self) -> bool: + """Break longlines on hyphens (True or False)""" + return self._break_on_hyphens + + @break_on_hyphens.setter + def break_on_hyphens(self, val: bool) -> None: + self._validate_option("break_on_hyphens", val) + self._break_on_hyphens = val + ############################## # OPTION MIXER # ############################## @@ -1573,16 +1681,22 @@ class PrettyTable: # DATA INPUT METHODS # ############################## - def add_rows(self, rows: Iterable[RowType]) -> None: + def add_rows(self, rows: Sequence[RowType], *, divider: bool = False) -> None: """Add rows to the table Arguments: rows - rows of data, should be an iterable of lists, each list with as many - elements as the table has fields""" - for row in rows: + elements as the table has fields + + divider - add row divider after the row block + """ + for row in rows[:-1]: self.add_row(row) + if len(rows) > 0: + self.add_row(rows[-1], divider=divider) + def add_row(self, row: RowType, *, divider: bool = False) -> None: """Add a row to the table @@ -1598,7 +1712,7 @@ class PrettyTable: ) raise ValueError(msg) if not self._field_names: - self.field_names = [f"Field {n + 1}" for n in range(0, len(row))] + self.field_names = [f"Field {n + 1}" for n in range(len(row))] self._rows.append(list(row)) self._dividers.append(divider) @@ -1648,7 +1762,7 @@ class PrettyTable: self._field_names.append(fieldname) self._align[fieldname] = align self._valign[fieldname] = valign - for i in range(0, len(column)): + for i in range(len(column)): if len(self._rows) < i + 1: self._rows.append([]) self._dividers.append(False) @@ -1732,10 +1846,12 @@ class PrettyTable: return self.get_csv_string(**kwargs) if out_format == "latex": return self.get_latex_string(**kwargs) + if out_format == "mediawiki": + return self.get_mediawiki_string(**kwargs) msg = ( f"Invalid format {out_format}. " - "Must be one of: text, html, json, csv, or latex" + "Must be one of: text, html, json, csv, latex or mediawiki" ) raise ValueError(msg) @@ -1768,7 +1884,7 @@ class PrettyTable: return table_width def _compute_widths(self, rows: list[list[str]], options: OptionsType) -> None: - if options["header"]: + if options["header"] and options["use_header_width"]: widths = [_get_size(field)[0] for field in self._field_names] else: widths = len(self.field_names) * [0] @@ -1816,7 +1932,7 @@ class PrettyTable: # Are we under min_table_width or title width? if self._min_table_width or options["title"]: if options["title"]: - title_width = len(options["title"]) + per_col_padding + title_width = _str_block_width(options["title"]) + per_col_padding if options["vrules"] in (VRuleStyle.FRAME, VRuleStyle.ALL): title_width += 2 else: @@ -1861,12 +1977,13 @@ class PrettyTable: Arguments: options - dictionary of option settings.""" - import copy if options["oldsortslice"]: - rows = copy.deepcopy(self._rows[options["start"] : options["end"]]) + rows = self._rows[options["start"] : options["end"]] else: - rows = copy.deepcopy(self._rows) + rows = self._rows + + rows = [row for row in rows if options["row_filter"](row)] # Sort if options["sortby"]: @@ -1890,12 +2007,10 @@ class PrettyTable: Arguments: options - dictionary of option settings.""" - import copy - if options["oldsortslice"]: - dividers = copy.deepcopy(self._dividers[options["start"] : options["end"]]) + dividers = self._dividers[options["start"] : options["end"]] else: - dividers = copy.deepcopy(self._dividers) + dividers = self._dividers if options["sortby"]: dividers = [False for divider in dividers] @@ -1925,6 +2040,7 @@ class PrettyTable: end - index of last data row to include in output PLUS ONE (list slice style) fields - names of fields (columns) to include header - print a header showing field names (True or False) + use_header_width - reflect width of header (True or False) border - print a border around the table (True or False) preserve_internal_border - print a border inside the table even if border is disabled (True or False) @@ -1960,6 +2076,7 @@ class PrettyTable: sortby - name of field to sort rows by sort_key - sorting key function, applied to data points before sorting reversesort - True or False to sort in descending or ascending order + row_filter - filter function applied on rows print empty - if True, stringify just the header for an empty table, if False return an empty string""" @@ -2177,7 +2294,7 @@ class PrettyTable: import textwrap for index, field, value, width in zip( - range(0, len(row)), self._field_names, row, self._widths + range(len(row)), self._field_names, row, self._widths ): # Enforce max widths lines = value.split("\n") @@ -2189,7 +2306,9 @@ class PrettyTable: ): line = none_val if _str_block_width(line) > width: - line = textwrap.fill(line, width) + line = textwrap.fill( + line, width, break_on_hyphens=options["break_on_hyphens"] + ) new_lines.append(line) lines = new_lines value = "\n".join(lines) @@ -2203,7 +2322,7 @@ class PrettyTable: bits: list[list[str]] = [] lpad, rpad = self._get_padding_widths(options) - for y in range(0, row_height): + for y in range(row_height): bits.append([]) if options["border"]: if options["vrules"] in (VRuleStyle.ALL, VRuleStyle.FRAME): @@ -2250,7 +2369,7 @@ class PrettyTable: # If vrules is FRAME, then we just appended a space at the end # of the last field, when we really want a vertical character - for y in range(0, row_height): + for y in range(row_height): if options["border"] and options["vrules"] == VRuleStyle.FRAME: bits[y].pop() bits[y].append(options["vertical_char"]) @@ -2392,12 +2511,15 @@ class PrettyTable: right_padding_width - number of spaces on right hand side of column data sortby - name of field to sort rows by sort_key - sorting key function, applied to data points before sorting + row_filter - filter function applied on rows attributes - dictionary of name/value pairs to include as HTML attributes in the <table> tag format - Controls whether or not HTML tables are formatted to match styling options (True or False) escape_data - escapes the text within a data field (True or False) - xhtml - print <br/> tags if True, <br> tags if False""" + xhtml - print <br/> tags if True, <br> tags if False + break_on_hyphens - Whether long lines are broken on hypens or not, default: True + """ options = self._get_options(kwargs) @@ -2597,6 +2719,7 @@ class PrettyTable: float_format - controls formatting of floating point data sortby - name of field to sort rows by sort_key - sorting key function, applied to data points before sorting + row_filter - filter function applied on rows format - Controls whether or not HTML tables are formatted to match styling options (True or False) """ @@ -2644,7 +2767,6 @@ class PrettyTable: def _get_formatted_latex_string(self, options: OptionsType) -> str: lines: list[str] = [] - wanted_fields: list[str] = [] if options["fields"]: wanted_fields = [ field for field in self._field_names if field in options["fields"] @@ -2701,15 +2823,81 @@ class PrettyTable: return "\r\n".join(lines) + ############################## + # MEDIAWIKI STRING METHODS # + ############################## + + def get_mediawiki_string(self, **kwargs) -> str: + """ + Return string representation of the table in MediaWiki table markup. + The generated markup follows simple MediaWiki syntax. For example: + {| class="wikitable" + |+ Optional caption + |- + ! Header1 !! Header2 !! Header3 + |- + | Data1 || Data2 || Data3 + |- + | Data4 || Data5 || Data6 + |} + """ + + options = self._get_options(kwargs) + lines: list[str] = [] + + if ( + options.get("attributes") + and isinstance(options["attributes"], dict) + and options["attributes"] + ): + attr_str = " ".join(f'{k}="{v}"' for k, v in options["attributes"].items()) + lines.append("{| " + attr_str) + else: + lines.append('{| class="wikitable"') + + caption = options.get("title", self._title) + if caption: + lines.append("|+ " + caption) + + if options.get("header"): + lines.append("|-") + headers = [] + fields_option = options.get("fields") + for field in self._field_names: + if fields_option is not None and field not in fields_option: + continue + headers.append(field) + if headers: + header_line = " !! ".join(headers) + lines.append("! " + header_line) + + rows = self._get_rows(options) + formatted_rows = self._format_rows(rows) + for row in formatted_rows: + lines.append("|-") + cells = [] + fields_option = options.get("fields") + for field, cell in zip(self._field_names, row): + if fields_option is not None and field not in fields_option: + continue + cells.append(cell) + if cells: + lines.append("| " + " || ".join(cells)) + + lines.append("|}") + return "\n".join(lines) + ############################## # UNICODE WIDTH FUNCTION # ############################## +@lru_cache def _str_block_width(val: str) -> int: - import wcwidth # type: ignore[import-untyped] + import wcwidth + val = _osc8_re.sub(r"\1", val) return wcwidth.wcswidth(_re.sub("", val)) @@ -2842,7 +3030,7 @@ class TableHandler(HTMLParser): """ iterates over the row and make each field unique """ - for i in range(0, len(fields)): + for i in range(len(fields)): for j in range(i + 1, len(fields)): if fields[i] == fields[j]: fields[j] += "'" @@ -2874,6 +3062,53 @@ def from_html_one(html_code: str, **kwargs) -> PrettyTable: return tables[0] +def from_mediawiki(wiki_text: str, **kwargs) -> PrettyTable: + """ + Returns a PrettyTable instance from simple MediaWiki table markup. + Note that the table should have a header row. + Arguments: + wiki_text -- Multiline string containing MediaWiki table markup + (Enter within ''' ''') + """ + lines = wiki_text.strip().split("\n") + table = PrettyTable(**kwargs) + header = None + rows = [] + inside_table = False + for line in lines: + line = line.strip() + if line.startswith("{|"): + inside_table = True + continue + if line.startswith("|}"): + break + if not inside_table: + continue + if line.startswith("|-"): + continue + if line.startswith("|+"): + continue + if line.startswith("!"): + header = [cell.strip() for cell in re.split(r"\s*!!\s*", line[1:])] + table.field_names = header + continue + if line.startswith("|"): + row_data = [cell.strip() for cell in re.split(r"\s*\|\|\s*", line[1:])] + rows.append(row_data) + continue + + if header: + for row in rows: + if len(row) != len(header): + error_message = "Row length mismatch between header and body." + raise ValueError(error_message) + table.add_row(row) + else: + msg = "No valid header found in the MediaWiki table." + raise ValueError(msg) + return table + + def _warn_deprecation(name: str, module_globals: dict[str, Any]) -> Any: if (val := module_globals.get(f"_DEPRECATED_{name}")) is None: msg = f"module '{__name__}' has no attribute '{name}'" @@ -2886,6 +3121,9 @@ def _warn_deprecation(name: str, module_globals: dict[str, Any]) -> Any: ) else: msg = f"the '{name}' constant is deprecated, use the 'TableStyle' enum instead" + + import warnings + warnings.warn(msg, DeprecationWarning, stacklevel=3) return val diff --git a/contrib/python/prettytable/py3/tests/conftest.py b/contrib/python/prettytable/py3/tests/conftest.py new file mode 100644 index 00000000000..22aa54c8b27 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/conftest.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import io + +import pytest +from test_prettytable import CITY_DATA, CITY_DATA_HEADER + +from prettytable import PrettyTable, from_csv, from_mediawiki + + [email protected](scope="function") +def col_prettytable() -> PrettyTable: + # Column by column... + table = PrettyTable() + for idx, colname in enumerate(CITY_DATA_HEADER): + table.add_column(colname, [row[idx] for row in CITY_DATA]) + return table + + [email protected](scope="function") +def city_data() -> PrettyTable: + """Just build the Australian capital city data example table.""" + table = PrettyTable(CITY_DATA_HEADER) + for row in CITY_DATA: + table.add_row(row) + return table + + [email protected](scope="function") +def city_data_from_csv() -> PrettyTable: + csv_string = ", ".join(CITY_DATA_HEADER) + for row in CITY_DATA: + csv_string += "\n" + ",".join(str(fld) for fld in row) + csv_fp = io.StringIO(csv_string) + return from_csv(csv_fp) + + [email protected](scope="function") +def city_data_from_mediawiki() -> PrettyTable: + wiki_text = '{| class="wikitable"\n' + wiki_text += "|-\n" + wiki_text += "! " + " !! ".join(CITY_DATA_HEADER) + "\n" + for row in CITY_DATA: + wiki_text += "|-\n" + wiki_text += "| " + " || ".join(str(item) for item in row) + "\n" + wiki_text += "|}" + return from_mediawiki(wiki_text) + + +def mix_prettytable() -> PrettyTable: + # A mix of both! + table = PrettyTable() + table.field_names = [CITY_DATA_HEADER[0], CITY_DATA_HEADER[1]] + for row in CITY_DATA: + table.add_row([row[0], row[1]]) + for idx, colname in enumerate(CITY_DATA_HEADER[2:]): + table.add_column(colname, [row[idx + 2] for row in CITY_DATA]) + return table + + [email protected](scope="function") +def field_name_less_table() -> PrettyTable: + table = PrettyTable() + for row in CITY_DATA: + table.add_row(row) + return table + + [email protected](scope="function") +def row_prettytable(field_name_less_table: PrettyTable) -> PrettyTable: + # Row by row... + field_name_less_table.field_names = CITY_DATA_HEADER + return field_name_less_table + + [email protected](scope="function") +def empty_helper_table() -> PrettyTable: + return PrettyTable(["", "Field 1", "Field 2", "Field 3"]) + + [email protected](scope="function") +def helper_table(empty_helper_table: PrettyTable) -> PrettyTable: + v = 1 + for row in range(3): + # Some have spaces, some not, to help test padding columns of different widths + empty_helper_table.add_row([v, f"value {v}", f"value{v+1}", f"value{v+2}"]) + v += 3 + return empty_helper_table diff --git a/contrib/python/prettytable/py3/tests/test_colortable.py b/contrib/python/prettytable/py3/tests/test_colortable.py index 50556384383..40a287169a0 100644 --- a/contrib/python/prettytable/py3/tests/test_colortable.py +++ b/contrib/python/prettytable/py3/tests/test_colortable.py @@ -1,37 +1,21 @@ from __future__ import annotations import pytest +from test_prettytable import CITY_DATA, CITY_DATA_HEADER -from prettytable import PrettyTable from prettytable.colortable import RESET_CODE, ColorTable, Theme, Themes - -def row_prettytable() -> PrettyTable: - # Row by row... - table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - return table +TYPE_CHECKING = False +if TYPE_CHECKING: + from prettytable import PrettyTable @pytest.fixture -def row_colortable(): +def row_colortable() -> PrettyTable: table = ColorTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) + table.field_names = CITY_DATA_HEADER + for row in CITY_DATA: + table.add_row(row) return table @@ -68,8 +52,9 @@ class TestColorTable: dict2 = table2.__dict__ # So we don't compare functions - del dict1["_sort_key"] - del dict2["_sort_key"] + for func in ("_sort_key", "_row_filter"): + del dict1[func] + del dict2[func] assert dict1 == dict2 @@ -127,6 +112,10 @@ class TestColorTableRendering: minus = chars.get("-") pipe = chars.get("|") space = chars.get(" ") + assert isinstance(plus, str) + assert isinstance(minus, str) + assert isinstance(pipe, str) + assert isinstance(space, str) # +-----------------------+ # | Efforts | diff --git a/contrib/python/prettytable/py3/tests/test_html.py b/contrib/python/prettytable/py3/tests/test_html.py new file mode 100644 index 00000000000..0c3ef03a21d --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_html.py @@ -0,0 +1,585 @@ +from __future__ import annotations + +import pytest + +from prettytable import HRuleStyle, PrettyTable, from_html, from_html_one + + +class TestHtmlConstructor: + def test_html_and_back(self, city_data: PrettyTable) -> None: + html_string = city_data.get_html_string() + new_table = from_html(html_string)[0] + assert new_table.get_string() == city_data.get_string() + + def test_html_one_and_back(self, city_data: PrettyTable) -> None: + html_string = city_data.get_html_string() + new_table = from_html_one(html_string) + assert new_table.get_string() == city_data.get_string() + + def test_html_one_fail_on_many(self, city_data: PrettyTable) -> None: + html_string = city_data.get_html_string() + html_string += city_data.get_html_string() + with pytest.raises(ValueError): + from_html_one(html_string) + + +class TestHtmlOutput: + def test_html_output(self, helper_table: PrettyTable) -> None: + result = helper_table.get_html_string() + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th>Field 2</th> + <th>Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td>value 1</td> + <td>value2</td> + <td>value3</td> + </tr> + <tr> + <td>4</td> + <td>value 4</td> + <td>value5</td> + <td>value6</td> + </tr> + <tr> + <td>7</td> + <td>value 7</td> + <td>value8</td> + <td>value9</td> + </tr> + </tbody> +</table> +""".strip() + ) + + def test_html_output_formatted(self, helper_table: PrettyTable) -> None: + result = helper_table.get_html_string(format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_with_title(self, helper_table: PrettyTable) -> None: + helper_table.title = "Title & Title" + result = helper_table.get_html_string( + attributes={"bgcolor": "red", "a<b": "1<2"} + ) + assert ( + result.strip() + == """ +<table bgcolor="red" a<b="1<2"> + <caption>Title & Title</caption> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th>Field 2</th> + <th>Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td>value 1</td> + <td>value2</td> + <td>value3</td> + </tr> + <tr> + <td>4</td> + <td>value 4</td> + <td>value5</td> + <td>value6</td> + </tr> + <tr> + <td>7</td> + <td>value 7</td> + <td>value8</td> + <td>value9</td> + </tr> + </tbody> +</table> +""".strip() + ) + + def test_html_output_formatted_with_title(self, helper_table: PrettyTable) -> None: + helper_table.title = "Title & Title" + result = helper_table.get_html_string( + attributes={"bgcolor": "red", "a<b": "1<2"}, format=True + ) + assert ( + result.strip() + == """ +<table frame="box" rules="cols" bgcolor="red" a<b="1<2"> + <caption>Title & Title</caption> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_without_escaped_header( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.field_names = [ + "", + "Field 1", + "<em>Field 2</em>", + "<a href='#'>Field 3</a>", + ] + result = empty_helper_table.get_html_string(escape_header=False) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th><em>Field 2</em></th> + <th><a href='#'>Field 3</a></th> + </tr> + </thead> + <tbody> + </tbody> +</table> +""".strip() + ) + + def test_html_output_without_escaped_data( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.add_row( + [ + 1, + "<b>value 1</b>", + "<span style='text-decoration: underline;'>value2</span>", + "<a href='#'>value3</a>", + ] + ) + result = empty_helper_table.get_html_string(escape_data=False) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th>Field 2</th> + <th>Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td><b>value 1</b></td> + <td><span style='text-decoration: underline;'>value2</span></td> + <td><a href='#'>value3</a></td> + </tr> + </tbody> +</table> +""".strip() + ) + + def test_html_output_with_escaped_header( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.field_names = [ + "", + "Field 1", + "<em>Field 2</em>", + "<a href='#'>Field 3</a>", + ] + result = empty_helper_table.get_html_string(escape_header=True) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th><em>Field 2</em></th> + <th><a href='#'>Field 3</a></th> + </tr> + </thead> + <tbody> + </tbody> +</table> +""".strip() + ) + + def test_html_output_with_escaped_data( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.add_row( + [ + 1, + "<b>value 1</b>", + "<span style='text-decoration: underline;'>value2</span>", + "<a href='#'>value3</a>", + ] + ) + result = empty_helper_table.get_html_string(escape_data=True) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th></th> + <th>Field 1</th> + <th>Field 2</th> + <th>Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td><b>value 1</b></td> + <td><span style='text-decoration: underline;'>value2</span></td> + <td><a href='#'>value3</a></td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_formatted_without_escaped_header( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.field_names = [ + "", + "Field 1", + "<em>Field 2</em>", + "<a href='#'>Field 3</a>", + ] + result = empty_helper_table.get_html_string(escape_header=False, format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th> + </tr> + </thead> + <tbody> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_formatted_without_escaped_data( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.add_row( + [ + 1, + "<b>value 1</b>", + "<span style='text-decoration: underline;'>value2</span>", + "<a href='#'>value3</a>", + ] + ) + result = empty_helper_table.get_html_string(escape_data=False, format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_formatted_with_escaped_header( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.field_names = [ + "", + "Field 1", + "<em>Field 2</em>", + "<a href='#'>Field 3</a>", + ] + result = empty_helper_table.get_html_string(escape_header=True, format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th> + </tr> + </thead> + <tbody> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_html_output_formatted_with_escaped_data( + self, empty_helper_table: PrettyTable + ) -> None: + empty_helper_table.add_row( + [ + 1, + "<b>value 1</b>", + "<span style='text-decoration: underline;'>value2</span>", + "<a href='#'>value3</a>", + ] + ) + result = empty_helper_table.get_html_string(escape_data=True, format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_table_formatted_html_autoindex(self) -> None: + """See also #199""" + table = PrettyTable(["Field 1", "Field 2", "Field 3"]) + for row in range(1, 3 * 3, 3): + table.add_row( + [f"value {row*100}", f"value {row+1*100}", f"value {row+2*100}"] + ) + table.format = True + table.add_autoindex("I") + + assert ( + table.get_html_string().strip() + == """ +<table frame="box" rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">I</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 100</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 101</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 201</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">2</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 400</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 104</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 204</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">3</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 700</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 107</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 207</td> + </tr> + </tbody> +</table>""".strip() # noqa: E501 + ) + + def test_internal_border_preserved_html(self, helper_table: PrettyTable) -> None: + helper_table.format = True + helper_table.border = False + helper_table.preserve_internal_border = True + + assert ( + helper_table.get_html_string().strip() + == """ +<table rules="cols"> + <thead> + <tr> + <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> + <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> + </tr> + <tr> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> + <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> + </tr> + </tbody> +</table> +""".strip() # noqa: E501 + ) + + def test_break_line_html(self) -> None: + table = PrettyTable(["Field 1", "Field 2"]) + table.add_row(["value 1", "value2\nsecond line"]) + table.add_row(["value 3", "value4"]) + result = table.get_html_string(hrules=HRuleStyle.ALL) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th>Field 1</th> + <th>Field 2</th> + </tr> + </thead> + <tbody> + <tr> + <td>value 1</td> + <td>value2<br>second line</td> + </tr> + <tr> + <td>value 3</td> + <td>value4</td> + </tr> + </tbody> +</table> +""".strip() + ) + + def test_break_line_xhtml(self) -> None: + table = PrettyTable(["Field 1", "Field 2"]) + table.add_row(["value 1", "value2\nsecond line"]) + table.add_row(["value 3", "value4"]) + result = table.get_html_string(hrules=HRuleStyle.ALL, xhtml=True) + assert ( + result.strip() + == """ +<table> + <thead> + <tr> + <th>Field 1</th> + <th>Field 2</th> + </tr> + </thead> + <tbody> + <tr> + <td>value 1</td> + <td>value2<br/>second line</td> + </tr> + <tr> + <td>value 3</td> + <td>value4</td> + </tr> + </tbody> +</table> +""".strip() + ) diff --git a/contrib/python/prettytable/py3/tests/test_json.py b/contrib/python/prettytable/py3/tests/test_json.py new file mode 100644 index 00000000000..0e2b3659397 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_json.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from prettytable import PrettyTable, from_json + + +class TestJSONOutput: + def test_json_output(self, helper_table: PrettyTable) -> None: + result = helper_table.get_json_string() + assert ( + result.strip() + == """ +[ + [ + "", + "Field 1", + "Field 2", + "Field 3" + ], + { + "": 1, + "Field 1": "value 1", + "Field 2": "value2", + "Field 3": "value3" + }, + { + "": 4, + "Field 1": "value 4", + "Field 2": "value5", + "Field 3": "value6" + }, + { + "": 7, + "Field 1": "value 7", + "Field 2": "value8", + "Field 3": "value9" + } +]""".strip() + ) + options = {"fields": ["Field 1", "Field 3"]} + result = helper_table.get_json_string(**options) + assert ( + result.strip() + == """ +[ + [ + "Field 1", + "Field 3" + ], + { + "Field 1": "value 1", + "Field 3": "value3" + }, + { + "Field 1": "value 4", + "Field 3": "value6" + }, + { + "Field 1": "value 7", + "Field 3": "value9" + } +]""".strip() + ) + + def test_json_output_options(self, helper_table: PrettyTable) -> None: + result = helper_table.get_json_string( + header=False, indent=None, separators=(",", ":") + ) + assert ( + result + == """[{"":1,"Field 1":"value 1","Field 2":"value2","Field 3":"value3"},""" + """{"":4,"Field 1":"value 4","Field 2":"value5","Field 3":"value6"},""" + """{"":7,"Field 1":"value 7","Field 2":"value8","Field 3":"value9"}]""" + ) + + +class TestJSONConstructor: + def test_json_and_back(self, city_data: PrettyTable) -> None: + json_string = city_data.get_json_string() + new_table = from_json(json_string) + assert new_table.get_string() == city_data.get_string() diff --git a/contrib/python/prettytable/py3/tests/test_latex.py b/contrib/python/prettytable/py3/tests/test_latex.py new file mode 100644 index 00000000000..7faa7c81921 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_latex.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from prettytable import HRuleStyle, PrettyTable, VRuleStyle + + +class TestLatexOutput: + def test_latex_output(self, helper_table: PrettyTable) -> None: + assert helper_table.get_latex_string() == ( + "\\begin{tabular}{cccc}\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\end{tabular}" + ) + options = {"fields": ["Field 1", "Field 3"]} + assert helper_table.get_latex_string(**options) == ( + "\\begin{tabular}{cc}\r\n" + "Field 1 & Field 3 \\\\\r\n" + "value 1 & value3 \\\\\r\n" + "value 4 & value6 \\\\\r\n" + "value 7 & value9 \\\\\r\n" + "\\end{tabular}" + ) + + def test_latex_output_formatted(self, helper_table: PrettyTable) -> None: + assert helper_table.get_latex_string(format=True) == ( + "\\begin{tabular}{|c|c|c|c|}\r\n" + "\\hline\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\hline\r\n" + "\\end{tabular}" + ) + + options = {"fields": ["Field 1", "Field 3"]} + assert helper_table.get_latex_string(format=True, **options) == ( + "\\begin{tabular}{|c|c|}\r\n" + "\\hline\r\n" + "Field 1 & Field 3 \\\\\r\n" + "value 1 & value3 \\\\\r\n" + "value 4 & value6 \\\\\r\n" + "value 7 & value9 \\\\\r\n" + "\\hline\r\n" + "\\end{tabular}" + ) + + vrule_options: dict[str, VRuleStyle] = {"vrules": VRuleStyle.FRAME} + assert helper_table.get_latex_string(format=True, **vrule_options) == ( + "\\begin{tabular}{|cccc|}\r\n" + "\\hline\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\hline\r\n" + "\\end{tabular}" + ) + + hrule_options: dict[str, HRuleStyle] = {"hrules": HRuleStyle.ALL} + assert helper_table.get_latex_string(format=True, **hrule_options) == ( + "\\begin{tabular}{|c|c|c|c|}\r\n" + "\\hline\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "\\hline\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "\\hline\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "\\hline\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\hline\r\n" + "\\end{tabular}" + ) + + def test_latex_output_header(self, helper_table: PrettyTable) -> None: + assert helper_table.get_latex_string(format=True, hrules=HRuleStyle.HEADER) == ( + "\\begin{tabular}{|c|c|c|c|}\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "\\hline\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\end{tabular}" + ) + + def test_internal_border_preserved_latex(self, helper_table: PrettyTable) -> None: + helper_table.border = False + helper_table.format = True + helper_table.preserve_internal_border = True + + assert helper_table.get_latex_string().strip() == ( + "\\begin{tabular}{c|c|c|c}\r\n" + " & Field 1 & Field 2 & Field 3 \\\\\r\n" + "1 & value 1 & value2 & value3 \\\\\r\n" + "4 & value 4 & value5 & value6 \\\\\r\n" + "7 & value 7 & value8 & value9 \\\\\r\n" + "\\end{tabular}" + ) diff --git a/contrib/python/prettytable/py3/tests/test_mediawiki.py b/contrib/python/prettytable/py3/tests/test_mediawiki.py new file mode 100644 index 00000000000..7684b617324 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_mediawiki.py @@ -0,0 +1,171 @@ +from __future__ import annotations + +import pytest + +from prettytable import PrettyTable, from_mediawiki + + +class TestMediaWikiOutput: + def test_mediawiki_output(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_mediawiki_string(header=True).strip() + == """ +{| class="wikitable" +|- +! !! Field 1 !! Field 2 !! Field 3 +|- +| 1 || value 1 || value2 || value3 +|- +| 4 || value 4 || value5 || value6 +|- +| 7 || value 7 || value8 || value9 +|} +""".strip() + ) + + def test_mediawiki_output_without_header(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_mediawiki_string(header=False).strip() + == """ +{| class="wikitable" +|- +| 1 || value 1 || value2 || value3 +|- +| 4 || value 4 || value5 || value6 +|- +| 7 || value 7 || value8 || value9 +|} +""".strip() + ) + + def test_mediawiki_output_with_caption(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_mediawiki_string( + title="Optional caption", header=True + ).strip() + == """ +{| class="wikitable" +|+ Optional caption +|- +! !! Field 1 !! Field 2 !! Field 3 +|- +| 1 || value 1 || value2 || value3 +|- +| 4 || value 4 || value5 || value6 +|- +| 7 || value 7 || value8 || value9 +|} +""".strip() + ) + + def test_mediawiki_output_with_attributes(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_mediawiki_string( + attributes={"class": "mytable", "id": "table1"}, header=True + ).strip() + == """ +{| class="mytable" id="table1" +|- +! !! Field 1 !! Field 2 !! Field 3 +|- +| 1 || value 1 || value2 || value3 +|- +| 4 || value 4 || value5 || value6 +|- +| 7 || value 7 || value8 || value9 +|} + """.strip() + ) + + def test_mediawiki_output_with_fields_option( + self, helper_table: PrettyTable + ) -> None: + assert ( + helper_table.get_mediawiki_string( + fields=["Field 1", "Field 3"], header=True + ).strip() + == """ +{| class="wikitable" +|- +! Field 1 !! Field 3 +|- +| value 1 || value3 +|- +| value 4 || value6 +|- +| value 7 || value9 +|} + """.strip() + ) + + +class TestMediaWikiConstructor: + def test_mediawiki_and_back(self, city_data: PrettyTable) -> None: + mediawiki_string = city_data.get_mediawiki_string() + new_table = from_mediawiki(mediawiki_string) + assert new_table.get_string() == city_data.get_string() + + def test_from_mediawiki_ignores_non_table_text( + self, city_data: PrettyTable + ) -> None: + wiki_table = city_data.get_mediawiki_string() + wiki_text = f"Before table text.\n{wiki_table}\nAfter table text." + table = from_mediawiki(wiki_text) + output = table.get_string() + + for header in city_data.field_names: + assert header in output + for row in city_data._rows: + for cell in row: + assert str(cell) in output + assert "Before table text." not in output + assert "After table text." not in output + + def test_from_mediawiki_ignores_caption(self) -> None: + wiki_text = """ +{| class="wikitable" +|+ Optional caption +|- +! Field 1 !! Field 2 +|- +| value 1 || value2 +|- +| value 4 || value5 +|} + """ + table = from_mediawiki(wiki_text) + output = table.get_string() + assert "Optional caption" not in output + assert "value 1" in output + + def test_from_mediawiki_no_header(self) -> None: + wiki_text = """ +{| class="wikitable" +|- +| value 1 || value2 +|- +| value 4 || value5 +|- +| value 7 || value8 +|} + """ + with pytest.raises( + ValueError, match="No valid header found in the MediaWiki table." + ): + from_mediawiki(wiki_text) + + def test_from_mediawiki_row_length_mismatch(self) -> None: + wiki_text = """ +{| class="wikitable" +|- +! Field 1 !! Field 2 +|- +| value 1 || value2 || value3 +|- +| value 4 || value5 || value6 +|} + """ + with pytest.raises( + ValueError, match="Row length mismatch between header and body." + ): + from_mediawiki(wiki_text) diff --git a/contrib/python/prettytable/py3/tests/test_prettytable.py b/contrib/python/prettytable/py3/tests/test_prettytable.py index e4777eb665e..7d85fad86f1 100644 --- a/contrib/python/prettytable/py3/tests/test_prettytable.py +++ b/contrib/python/prettytable/py3/tests/test_prettytable.py @@ -1,9 +1,8 @@ from __future__ import annotations import datetime as dt -import io -import random import sqlite3 +from collections.abc import Generator from math import e, pi, sqrt from typing import Any @@ -14,13 +13,10 @@ import prettytable from prettytable import ( HRuleStyle, PrettyTable, + RowType, TableStyle, VRuleStyle, - from_csv, from_db_cursor, - from_html, - from_html_one, - from_json, ) @@ -31,68 +27,17 @@ def test_version() -> None: assert prettytable.__version__[-1].isdigit() -def helper_table(*, rows: int = 3) -> PrettyTable: - table = PrettyTable(["", "Field 1", "Field 2", "Field 3"]) - v = 1 - for row in range(rows): - # Some have spaces, some not, to help test padding columns of different widths - table.add_row([v, f"value {v}", f"value{v+1}", f"value{v+2}"]) - v += 3 - return table - - -def row_prettytable() -> PrettyTable: - # Row by row... - table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - return table - - -def col_prettytable() -> PrettyTable: - # Column by column... - table = PrettyTable() - table.add_column( - "City name", - ["Adelaide", "Brisbane", "Darwin", "Hobart", "Sydney", "Melbourne", "Perth"], - ) - table.add_column("Area", [1295, 5905, 112, 1357, 2058, 1566, 5386]) - table.add_column( - "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769] - ) - table.add_column( - "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4] - ) - return table - - -def mix_prettytable() -> PrettyTable: - # A mix of both! - table = PrettyTable() - table.field_names = ["City name", "Area"] - table.add_row(["Adelaide", 1295]) - table.add_row(["Brisbane", 5905]) - table.add_row(["Darwin", 112]) - table.add_row(["Hobart", 1357]) - table.add_row(["Sydney", 2058]) - table.add_row(["Melbourne", 1566]) - table.add_row(["Perth", 5386]) - table.add_column( - "Population", [1158259, 1857594, 120900, 205556, 4336374, 3806092, 1554769] - ) - table.add_column( - "Annual Rainfall", [600.5, 1146.4, 1714.7, 619.5, 1214.8, 646.9, 869.4] - ) - return table +# Australian capital city data example table +CITY_DATA_HEADER = ["City name", "Area", "Population", "Annual Rainfall"] +CITY_DATA = [ + ["Adelaide", 1295, 1158259, 600.5], + ["Brisbane", 5905, 1857594, 1146.4], + ["Darwin", 112, 120900, 1714.7], + ["Hobart", 1357, 205556, 619.5], + ["Sydney", 2058, 4336374, 1214.8], + ["Melbourne", 1566, 3806092, 646.9], + ["Perth", 5386, 1554769, 869.4], +] class TestNoneOption: @@ -133,16 +78,18 @@ class TestNoneOption: ) def test_replace_none_all(self) -> None: - table = PrettyTable(["Field 1", "Field 2", "Field 3"], none_format="N/A") - table.add_row(["value 1", None, "None"]) + table = PrettyTable( + ["Field 1", "Field 2", "Field 3", "Field 4"], none_format="N/A" + ) + table.add_row(["value 1", None, "None", ""]) assert ( table.get_string().strip() == """ -+---------+---------+---------+ -| Field 1 | Field 2 | Field 3 | -+---------+---------+---------+ -| value 1 | N/A | N/A | -+---------+---------+---------+ ++---------+---------+---------+---------+ +| Field 1 | Field 2 | Field 3 | Field 4 | ++---------+---------+---------+---------+ +| value 1 | N/A | N/A | | ++---------+---------+---------+---------+ """.strip() ) @@ -266,40 +213,70 @@ class TestBuildEquivalence: ) -> None: assert left_hand.get_latex_string() == right_hand.get_latex_string() + @pytest.mark.parametrize( + ["left_hand", "right_hand"], + [ + ( + lf("row_prettytable"), + lf("col_prettytable"), + ), + ( + lf("row_prettytable"), + lf("mix_prettytable"), + ), + ], + ) + def test_equivalence_mediawiki( + self, left_hand: PrettyTable, right_hand: PrettyTable + ) -> None: + assert left_hand.get_mediawiki_string() == right_hand.get_mediawiki_string() -class TestDeleteColumn: - def test_delete_column(self) -> None: - table = PrettyTable() - table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) - table.add_column("Area", [1295, 5905, 112]) - table.add_column("Population", [1158259, 1857594, 120900]) - table.del_column("Area") - - without_row = PrettyTable() - without_row.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) - without_row.add_column("Population", [1158259, 1857594, 120900]) - assert table.get_string() == without_row.get_string() +class TestDelete: + def test_delete_column(self, col_prettytable: PrettyTable) -> None: + col_prettytable.del_column("Area") - def test_delete_illegal_column_raises_error(self) -> None: - table = PrettyTable() - table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) + assert ( + col_prettytable.get_string() + == """+-----------+------------+-----------------+ +| City name | Population | Annual Rainfall | ++-----------+------------+-----------------+ +| Adelaide | 1158259 | 600.5 | +| Brisbane | 1857594 | 1146.4 | +| Darwin | 120900 | 1714.7 | +| Hobart | 205556 | 619.5 | +| Sydney | 4336374 | 1214.8 | +| Melbourne | 3806092 | 646.9 | +| Perth | 1554769 | 869.4 | ++-----------+------------+-----------------+""" + ) + def test_delete_illegal_column_raises_error( + self, col_prettytable: PrettyTable + ) -> None: with pytest.raises(ValueError): - table.del_column("City not-a-name") + col_prettytable.del_column("City not-a-name") + def test_delete_row(self, city_data: PrettyTable) -> None: + city_data.del_row(2) [email protected](scope="function") -def field_name_less_table() -> PrettyTable: - table = PrettyTable() - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - return table + assert ( + city_data.get_string() + == """+-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+------------+-----------------+""" + ) + + def test_delete_row_unavailable(self, city_data: PrettyTable) -> None: + with pytest.raises(IndexError): + city_data.del_row(10) class TestFieldNameLessTable: @@ -320,13 +297,13 @@ class TestFieldNameLessTable: assert "Field 1 & Field 2 & Field 3 & Field 4 \\\\" in output assert "Adelaide & 1295 & 1158259 & 600.5 \\\\" in output + def test_can_string_mediawiki(self, field_name_less_table: PrettyTable) -> None: + output = field_name_less_table.get_mediawiki_string(header=True) + assert "! Field 1 !! Field 2 !! Field 3 !! Field 4" in output + assert "| Adelaide || 1295 || 1158259 || 600.5" in output + def test_add_field_names_later(self, field_name_less_table: PrettyTable) -> None: - field_name_less_table.field_names = [ - "City name", - "Area", - "Population", - "Annual Rainfall", - ] + field_name_less_table.field_names = CITY_DATA_HEADER assert ( "City name | Area | Population | Annual Rainfall" in field_name_less_table.get_string() @@ -337,28 +314,18 @@ class TestFieldNameLessTable: def aligned_before_table() -> PrettyTable: table = PrettyTable() table.align = "r" - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) + table.field_names = CITY_DATA_HEADER + for row in CITY_DATA: + table.add_row(row) return table @pytest.fixture(scope="function") def aligned_after_table() -> PrettyTable: table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) + table.field_names = CITY_DATA_HEADER + for row in CITY_DATA: + table.add_row(row) table.align = "r" return table @@ -369,75 +336,46 @@ class TestAlignment: def test_aligned_ascii( self, aligned_before_table: PrettyTable, aligned_after_table: PrettyTable ) -> None: - before = aligned_before_table.get_string() - after = aligned_after_table.get_string() - assert before == after + assert aligned_before_table.get_string() == aligned_after_table.get_string() def test_aligned_html( self, aligned_before_table: PrettyTable, aligned_after_table: PrettyTable ) -> None: - before = aligned_before_table.get_html_string() - after = aligned_after_table.get_html_string() - assert before == after + assert ( + aligned_before_table.get_html_string() + == aligned_after_table.get_html_string() + ) def test_aligned_latex( self, aligned_before_table: PrettyTable, aligned_after_table: PrettyTable ) -> None: - before = aligned_before_table.get_latex_string() - after = aligned_after_table.get_latex_string() - assert before == after - - [email protected](scope="function") -def city_data_prettytable() -> PrettyTable: - """Just build the Australian capital city data example table.""" - table = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"]) - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - return table - + assert ( + aligned_before_table.get_latex_string() + == aligned_after_table.get_latex_string() + ) [email protected](scope="function") -def city_data_from_csv() -> PrettyTable: - csv_string = """City name, Area, Population, Annual Rainfall - Sydney, 2058, 4336374, 1214.8 - Melbourne, 1566, 3806092, 646.9 - Brisbane, 5905, 1857594, 1146.4 - Perth, 5386, 1554769, 869.4 - Adelaide, 1295, 1158259, 600.5 - Hobart, 1357, 205556, 619.5 - Darwin, 0112, 120900, 1714.7""" - csv_fp = io.StringIO(csv_string) - return from_csv(csv_fp) + def test_aligned_mediawiki( + self, aligned_before_table: PrettyTable, aligned_after_table: PrettyTable + ) -> None: + assert aligned_before_table.get_mediawiki_string( + header=True + ) == aligned_after_table.get_mediawiki_string(header=True) class TestOptionOverride: """Make sure all options are properly overwritten by get_string.""" - def test_border(self, city_data_prettytable: PrettyTable) -> None: - default = city_data_prettytable.get_string() - override = city_data_prettytable.get_string(border=False) - assert default != override + def test_border(self, city_data: PrettyTable) -> None: + assert city_data.get_string() != city_data.get_string(border=False) - def test_header(self, city_data_prettytable) -> None: - default = city_data_prettytable.get_string() - override = city_data_prettytable.get_string(header=False) - assert default != override + def test_header(self, city_data: PrettyTable) -> None: + assert city_data.get_string() != city_data.get_string(header=False) - def test_hrules_all(self, city_data_prettytable) -> None: - default = city_data_prettytable.get_string() - override = city_data_prettytable.get_string(hrules=HRuleStyle.ALL) - assert default != override + def test_hrules_all(self, city_data: PrettyTable) -> None: + assert city_data.get_string() != city_data.get_string(hrules=HRuleStyle.ALL) - def test_hrules_none(self, city_data_prettytable) -> None: - default = city_data_prettytable.get_string() - override = city_data_prettytable.get_string(hrules=HRuleStyle.NONE) - assert default != override + def test_hrules_none(self, city_data: PrettyTable) -> None: + assert city_data.get_string() != city_data.get_string(hrules=HRuleStyle.NONE) class TestOptionAttribute: @@ -445,55 +383,66 @@ class TestOptionAttribute: Also make sure option settings are copied correctly when a table is cloned by slicing.""" - def test_set_for_all_columns(self, city_data_prettytable) -> None: - city_data_prettytable.field_names = sorted(city_data_prettytable.field_names) - city_data_prettytable.align = "l" - city_data_prettytable.max_width = 10 - city_data_prettytable.start = 2 - city_data_prettytable.end = 4 - city_data_prettytable.sortby = "Area" - city_data_prettytable.reversesort = True - city_data_prettytable.header = True - city_data_prettytable.border = False - city_data_prettytable.hrules = True - city_data_prettytable.int_format = "4" - city_data_prettytable.float_format = "2.2" - city_data_prettytable.padding_width = 2 - city_data_prettytable.left_padding_width = 2 - city_data_prettytable.right_padding_width = 2 - city_data_prettytable.vertical_char = "!" - city_data_prettytable.horizontal_char = "~" - city_data_prettytable.junction_char = "*" - city_data_prettytable.top_junction_char = "@" - city_data_prettytable.bottom_junction_char = "#" - city_data_prettytable.right_junction_char = "$" - city_data_prettytable.left_junction_char = "%" - city_data_prettytable.top_right_junction_char = "^" - city_data_prettytable.top_left_junction_char = "&" - city_data_prettytable.bottom_right_junction_char = "(" - city_data_prettytable.bottom_left_junction_char = ")" - city_data_prettytable.format = True - city_data_prettytable.attributes = {"class": "prettytable"} - assert ( - city_data_prettytable.get_string() == city_data_prettytable[:].get_string() - ) + def test_set_for_all_columns(self, city_data: PrettyTable) -> None: + city_data.field_names = sorted(city_data.field_names) + city_data.align = "l" + city_data.max_width = 10 + city_data.start = 2 + city_data.end = 4 + city_data.sortby = "Area" + city_data.reversesort = True + city_data.header = True + city_data.border = False + city_data.hrules = HRuleStyle.ALL + city_data.int_format = "4" + city_data.float_format = "2.2" + city_data.padding_width = 2 + city_data.left_padding_width = 2 + city_data.right_padding_width = 2 + city_data.vertical_char = "!" + city_data.horizontal_char = "~" + city_data.junction_char = "*" + city_data.top_junction_char = "@" + city_data.bottom_junction_char = "#" + city_data.right_junction_char = "$" + city_data.left_junction_char = "%" + city_data.top_right_junction_char = "^" + city_data.top_left_junction_char = "&" + city_data.bottom_right_junction_char = "(" + city_data.bottom_left_junction_char = ")" + city_data.format = True + city_data.attributes = {"class": "prettytable"} + assert city_data.get_string() == city_data[:].get_string() - def test_set_for_one_column(self, city_data_prettytable) -> None: - city_data_prettytable.align["Rainfall"] = "l" - city_data_prettytable.max_width["Name"] = 10 - city_data_prettytable.int_format["Population"] = "4" - city_data_prettytable.float_format["Area"] = "2.2" - assert ( - city_data_prettytable.get_string() == city_data_prettytable[:].get_string() - ) + def test_set_for_one_column(self, city_data: PrettyTable) -> None: + city_data.align["Rainfall"] = "l" + city_data.max_width["Name"] = 10 + city_data.int_format["Population"] = "4" + city_data.float_format["Area"] = "2.2" + assert city_data.get_string() == city_data[:].get_string() def test_preserve_internal_border(self) -> None: table = PrettyTable(preserve_internal_border=True) assert table.preserve_internal_border is True + def test_internal_border_preserved(self, helper_table: PrettyTable) -> None: + helper_table.border = False + helper_table.preserve_internal_border = True + + assert ( + helper_table.get_string().strip() + == """ + | Field 1 | Field 2 | Field 3 +---+---------+---------+--------- + 1 | value 1 | value2 | value3 + 4 | value 4 | value5 | value6 + 7 | value 7 | value8 | value9 +""".strip() # noqa: W291 + ) + @pytest.fixture(scope="module") -def db_cursor(): +def db_cursor() -> Generator[sqlite3.Cursor]: conn = sqlite3.connect(":memory:") cur = conn.cursor() yield cur @@ -502,18 +451,13 @@ def db_cursor(): @pytest.fixture(scope="module") -def init_db(db_cursor): +def init_db(db_cursor: sqlite3.Cursor) -> Generator[Any]: db_cursor.execute( "CREATE TABLE cities " "(name TEXT, area INTEGER, population INTEGER, rainfall REAL)" ) - db_cursor.execute('INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)') - db_cursor.execute('INSERT INTO cities VALUES ("Brisbane", 5905, 1857594, 1146.4)') - db_cursor.execute('INSERT INTO cities VALUES ("Darwin", 112, 120900, 1714.7)') - db_cursor.execute('INSERT INTO cities VALUES ("Hobart", 1357, 205556, 619.5)') - db_cursor.execute('INSERT INTO cities VALUES ("Sydney", 2058, 4336374, 1214.8)') - db_cursor.execute('INSERT INTO cities VALUES ("Melbourne", 1566, 3806092, 646.9)') - db_cursor.execute('INSERT INTO cities VALUES ("Perth", 5386, 1554769, 869.4)') + for row in CITY_DATA: + db_cursor.execute(f"INSERT INTO cities VALUES {tuple(row)}") yield db_cursor.execute("DROP TABLE cities") @@ -521,10 +465,21 @@ def init_db(db_cursor): class TestBasic: """Some very basic tests.""" - def test_table_rows(self, city_data_prettytable: PrettyTable) -> None: - rows = city_data_prettytable.rows + def test_table_rows(self, city_data: PrettyTable) -> None: + rows = city_data.rows assert len(rows) == 7 - assert rows[0] == ["Adelaide", 1295, 1158259, 600.5] + assert rows[0] == CITY_DATA[0] + + def test_add_rows(self, city_data: PrettyTable) -> None: + """A table created with multiple add_row calls is the same as one created + with a single add_rows + """ + table = PrettyTable(CITY_DATA_HEADER) + table.add_rows(CITY_DATA) + assert str(city_data) == str(table) + + table.add_rows([]) + assert str(city_data) == str(table) def _test_no_blank_lines(self, table: PrettyTable) -> None: string = table.get_string() @@ -534,136 +489,101 @@ class TestBasic: def _test_all_length_equal(self, table: PrettyTable) -> None: string = table.get_string() lines = string.split("\n") - lengths = [len(line) for line in lines] - lengths = set(lengths) + lengths = {len(line) for line in lines} assert len(lengths) == 1 - def test_no_blank_lines(self, city_data_prettytable) -> None: + def test_no_blank_lines(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - self._test_no_blank_lines(city_data_prettytable) + self._test_no_blank_lines(city_data) - def test_all_lengths_equal(self, city_data_prettytable) -> None: + def test_all_lengths_equal(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - self._test_all_length_equal(city_data_prettytable) + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_title( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_title(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.title = "My table" - self._test_no_blank_lines(city_data_prettytable) + city_data.title = "My table" + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_title( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_title(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.title = "My table" - self._test_all_length_equal(city_data_prettytable) + city_data.title = "My table" + self._test_all_length_equal(city_data) - def test_all_lengths_equal_with_long_title( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_long_title(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length, even with a long title.""" - city_data_prettytable.title = "My table (75 characters wide) " + "=" * 45 - self._test_all_length_equal(city_data_prettytable) + city_data.title = "My table (75 characters wide) " + "=" * 45 + self._test_all_length_equal(city_data) - def test_no_blank_lines_without_border( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_without_border(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.border = False - self._test_no_blank_lines(city_data_prettytable) + city_data.border = False + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_without_border( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_without_border(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.border = False - self._test_all_length_equal(city_data_prettytable) + city_data.border = False + self._test_all_length_equal(city_data) - def test_no_blank_lines_without_header( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_without_header(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.header = False - self._test_no_blank_lines(city_data_prettytable) + city_data.header = False + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_without_header( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_without_header(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.header = False - self._test_all_length_equal(city_data_prettytable) + city_data.header = False + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_hrules_none( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_hrules_none(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.hrules = HRuleStyle.NONE - self._test_no_blank_lines(city_data_prettytable) + city_data.hrules = HRuleStyle.NONE + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_hrules_none( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_hrules_none(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.hrules = HRuleStyle.NONE - self._test_all_length_equal(city_data_prettytable) + city_data.hrules = HRuleStyle.NONE + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_hrules_all( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_hrules_all(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.hrules = HRuleStyle.ALL - self._test_no_blank_lines(city_data_prettytable) + city_data.hrules = HRuleStyle.ALL + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_hrules_all( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_hrules_all(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.hrules = HRuleStyle.ALL - self._test_all_length_equal(city_data_prettytable) + city_data.hrules = HRuleStyle.ALL + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_style_msword( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_style_msword(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.set_style(TableStyle.MSWORD_FRIENDLY) - self._test_no_blank_lines(city_data_prettytable) + city_data.set_style(TableStyle.MSWORD_FRIENDLY) + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_style_msword( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_style_msword(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.set_style(TableStyle.MSWORD_FRIENDLY) - self._test_all_length_equal(city_data_prettytable) + city_data.set_style(TableStyle.MSWORD_FRIENDLY) + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_int_format( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_int_format(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.int_format = "04" - self._test_no_blank_lines(city_data_prettytable) + city_data.int_format = "04" + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_int_format( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_int_format(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.int_format = "04" - self._test_all_length_equal(city_data_prettytable) + city_data.int_format = "04" + self._test_all_length_equal(city_data) - def test_no_blank_lines_with_float_format( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_no_blank_lines_with_float_format(self, city_data: PrettyTable) -> None: """No table should ever have blank lines in it.""" - city_data_prettytable.float_format = "6.2f" - self._test_no_blank_lines(city_data_prettytable) + city_data.float_format = "6.2f" + self._test_no_blank_lines(city_data) - def test_all_lengths_equal_with_float_format( - self, city_data_prettytable: PrettyTable - ) -> None: + def test_all_lengths_equal_with_float_format(self, city_data: PrettyTable) -> None: """All lines in a table should be of the same length.""" - city_data_prettytable.float_format = "6.2f" - self._test_all_length_equal(city_data_prettytable) + city_data.float_format = "6.2f" + self._test_all_length_equal(city_data) def test_no_blank_lines_from_csv(self, city_data_from_csv: PrettyTable) -> None: """No table should ever have blank lines in it.""" @@ -673,197 +593,143 @@ class TestBasic: """All lines in a table should be of the same length.""" self._test_all_length_equal(city_data_from_csv) + def test_no_blank_lines_from_mediawiki( + self, city_data_from_mediawiki: PrettyTable + ) -> None: + """No table should ever have blank lines in it.""" + self._test_no_blank_lines(city_data_from_mediawiki) + + def test_all_lengths_equal_from_mediawiki( + self, city_data_from_mediawiki: PrettyTable + ) -> None: + """All lines in a table should be of the same length.""" + self._test_all_length_equal(city_data_from_mediawiki) + + def test_rowcount(self, city_data: PrettyTable) -> None: + assert city_data.rowcount == 7 + + def test_colcount(self, city_data: PrettyTable) -> None: + assert city_data.colcount == 4 + + def test_getitem(self, city_data: PrettyTable) -> None: + assert ( + city_data[1].get_string() + == """+-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Brisbane | 5905 | 1857594 | 1146.4 | ++-----------+------+------------+-----------------+""" + ) + + def test_invalid_getitem(self, city_data: PrettyTable) -> None: + with pytest.raises(IndexError): + assert city_data[10] + @pytest.mark.usefixtures("init_db") - def test_no_blank_lines_from_db(self, db_cursor) -> None: + def test_no_blank_lines_from_db(self, db_cursor: sqlite3.Cursor) -> None: """No table should ever have blank lines in it.""" db_cursor.execute("SELECT * FROM cities") - pt = from_db_cursor(db_cursor) - self._test_no_blank_lines(pt) + table = from_db_cursor(db_cursor) + assert table is not None + self._test_no_blank_lines(table) @pytest.mark.usefixtures("init_db") - def test_all_lengths_equal_from_db(self, db_cursor) -> None: + def test_all_lengths_equal_from_db(self, db_cursor: sqlite3.Cursor) -> None: """No table should ever have blank lines in it.""" db_cursor.execute("SELECT * FROM cities") - pt = from_db_cursor(db_cursor) - self._test_all_length_equal(pt) + table = from_db_cursor(db_cursor) + assert table is not None + self._test_all_length_equal(table) class TestEmptyTable: """Make sure the print_empty option works""" - def test_print_empty_true(self, city_data_prettytable: PrettyTable) -> None: + def test_print_empty_true(self, city_data: PrettyTable) -> None: table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + table.field_names = CITY_DATA_HEADER assert table.get_string(print_empty=True) != "" - assert table.get_string(print_empty=True) != city_data_prettytable.get_string( + assert table.get_string(print_empty=True) != city_data.get_string( print_empty=True ) - def test_print_empty_false(self, city_data_prettytable: PrettyTable) -> None: + def test_print_empty_false(self, city_data: PrettyTable) -> None: table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + table.field_names = CITY_DATA_HEADER assert table.get_string(print_empty=False) == "" - assert table.get_string(print_empty=False) != city_data_prettytable.get_string( + assert table.get_string(print_empty=False) != city_data.get_string( print_empty=False ) def test_interaction_with_border(self) -> None: table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + table.field_names = CITY_DATA_HEADER assert table.get_string(border=False, print_empty=True) == "" class TestSlicing: - def test_slice_all(self, city_data_prettytable: PrettyTable) -> None: - table = city_data_prettytable[:] - assert city_data_prettytable.get_string() == table.get_string() + def test_slice_all(self, city_data: PrettyTable) -> None: + table = city_data[:] + assert city_data.get_string() == table.get_string() - def test_slice_first_two_rows(self, city_data_prettytable: PrettyTable) -> None: - table = city_data_prettytable[0:2] + def test_slice_first_two_rows(self, city_data: PrettyTable) -> None: + table = city_data[0:2] string = table.get_string() assert len(string.split("\n")) == 6 - assert "Adelaide" in string - assert "Brisbane" in string - assert "Melbourne" not in string - assert "Perth" not in string + for row_index in (0, 1): + city = CITY_DATA[row_index][0] + assert isinstance(city, str) + assert city in string + for row_index in (2, 3, 4, 5, 6): + city = CITY_DATA[row_index][0] + assert isinstance(city, str) + assert city not in string - def test_slice_last_two_rows(self, city_data_prettytable: PrettyTable) -> None: - table = city_data_prettytable[-2:] + def test_slice_last_two_rows(self, city_data: PrettyTable) -> None: + table = city_data[-2:] string = table.get_string() assert len(string.split("\n")) == 6 - assert "Adelaide" not in string - assert "Brisbane" not in string - assert "Melbourne" in string - assert "Perth" in string - - -class TestSorting: - def test_sort_by_different_per_columns( - self, city_data_prettytable: PrettyTable - ) -> None: - city_data_prettytable.sortby = city_data_prettytable.field_names[0] - old = city_data_prettytable.get_string() - for field in city_data_prettytable.field_names[1:]: - city_data_prettytable.sortby = field - new = city_data_prettytable.get_string() - assert new != old - - def test_reverse_sort(self, city_data_prettytable: PrettyTable) -> None: - for field in city_data_prettytable.field_names: - city_data_prettytable.sortby = field - city_data_prettytable.reversesort = False - forward = city_data_prettytable.get_string() - city_data_prettytable.reversesort = True - backward = city_data_prettytable.get_string() - forward_lines = forward.split("\n")[2:] # Discard header lines - backward_lines = backward.split("\n")[2:] - backward_lines.reverse() - assert forward_lines == backward_lines + for row_index in (0, 1, 2, 3, 4): + city = CITY_DATA[row_index][0] + assert isinstance(city, str) + assert city not in string + for row_index in (5, 6): + city = CITY_DATA[row_index][0] + assert isinstance(city, str) + assert city in string - def test_sort_key(self, city_data_prettytable: PrettyTable) -> None: - # Test sorting by length of city name - def key(vals): - vals[0] = len(vals[0]) - return vals - city_data_prettytable.sortby = "City name" - city_data_prettytable.sort_key = key - assert ( - city_data_prettytable.get_string().strip() - == """ -+-----------+------+------------+-----------------+ +class TestRowFilter: + EXPECTED_RESULT = """+-----------+------+------------+-----------------+ | City name | Area | Population | Annual Rainfall | +-----------+------+------------+-----------------+ -| Perth | 5386 | 1554769 | 869.4 | -| Darwin | 112 | 120900 | 1714.7 | -| Hobart | 1357 | 205556 | 619.5 | -| Sydney | 2058 | 4336374 | 1214.8 | | Adelaide | 1295 | 1158259 | 600.5 | | Brisbane | 5905 | 1857594 | 1146.4 | -| Melbourne | 1566 | 3806092 | 646.9 | -+-----------+------+------------+-----------------+ -""".strip() - ) - - def test_sort_key_at_class_declaration(self) -> None: - # Test sorting by length of city name - def key(vals): - vals[0] = len(vals[0]) - return vals - - table = PrettyTable( - field_names=["City name", "Area", "Population", "Annual Rainfall"], - sortby="City name", - sort_key=key, - ) - assert table.sort_key == key - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - assert ( - """+-----------+------+------------+-----------------+ -| City name | Area | Population | Annual Rainfall | -+-----------+------+------------+-----------------+ -| Perth | 5386 | 1554769 | 869.4 | -| Darwin | 112 | 120900 | 1714.7 | -| Hobart | 1357 | 205556 | 619.5 | | Sydney | 2058 | 4336374 | 1214.8 | -| Adelaide | 1295 | 1158259 | 600.5 | -| Brisbane | 5905 | 1857594 | 1146.4 | | Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | +-----------+------+------------+-----------------+""" - == table.get_string().strip() - ) - def test_sort_slice(self) -> None: - """Make sure sorting and slicing interact in the expected way""" - table = PrettyTable(["Foo"]) - for i in range(20, 0, -1): - table.add_row([i]) - new_style = table.get_string(sortby="Foo", end=10) - assert "10" in new_style - assert "20" not in new_style - oldstyle = table.get_string(sortby="Foo", end=10, oldsortslice=True) - assert "10" not in oldstyle - assert "20" in oldstyle + def filter_function(self, vals: RowType) -> bool: + return vals[2] > 999999 - def test_sortby_at_class_declaration(self) -> None: - """ - Fix #354 where initialization of a table with sortby fails - """ + def test_row_filter(self, city_data: PrettyTable) -> None: + city_data.row_filter = self.filter_function + assert city_data.row_filter == self.filter_function + assert self.EXPECTED_RESULT == city_data.get_string() + + def test_row_filter_at_class_declaration(self) -> None: table = PrettyTable( - field_names=["City name", "Area", "Population", "Annual Rainfall"], - sortby="Area", - ) - assert table.sortby == "Area" - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) - assert ( - """+-----------+------+------------+-----------------+ -| City name | Area | Population | Annual Rainfall | -+-----------+------+------------+-----------------+ -| Darwin | 112 | 120900 | 1714.7 | -| Adelaide | 1295 | 1158259 | 600.5 | -| Hobart | 1357 | 205556 | 619.5 | -| Melbourne | 1566 | 3806092 | 646.9 | -| Sydney | 2058 | 4336374 | 1214.8 | -| Perth | 5386 | 1554769 | 869.4 | -| Brisbane | 5905 | 1857594 | 1146.4 | -+-----------+------+------------+-----------------+""" - == table.get_string().strip() + field_names=CITY_DATA_HEADER, + row_filter=self.filter_function, ) + for row in CITY_DATA: + table.add_row(row) + assert table.row_filter == self.filter_function + assert self.EXPECTED_RESULT == table.get_string().strip() @pytest.fixture(scope="function") @@ -878,7 +744,6 @@ def float_pt() -> PrettyTable: class TestFloatFormat: def test_no_decimals(self, float_pt: PrettyTable) -> None: float_pt.float_format = ".0f" - float_pt.caching = False assert "." not in float_pt.get_string() def test_round_to_5dp(self, float_pt: PrettyTable) -> None: @@ -900,6 +765,194 @@ class TestFloatFormat: assert "001.41" in string +class TestColumnFormattingfromDict: + def test_set_align_format(self, city_data: PrettyTable) -> None: + city_data.align = {"Annual Rainfall": "r"} + assert ( + city_data.get_string() + == """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+------------+-----------------+ +""".strip() + ) + + def test_set_valign_format(self, city_data: PrettyTable) -> None: + table = PrettyTable( + ["Field 1", "Field 2", "Field 3", "Field 4", "Field 5", "Field 6"], + ) + table.valign = {"Field 1": "m"} + table.max_width = {"Field 2": 20, "Field 4": 10, "Field 6": 10} + table.add_row( + [ + "Lorem", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + "ipsum", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + "dolor", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + ] + ) + + assert ( + table.get_string() + == """ ++---------+----------------------+---------+------------+---------+------------+ +| Field 1 | Field 2 | Field 3 | Field 4 | Field 5 | Field 6 | ++---------+----------------------+---------+------------+---------+------------+ +| | Lorem ipsum dolor | ipsum | Lorem | dolor | Lorem | +| | sit amet, consetetur | | ipsum | | ipsum | +| | sadipscing elitr, | | dolor sit | | dolor sit | +| Lorem | sed diam | | amet, | | amet, | +| | | | consetetur | | consetetur | +| | | | sadipscing | | sadipscing | +| | | | elitr, sed | | elitr, sed | +| | | | diam | | diam | ++---------+----------------------+---------+------------+---------+------------+ +""".strip() + ) + + def test_max_width( + self, + ) -> None: + table = PrettyTable( + ["Field 1", "Field 2", "Field 3", "Field 4", "Field 5", "Field 6"], + ) + table.max_width = {"Field 2": 20, "Field 4": 10, "Field 6": 10} + table.add_row( + [ + "Lorem", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + "ipsum", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + "dolor", + "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam ", + ] + ) + + assert ( + table.get_string() + == """ ++---------+----------------------+---------+------------+---------+------------+ +| Field 1 | Field 2 | Field 3 | Field 4 | Field 5 | Field 6 | ++---------+----------------------+---------+------------+---------+------------+ +| Lorem | Lorem ipsum dolor | ipsum | Lorem | dolor | Lorem | +| | sit amet, consetetur | | ipsum | | ipsum | +| | sadipscing elitr, | | dolor sit | | dolor sit | +| | sed diam | | amet, | | amet, | +| | | | consetetur | | consetetur | +| | | | sadipscing | | sadipscing | +| | | | elitr, sed | | elitr, sed | +| | | | diam | | diam | ++---------+----------------------+---------+------------+---------+------------+ +""".strip() + ) + + def test_min_width(self, city_data: PrettyTable) -> None: + city_data.min_width = { + "City name": 20, + "Area": 10, + "Population": 20, + "Annual Rainfall": 20, + } + assert ( + city_data.get_string() + == """ ++----------------------+------------+----------------------+----------------------+ +| City name | Area | Population | Annual Rainfall | ++----------------------+------------+----------------------+----------------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++----------------------+------------+----------------------+----------------------+ +""".strip() + ) + + def test_set_int_format(self, city_data: PrettyTable) -> None: + city_data.int_format = {"Population": "20"} + assert ( + city_data.get_string() + == """ ++-----------+------+----------------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+----------------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+----------------------+-----------------+ +""".strip() + ) + + def test_set_float_format(self, city_data: PrettyTable) -> None: + city_data.float_format = {"Annual Rainfall": "4.2"} + assert ( + city_data.get_string() + == """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.50 | +| Brisbane | 5905 | 1857594 | 1146.40 | +| Darwin | 112 | 120900 | 1714.70 | +| Hobart | 1357 | 205556 | 619.50 | +| Sydney | 2058 | 4336374 | 1214.80 | +| Melbourne | 1566 | 3806092 | 646.90 | +| Perth | 5386 | 1554769 | 869.40 | ++-----------+------+------------+-----------------+ +""".strip() + ) + + def test_set_custom_format(self, city_data: PrettyTable) -> None: + city_data.custom_format = {"Annual Rainfall": lambda f, v: f"{v:.2f}"} + assert ( + city_data.get_string() + == """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.50 | +| Brisbane | 5905 | 1857594 | 1146.40 | +| Darwin | 112 | 120900 | 1714.70 | +| Hobart | 1357 | 205556 | 619.50 | +| Sydney | 2058 | 4336374 | 1214.80 | +| Melbourne | 1566 | 3806092 | 646.90 | +| Perth | 5386 | 1554769 | 869.40 | ++-----------+------+------------+-----------------+ +""".strip() + ) + + def test_set_none_format(self, city_data: PrettyTable) -> None: + city_data.clear_rows() + city_data.add_row([None, None, None, None]) + city_data.none_format = {"Annual Rainfall": "N/A"} + assert ( + city_data.get_string() + == """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| None | None | None | N/A | ++-----------+------+------------+-----------------+ +""".strip() + ) + + class TestBreakLine: @pytest.mark.parametrize( ["rows", "hrule", "expected_result"], @@ -968,996 +1021,29 @@ class TestBreakLine: result = table.get_string(hrules=hrule) assert result.strip() == expected_result.strip() - def test_break_line_html(self) -> None: - table = PrettyTable(["Field 1", "Field 2"]) - table.add_row(["value 1", "value2\nsecond line"]) - table.add_row(["value 3", "value4"]) - result = table.get_html_string(hrules=HRuleStyle.ALL) - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th>Field 1</th> - <th>Field 2</th> - </tr> - </thead> - <tbody> - <tr> - <td>value 1</td> - <td>value2<br>second line</td> - </tr> - <tr> - <td>value 3</td> - <td>value4</td> - </tr> - </tbody> -</table> -""".strip() - ) - - -class TestAnsiWidth: - colored = "\033[31mC\033[32mO\033[31mL\033[32mO\033[31mR\033[32mE\033[31mD\033[0m" - - def test_color(self) -> None: - table = PrettyTable(["Field 1", "Field 2"]) - table.add_row([self.colored, self.colored]) - table.add_row(["nothing", "neither"]) - result = table.get_string() - assert ( - result.strip() - == f""" -+---------+---------+ -| Field 1 | Field 2 | -+---------+---------+ -| {self.colored} | {self.colored} | -| nothing | neither | -+---------+---------+ -""".strip() - ) - - def test_reset(self) -> None: - table = PrettyTable(["Field 1", "Field 2"]) - table.add_row(["abc def\033(B", "\033[31mabc def\033[m"]) - table.add_row(["nothing", "neither"]) - result = table.get_string() - assert ( - result.strip() - == """ -+---------+---------+ -| Field 1 | Field 2 | -+---------+---------+ -| abc def\033(B | \033[31mabc def\033[m | -| nothing | neither | -+---------+---------+ -""".strip() - ) - class TestFromDB: @pytest.mark.usefixtures("init_db") - def test_non_select_cursor(self, db_cursor) -> None: - db_cursor.execute( - 'INSERT INTO cities VALUES ("Adelaide", 1295, 1158259, 600.5)' - ) + def test_non_select_cursor(self, db_cursor: sqlite3.Cursor) -> None: + db_cursor.execute(f"INSERT INTO cities VALUES {tuple(CITY_DATA[0])}") assert from_db_cursor(db_cursor) is None -class TestJSONOutput: - def test_json_output(self) -> None: - t = helper_table() - result = t.get_json_string() - assert ( - result.strip() - == """ -[ - [ - "", - "Field 1", - "Field 2", - "Field 3" - ], - { - "": 1, - "Field 1": "value 1", - "Field 2": "value2", - "Field 3": "value3" - }, - { - "": 4, - "Field 1": "value 4", - "Field 2": "value5", - "Field 3": "value6" - }, - { - "": 7, - "Field 1": "value 7", - "Field 2": "value8", - "Field 3": "value9" - } -]""".strip() - ) - options = {"fields": ["Field 1", "Field 3"]} - result = t.get_json_string(**options) - assert ( - result.strip() - == """ -[ - [ - "Field 1", - "Field 3" - ], - { - "Field 1": "value 1", - "Field 3": "value3" - }, - { - "Field 1": "value 4", - "Field 3": "value6" - }, - { - "Field 1": "value 7", - "Field 3": "value9" - } -]""".strip() - ) - - def test_json_output_options(self) -> None: - t = helper_table() - result = t.get_json_string(header=False, indent=None, separators=(",", ":")) - assert ( - result - == """[{"":1,"Field 1":"value 1","Field 2":"value2","Field 3":"value3"},""" - """{"":4,"Field 1":"value 4","Field 2":"value5","Field 3":"value6"},""" - """{"":7,"Field 1":"value 7","Field 2":"value8","Field 3":"value9"}]""" - ) - - -class TestHtmlOutput: - def test_html_output(self) -> None: - t = helper_table() - result = t.get_html_string() - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th>Field 2</th> - <th>Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td>1</td> - <td>value 1</td> - <td>value2</td> - <td>value3</td> - </tr> - <tr> - <td>4</td> - <td>value 4</td> - <td>value5</td> - <td>value6</td> - </tr> - <tr> - <td>7</td> - <td>value 7</td> - <td>value8</td> - <td>value9</td> - </tr> - </tbody> -</table> -""".strip() - ) - - def test_html_output_formatted(self) -> None: - t = helper_table() - result = t.get_html_string(format=True) - assert ( - result.strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_with_title(self) -> None: - t = helper_table() - t.title = "Title & Title" - result = t.get_html_string(attributes={"bgcolor": "red", "a<b": "1<2"}) - assert ( - result.strip() - == """ -<table bgcolor="red" a<b="1<2"> - <caption>Title & Title</caption> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th>Field 2</th> - <th>Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td>1</td> - <td>value 1</td> - <td>value2</td> - <td>value3</td> - </tr> - <tr> - <td>4</td> - <td>value 4</td> - <td>value5</td> - <td>value6</td> - </tr> - <tr> - <td>7</td> - <td>value 7</td> - <td>value8</td> - <td>value9</td> - </tr> - </tbody> -</table> -""".strip() - ) - - def test_html_output_formatted_with_title(self) -> None: - t = helper_table() - t.title = "Title & Title" - result = t.get_html_string( - attributes={"bgcolor": "red", "a<b": "1<2"}, format=True - ) - assert ( - result.strip() - == """ -<table frame="box" rules="cols" bgcolor="red" a<b="1<2"> - <caption>Title & Title</caption> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_without_escaped_header(self) -> None: - t = helper_table(rows=0) - t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"] - result = t.get_html_string(escape_header=False) - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th><em>Field 2</em></th> - <th><a href='#'>Field 3</a></th> - </tr> - </thead> - <tbody> - </tbody> -</table> -""".strip() - ) - - def test_html_output_without_escaped_data(self) -> None: - t = helper_table(rows=0) - t.add_row( - [ - 1, - "<b>value 1</b>", - "<span style='text-decoration: underline;'>value2</span>", - "<a href='#'>value3</a>", - ] - ) - result = t.get_html_string(escape_data=False) - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th>Field 2</th> - <th>Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td>1</td> - <td><b>value 1</b></td> - <td><span style='text-decoration: underline;'>value2</span></td> - <td><a href='#'>value3</a></td> - </tr> - </tbody> -</table> -""".strip() - ) - - def test_html_output_with_escaped_header(self) -> None: - t = helper_table(rows=0) - t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"] - result = t.get_html_string(escape_header=True) - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th><em>Field 2</em></th> - <th><a href='#'>Field 3</a></th> - </tr> - </thead> - <tbody> - </tbody> -</table> -""".strip() - ) - - def test_html_output_with_escaped_data(self) -> None: - t = helper_table(rows=0) - t.add_row( - [ - 1, - "<b>value 1</b>", - "<span style='text-decoration: underline;'>value2</span>", - "<a href='#'>value3</a>", - ] - ) - result = t.get_html_string(escape_data=True) - assert ( - result.strip() - == """ -<table> - <thead> - <tr> - <th></th> - <th>Field 1</th> - <th>Field 2</th> - <th>Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td>1</td> - <td><b>value 1</b></td> - <td><span style='text-decoration: underline;'>value2</span></td> - <td><a href='#'>value3</a></td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_formatted_without_escaped_header(self) -> None: - t = helper_table(rows=0) - t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"] - result = t.get_html_string(escape_header=False, format=True) - assert ( - result.strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th> - </tr> - </thead> - <tbody> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_formatted_without_escaped_data(self) -> None: - t = helper_table(rows=0) - t.add_row( - [ - 1, - "<b>value 1</b>", - "<span style='text-decoration: underline;'>value2</span>", - "<a href='#'>value3</a>", - ] - ) - result = t.get_html_string(escape_data=False, format=True) - assert ( - result.strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_formatted_with_escaped_header(self) -> None: - t = helper_table(rows=0) - t.field_names = ["", "Field 1", "<em>Field 2</em>", "<a href='#'>Field 3</a>"] - result = t.get_html_string(escape_header=True, format=True) - assert ( - result.strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"><em>Field 2</em></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"><a href='#'>Field 3</a></th> - </tr> - </thead> - <tbody> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - def test_html_output_formatted_with_escaped_data(self) -> None: - t = helper_table(rows=0) - t.add_row( - [ - 1, - "<b>value 1</b>", - "<span style='text-decoration: underline;'>value2</span>", - "<a href='#'>value3</a>", - ] - ) - result = t.get_html_string(escape_data=True, format=True) - assert ( - result.strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><b>value 1</b></td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><span style='text-decoration: underline;'>value2</span></td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top"><a href='#'>value3</a></td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - -class TestPositionalJunctions: - """Verify different cases for positional-junction characters""" - - def test_default(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═══════════╦══════╦════════════╦═════════════════╗ -║ City name ║ Area ║ Population ║ Annual Rainfall ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ -║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ -║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ -║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ -║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ -║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ -║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ -╚═══════════╩══════╩════════════╩═════════════════╝""".strip() - ) - - def test_no_header(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.header = False - - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═══════════╦══════╦═════════╦════════╗ -║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ -║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ -║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ -║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ -║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ -║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ -║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ -╚═══════════╩══════╩═════════╩════════╝""".strip() - ) - - def test_with_title(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.title = "Title" - - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═════════════════════════════════════════════════╗ -║ Title ║ -╠═══════════╦══════╦════════════╦═════════════════╣ -║ City name ║ Area ║ Population ║ Annual Rainfall ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ -║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ -║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ -║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ -║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ -║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ -║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ -╚═══════════╩══════╩════════════╩═════════════════╝""".strip() - ) - - def test_with_title_no_header(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.title = "Title" - city_data_prettytable.header = False - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═════════════════════════════════════╗ -║ Title ║ -╠═══════════╦══════╦═════════╦════════╣ -║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ -║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ -║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ -║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ -║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ -║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ -║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ -╚═══════════╩══════╩═════════╩════════╝""".strip() - ) - - def test_hrule_all(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.title = "Title" - city_data_prettytable.hrules = HRuleStyle.ALL - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═════════════════════════════════════════════════╗ -║ Title ║ -╠═══════════╦══════╦════════════╦═════════════════╣ -║ City name ║ Area ║ Population ║ Annual Rainfall ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ -╠═══════════╬══════╬════════════╬═════════════════╣ -║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ -╚═══════════╩══════╩════════════╩═════════════════╝""".strip() - ) - - def test_vrules_none(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.vrules = VRuleStyle.NONE - assert ( - city_data_prettytable.get_string().strip() - == "═══════════════════════════════════════════════════\n" - " City name Area Population Annual Rainfall \n" - "═══════════════════════════════════════════════════\n" - " Adelaide 1295 1158259 600.5 \n" - " Brisbane 5905 1857594 1146.4 \n" - " Darwin 112 120900 1714.7 \n" - " Hobart 1357 205556 619.5 \n" - " Sydney 2058 4336374 1214.8 \n" - " Melbourne 1566 3806092 646.9 \n" - " Perth 5386 1554769 869.4 \n" - "═══════════════════════════════════════════════════".strip() - ) - - def test_vrules_frame_with_title(self, city_data_prettytable: PrettyTable) -> None: - city_data_prettytable.set_style(TableStyle.DOUBLE_BORDER) - city_data_prettytable.vrules = VRuleStyle.FRAME - city_data_prettytable.title = "Title" - assert ( - city_data_prettytable.get_string().strip() - == """ -╔═════════════════════════════════════════════════╗ -║ Title ║ -╠═════════════════════════════════════════════════╣ -║ City name Area Population Annual Rainfall ║ -╠═════════════════════════════════════════════════╣ -║ Adelaide 1295 1158259 600.5 ║ -║ Brisbane 5905 1857594 1146.4 ║ -║ Darwin 112 120900 1714.7 ║ -║ Hobart 1357 205556 619.5 ║ -║ Sydney 2058 4336374 1214.8 ║ -║ Melbourne 1566 3806092 646.9 ║ -║ Perth 5386 1554769 869.4 ║ -╚═════════════════════════════════════════════════╝""".strip() - ) - - -class TestStyle: - @pytest.mark.parametrize( - "style, expected", - [ - pytest.param( - TableStyle.DEFAULT, - """ -+---+---------+---------+---------+ -| | Field 1 | Field 2 | Field 3 | -+---+---------+---------+---------+ -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -+---+---------+---------+---------+ -""", - id="DEFAULT", - ), - pytest.param( - TableStyle.MARKDOWN, # TODO fix - """ -| | Field 1 | Field 2 | Field 3 | -| :-: | :-----: | :-----: | :-----: | -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -""", - id="MARKDOWN", - ), - pytest.param( - TableStyle.MSWORD_FRIENDLY, - """ -| | Field 1 | Field 2 | Field 3 | -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -""", - id="MSWORD_FRIENDLY", - ), - pytest.param( - TableStyle.ORGMODE, - """ -|---+---------+---------+---------| -| | Field 1 | Field 2 | Field 3 | -|---+---------+---------+---------| -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -|---+---------+---------+---------| -""", - id="ORGMODE", - ), - pytest.param( - TableStyle.PLAIN_COLUMNS, - """ - Field 1 Field 2 Field 3 -1 value 1 value2 value3 -4 value 4 value5 value6 -7 value 7 value8 value9 -""", # noqa: W291 - id="PLAIN_COLUMNS", - ), - pytest.param( - TableStyle.RANDOM, - """ -'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' -% 1 value 1 value2 value3% -% 4 value 4 value5 value6% -% 7 value 7 value8 value9% -'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' -""", - id="RANDOM", - ), - pytest.param( - TableStyle.DOUBLE_BORDER, - """ -╔═══╦═════════╦═════════╦═════════╗ -║ ║ Field 1 ║ Field 2 ║ Field 3 ║ -╠═══╬═════════╬═════════╬═════════╣ -║ 1 ║ value 1 ║ value2 ║ value3 ║ -║ 4 ║ value 4 ║ value5 ║ value6 ║ -║ 7 ║ value 7 ║ value8 ║ value9 ║ -╚═══╩═════════╩═════════╩═════════╝ -""", - ), - pytest.param( - TableStyle.SINGLE_BORDER, - """ -┌───┬─────────┬─────────┬─────────┐ -│ │ Field 1 │ Field 2 │ Field 3 │ -├───┼─────────┼─────────┼─────────┤ -│ 1 │ value 1 │ value2 │ value3 │ -│ 4 │ value 4 │ value5 │ value6 │ -│ 7 │ value 7 │ value8 │ value9 │ -└───┴─────────┴─────────┴─────────┘ -""", - ), - ], - ) - def test_style(self, style, expected) -> None: - # Arrange - t = helper_table() - random.seed(1234) - - # Act - t.set_style(style) - - # Assert - result = t.get_string() - assert result.strip() == expected.strip() - - def test_style_invalid(self) -> None: - # Arrange - t = helper_table() - - # Act / Assert - # This is an hrule style, not a table style - with pytest.raises(ValueError): - t.set_style(HRuleStyle.ALL) # type: ignore[arg-type] - - @pytest.mark.parametrize( - "original_style,style, expected", - [ - pytest.param( - TableStyle.MARKDOWN, - TableStyle.DEFAULT, - """ -+---+---------+---------+---------+ -| | Field 1 | Field 2 | Field 3 | -+---+---------+---------+---------+ -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -+---+---------+---------+---------+ -""", - id="DEFAULT", - ), - pytest.param( - TableStyle.MSWORD_FRIENDLY, - TableStyle.MARKDOWN, - """ -| | Field 1 | Field 2 | Field 3 | -| :-: | :-----: | :-----: | :-----: | -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -""", - id="MARKDOWN", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.MSWORD_FRIENDLY, - """ -| | Field 1 | Field 2 | Field 3 | -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -""", - id="MSWORD_FRIENDLY", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.ORGMODE, - """ -|---+---------+---------+---------| -| | Field 1 | Field 2 | Field 3 | -|---+---------+---------+---------| -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -|---+---------+---------+---------| -""", - id="ORGMODE", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.PLAIN_COLUMNS, - """ - Field 1 Field 2 Field 3 -1 value 1 value2 value3 -4 value 4 value5 value6 -7 value 7 value8 value9 -""", # noqa: W291 - id="PLAIN_COLUMNS", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.RANDOM, - """ -'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' -% 1 value 1 value2 value3% -% 4 value 4 value5 value6% -% 7 value 7 value8 value9% -'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' -""", - id="RANDOM", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.DOUBLE_BORDER, - """ -╔═══╦═════════╦═════════╦═════════╗ -║ ║ Field 1 ║ Field 2 ║ Field 3 ║ -╠═══╬═════════╬═════════╬═════════╣ -║ 1 ║ value 1 ║ value2 ║ value3 ║ -║ 4 ║ value 4 ║ value5 ║ value6 ║ -║ 7 ║ value 7 ║ value8 ║ value9 ║ -╚═══╩═════════╩═════════╩═════════╝ -""", - id="DOUBLE_BORDER", - ), - pytest.param( - TableStyle.MARKDOWN, - TableStyle.SINGLE_BORDER, - """ -┌───┬─────────┬─────────┬─────────┐ -│ │ Field 1 │ Field 2 │ Field 3 │ -├───┼─────────┼─────────┼─────────┤ -│ 1 │ value 1 │ value2 │ value3 │ -│ 4 │ value 4 │ value5 │ value6 │ -│ 7 │ value 7 │ value8 │ value9 │ -└───┴─────────┴─────────┴─────────┘ -""", - id="SINGLE_BORDER", - ), - ], - ) - def test_style_reset(self, original_style, style, expected) -> None: - """ - Testing to ensure that default styling is reset between changes - of styles on a PrettyTable - - Args: - style (str): Style to be used (Default, markdown, etc) - expected (str): The expected format of style as a string representation - """ - # Arrange - t = helper_table() - random.seed(1234) - - # Act - t.set_style(original_style) - t.set_style(style) - - # Assert - result = t.get_string() - assert result.strip() == expected.strip() - - @pytest.mark.parametrize( - "style, expected", - [ - pytest.param( - TableStyle.MARKDOWN, - """ -| l | c | r | Align left | Align centre | Align right | -| :-| :-: |-: | :----------| :----------: |-----------: | -| 1 | 2 | 3 | value 1 | value2 | value3 | -| 4 | 5 | 6 | value 4 | value5 | value6 | -| 7 | 8 | 9 | value 7 | value8 | value9 | -""", - id="MARKDOWN", - ), - ], - ) - def test_style_align(self, style, expected) -> None: - # Arrange - t = PrettyTable(["l", "c", "r", "Align left", "Align centre", "Align right"]) - v = 1 - for row in range(3): - # Some have spaces, some not, to help test padding columns of - # different widths - t.add_row([v, v + 1, v + 2, f"value {v}", f"value{v + 1}", f"value{v + 2}"]) - v += 3 - - # Act - t.set_style(style) - t.align["l"] = t.align["Align left"] = "l" - t.align["c"] = t.align["Align centre"] = "c" - t.align["r"] = t.align["Align right"] = "r" - - # Assert - result = t.get_string() - assert result.strip() == expected.strip() - - class TestCsvOutput: - def test_csv_output(self) -> None: - t = helper_table() - assert t.get_csv_string(delimiter="\t", header=False) == ( + def test_csv_output(self, helper_table: PrettyTable) -> None: + assert helper_table.get_csv_string(delimiter="\t", header=False) == ( "1\tvalue 1\tvalue2\tvalue3\r\n" "4\tvalue 4\tvalue5\tvalue6\r\n" "7\tvalue 7\tvalue8\tvalue9\r\n" ) - assert t.get_csv_string() == ( + assert helper_table.get_csv_string() == ( ",Field 1,Field 2,Field 3\r\n" "1,value 1,value2,value3\r\n" "4,value 4,value5,value6\r\n" "7,value 7,value8,value9\r\n" ) options = {"fields": ["Field 1", "Field 3"]} - assert t.get_csv_string(**options) == ( + assert helper_table.get_csv_string(**options) == ( "Field 1,Field 3\r\n" "value 1,value3\r\n" "value 4,value6\r\n" @@ -1965,162 +1051,8 @@ class TestCsvOutput: ) -class TestLatexOutput: - def test_latex_output(self) -> None: - t = helper_table() - assert t.get_latex_string() == ( - "\\begin{tabular}{cccc}\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\end{tabular}" - ) - options = {"fields": ["Field 1", "Field 3"]} - assert t.get_latex_string(**options) == ( - "\\begin{tabular}{cc}\r\n" - "Field 1 & Field 3 \\\\\r\n" - "value 1 & value3 \\\\\r\n" - "value 4 & value6 \\\\\r\n" - "value 7 & value9 \\\\\r\n" - "\\end{tabular}" - ) - - def test_latex_output_formatted(self) -> None: - t = helper_table() - assert t.get_latex_string(format=True) == ( - "\\begin{tabular}{|c|c|c|c|}\r\n" - "\\hline\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\hline\r\n" - "\\end{tabular}" - ) - - options = {"fields": ["Field 1", "Field 3"]} - assert t.get_latex_string(format=True, **options) == ( - "\\begin{tabular}{|c|c|}\r\n" - "\\hline\r\n" - "Field 1 & Field 3 \\\\\r\n" - "value 1 & value3 \\\\\r\n" - "value 4 & value6 \\\\\r\n" - "value 7 & value9 \\\\\r\n" - "\\hline\r\n" - "\\end{tabular}" - ) - - options = {"vrules": VRuleStyle.FRAME} - assert t.get_latex_string(format=True, **options) == ( - "\\begin{tabular}{|cccc|}\r\n" - "\\hline\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\hline\r\n" - "\\end{tabular}" - ) - - options = {"hrules": HRuleStyle.ALL} - assert t.get_latex_string(format=True, **options) == ( - "\\begin{tabular}{|c|c|c|c|}\r\n" - "\\hline\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "\\hline\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "\\hline\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "\\hline\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\hline\r\n" - "\\end{tabular}" - ) - - def test_latex_output_header(self) -> None: - t = helper_table() - assert t.get_latex_string(format=True, hrules=HRuleStyle.HEADER) == ( - "\\begin{tabular}{|c|c|c|c|}\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "\\hline\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\end{tabular}" - ) - - -class TestJSONConstructor: - def test_json_and_back(self, city_data_prettytable: PrettyTable) -> None: - json_string = city_data_prettytable.get_json_string() - new_table = from_json(json_string) - assert new_table.get_string() == city_data_prettytable.get_string() - - -class TestHtmlConstructor: - def test_html_and_back(self, city_data_prettytable: PrettyTable) -> None: - html_string = city_data_prettytable.get_html_string() - new_table = from_html(html_string)[0] - assert new_table.get_string() == city_data_prettytable.get_string() - - def test_html_one_and_back(self, city_data_prettytable: PrettyTable) -> None: - html_string = city_data_prettytable.get_html_string() - new_table = from_html_one(html_string) - assert new_table.get_string() == city_data_prettytable.get_string() - - def test_html_one_fail_on_many(self, city_data_prettytable: PrettyTable) -> None: - html_string = city_data_prettytable.get_html_string() - html_string += city_data_prettytable.get_html_string() - with pytest.raises(ValueError): - from_html_one(html_string) - - -def japanese_pretty_table() -> PrettyTable: - table = PrettyTable(["Kanji", "Hiragana", "English"]) - table.add_row(["神戸", "こうべ", "Kobe"]) - table.add_row(["京都", "きょうと", "Kyoto"]) - table.add_row(["長崎", "ながさき", "Nagasaki"]) - table.add_row(["名古屋", "なごや", "Nagoya"]) - table.add_row(["大阪", "おおさか", "Osaka"]) - table.add_row(["札幌", "さっぽろ", "Sapporo"]) - table.add_row(["東京", "とうきょう", "Tokyo"]) - table.add_row(["横浜", "よこはま", "Yokohama"]) - return table - - -def emoji_pretty_table() -> PrettyTable: - thunder1 = [ - '\033[38;5;226m _`/""\033[38;5;250m.-. \033[0m', - "\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m", - "\033[38;5;226m /\033[38;5;250m(___(__) \033[0m", - "\033[38;5;228;5m ⚡\033[38;5;111;25mʻ ʻ\033[38;5;228;5m" - "⚡\033[38;5;111;25mʻ ʻ \033[0m", - "\033[38;5;111m ʻ ʻ ʻ ʻ \033[0m", - ] - thunder2 = [ - "\033[38;5;240;1m .-. \033[0m", - "\033[38;5;240;1m ( ). \033[0m", - "\033[38;5;240;1m (___(__) \033[0m", - "\033[38;5;21;1m ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚\033[38;5;228;5m" - "⚡\033[38;5;21;25m‚ʻ \033[0m", - "\033[38;5;21;1m ‚ʻ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚ʻ \033[0m", - ] - table = PrettyTable(["Thunderbolt", "Lightning"]) - for i in range(len(thunder1)): - table.add_row([thunder1[i], thunder2[i]]) - return table - - -class TestMultiPattern: - @pytest.mark.parametrize( - ["pt", "expected_output", "test_type"], - [ - ( - lf("city_data_prettytable"), - """ +def test_paginate(city_data: PrettyTable) -> None: + expected_page_1 = """ +-----------+------+------------+-----------------+ | City name | Area | Population | Annual Rainfall | +-----------+------+------------+-----------------+ @@ -2128,154 +1060,48 @@ class TestMultiPattern: | Brisbane | 5905 | 1857594 | 1146.4 | | Darwin | 112 | 120900 | 1714.7 | | Hobart | 1357 | 205556 | 619.5 | ++-----------+------+------------+-----------------+""".strip() + expected_page_2 = """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ | Sydney | 2058 | 4336374 | 1214.8 | | Melbourne | 1566 | 3806092 | 646.9 | | Perth | 5386 | 1554769 | 869.4 | -+-----------+------+------------+-----------------+ -""", - "English Table", - ), - ( - lf("japanese_pretty_table"), - """ -+--------+------------+----------+ -| Kanji | Hiragana | English | -+--------+------------+----------+ -| 神戸 | こうべ | Kobe | -| 京都 | きょうと | Kyoto | -| 長崎 | ながさき | Nagasaki | -| 名古屋 | なごや | Nagoya | -| 大阪 | おおさか | Osaka | -| 札幌 | さっぽろ | Sapporo | -| 東京 | とうきょう | Tokyo | -| 横浜 | よこはま | Yokohama | -+--------+------------+----------+ - -""", - "Japanese table", - ), - ( - lf("emoji_pretty_table"), - """ -+-----------------+-----------------+ -| Thunderbolt | Lightning | -+-----------------+-----------------+ -| \x1b[38;5;226m _`/""\x1b[38;5;250m.-. \x1b[0m | \x1b[38;5;240;1m .-. \x1b[0m | -| \x1b[38;5;226m ,\\_\x1b[38;5;250m( ). \x1b[0m | \x1b[38;5;240;1m ( ). \x1b[0m | -| \x1b[38;5;226m /\x1b[38;5;250m(___(__) \x1b[0m | \x1b[38;5;240;1m (___(__) \x1b[0m | -| \x1b[38;5;228;5m ⚡\x1b[38;5;111;25mʻ ʻ\x1b[38;5;228;5m⚡\x1b[38;5;111;25mʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚\x1b[38;5;228;5m⚡\x1b[38;5;21;25m‚ʻ \x1b[0m | -| \x1b[38;5;111m ʻ ʻ ʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚ʻ \x1b[0m | -+-----------------+-----------------+ - """, # noqa: E501 - "Emoji table", - ), - ], - ) - def test_multi_pattern_outputs( - self, pt: PrettyTable, expected_output: str, test_type: str - ) -> None: - printed_table = pt.get_string() - assert ( - printed_table.strip() == expected_output.strip() - ), f"Error output for test output of type {test_type}" - ++-----------+------+------------+-----------------+""".strip() -def test_paginate() -> None: - # Arrange - t = helper_table(rows=7) - expected_page_1 = """ -+----+----------+---------+---------+ -| | Field 1 | Field 2 | Field 3 | -+----+----------+---------+---------+ -| 1 | value 1 | value2 | value3 | -| 4 | value 4 | value5 | value6 | -| 7 | value 7 | value8 | value9 | -| 10 | value 10 | value11 | value12 | -+----+----------+---------+---------+ - """.strip() - expected_page_2 = """ -+----+----------+---------+---------+ -| | Field 1 | Field 2 | Field 3 | -+----+----------+---------+---------+ -| 13 | value 13 | value14 | value15 | -| 16 | value 16 | value17 | value18 | -| 19 | value 19 | value20 | value21 | -+----+----------+---------+---------+ -""".strip() - - # Act - paginated = t.paginate(page_length=4) - - # Assert - paginated = paginated.strip() + paginated = city_data.paginate(page_length=4).strip() assert paginated.startswith(expected_page_1) assert "\f" in paginated assert paginated.endswith(expected_page_2) - # Act - paginated = t.paginate(page_length=4, line_break="\n") - - # Assert + paginated = city_data.paginate(page_length=4, line_break="\n") assert "\f" not in paginated assert "\n" in paginated -def test_add_rows() -> None: - """A table created with multiple add_row calls - is the same as one created with a single add_rows - """ - # Arrange - table1 = PrettyTable(["A", "B", "C"]) - table2 = PrettyTable(["A", "B", "C"]) - table1.add_row([1, 2, 3]) - table1.add_row([4, 5, 6]) - rows = [ - [1, 2, 3], - [4, 5, 6], - ] - - # Act - table2.add_rows(rows) - - # Assert - assert str(table1) == str(table2) - - -def test_autoindex() -> None: +def test_autoindex(city_data: PrettyTable) -> None: """Testing that a table with a custom index row is equal to the one produced by the function .add_autoindex() """ - table1 = PrettyTable() - table1.field_names = ["City name", "Area", "Population", "Annual Rainfall"] - table1.add_row(["Adelaide", 1295, 1158259, 600.5]) - table1.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table1.add_row(["Darwin", 112, 120900, 1714.7]) - table1.add_row(["Hobart", 1357, 205556, 619.5]) - table1.add_row(["Sydney", 2058, 4336374, 1214.8]) - table1.add_row(["Melbourne", 1566, 3806092, 646.9]) - table1.add_row(["Perth", 5386, 1554769, 869.4]) - table1.add_autoindex(fieldname="Test") + city_data.field_names = CITY_DATA_HEADER + city_data.add_autoindex(fieldname="Test") table2 = PrettyTable() - table2.field_names = ["Test", "City name", "Area", "Population", "Annual Rainfall"] - table2.add_row([1, "Adelaide", 1295, 1158259, 600.5]) - table2.add_row([2, "Brisbane", 5905, 1857594, 1146.4]) - table2.add_row([3, "Darwin", 112, 120900, 1714.7]) - table2.add_row([4, "Hobart", 1357, 205556, 619.5]) - table2.add_row([5, "Sydney", 2058, 4336374, 1214.8]) - table2.add_row([6, "Melbourne", 1566, 3806092, 646.9]) - table2.add_row([7, "Perth", 5386, 1554769, 869.4]) + table2.field_names = ["Test"] + CITY_DATA_HEADER + for idx, row in enumerate(CITY_DATA): + table2.add_row([idx + 1] + row) - assert str(table1) == str(table2) + assert str(city_data) == str(table2) @pytest.fixture(scope="function") def unpadded_pt() -> PrettyTable: table = PrettyTable(header=False, padding_width=0) - table.add_row("abc") - table.add_row("def") - table.add_row("g..") + table.add_row(list("abc")) + table.add_row(list("def")) + table.add_row(list("g..")) return table @@ -2330,23 +1156,20 @@ class TestCustomFormatter: def test_set_custom_format_to_none_set_empty_dict(self) -> None: table = PrettyTable() table.custom_format = None - assert len(table.custom_format) == 0 assert isinstance(table.custom_format, dict) def test_set_custom_format_invalid_type_throw_error(self) -> None: table = PrettyTable() with pytest.raises(TypeError) as e: - table.custom_format = "Some String" + table.custom_format = "Some String" # type: ignore[assignment] assert "The custom_format property need to be a dictionary or callable" in str( e.value ) - def test_use_custom_formatter_for_int( - self, city_data_prettytable: PrettyTable - ) -> None: - city_data_prettytable.custom_format["Annual Rainfall"] = lambda n, v: f"{v:.2f}" + def test_use_custom_formatter_for_int(self, city_data: PrettyTable) -> None: + city_data.custom_format["Annual Rainfall"] = lambda n, v: f"{v:.2f}" assert ( - city_data_prettytable.get_string().strip() + city_data.get_string().strip() == """ +-----------+------+------------+-----------------+ | City name | Area | Population | Annual Rainfall | @@ -2417,7 +1240,84 @@ class TestRepr: assert row_prettytable._repr_html_() == row_prettytable.get_html_string() -class TestMinTableWidth: +class TestBreakOnHyphens: + row = [ + "bluedevil breeze breeze-gtk eos-bash-shared glib2 " + "kactivitymanagerd kde-cli-tools kde-gtk-config kdecoration" + ] + EXPECTED_TRUE = """+------------------------------------------+ +| Field 1 | ++------------------------------------------+ +| bluedevil breeze breeze-gtk eos-bash- | +| shared glib2 kactivitymanagerd kde-cli- | +| tools kde-gtk-config kdecoration | ++------------------------------------------+""" + EXPECTED_FALSE = """+------------------------------------------+ +| Field 1 | ++------------------------------------------+ +| bluedevil breeze breeze-gtk | +| eos-bash-shared glib2 kactivitymanagerd | +| kde-cli-tools kde-gtk-config kdecoration | ++------------------------------------------+""" + + def test_break_on_hyphens(self) -> None: + table = PrettyTable(max_width=40) + table.break_on_hyphens = False + assert not table.break_on_hyphens + table.add_row(self.row) + assert table.get_string().strip() == self.EXPECTED_FALSE + + def test_break_on_hyphens_on_init(self) -> None: + table = PrettyTable(max_width=40, break_on_hyphens=False) + assert not table._break_on_hyphens + assert not table.break_on_hyphens + table.add_row(self.row) + assert table.get_string().strip() == self.EXPECTED_FALSE + + def test_break_on_hyphens_default(self) -> None: + table = PrettyTable(max_width=40) + assert table.break_on_hyphens + table.add_row(self.row) + assert table.get_string().strip() == self.EXPECTED_TRUE + + +class TestWidth: + colored = "\033[31mC\033[32mO\033[31mL\033[32mO\033[31mR\033[32mE\033[31mD\033[0m" + + def test_color(self) -> None: + table = PrettyTable(["Field 1", "Field 2"]) + table.add_row([self.colored, self.colored]) + table.add_row(["nothing", "neither"]) + result = table.get_string() + assert ( + result.strip() + == f""" ++---------+---------+ +| Field 1 | Field 2 | ++---------+---------+ +| {self.colored} | {self.colored} | +| nothing | neither | ++---------+---------+ +""".strip() + ) + + def test_reset(self) -> None: + table = PrettyTable(["Field 1", "Field 2"]) + table.add_row(["abc def\033(B", "\033[31mabc def\033[m"]) + table.add_row(["nothing", "neither"]) + result = table.get_string() + assert ( + result.strip() + == """ ++---------+---------+ +| Field 1 | Field 2 | ++---------+---------+ +| abc def\033(B | \033[31mabc def\033[m | +| nothing | neither | ++---------+---------+ +""".strip() + ) + @pytest.mark.parametrize( "loops, fields, desired_width, border, internal_border", [ @@ -2442,7 +1342,12 @@ class TestMinTableWidth: ], ) def test_min_table_width( - self, loops, fields, desired_width, border, internal_border + self, + loops: int, + fields: list[str], + desired_width: int, + border: bool, + internal_border: bool, ) -> None: for col_width in range(loops): x = prettytable.PrettyTable() @@ -2469,8 +1374,6 @@ class TestMinTableWidth: desired_width, ] - -class TestMaxTableWidth: def test_max_table_width(self) -> None: table = PrettyTable() table.max_table_width = 5 @@ -2547,6 +1450,38 @@ class TestMaxTableWidth: +---+-----------------+---+-----------------+---+-----------------+""".strip() ) + @pytest.mark.parametrize("set_width_parameter", [True, False]) + def test_table_max_width_wo_header_width(self, set_width_parameter: bool) -> None: + headers = [ + "A Field Name", + "B Field Name", + "D Field Name", + "E Field Name", + "F Field Name", + "G Field Name", + "H Field Name", + "I Field Name", + "J Field Name", + "K Field Name", + "L Field Name", + "M Field Name", + ] + row = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + expected = """+---+---+---+---+---+---+---+---+---+---+----+----+ +| A | B | D | E | F | G | H | I | J | K | L | M | ++---+---+---+---+---+---+---+---+---+---+----+----+ +| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ++---+---+---+---+---+---+---+---+---+---+----+----+""" + + if set_width_parameter: + table = PrettyTable(headers, use_header_width=False) + else: + table = PrettyTable(headers) + table.use_header_width = False + table.add_row(row) + + assert table.get_string() == expected + def test_table_width_on_init_wo_columns(self) -> None: """See also #272""" table = PrettyTable(max_width=10) @@ -2665,51 +1600,6 @@ class TestMaxTableWidth: +--------+--------------+------------+""".strip() ) - def test_table_formatted_html_autoindex(self) -> None: - """See also #199""" - table = PrettyTable(["Field 1", "Field 2", "Field 3"]) - for row in range(1, 3 * 3, 3): - table.add_row( - [f"value {row*100}", f"value {row+1*100}", f"value {row+2*100}"] - ) - table.format = True - table.add_autoindex("I") - - assert ( - table.get_html_string().strip() - == """ -<table frame="box" rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">I</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 100</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 101</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 201</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">2</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 400</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 104</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 204</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">3</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 700</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 107</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 207</td> - </tr> - </tbody> -</table>""".strip() # noqa: E501 - ) - def test_max_table_width_wide_vrules_frame(self) -> None: table = PrettyTable() table.max_table_width = 52 @@ -2778,16 +1668,11 @@ class TestMaxTableWidth: class TestFields: def test_fields_at_class_declaration(self) -> None: table = PrettyTable( - field_names=["City name", "Area", "Population", "Annual Rainfall"], + field_names=CITY_DATA_HEADER, fields=["City name", "Annual Rainfall"], ) - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) + for row in CITY_DATA: + table.add_row(row) assert ( """+-----------+-----------------+ | City name | Annual Rainfall | @@ -2805,15 +1690,10 @@ class TestFields: def test_fields(self) -> None: table = PrettyTable() - table.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + table.field_names = CITY_DATA_HEADER table.fields = ["City name", "Annual Rainfall"] - table.add_row(["Adelaide", 1295, 1158259, 600.5]) - table.add_row(["Brisbane", 5905, 1857594, 1146.4]) - table.add_row(["Darwin", 112, 120900, 1714.7]) - table.add_row(["Hobart", 1357, 205556, 619.5]) - table.add_row(["Sydney", 2058, 4336374, 1214.8]) - table.add_row(["Melbourne", 1566, 3806092, 646.9]) - table.add_row(["Perth", 5386, 1554769, 869.4]) + for row in CITY_DATA: + table.add_row(row) assert ( """+-----------+-----------------+ | City name | Annual Rainfall | @@ -2830,136 +1710,63 @@ class TestFields: ) -class TestPreservingInternalBorders: - def test_internal_border_preserved(self) -> None: - pt = helper_table() - pt.border = False - pt.preserve_internal_border = True - - assert ( - pt.get_string().strip() - == """ - | Field 1 | Field 2 | Field 3 ----+---------+---------+--------- - 1 | value 1 | value2 | value3 - 4 | value 4 | value5 | value6 - 7 | value 7 | value8 | value9 -""".strip() # noqa: W291 - ) - - def test_internal_border_preserved_latex(self) -> None: - pt = helper_table() - pt.border = False - pt.format = True - pt.preserve_internal_border = True - - assert pt.get_latex_string().strip() == ( - "\\begin{tabular}{c|c|c|c}\r\n" - " & Field 1 & Field 2 & Field 3 \\\\\r\n" - "1 & value 1 & value2 & value3 \\\\\r\n" - "4 & value 4 & value5 & value6 \\\\\r\n" - "7 & value 7 & value8 & value9 \\\\\r\n" - "\\end{tabular}" - ) - - def test_internal_border_preserved_html(self) -> None: - pt = helper_table() - pt.format = True - pt.border = False - pt.preserve_internal_border = True - - assert ( - pt.get_html_string().strip() - == """ -<table rules="cols"> - <thead> - <tr> - <th style="padding-left: 1em; padding-right: 1em; text-align: center"></th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th> - <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th> - </tr> - </thead> - <tbody> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td> - </tr> - <tr> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td> - <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td> - </tr> - </tbody> -</table> -""".strip() # noqa: E501 - ) - - class TestGeneralOutput: - def test_copy(self) -> None: - # Arrange - t = helper_table() + def test_copy(self, helper_table: PrettyTable) -> None: + t_copy = helper_table.copy() + assert helper_table.get_string() == t_copy.get_string() - # Act - t_copy = t.copy() - - # Assert - assert t.get_string() == t_copy.get_string() - - def test_text(self) -> None: - t = helper_table() - assert t.get_formatted_string("text") == t.get_string() + def test_text(self, helper_table: PrettyTable) -> None: + assert helper_table.get_formatted_string("text") == helper_table.get_string() # test with default arg, too - assert t.get_formatted_string() == t.get_string() + assert helper_table.get_formatted_string() == helper_table.get_string() # args passed through - assert t.get_formatted_string(border=False) == t.get_string(border=False) - - def test_csv(self) -> None: - t = helper_table() - assert t.get_formatted_string("csv") == t.get_csv_string() - # args passed through - assert t.get_formatted_string("csv", border=False) == t.get_csv_string( + assert helper_table.get_formatted_string( border=False - ) + ) == helper_table.get_string(border=False) - def test_json(self) -> None: - t = helper_table() - assert t.get_formatted_string("json") == t.get_json_string() + def test_csv(self, helper_table: PrettyTable) -> None: + assert helper_table.get_formatted_string("csv") == helper_table.get_csv_string() # args passed through - assert t.get_formatted_string("json", border=False) == t.get_json_string( - border=False - ) + assert helper_table.get_formatted_string( + "csv", border=False + ) == helper_table.get_csv_string(border=False) - def test_html(self) -> None: - t = helper_table() - assert t.get_formatted_string("html") == t.get_html_string() - # args passed through - assert t.get_formatted_string("html", border=False) == t.get_html_string( - border=False + def test_json(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_formatted_string("json") == helper_table.get_json_string() ) + # args passed through + assert helper_table.get_formatted_string( + "json", border=False + ) == helper_table.get_json_string(border=False) - def test_latex(self) -> None: - t = helper_table() - assert t.get_formatted_string("latex") == t.get_latex_string() + def test_html(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_formatted_string("html") == helper_table.get_html_string() + ) # args passed through - assert t.get_formatted_string("latex", border=False) == t.get_latex_string( - border=False + assert helper_table.get_formatted_string( + "html", border=False + ) == helper_table.get_html_string(border=False) + + def test_latex(self, helper_table: PrettyTable) -> None: + assert ( + helper_table.get_formatted_string("latex") + == helper_table.get_latex_string() ) + # args passed through + assert helper_table.get_formatted_string( + "latex", border=False + ) == helper_table.get_latex_string(border=False) + + def test_mediawiki(self, helper_table: PrettyTable) -> None: + assert helper_table.get_formatted_string( + "mediawiki", border=False + ) == helper_table.get_mediawiki_string(border=False) - def test_invalid(self) -> None: - t = helper_table() + def test_invalid(self, helper_table: PrettyTable) -> None: with pytest.raises(ValueError): - t.get_formatted_string("pdf") + helper_table.get_formatted_string("pdf") class TestDeprecations: diff --git a/contrib/python/prettytable/py3/tests/test_sections.py b/contrib/python/prettytable/py3/tests/test_sections.py index 12c56d0021b..55b7065bb0b 100644 --- a/contrib/python/prettytable/py3/tests/test_sections.py +++ b/contrib/python/prettytable/py3/tests/test_sections.py @@ -1,7 +1,5 @@ from __future__ import annotations -from test_prettytable import helper_table - from prettytable import PrettyTable, TableStyle @@ -17,39 +15,50 @@ class TestRowEndSection: └──────────┴──────────┴──────────┘ """.strip() + TEST_ROWS = [ + ["value 4", "value 5", "value 6"], + ["value 7", "value 8", "value 9"], + ["value 10", "value 11", "value 12"], + ] + def test_row_end_section_via_argument(self) -> None: table = PrettyTable() table.set_style(TableStyle.SINGLE_BORDER) - table.add_row(["value 4", "value 5", "value 6"]) - table.add_row(["value 7", "value 8", "value 9"], divider=True) - table.add_row(["value 10", "value 11", "value 12"]) - + table.add_row(self.TEST_ROWS[0]) + table.add_row(self.TEST_ROWS[1], divider=True) + table.add_row(self.TEST_ROWS[2]) assert table.get_string().strip() == self.EXPECTED_RESULT def test_row_end_section_via_method(self) -> None: table = PrettyTable() table.set_style(TableStyle.SINGLE_BORDER) - table.add_row(["value 4", "value 5", "value 6"]) - table.add_row(["value 7", "value 8", "value 9"]) + table.add_row(self.TEST_ROWS[0]) + table.add_row(self.TEST_ROWS[1]) table.add_divider() - table.add_row(["value 10", "value 11", "value 12"]) + table.add_row(self.TEST_ROWS[2]) + assert table.get_string().strip() == self.EXPECTED_RESULT + def test_add_rows_divider(self) -> None: + """A table created with two add_rows calls, one with divider=True has a + divider""" + table = PrettyTable() + table.set_style(TableStyle.SINGLE_BORDER) + table.add_rows(self.TEST_ROWS[0:2], divider=True) + table.add_rows(self.TEST_ROWS[2:]) assert table.get_string().strip() == self.EXPECTED_RESULT class TestClearing: - def test_clear_rows(self) -> None: - t = helper_table() - t.add_row([0, "a", "b", "c"], divider=True) - t.clear_rows() - assert t.rows == [] - assert t.dividers == [] - assert t.field_names == ["", "Field 1", "Field 2", "Field 3"] + def test_clear_rows(self, helper_table: PrettyTable) -> None: + helper_table.add_row([0, "a", "b", "c"], divider=True) + helper_table.clear_rows() + assert helper_table.rows == [] + assert helper_table.dividers == [] + assert helper_table.field_names == ["", "Field 1", "Field 2", "Field 3"] - def test_clear(self) -> None: - t = helper_table() - t.add_row([0, "a", "b", "c"], divider=True) - t.clear() - assert t.rows == [] - assert t.dividers == [] - assert t.field_names == [] + def test_clear(self, helper_table: PrettyTable) -> None: + helper_table.add_row([0, "a", "b", "c"], divider=True) + helper_table.clear() + assert helper_table.rows == [] + assert helper_table.dividers == [] + assert helper_table.field_names == [] diff --git a/contrib/python/prettytable/py3/tests/test_sorting.py b/contrib/python/prettytable/py3/tests/test_sorting.py new file mode 100644 index 00000000000..1968c718f40 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_sorting.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from test_prettytable import CITY_DATA, CITY_DATA_HEADER + +from prettytable import PrettyTable, RowType + + +class TestSorting: + def test_sort_by_different_per_columns(self, city_data: PrettyTable) -> None: + city_data.sortby = city_data.field_names[0] + old = city_data.get_string() + for field in city_data.field_names[1:]: + city_data.sortby = field + new = city_data.get_string() + assert new != old + + def test_reverse_sort(self, city_data: PrettyTable) -> None: + for field in city_data.field_names: + city_data.sortby = field + city_data.reversesort = False + forward = city_data.get_string() + city_data.reversesort = True + backward = city_data.get_string() + forward_lines = forward.split("\n")[2:] # Discard header lines + backward_lines = backward.split("\n")[2:] + backward_lines.reverse() + assert forward_lines == backward_lines + + def test_sort_key(self, city_data: PrettyTable) -> None: + # Test sorting by length of city name + def key(vals: RowType) -> list[int]: + vals[0] = len(vals[0]) + return vals + + city_data.sortby = "City name" + city_data.sort_key = key + assert ( + city_data.get_string().strip() + == """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Perth | 5386 | 1554769 | 869.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Melbourne | 1566 | 3806092 | 646.9 | ++-----------+------+------------+-----------------+ +""".strip() + ) + + def test_sort_key_at_class_declaration(self) -> None: + # Test sorting by length of city name + def key(vals: RowType) -> list[int]: + vals[0] = len(vals[0]) + return vals + + table = PrettyTable( + field_names=CITY_DATA_HEADER, + sortby="City name", + sort_key=key, + ) + assert table.sort_key == key + for row in CITY_DATA: + table.add_row(row) + assert ( + """+-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Perth | 5386 | 1554769 | 869.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Melbourne | 1566 | 3806092 | 646.9 | ++-----------+------+------------+-----------------+""" + == table.get_string().strip() + ) + + def test_sort_slice(self) -> None: + """Make sure sorting and slicing interact in the expected way""" + table = PrettyTable(["Foo"]) + for i in range(20, 0, -1): + table.add_row([i]) + new_style = table.get_string(sortby="Foo", end=10) + assert "10" in new_style + assert "20" not in new_style + oldstyle = table.get_string(sortby="Foo", end=10, oldsortslice=True) + assert "10" not in oldstyle + assert "20" in oldstyle + + def test_sortby_at_class_declaration(self) -> None: + """ + Fix #354 where initialization of a table with sortby fails + """ + table = PrettyTable( + field_names=CITY_DATA_HEADER, + sortby="Area", + ) + assert table.sortby == "Area" + for row in CITY_DATA: + table.add_row(row) + assert ( + """+-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Darwin | 112 | 120900 | 1714.7 | +| Adelaide | 1295 | 1158259 | 600.5 | +| Hobart | 1357 | 205556 | 619.5 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Perth | 5386 | 1554769 | 869.4 | +| Brisbane | 5905 | 1857594 | 1146.4 | ++-----------+------+------------+-----------------+""" + == table.get_string().strip() + ) diff --git a/contrib/python/prettytable/py3/tests/test_style.py b/contrib/python/prettytable/py3/tests/test_style.py new file mode 100644 index 00000000000..2ae696e4bc3 --- /dev/null +++ b/contrib/python/prettytable/py3/tests/test_style.py @@ -0,0 +1,591 @@ +from __future__ import annotations + +import random + +import pytest +from pytest_lazy_fixtures import lf + +from prettytable import HRuleStyle, PrettyTable, TableStyle, VRuleStyle +from prettytable.prettytable import _str_block_width + + +class TestPositionalJunctions: + """Verify different cases for positional-junction characters""" + + def test_default(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + + assert ( + city_data.get_string().strip() + == """ +╔═══════════╦══════╦════════════╦═════════════════╗ +║ City name ║ Area ║ Population ║ Annual Rainfall ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ +║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ +║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ +║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ +║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ +║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ +║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ +╚═══════════╩══════╩════════════╩═════════════════╝""".strip() + ) + + def test_no_header(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.header = False + + assert ( + city_data.get_string().strip() + == """ +╔═══════════╦══════╦═════════╦════════╗ +║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ +║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ +║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ +║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ +║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ +║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ +║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ +╚═══════════╩══════╩═════════╩════════╝""".strip() + ) + + def test_with_title(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.title = "Title" + + assert ( + city_data.get_string().strip() + == """ +╔═════════════════════════════════════════════════╗ +║ Title ║ +╠═══════════╦══════╦════════════╦═════════════════╣ +║ City name ║ Area ║ Population ║ Annual Rainfall ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ +║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ +║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ +║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ +║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ +║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ +║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ +╚═══════════╩══════╩════════════╩═════════════════╝""".strip() + ) + + def test_with_title_no_header(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.title = "Title" + city_data.header = False + assert ( + city_data.get_string().strip() + == """ +╔═════════════════════════════════════╗ +║ Title ║ +╠═══════════╦══════╦═════════╦════════╣ +║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ +║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ +║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ +║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ +║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ +║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ +║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ +╚═══════════╩══════╩═════════╩════════╝""".strip() + ) + + def test_hrule_all(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.title = "Title" + city_data.hrules = HRuleStyle.ALL + assert ( + city_data.get_string().strip() + == """ +╔═════════════════════════════════════════════════╗ +║ Title ║ +╠═══════════╦══════╦════════════╦═════════════════╣ +║ City name ║ Area ║ Population ║ Annual Rainfall ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Adelaide ║ 1295 ║ 1158259 ║ 600.5 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Brisbane ║ 5905 ║ 1857594 ║ 1146.4 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Darwin ║ 112 ║ 120900 ║ 1714.7 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Hobart ║ 1357 ║ 205556 ║ 619.5 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Sydney ║ 2058 ║ 4336374 ║ 1214.8 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Melbourne ║ 1566 ║ 3806092 ║ 646.9 ║ +╠═══════════╬══════╬════════════╬═════════════════╣ +║ Perth ║ 5386 ║ 1554769 ║ 869.4 ║ +╚═══════════╩══════╩════════════╩═════════════════╝""".strip() + ) + + def test_vrules_none(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.vrules = VRuleStyle.NONE + assert ( + city_data.get_string().strip() + == "═══════════════════════════════════════════════════\n" + " City name Area Population Annual Rainfall \n" + "═══════════════════════════════════════════════════\n" + " Adelaide 1295 1158259 600.5 \n" + " Brisbane 5905 1857594 1146.4 \n" + " Darwin 112 120900 1714.7 \n" + " Hobart 1357 205556 619.5 \n" + " Sydney 2058 4336374 1214.8 \n" + " Melbourne 1566 3806092 646.9 \n" + " Perth 5386 1554769 869.4 \n" + "═══════════════════════════════════════════════════".strip() + ) + + def test_vrules_frame_with_title(self, city_data: PrettyTable) -> None: + city_data.set_style(TableStyle.DOUBLE_BORDER) + city_data.vrules = VRuleStyle.FRAME + city_data.title = "Title" + assert ( + city_data.get_string().strip() + == """ +╔═════════════════════════════════════════════════╗ +║ Title ║ +╠═════════════════════════════════════════════════╣ +║ City name Area Population Annual Rainfall ║ +╠═════════════════════════════════════════════════╣ +║ Adelaide 1295 1158259 600.5 ║ +║ Brisbane 5905 1857594 1146.4 ║ +║ Darwin 112 120900 1714.7 ║ +║ Hobart 1357 205556 619.5 ║ +║ Sydney 2058 4336374 1214.8 ║ +║ Melbourne 1566 3806092 646.9 ║ +║ Perth 5386 1554769 869.4 ║ +╚═════════════════════════════════════════════════╝""".strip() + ) + + +class TestStyle: + @pytest.mark.parametrize( + "style, expected", + [ + pytest.param( + TableStyle.DEFAULT, + """ ++---+---------+---------+---------+ +| | Field 1 | Field 2 | Field 3 | ++---+---------+---------+---------+ +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | ++---+---------+---------+---------+ +""", + id="DEFAULT", + ), + pytest.param( + TableStyle.MARKDOWN, # TODO fix + """ +| | Field 1 | Field 2 | Field 3 | +| :-: | :-----: | :-----: | :-----: | +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +""", + id="MARKDOWN", + ), + pytest.param( + TableStyle.MSWORD_FRIENDLY, + """ +| | Field 1 | Field 2 | Field 3 | +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +""", + id="MSWORD_FRIENDLY", + ), + pytest.param( + TableStyle.ORGMODE, + """ +|---+---------+---------+---------| +| | Field 1 | Field 2 | Field 3 | +|---+---------+---------+---------| +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +|---+---------+---------+---------| +""", + id="ORGMODE", + ), + pytest.param( + TableStyle.PLAIN_COLUMNS, + """ + Field 1 Field 2 Field 3 +1 value 1 value2 value3 +4 value 4 value5 value6 +7 value 7 value8 value9 +""", # noqa: W291 + id="PLAIN_COLUMNS", + ), + pytest.param( + TableStyle.RANDOM, + """ +'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' +% 1 value 1 value2 value3% +% 4 value 4 value5 value6% +% 7 value 7 value8 value9% +'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' +""", + id="RANDOM", + ), + pytest.param( + TableStyle.DOUBLE_BORDER, + """ +╔═══╦═════════╦═════════╦═════════╗ +║ ║ Field 1 ║ Field 2 ║ Field 3 ║ +╠═══╬═════════╬═════════╬═════════╣ +║ 1 ║ value 1 ║ value2 ║ value3 ║ +║ 4 ║ value 4 ║ value5 ║ value6 ║ +║ 7 ║ value 7 ║ value8 ║ value9 ║ +╚═══╩═════════╩═════════╩═════════╝ +""", + ), + pytest.param( + TableStyle.SINGLE_BORDER, + """ +┌───┬─────────┬─────────┬─────────┐ +│ │ Field 1 │ Field 2 │ Field 3 │ +├───┼─────────┼─────────┼─────────┤ +│ 1 │ value 1 │ value2 │ value3 │ +│ 4 │ value 4 │ value5 │ value6 │ +│ 7 │ value 7 │ value8 │ value9 │ +└───┴─────────┴─────────┴─────────┘ +""", + ), + ], + ) + def test_style( + self, helper_table: PrettyTable, style: TableStyle, expected: str + ) -> None: + random.seed(1234) + helper_table.set_style(style) + assert helper_table.get_string().strip() == expected.strip() + + def test_style_invalid(self, helper_table: PrettyTable) -> None: + # This is an hrule style, not a table style + with pytest.raises(ValueError): + helper_table.set_style(HRuleStyle.ALL) # type: ignore[arg-type] + + @pytest.mark.parametrize( + "original_style,style, expected", + [ + pytest.param( + TableStyle.MARKDOWN, + TableStyle.DEFAULT, + """ ++---+---------+---------+---------+ +| | Field 1 | Field 2 | Field 3 | ++---+---------+---------+---------+ +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | ++---+---------+---------+---------+ +""", + id="DEFAULT", + ), + pytest.param( + TableStyle.MSWORD_FRIENDLY, + TableStyle.MARKDOWN, + """ +| | Field 1 | Field 2 | Field 3 | +| :-: | :-----: | :-----: | :-----: | +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +""", + id="MARKDOWN", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.MSWORD_FRIENDLY, + """ +| | Field 1 | Field 2 | Field 3 | +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +""", + id="MSWORD_FRIENDLY", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.ORGMODE, + """ +|---+---------+---------+---------| +| | Field 1 | Field 2 | Field 3 | +|---+---------+---------+---------| +| 1 | value 1 | value2 | value3 | +| 4 | value 4 | value5 | value6 | +| 7 | value 7 | value8 | value9 | +|---+---------+---------+---------| +""", + id="ORGMODE", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.PLAIN_COLUMNS, + """ + Field 1 Field 2 Field 3 +1 value 1 value2 value3 +4 value 4 value5 value6 +7 value 7 value8 value9 +""", # noqa: W291 + id="PLAIN_COLUMNS", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.RANDOM, + """ +'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' +% 1 value 1 value2 value3% +% 4 value 4 value5 value6% +% 7 value 7 value8 value9% +'^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^' +""", + id="RANDOM", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.DOUBLE_BORDER, + """ +╔═══╦═════════╦═════════╦═════════╗ +║ ║ Field 1 ║ Field 2 ║ Field 3 ║ +╠═══╬═════════╬═════════╬═════════╣ +║ 1 ║ value 1 ║ value2 ║ value3 ║ +║ 4 ║ value 4 ║ value5 ║ value6 ║ +║ 7 ║ value 7 ║ value8 ║ value9 ║ +╚═══╩═════════╩═════════╩═════════╝ +""", + id="DOUBLE_BORDER", + ), + pytest.param( + TableStyle.MARKDOWN, + TableStyle.SINGLE_BORDER, + """ +┌───┬─────────┬─────────┬─────────┐ +│ │ Field 1 │ Field 2 │ Field 3 │ +├───┼─────────┼─────────┼─────────┤ +│ 1 │ value 1 │ value2 │ value3 │ +│ 4 │ value 4 │ value5 │ value6 │ +│ 7 │ value 7 │ value8 │ value9 │ +└───┴─────────┴─────────┴─────────┘ +""", + id="SINGLE_BORDER", + ), + ], + ) + def test_style_reset( + self, + helper_table: PrettyTable, + original_style: TableStyle, + style: TableStyle, + expected: str, + ) -> None: + """ + Testing to ensure that default styling is reset between changes + of styles on a PrettyTable + + Args: + style (str): Style to be used (Default, markdown, etc) + expected (str): The expected format of style as a string representation + """ + random.seed(1234) + helper_table.set_style(original_style) + helper_table.set_style(style) + assert helper_table.get_string().strip() == expected.strip() + + @pytest.mark.parametrize( + "style, expected", + [ + pytest.param( + TableStyle.MARKDOWN, + """ +| l | c | r | Align left | Align centre | Align right | +| :-| :-: |-: | :----------| :----------: |-----------: | +| 1 | 2 | 3 | value 1 | value2 | value3 | +| 4 | 5 | 6 | value 4 | value5 | value6 | +| 7 | 8 | 9 | value 7 | value8 | value9 | +""", + id="MARKDOWN", + ), + ], + ) + def test_style_align(self, style: TableStyle, expected: str) -> None: + table = PrettyTable( + ["l", "c", "r", "Align left", "Align centre", "Align right"] + ) + v = 1 + for row in range(3): + # Some have spaces, some not, to help test padding columns of + # different widths + table.add_row( + [v, v + 1, v + 2, f"value {v}", f"value{v + 1}", f"value{v + 2}"] + ) + v += 3 + + table.set_style(style) + table.align["l"] = table.align["Align left"] = "l" + table.align["c"] = table.align["Align centre"] = "c" + table.align["r"] = table.align["Align right"] = "r" + assert table.get_string().strip() == expected.strip() + + +def japanese_pretty_table() -> PrettyTable: + table = PrettyTable(["Kanji", "Hiragana", "English"]) + table.add_row(["神戸", "こうべ", "Kobe"]) + table.add_row(["京都", "きょうと", "Kyoto"]) + table.add_row(["長崎", "ながさき", "Nagasaki"]) + table.add_row(["名古屋", "なごや", "Nagoya"]) + table.add_row(["大阪", "おおさか", "Osaka"]) + table.add_row(["札幌", "さっぽろ", "Sapporo"]) + table.add_row(["東京", "とうきょう", "Tokyo"]) + table.add_row(["横浜", "よこはま", "Yokohama"]) + return table + + +def emoji_pretty_table() -> PrettyTable: + thunder1 = [ + '\033[38;5;226m _`/""\033[38;5;250m.-. \033[0m', + "\033[38;5;226m ,\\_\033[38;5;250m( ). \033[0m", + "\033[38;5;226m /\033[38;5;250m(___(__) \033[0m", + "\033[38;5;228;5m ⚡\033[38;5;111;25mʻ ʻ\033[38;5;228;5m" + "⚡\033[38;5;111;25mʻ ʻ \033[0m", + "\033[38;5;111m ʻ ʻ ʻ ʻ \033[0m", + ] + thunder2 = [ + "\033[38;5;240;1m .-. \033[0m", + "\033[38;5;240;1m ( ). \033[0m", + "\033[38;5;240;1m (___(__) \033[0m", + "\033[38;5;21;1m ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚\033[38;5;228;5m" + "⚡\033[38;5;21;25m‚ʻ \033[0m", + "\033[38;5;21;1m ‚ʻ‚ʻ\033[38;5;228;5m⚡\033[38;5;21;25mʻ‚ʻ \033[0m", + ] + table = PrettyTable(["Thunderbolt", "Lightning"]) + for i, t1 in enumerate(thunder1): + table.add_row([t1, thunder2[i]]) + return table + + +class TestMultiPattern: + @pytest.mark.parametrize( + ["pt", "expected_output", "test_type"], + [ + ( + lf("city_data"), + """ ++-----------+------+------------+-----------------+ +| City name | Area | Population | Annual Rainfall | ++-----------+------+------------+-----------------+ +| Adelaide | 1295 | 1158259 | 600.5 | +| Brisbane | 5905 | 1857594 | 1146.4 | +| Darwin | 112 | 120900 | 1714.7 | +| Hobart | 1357 | 205556 | 619.5 | +| Sydney | 2058 | 4336374 | 1214.8 | +| Melbourne | 1566 | 3806092 | 646.9 | +| Perth | 5386 | 1554769 | 869.4 | ++-----------+------+------------+-----------------+ +""", + "English Table", + ), + ( + lf("japanese_pretty_table"), + """ ++--------+------------+----------+ +| Kanji | Hiragana | English | ++--------+------------+----------+ +| 神戸 | こうべ | Kobe | +| 京都 | きょうと | Kyoto | +| 長崎 | ながさき | Nagasaki | +| 名古屋 | なごや | Nagoya | +| 大阪 | おおさか | Osaka | +| 札幌 | さっぽろ | Sapporo | +| 東京 | とうきょう | Tokyo | +| 横浜 | よこはま | Yokohama | ++--------+------------+----------+ + +""", + "Japanese table", + ), + ( + lf("emoji_pretty_table"), + """ ++-----------------+-----------------+ +| Thunderbolt | Lightning | ++-----------------+-----------------+ +| \x1b[38;5;226m _`/""\x1b[38;5;250m.-. \x1b[0m | \x1b[38;5;240;1m .-. \x1b[0m | +| \x1b[38;5;226m ,\\_\x1b[38;5;250m( ). \x1b[0m | \x1b[38;5;240;1m ( ). \x1b[0m | +| \x1b[38;5;226m /\x1b[38;5;250m(___(__) \x1b[0m | \x1b[38;5;240;1m (___(__) \x1b[0m | +| \x1b[38;5;228;5m ⚡\x1b[38;5;111;25mʻ ʻ\x1b[38;5;228;5m⚡\x1b[38;5;111;25mʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚\x1b[38;5;228;5m⚡\x1b[38;5;21;25m‚ʻ \x1b[0m | +| \x1b[38;5;111m ʻ ʻ ʻ ʻ \x1b[0m | \x1b[38;5;21;1m ‚ʻ‚ʻ\x1b[38;5;228;5m⚡\x1b[38;5;21;25mʻ‚ʻ \x1b[0m | ++-----------------+-----------------+ + """, # noqa: E501 + "Emoji table", + ), + ], + ) + def test_multi_pattern_outputs( + self, pt: PrettyTable, expected_output: str, test_type: str + ) -> None: + assert ( + pt.get_string().strip() == expected_output.strip() + ), f"Error output for test output of type {test_type}" + + +def test_colored_table() -> None: + table = PrettyTable(field_names=["Namespace", "Count"]) + table.title = "\x1b[34mHere be Table caption\x1b[39m" + assert ( + table.get_string() + == """+-----------------------+ +| \x1b[34mHere be Table caption\x1b[39m | ++-------------+---------+ +| Namespace | Count | ++-------------+---------+ ++-------------+---------+""" + ) + + +def test_link_and_color() -> None: + table = PrettyTable(["Link", "Count"]) + # Add link + text = "Click here" + table.add_row([f"\033]8;;https://example.com\033\\{text}\033]8;;\033\\", "1"]) + table.add_row(["No link", "2"]) + # Add link with colour + text = "Click \x1b[34mhere\x1b[39m" + table.add_row([f"\033]8;;https://example.com\033\\{text}\033]8;;\033\\", "3"]) + + assert ( + table.get_string() + == """\ ++------------+-------+ +| Link | Count | ++------------+-------+ +| \033]8;;https://example.com\033\\Click here\033]8;;\033\\ | 1 | +| No link | 2 | +| \033]8;;https://example.com\033\\Click \x1b[34mhere\x1b[39m\033]8;;\033\\ | 3 | ++------------+-------+""" + ) + + + ["test_input", "expected"], + [ + ("a", 1), + ("abc", 3), + ("abc def", 7), + ("\x1b[34mblue\x1b[39m", 4), + ("\033]8;;https://example.com\033\\link\033]8;;\033\\", 4), + # colour inside link + ("\033]8;;https://example.com\033\\\x1b[34mblue link\x1b[39m\033]8;;\033\\", 9), + # link inside colour + ("\x1b[34m\033]8;;https://example.com\033\\blue link\033]8;;\033\\\x1b[39m", 9), + ], +) +def test__str_block_width(test_input: str, expected: int) -> None: + assert _str_block_width(test_input) == expected diff --git a/contrib/python/prettytable/py3/ya.make b/contrib/python/prettytable/py3/ya.make index 0ed2577e62c..487fd84e582 100644 --- a/contrib/python/prettytable/py3/ya.make +++ b/contrib/python/prettytable/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(3.14.0) +VERSION(3.17.0) LICENSE(BSD-3-Clause) |
