Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-prettytable for openSUSE:Factory checked in at 2022-04-02 18:20:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-prettytable (Old) and /work/SRC/openSUSE:Factory/.python-prettytable.new.1900 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-prettytable" Sat Apr 2 18:20:02 2022 rev:3 rq:966024 version:3.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-prettytable/python-prettytable.changes 2022-01-05 13:39:37.393519475 +0100 +++ /work/SRC/openSUSE:Factory/.python-prettytable.new.1900/python-prettytable.changes 2022-04-02 18:20:06.614564916 +0200 @@ -1,0 +2,15 @@ +Wed Mar 30 09:21:10 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Update to 3.2.0 + * Drop support for EOL Python 3.6 (#152) @hugovk + * Use <caption> tags to print html table titles (#160) @daibhid + * Add colorful tables and themes (#140) @BD103 + * Convert None to empty cell or custom value (#164) @av-guy + * Resolve "KeyError" issue on _stringify_row (#167) + @michal-jagiello-tmpl + * Use concrete built-in exceptions instead of Exception base + class (#169) @hugovk + * Fix width for custom none_format (#174) @av-guy + * Enforce max widths for field names (#171) @OlafvdSpek + +------------------------------------------------------------------- Old: ---- prettytable-2.5.0.tar.gz New: ---- prettytable-3.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-prettytable.spec ++++++ --- /var/tmp/diff_new_pack.lrieLG/_old 2022-04-02 18:20:07.102559438 +0200 +++ /var/tmp/diff_new_pack.lrieLG/_new 2022-04-02 18:20:07.110559348 +0200 @@ -17,21 +17,24 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} +%{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 Name: python-prettytable -Version: 2.5.0 +Version: 3.2.0 Release: 0 Summary: Library for displaying tabular data in formatted fashion License: BSD-2-Clause URL: https://github.com/jazzband/prettytable Source0: https://files.pythonhosted.org/packages/source/p/prettytable/prettytable-%{version}.tar.gz -BuildRequires: %{python_module dbm} +BuildRequires: %{python_module base >= 3.7} BuildRequires: %{python_module importlib-metadata if %python-base < 3.8} BuildRequires: %{python_module pytest-lazy-fixture} BuildRequires: %{python_module pytest} BuildRequires: %{python_module setuptools_scm} BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module wcwidth} +BuildRequires: %{pythons} +BuildRequires: fdupes BuildRequires: python-rpm-macros BuildArch: noarch Requires: python-wcwidth @@ -51,12 +54,14 @@ %prep %setup -q -n prettytable-%{version} +sed -i '1 {/env python/d}' src/prettytable/prettytable.py %build %python_build %install %python_install +%python_expand %fdupes %{buildroot}%{$python_sitelib} %check export LANG=en_US.UTF-8 @@ -65,6 +70,7 @@ %files %{python_files} %license COPYING %doc CHANGELOG.md README.md -%{python_sitelib}/prettytable* +%{python_sitelib}/prettytable +%{python_sitelib}/prettytable-%{version}*-info %changelog ++++++ prettytable-2.5.0.tar.gz -> prettytable-3.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.github/workflows/labels.yml new/prettytable-3.2.0/.github/workflows/labels.yml --- old/prettytable-2.5.0/.github/workflows/labels.yml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.github/workflows/labels.yml 2022-03-07 08:13:49.000000000 +0100 @@ -1,12 +1,15 @@ name: Sync labels + on: push: branches: - master paths: - .github/labels.yml + workflow_dispatch: + jobs: - build: + sync: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.github/workflows/lint.yml new/prettytable-3.2.0/.github/workflows/lint.yml --- old/prettytable-2.5.0/.github/workflows/lint.yml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.github/workflows/lint.yml 2022-03-07 08:13:49.000000000 +0100 @@ -1,6 +1,6 @@ name: Lint -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: lint: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.github/workflows/release-drafter.yml new/prettytable-3.2.0/.github/workflows/release-drafter.yml --- old/prettytable-2.5.0/.github/workflows/release-drafter.yml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.github/workflows/release-drafter.yml 2022-03-07 08:13:49.000000000 +0100 @@ -5,13 +5,14 @@ # branches to consider in the event; optional, defaults to all branches: - master + workflow_dispatch: jobs: update_release_draft: permissions: contents: write pull-requests: read - if: github.repository == 'jazzband/prettytable' + if: github.repository_owner == 'jazzband' runs-on: ubuntu-latest steps: # Drafts your next release notes as pull requests are merged into "master" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.github/workflows/release.yml new/prettytable-3.2.0/.github/workflows/release.yml --- old/prettytable-2.5.0/.github/workflows/release.yml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.github/workflows/release.yml 2022-03-07 08:13:49.000000000 +0100 @@ -7,10 +7,11 @@ release: types: - published + workflow_dispatch: jobs: build: - if: github.repository == 'jazzband/prettytable' + if: github.repository_owner == 'jazzband' runs-on: ubuntu-latest steps: @@ -21,31 +22,20 @@ - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.8 - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - - name: Cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: release-${{ hashFiles('**/setup.py') }} - restore-keys: | - release- + python-version: "3.10" + cache: pip + cache-dependency-path: "setup.py" - name: Install dependencies run: | python -m pip install -U pip - python -m pip install -U setuptools twine wheel + python -m pip install -U build twine wheel - name: Build package run: | python setup.py --version - python setup.py sdist --format=gztar bdist_wheel - twine check dist/* + python -m build + twine check --strict dist/* - name: Upload packages to Jazzband if: github.event.action == 'published' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.github/workflows/test.yml new/prettytable-3.2.0/.github/workflows/test.yml --- old/prettytable-2.5.0/.github/workflows/test.yml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.github/workflows/test.yml 2022-03-07 08:13:49.000000000 +0100 @@ -1,17 +1,17 @@ name: Test -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] env: FORCE_COLOR: 1 jobs: - build: + test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: ["pypy-3.8", "3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["pypy-3.8", "3.7", "3.8", "3.9", "3.10"] os: [ubuntu-latest, macos-latest, windows-latest] include: # Include new variables for Codecov @@ -26,21 +26,8 @@ uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - - name: Cache - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: - ${{ matrix.os }}-${{ matrix.python-version }}-v1-${{ - hashFiles('**/setup.py') }} - restore-keys: | - ${{ matrix.os }}-${{ matrix.python-version }}-v1- + cache: pip + cache-dependency-path: "setup.py" - name: Install dependencies run: | @@ -52,7 +39,7 @@ tox -e py - name: Upload coverage - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: flags: ${{ matrix.codecov-flag }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/.pre-commit-config.yaml new/prettytable-3.2.0/.pre-commit-config.yaml --- old/prettytable-2.5.0/.pre-commit-config.yaml 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/.pre-commit-config.yaml 2022-03-07 08:13:49.000000000 +0100 @@ -1,18 +1,18 @@ repos: - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v2.31.0 hooks: - id: pyupgrade - args: ["--py36-plus"] + args: [--py37-plus] - repo: https://github.com/psf/black - rev: 21.9b0 + rev: 21.12b0 hooks: - id: black - args: ["--target-version", "py36"] + args: [--target-version=py37] - repo: https://github.com/PyCQA/isort - rev: 5.9.3 + rev: 5.10.1 hooks: - id: isort @@ -28,24 +28,23 @@ - id: python-check-blanket-noqa - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-merge-conflict - id: check-yaml - repo: https://github.com/asottile/setup-cfg-fmt - rev: v1.18.0 + rev: v1.20.0 hooks: - id: setup-cfg-fmt - args: ["--max-py-version=3.10"] - repo: https://github.com/tox-dev/tox-ini-fmt - rev: 0.5.1 + rev: 0.5.2 hooks: - id: tox-ini-fmt - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.4.1 + rev: v2.5.1 hooks: - id: prettier args: [--prose-wrap=always, --print-width=88] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/PKG-INFO new/prettytable-3.2.0/PKG-INFO --- old/prettytable-2.5.0/PKG-INFO 2021-12-20 16:34:33.773119000 +0100 +++ new/prettytable-3.2.0/PKG-INFO 2022-03-07 08:14:08.130133000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: prettytable -Version: 2.5.0 +Version: 3.2.0 Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format Home-page: https://github.com/jazzband/prettytable Author: Luke Maurits @@ -13,7 +13,6 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 @@ -21,7 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Text Processing -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/markdown Provides-Extra: tests License-File: COPYING @@ -523,6 +522,46 @@ print(x) ``` +### Changing the appearance of your table - with _colors_! + +PrettyTable has the functionality of printing your table with ANSI color codes. This +includes support for most Windows versions through +[Colorama](https://pypi.org/project/colorama/). To get started, import the `ColorTable` +class instead of `PrettyTable`. + +```diff +-from prettytable import PrettyTable ++from prettytable.colortable import ColorTable +``` + +The `ColorTable` class can be used the same as `PrettyTable`, but it adds an extra +property. You can now specify a custom _theme_ that will format your table with colors. + +```python +from prettytable.colortable import ColorTable, Themes + +x = ColorTable(theme=Themes.OCEAN) + +print(x) +``` + +#### Creating a custom theme + +The `Theme` class allows you to customize both the characters and colors used in your +table. + +| Argument | Description | +| ---------------------------------------------------------- | --------------------------------------------------------- | +| `default_color` | The color to use as default | +| `vertical_char`, `horizontal_char`, and `junction_char` | The characters used for creating the outline of the table | +| `vertical_color`, `horizontal_color`, and `junction_color` | The colors used to style each character. | + +> **Note:** Colors are formatted with the `Theme.format_code(s: str)` function. It +> accepts a string. If the string starts with an escape code (like `\x1b`) then it will +> return the given string. If the string is just whitespace, it will return `""`. If the +> string is a number (like `"34"`), it will automatically format it into an escape code. +> I recommend you look into the source code for more information. + ### Displaying your table in JSON PrettyTable will also print your tables in JSON, as a list of fields and an array of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/README.md new/prettytable-3.2.0/README.md --- old/prettytable-2.5.0/README.md 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/README.md 2022-03-07 08:13:49.000000000 +0100 @@ -495,6 +495,46 @@ print(x) ``` +### Changing the appearance of your table - with _colors_! + +PrettyTable has the functionality of printing your table with ANSI color codes. This +includes support for most Windows versions through +[Colorama](https://pypi.org/project/colorama/). To get started, import the `ColorTable` +class instead of `PrettyTable`. + +```diff +-from prettytable import PrettyTable ++from prettytable.colortable import ColorTable +``` + +The `ColorTable` class can be used the same as `PrettyTable`, but it adds an extra +property. You can now specify a custom _theme_ that will format your table with colors. + +```python +from prettytable.colortable import ColorTable, Themes + +x = ColorTable(theme=Themes.OCEAN) + +print(x) +``` + +#### Creating a custom theme + +The `Theme` class allows you to customize both the characters and colors used in your +table. + +| Argument | Description | +| ---------------------------------------------------------- | --------------------------------------------------------- | +| `default_color` | The color to use as default | +| `vertical_char`, `horizontal_char`, and `junction_char` | The characters used for creating the outline of the table | +| `vertical_color`, `horizontal_color`, and `junction_color` | The colors used to style each character. | + +> **Note:** Colors are formatted with the `Theme.format_code(s: str)` function. It +> accepts a string. If the string starts with an escape code (like `\x1b`) then it will +> return the given string. If the string is just whitespace, it will return `""`. If the +> string is a number (like `"34"`), it will automatically format it into an escape code. +> I recommend you look into the source code for more information. + ### Displaying your table in JSON PrettyTable will also print your tables in JSON, as a list of fields and an array of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/setup.cfg new/prettytable-3.2.0/setup.cfg --- old/prettytable-2.5.0/setup.cfg 2021-12-20 16:34:33.773119000 +0100 +++ new/prettytable-3.2.0/setup.cfg 2022-03-07 08:14:08.130133000 +0100 @@ -13,7 +13,6 @@ Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -29,10 +28,10 @@ install_requires = wcwidth importlib-metadata;python_version < '3.8' -python_requires = >=3.6 +python_requires = >=3.7 package_dir = =src setup_requires = - setuptools_scm + setuptools-scm [options.packages.find] where = src diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/src/prettytable/colortable.py new/prettytable-3.2.0/src/prettytable/colortable.py --- old/prettytable-2.5.0/src/prettytable/colortable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/prettytable-3.2.0/src/prettytable/colortable.py 2022-03-07 08:13:49.000000000 +0100 @@ -0,0 +1,96 @@ +from .prettytable import PrettyTable + +try: + from colorama import init +except ImportError: + # Do nothing if not installed + def init(): + pass + + +init() + +RESET_CODE = "\x1b[0m" + + +class Theme: + def __init__( + self, + default_color: str = "", + vertical_char: str = "|", + vertical_color: str = "", + horizontal_char: str = "-", + horizontal_color: str = "", + junction_char: str = "+", + junction_color: str = "", + ): + self.default_color = Theme.format_code(default_color) + self.vertical_char = vertical_char + self.vertical_color = Theme.format_code(vertical_color) + self.horizontal_char = horizontal_char + self.horizontal_color = Theme.format_code(horizontal_color) + self.junction_char = junction_char + self.junction_color = Theme.format_code(junction_color) + + def format_code(s: str) -> str: + """Takes string and intelligently puts it into an ANSI escape sequence""" + if s.strip() == "": + return "" + elif s.startswith("\x1b["): + return s + else: + return f"\x1b[{s}m" + + +class Themes: + DEFAULT = Theme() + OCEAN = Theme( + default_color="96", + vertical_color="34", + horizontal_color="34", + junction_color="36", + ) + + +class ColorTable(PrettyTable): + def __init__(self, field_names=None, **kwargs): + super().__init__(field_names=field_names, **kwargs) + # TODO: Validate option + + self.theme = kwargs.get("theme") or Themes.DEFAULT + + @property + def theme(self) -> Theme: + return self._theme + + @theme.setter + def theme(self, value: Theme): + self._theme = value + self.update_theme() + + def update_theme(self): + theme = self._theme + + self._vertical_char = ( + theme.vertical_color + + theme.vertical_char + + RESET_CODE + + theme.default_color + ) + + self._horizontal_char = ( + theme.horizontal_color + + theme.horizontal_char + + RESET_CODE + + theme.default_color + ) + + self._junction_char = ( + theme.junction_color + + theme.junction_char + + RESET_CODE + + theme.default_color + ) + + def get_string(self, **kwargs): + return super().get_string(**kwargs) + RESET_CODE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/src/prettytable/prettytable.py new/prettytable-3.2.0/src/prettytable/prettytable.py --- old/prettytable-2.5.0/src/prettytable/prettytable.py 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/src/prettytable/prettytable.py 2022-03-07 08:13:49.000000000 +0100 @@ -72,7 +72,6 @@ class PrettyTable: def __init__(self, field_names=None, **kwargs): - """Return a new PrettyTable instance Arguments: @@ -188,6 +187,7 @@ "valign", "max_width", "min_width", + "none_format", ] for option in self._options: if option in kwargs: @@ -199,6 +199,7 @@ self._start = kwargs["start"] or 0 self._end = kwargs["end"] or None self._fields = kwargs["fields"] or None + self._none_format = {} if kwargs["header"] in (True, False): self._header = kwargs["header"] @@ -227,6 +228,7 @@ self.int_format = kwargs["int_format"] or {} self.float_format = kwargs["float_format"] or {} self.custom_format = kwargs["custom_format"] or {} + self.none_format = kwargs["none_format"] or {} self._min_table_width = kwargs["min_table_width"] or None self._max_table_width = kwargs["max_table_width"] or None @@ -310,7 +312,7 @@ elif isinstance(index, int): new.add_row(self._rows[index]) else: - raise Exception(f"Index {index} is invalid, must be an integer or slice") + raise IndexError(f"Index {index} is invalid, must be an integer or slice") return new def __str__(self): @@ -342,6 +344,8 @@ def _validate_option(self, option, val): if option == "field_names": self._validate_field_names(val) + elif option == "none_format": + self._validate_none_format(val) elif option in ( "start", "end", @@ -407,7 +411,7 @@ try: assert len(val) == len(self._field_names) except AssertionError: - raise Exception( + raise ValueError( "Field name list has incorrect number of values, " f"(actual) {len(val)}!={len(self._field_names)} (expected)" ) @@ -415,7 +419,7 @@ try: assert len(val) == len(self._rows[0]) except AssertionError: - raise Exception( + raise ValueError( "Field name list has incorrect number of values, " f"(actual) {len(val)}!={len(self._rows[0])} (expected)" ) @@ -423,13 +427,22 @@ try: assert len(val) == len(set(val)) except AssertionError: - raise Exception("Field names must be unique") + raise ValueError("Field names must be unique") + + def _validate_none_format(self, val): + try: + if val is not None: + assert isinstance(val, str) + except AssertionError: + raise TypeError( + "Replacement for None value must be a string if being supplied." + ) def _validate_header_style(self, val): try: assert val in ("cap", "title", "upper", "lower", None) except AssertionError: - raise Exception( + raise ValueError( "Invalid header style, use cap, title, upper, lower or None" ) @@ -437,25 +450,25 @@ try: assert val in ["l", "c", "r"] except AssertionError: - raise Exception(f"Alignment {val} is invalid, use l, c or r") + raise ValueError(f"Alignment {val} is invalid, use l, c or r") def _validate_valign(self, val): try: assert val in ["t", "m", "b", None] except AssertionError: - raise Exception(f"Alignment {val} is invalid, use t, m, b or None") + raise ValueError(f"Alignment {val} is invalid, use t, m, b or None") def _validate_nonnegative_int(self, name, val): try: assert int(val) >= 0 except AssertionError: - raise Exception(f"Invalid value for {name}: {val}") + raise ValueError(f"Invalid value for {name}: {val}") def _validate_true_or_false(self, name, val): try: assert val in (True, False) except AssertionError: - raise Exception(f"Invalid value for {name}. Must be True or False.") + raise ValueError(f"Invalid value for {name}. Must be True or False.") def _validate_int_format(self, name, val): if val == "": @@ -464,7 +477,7 @@ assert isinstance(val, str) assert val.isdigit() except AssertionError: - raise Exception( + raise ValueError( f"Invalid value for {name}. Must be an integer format string." ) @@ -483,19 +496,21 @@ or (bits[1][-1] == "f" and bits[1].rstrip("f").isdigit()) ) except AssertionError: - raise Exception(f"Invalid value for {name}. Must be a float format string.") + raise ValueError( + f"Invalid value for {name}. Must be a float format string." + ) def _validate_function(self, name, val): try: assert hasattr(val, "__call__") except AssertionError: - raise Exception(f"Invalid value for {name}. Must be a function.") + raise ValueError(f"Invalid value for {name}. Must be a function.") def _validate_hrules(self, name, val): try: assert val in (ALL, FRAME, HEADER, NONE) except AssertionError: - raise Exception( + raise ValueError( f"Invalid value for {name}. Must be ALL, FRAME, HEADER or NONE." ) @@ -503,32 +518,32 @@ try: assert val in (ALL, FRAME, NONE) except AssertionError: - raise Exception(f"Invalid value for {name}. Must be ALL, FRAME, or NONE.") + raise ValueError(f"Invalid value for {name}. Must be ALL, FRAME, or NONE.") def _validate_field_name(self, name, val): try: assert (val in self._field_names) or (val is None) except AssertionError: - raise Exception(f"Invalid field name: {val}") + raise ValueError(f"Invalid field name: {val}") def _validate_all_field_names(self, name, val): try: for x in val: self._validate_field_name(name, x) except AssertionError: - raise Exception("Fields must be a sequence of field names") + raise ValueError("Fields must be a sequence of field names") def _validate_single_char(self, name, val): try: assert _str_block_width(val) == 1 except AssertionError: - raise Exception(f"Invalid value for {name}. Must be a string of length 1.") + raise ValueError(f"Invalid value for {name}. Must be a string of length 1.") def _validate_attributes(self, name, val): try: assert isinstance(val, dict) except AssertionError: - raise Exception("Attributes must be a dictionary of name/value pairs") + raise TypeError("Attributes must be a dictionary of name/value pairs") ############################## # ATTRIBUTE MANAGEMENT # @@ -548,6 +563,22 @@ self._xhtml = val @property + def none_format(self): + return self._none_format + + @none_format.setter + def none_format(self, val): + if not self._field_names: + self._none_format = {} + elif val is None or (isinstance(val, dict) and len(val) == 0): + 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 + + @property def field_names(self): """List or tuple of field names @@ -894,7 +925,7 @@ for field in self._field_names: self._custom_format[field] = val else: - raise Exception( + raise TypeError( "The custom_format property need to be a dictionary or callable" ) @@ -1221,7 +1252,7 @@ elif style == RANDOM: self._set_random_style() else: - raise Exception("Invalid pre-set style") + raise ValueError("Invalid pre-set style") def _set_orgmode_style(self): self._set_default_style() @@ -1342,7 +1373,7 @@ has fields""" if self._field_names and len(row) != len(self._field_names): - raise Exception( + raise ValueError( "Row has incorrect number of values, " f"(actual) {len(row)}!={len(self._field_names)} (expected)" ) @@ -1359,7 +1390,7 @@ row_index - The index of the row you want to delete. Indexing starts at 0.""" if row_index > len(self._rows) - 1: - raise Exception( + raise IndexError( f"Can't delete row at index {row_index}, " f"table only has {len(self._rows)} rows" ) @@ -1390,7 +1421,7 @@ self._rows.append([]) self._rows[i].append(column[i]) else: - raise Exception( + raise ValueError( f"Column length {len(column)} does not match number of rows " f"{len(self._rows)}" ) @@ -1414,7 +1445,7 @@ fieldname - The field name of the column you want to delete.""" if fieldname not in self._field_names: - raise Exception( + raise ValueError( "Can't delete column %r which is not a field name of this table." " Field names are: %s" % (fieldname, ", ".join(map(repr, self._field_names))) @@ -1479,6 +1510,9 @@ for row in rows: for index, value in enumerate(row): fieldname = self.field_names[index] + if self.none_format.get(fieldname) is not None: + if value == "None" or value is None: + value = self.none_format.get(fieldname) if fieldname in self.max_width: widths[index] = max( widths[index], @@ -1772,6 +1806,8 @@ fieldname = field.lower() else: fieldname = field + if _str_block_width(fieldname) > width: + fieldname = fieldname[:width] bits.append( " " * lpad + self._justify(fieldname, width, self._align[field]) @@ -1801,6 +1837,8 @@ lines = value.split("\n") new_lines = [] for line in lines: + if line == "None" and self.none_format.get(field) is not None: + line = self.none_format[field] if _str_block_width(line) > width: line = textwrap.fill(line, width) new_lines.append(line) @@ -2005,12 +2043,7 @@ # Title title = options["title"] or self._title if title: - cols = ( - len(options["fields"]) if options["fields"] else len(self.field_names) - ) - lines.append(" <tr>") - lines.append(f" <td colspan={cols}>{title}</td>") - lines.append(" </tr>") + lines.append(f" <caption>{title}</caption>") # Headers if options["header"]: @@ -2077,12 +2110,7 @@ # Title title = options["title"] or self._title if title: - cols = ( - len(options["fields"]) if options["fields"] else len(self.field_names) - ) - lines.append(" <tr>") - lines.append(f" <td colspan={cols}>{title}</td>") - lines.append(" </tr>") + lines.append(f" <caption>{title}</caption>") # Headers if options["header"]: @@ -2413,7 +2441,7 @@ try: assert len(tables) == 1 except AssertionError: - raise Exception( + raise ValueError( "More than one <table> in provided HTML code. Use from_html instead." ) return tables[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/src/prettytable.egg-info/PKG-INFO new/prettytable-3.2.0/src/prettytable.egg-info/PKG-INFO --- old/prettytable-2.5.0/src/prettytable.egg-info/PKG-INFO 2021-12-20 16:34:33.000000000 +0100 +++ new/prettytable-3.2.0/src/prettytable.egg-info/PKG-INFO 2022-03-07 08:14:07.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: prettytable -Version: 2.5.0 +Version: 3.2.0 Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format Home-page: https://github.com/jazzband/prettytable Author: Luke Maurits @@ -13,7 +13,6 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 @@ -21,7 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Text Processing -Requires-Python: >=3.6 +Requires-Python: >=3.7 Description-Content-Type: text/markdown Provides-Extra: tests License-File: COPYING @@ -523,6 +522,46 @@ print(x) ``` +### Changing the appearance of your table - with _colors_! + +PrettyTable has the functionality of printing your table with ANSI color codes. This +includes support for most Windows versions through +[Colorama](https://pypi.org/project/colorama/). To get started, import the `ColorTable` +class instead of `PrettyTable`. + +```diff +-from prettytable import PrettyTable ++from prettytable.colortable import ColorTable +``` + +The `ColorTable` class can be used the same as `PrettyTable`, but it adds an extra +property. You can now specify a custom _theme_ that will format your table with colors. + +```python +from prettytable.colortable import ColorTable, Themes + +x = ColorTable(theme=Themes.OCEAN) + +print(x) +``` + +#### Creating a custom theme + +The `Theme` class allows you to customize both the characters and colors used in your +table. + +| Argument | Description | +| ---------------------------------------------------------- | --------------------------------------------------------- | +| `default_color` | The color to use as default | +| `vertical_char`, `horizontal_char`, and `junction_char` | The characters used for creating the outline of the table | +| `vertical_color`, `horizontal_color`, and `junction_color` | The colors used to style each character. | + +> **Note:** Colors are formatted with the `Theme.format_code(s: str)` function. It +> accepts a string. If the string starts with an escape code (like `\x1b`) then it will +> return the given string. If the string is just whitespace, it will return `""`. If the +> string is a number (like `"34"`), it will automatically format it into an escape code. +> I recommend you look into the source code for more information. + ### Displaying your table in JSON PrettyTable will also print your tables in JSON, as a list of fields and an array of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/src/prettytable.egg-info/SOURCES.txt new/prettytable-3.2.0/src/prettytable.egg-info/SOURCES.txt --- old/prettytable-2.5.0/src/prettytable.egg-info/SOURCES.txt 2021-12-20 16:34:33.000000000 +0100 +++ new/prettytable-3.2.0/src/prettytable.egg-info/SOURCES.txt 2022-03-07 08:14:08.000000000 +0100 @@ -20,10 +20,12 @@ .github/workflows/release.yml .github/workflows/test.yml src/prettytable/__init__.py +src/prettytable/colortable.py src/prettytable/prettytable.py src/prettytable.egg-info/PKG-INFO src/prettytable.egg-info/SOURCES.txt src/prettytable.egg-info/dependency_links.txt src/prettytable.egg-info/requires.txt src/prettytable.egg-info/top_level.txt +tests/test_colortable.py tests/test_prettytable.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/tests/test_colortable.py new/prettytable-3.2.0/tests/test_colortable.py --- old/prettytable-2.5.0/tests/test_colortable.py 1970-01-01 01:00:00.000000000 +0100 +++ new/prettytable-3.2.0/tests/test_colortable.py 2022-03-07 08:13:49.000000000 +0100 @@ -0,0 +1,92 @@ +import pytest + +from prettytable import PrettyTable +from prettytable.colortable import RESET_CODE, ColorTable, Theme + + +@pytest.fixture +def row_prettytable(): + # Row by row... + row = PrettyTable() + row.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + row.add_row(["Adelaide", 1295, 1158259, 600.5]) + row.add_row(["Brisbane", 5905, 1857594, 1146.4]) + row.add_row(["Darwin", 112, 120900, 1714.7]) + row.add_row(["Hobart", 1357, 205556, 619.5]) + row.add_row(["Sydney", 2058, 4336374, 1214.8]) + row.add_row(["Melbourne", 1566, 3806092, 646.9]) + row.add_row(["Perth", 5386, 1554769, 869.4]) + return row + + +@pytest.fixture +def row_colortable(): + row = ColorTable() + row.field_names = ["City name", "Area", "Population", "Annual Rainfall"] + row.add_row(["Adelaide", 1295, 1158259, 600.5]) + row.add_row(["Brisbane", 5905, 1857594, 1146.4]) + row.add_row(["Darwin", 112, 120900, 1714.7]) + row.add_row(["Hobart", 1357, 205556, 619.5]) + row.add_row(["Sydney", 2058, 4336374, 1214.8]) + row.add_row(["Melbourne", 1566, 3806092, 646.9]) + row.add_row(["Perth", 5386, 1554769, 869.4]) + return row + + +@pytest.fixture +def color_theme(): + return Theme( + default_color="31", + vertical_color="32", + horizontal_color="33", + junction_color="34", + ) + + +class TestColorTable: + def test_themeless(self, row_prettytable, row_colortable): + # Not worth the logic customizing the reset code + # For now we'll just get rid of it + assert ( + row_colortable.get_string().replace(RESET_CODE, "") + == row_prettytable.get_string() + ) + + def test_theme_setter(self, color_theme): + table1 = ColorTable(theme=color_theme) + + table2 = ColorTable() + table2.theme = color_theme + + assert table1.theme == table2.theme + + dict1 = table1.__dict__ + dict2 = table2.__dict__ + + # So we don't compare functions + del dict1["_sort_key"] + del dict2["_sort_key"] + + assert dict1 == dict2 + + +class TestFormatCode: + def test_basic(self): + assert Theme.format_code("31") == "\x1b[31m" + + def test_prefix(self): + assert Theme.format_code("\x1b[35m") == "\x1b[35m" + + def test_escapes(self): + assert Theme.format_code("\033[41m") == "\x1b[41m" + assert Theme.format_code("\u001b[41m") == "\x1b[41m" + + def test_empty(self): + assert Theme.format_code("") == "" + + def test_stripped(self): + assert Theme.format_code("\t\t \t") == "" + + def test_multiple(self): + assert Theme.format_code("30;42") == "\x1b[30;42m" + assert Theme.format_code("\x1b[30;42m") == "\x1b[30;42m" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/tests/test_prettytable.py new/prettytable-3.2.0/tests/test_prettytable.py --- old/prettytable-2.5.0/tests/test_prettytable.py 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/tests/test_prettytable.py 2022-03-07 08:13:49.000000000 +0100 @@ -94,6 +94,119 @@ return mix +class TestNoneOption: + def test_none_char_valid_option(self): + PrettyTable(["Field 1", "Field 2", "Field 3"], none_format="") + + def test_none_char_invalid_option(self): + with pytest.raises(TypeError) as exc: + PrettyTable(["Field 1", "Field 2", "Field 3"], none_format=2) + assert "must be a string." in exc.value + + def test_no_value_replace_none(self): + t = PrettyTable(["Field 1", "Field 2", "Field 3"]) + t.add_row(["value 1", None, "value 2"]) + assert ( + t.get_string().strip() + == """ ++---------+---------+---------+ +| Field 1 | Field 2 | Field 3 | ++---------+---------+---------+ +| value 1 | None | value 2 | ++---------+---------+---------+ +""".strip() + ) + + def test_no_value_replace_none_with_default_field_names(self): + t = PrettyTable() + t.add_row(["value 1", "None", "value 2"]) + assert ( + t.get_string().strip() + == """ ++---------+---------+---------+ +| Field 1 | Field 2 | Field 3 | ++---------+---------+---------+ +| value 1 | None | value 2 | ++---------+---------+---------+ +""".strip() + ) + + def test_replace_none_all(self): + t = PrettyTable(["Field 1", "Field 2", "Field 3"], none_format="N/A") + t.add_row(["value 1", None, "None"]) + assert ( + t.get_string().strip() + == """ ++---------+---------+---------+ +| Field 1 | Field 2 | Field 3 | ++---------+---------+---------+ +| value 1 | N/A | N/A | ++---------+---------+---------+ +""".strip() + ) + + def test_replace_none_by_col(self): + t = PrettyTable(["Field 1", "Field 2", "Field 3"]) + t.none_format["Field 2"] = "N/A" + t.none_format["Field 3"] = "" + t.add_row(["value 1", None, None]) + assert ( + t.get_string().strip() + == """ ++---------+---------+---------+ +| Field 1 | Field 2 | Field 3 | ++---------+---------+---------+ +| value 1 | N/A | | ++---------+---------+---------+ +""".strip() + ) + + def test_replace_none_recompute_width(self): + t = PrettyTable() + t.add_row([None]) + t.none_format = "0123456789" + assert ( + t.get_string().strip() + == """ ++------------+ +| Field 1 | ++------------+ +| 0123456789 | ++------------+ +""".strip() + ) + + def test_replace_none_maintain_width_on_recompute(self): + t = PrettyTable() + t.add_row(["Hello"]) + t.none_format = "0123456789" + assert ( + t.get_string().strip() + == """ ++---------+ +| Field 1 | ++---------+ +| Hello | ++---------+ +""".strip() + ) + + def test_replace_none_recompute_width_multi_column(self): + t = PrettyTable() + t.add_row(["Hello", None, "World"]) + t.none_format = "0123456789" + assert ( + t.get_string().strip() + == """ ++---------+------------+---------+ +| Field 1 | Field 2 | Field 3 | ++---------+------------+---------+ +| Hello | 0123456789 | World | ++---------+------------+---------+ +""".strip() + ) + + class TestBuildEquivalence: """Make sure that building a table row-by-row and column-by-column yield the same results""" @@ -161,11 +274,11 @@ assert with_del.get_string() == without_row.get_string() - def test_delete_illegal_column_raises_exception(self): + def test_delete_illegal_column_raises_error(self): table = PrettyTable() table.add_column("City name", ["Adelaide", "Brisbane", "Darwin"]) - with pytest.raises(Exception): + with pytest.raises(ValueError): table.del_column("City not-a-name") @@ -886,6 +999,80 @@ """.strip() # noqa: E501 ) + def test_HtmlOutputWithTitle(self): + t = helper_table() + t.title = "Title" + result = t.get_html_string() + assert ( + result.strip() + == """ +<table> + <caption>Title</caption> + <thead> + <tr> + <th>Field 1</th> + <th>Field 2</th> + <th>Field 3</th> + </tr> + </thead> + <tbody> + <tr> + <td>value 1</td> + <td>value2</td> + <td>value3</td> + </tr> + <tr> + <td>value 4</td> + <td>value5</td> + <td>value6</td> + </tr> + <tr> + <td>value 7</td> + <td>value8</td> + <td>value9</td> + </tr> + </tbody> +</table> +""".strip() + ) + + def test_HtmlOutputFormattedWithTitle(self): + t = helper_table() + t.title = "Title" + result = t.get_html_string(format=True) + assert ( + result.strip() + == """ +<table frame="box" rules="cols"> + <caption>Title</caption> + <thead> + <tr> + <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">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">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">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 TestPositionalJunctions: """Verify different cases for positional-junction characters""" @@ -1091,12 +1278,10 @@ ), pytest.param( PLAIN_COLUMNS, - """ -Field 1 Field 2 Field 3 -value 1 value2 value3 -value 4 value5 value6 -value 7 value8 value9 -""", # noqa: W291 + "Field 1 Field 2 Field 3 \n" + "value 1 value2 value3 \n" + "value 4 value5 value6 \n" + "value 7 value8 value9", id="PLAIN_COLUMNS", ), pytest.param( @@ -1156,7 +1341,7 @@ # Act / Assert # This is an hrule style, not a table style - with pytest.raises(Exception): + with pytest.raises(ValueError): t.set_style(ALL) @pytest.mark.parametrize( @@ -1314,7 +1499,7 @@ def test_HtmlOneFailOnMany(self, city_data_prettytable: PrettyTable): html_string = city_data_prettytable.get_html_string() html_string += city_data_prettytable.get_html_string() - with pytest.raises(Exception): + with pytest.raises(ValueError): from_html_one(html_string) @@ -1551,7 +1736,7 @@ assert len(pt.custom_format) == 1 def test_init_custom_format_throw_error_is_not_callable(self): - with pytest.raises(Exception) as e: + with pytest.raises(ValueError) as e: PrettyTable(custom_format={"col1": "{:.2}"}) assert "Invalid value for custom_format.col1. Must be a function." in str( @@ -1571,7 +1756,7 @@ def test_set_custom_format_invalid_type_throw_error(self): pt = PrettyTable() - with pytest.raises(Exception) as e: + with pytest.raises(TypeError) as e: pt.custom_format = "Some String" assert "The custom_format property need to be a dictionary or callable" in str( e.value @@ -1649,3 +1834,21 @@ def test_jupyter_repr(self, row_prettytable: PrettyTable): assert row_prettytable._repr_html_() == row_prettytable.get_html_string() + + +class TestMaxTableWidth: + def test_max_table_width(self): + pt = PrettyTable() + pt.max_table_width = 5 + pt.add_row([0]) + + assert ( + pt.get_string().strip() + == """ ++-----+ +| Fie | ++-----+ +| 0 | ++-----+ +""".strip() + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/prettytable-2.5.0/tox.ini new/prettytable-3.2.0/tox.ini --- old/prettytable-2.5.0/tox.ini 2021-12-20 16:34:13.000000000 +0100 +++ new/prettytable-3.2.0/tox.ini 2022-03-07 08:13:49.000000000 +0100 @@ -1,7 +1,7 @@ [tox] envlist = lint - py{py3, 310, 39, 38, 37, 36} + py{py3, 310, 39, 38, 37} [testenv] passenv =