diff -Nru python-tomli-w-0.4.0/.bumpversion.cfg python-tomli-w-1.0.0/.bumpversion.cfg --- python-tomli-w-0.4.0/.bumpversion.cfg 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/.bumpversion.cfg 2021-12-01 23:48:42.000000000 +0000 @@ -2,7 +2,7 @@ commit = True tag = True tag_name = {new_version} -current_version = 0.4.0 +current_version = 1.0.0 [bumpversion:file:pyproject.toml] search = version = "{current_version}" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT diff -Nru python-tomli-w-0.4.0/CHANGELOG.md python-tomli-w-1.0.0/CHANGELOG.md --- python-tomli-w-0.4.0/CHANGELOG.md 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/CHANGELOG.md 2021-12-01 23:48:42.000000000 +0000 @@ -1,5 +1,15 @@ # Changelog +## 1.0.0 + +- Removed + - Support for Python 3.6 + - Positional arguments of `dump` and `dumps` can no longer be passed by keyword. +- Changed + - Revised logic for when the "Array of Tables" syntax will be used. + AoT syntax is used when at least one of the tables needs multiple lines, or a single line wider than 100 chars, when rendered inline. + A nested structure no longer alone triggers the AoT syntax. + ## 0.4.0 - Added @@ -7,9 +17,9 @@ - Fixed - Formatting of `decimal.Decimal("inf")`, `decimal.Decimal("-inf")` and `decimal.Decimal("nan")`. - Changed - - A list of dicts is now rendered using the "Array of Tables" syntax, - but only if none of the tables is a nested structure, - and at least one of the tables would need a line wider than 100 chars to be rendered inline. + - A list of dicts is now rendered using the "Array of Tables" syntax + if at least one of the tables is a nested structure, + or at least one of the tables would need a line wider than 100 chars when rendered inline. Thank you [Anderson Bravalheri](https://github.com/abravalheri) for the [PR](https://github.com/hukkin/tomli-w/pull/15). diff -Nru python-tomli-w-0.4.0/debian/changelog python-tomli-w-1.0.0/debian/changelog --- python-tomli-w-0.4.0/debian/changelog 2021-12-01 15:55:19.000000000 +0000 +++ python-tomli-w-1.0.0/debian/changelog 2021-12-05 03:49:14.000000000 +0000 @@ -1,3 +1,10 @@ +python-tomli-w (1.0.0-1) unstable; urgency=medium + + * New upstream release + * Update autopkgtest to run for all supported python3 versions + + -- Scott Kitterman Sat, 04 Dec 2021 22:49:14 -0500 + python-tomli-w (0.4.0-2) unstable; urgency=medium * Source only upload to allow binary to be built on buildd diff -Nru python-tomli-w-0.4.0/debian/gbp.conf python-tomli-w-1.0.0/debian/gbp.conf --- python-tomli-w-0.4.0/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ python-tomli-w-1.0.0/debian/gbp.conf 2021-12-01 16:03:27.000000000 +0000 @@ -0,0 +1,2 @@ +[DEFAULT] +debian-branch=debian/master diff -Nru python-tomli-w-0.4.0/debian/tests/control python-tomli-w-1.0.0/debian/tests/control --- python-tomli-w-0.4.0/debian/tests/control 2021-11-27 01:54:25.000000000 +0000 +++ python-tomli-w-1.0.0/debian/tests/control 2021-12-05 03:49:14.000000000 +0000 @@ -1,3 +1,3 @@ Tests: run-unit-test -Depends: python3-tomli-w, python3-tomli, python3-pytest-xdist +Depends: python3-all, python3-tomli-w, python3-tomli, python3-pytest-xdist Restrictions: allow-stderr diff -Nru python-tomli-w-0.4.0/debian/tests/run-unit-test python-tomli-w-1.0.0/debian/tests/run-unit-test --- python-tomli-w-0.4.0/debian/tests/run-unit-test 2021-11-27 01:44:37.000000000 +0000 +++ python-tomli-w-1.0.0/debian/tests/run-unit-test 2021-12-05 03:49:14.000000000 +0000 @@ -15,5 +15,7 @@ cp -r ${START}/tests ./ cp -r ${START}/benchmark ./ -python3 -m pytest -n auto -k "not test_own_pyproject" --strict-markers \ - --strict-config -o xfail_strict=true +for py3vers in $(py3versions -s); do + echo "Testing with $py3vers:" + $py3vers -m pytest -v +done diff -Nru python-tomli-w-0.4.0/.github/workflows/tests.yaml python-tomli-w-1.0.0/.github/workflows/tests.yaml --- python-tomli-w-0.4.0/.github/workflows/tests.yaml 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/.github/workflows/tests.yaml 2021-12-01 23:48:42.000000000 +0000 @@ -32,7 +32,7 @@ runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['pypy-3.7', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11-dev'] + python-version: ['pypy-3.7', '3.7', '3.8', '3.9', '3.10', '3.11-dev'] os: [ubuntu-latest, macos-latest, windows-latest] continue-on-error: ${{ matrix.python-version == '3.11-dev' }} @@ -56,8 +56,8 @@ pytest --cov --cov-fail-under=100 - name: Report coverage - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.6' - uses: codecov/codecov-action@v1 + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' + uses: codecov/codecov-action@v2 allgood: runs-on: ubuntu-latest @@ -83,7 +83,7 @@ - name: Build and check run: | rm -rf dist/ && python -m build - twine check dist/* + twine check --strict dist/* - name: Publish run: | twine upload dist/* diff -Nru python-tomli-w-0.4.0/pyproject.toml python-tomli-w-1.0.0/pyproject.toml --- python-tomli-w-0.4.0/pyproject.toml 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/pyproject.toml 2021-12-01 23:48:42.000000000 +0000 @@ -4,13 +4,13 @@ [project] name = "tomli_w" -version = "0.4.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT +version = "1.0.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT description = "A lil' TOML writer" authors = [ { name = "Taneli Hukkinen", email = "hukkin@users.noreply.github.com" }, ] license = { file = "LICENSE" } -requires-python = ">=3.6" +requires-python = ">=3.7" readme = "README.md" classifiers = [ "License :: OSI Approved :: MIT License", @@ -18,7 +18,6 @@ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "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", @@ -35,15 +34,6 @@ "Changelog" = "https://github.com/hukkin/tomli-w/blob/master/CHANGELOG.md" -[tool.flit.sdist] -exclude = [ - "tests/", - "benchmark/", - ".*", - "CHANGELOG.md", -] - - [tool.isort] # Force imports to be sorted by module, independent of import type force_sort_within_sections = true @@ -66,17 +56,17 @@ legacy_tox_ini = ''' [tox] # Only run pytest envs when no args given to tox -envlist = py{36,37,38,39,310} +envlist = py{37,38,39,310} isolated_build = True -[testenv:py{36,37,38,39,310}] +[testenv:py{37,38,39,310}] description = run tests against unpackaged source skip_install = True deps = -r tests/requirements.txt commands = pytest {posargs} -[testenv:py{36,37,38,39,310}-package] +[testenv:py{37,38,39,310}-package] description = run tests against a built package deps = -r tests/requirements.txt commands = diff -Nru python-tomli-w-0.4.0/tests/test_style.py python-tomli-w-1.0.0/tests/test_style.py --- python-tomli-w-0.4.0/tests/test_style.py 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/tests/test_style.py 2021-12-01 23:48:42.000000000 +0000 @@ -251,3 +251,24 @@ actual = tomli_w.dumps(example) assert actual == expected assert tomli.loads(actual) == example + + +def test_multiline_in_aot(): + data = {"aot": [{"multiline_string": "line1\nline2"}]} + assert ( + tomli_w.dumps(data, multiline_strings=True) + == '''\ +[[aot]] +multiline_string = """ +line1 +line2""" +''' + ) + assert ( + tomli_w.dumps(data, multiline_strings=False) + == """\ +aot = [ + { multiline_string = "line1\\nline2" }, +] +""" + ) diff -Nru python-tomli-w-0.4.0/tomli_w/__init__.py python-tomli-w-1.0.0/tomli_w/__init__.py --- python-tomli-w-0.4.0/tomli_w/__init__.py 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/tomli_w/__init__.py 2021-12-01 23:48:42.000000000 +0000 @@ -1,4 +1,4 @@ __all__ = ("dumps", "dump") -__version__ = "0.4.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT +__version__ = "1.0.0" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT from tomli_w._writer import dump, dumps diff -Nru python-tomli-w-0.4.0/tomli_w/_writer.py python-tomli-w-1.0.0/tomli_w/_writer.py --- python-tomli-w-0.4.0/tomli_w/_writer.py 2021-10-19 15:22:44.000000000 +0000 +++ python-tomli-w-1.0.0/tomli_w/_writer.py 2021-12-01 23:48:42.000000000 +0000 @@ -1,18 +1,11 @@ +from __future__ import annotations + +from collections.abc import Generator, Mapping from datetime import date, datetime, time from decimal import Decimal import string from types import MappingProxyType -from typing import ( - Any, - BinaryIO, - Dict, - Generator, - List, - Mapping, - NamedTuple, - Tuple, - Union, -) +from typing import Any, BinaryIO, NamedTuple ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127)) ILLEGAL_BASIC_STR_CHARS = frozenset('"\\') | ASCII_CTRL - frozenset("\t") @@ -33,35 +26,39 @@ ) -def dump(obj: Dict[str, Any], fp: BinaryIO, *, multiline_strings: bool = False) -> None: - opts = Opts(multiline_strings) - for chunk in gen_table_chunks(obj, opts, name=""): - fp.write(chunk.encode()) +def dump( + __obj: dict[str, Any], __fp: BinaryIO, *, multiline_strings: bool = False +) -> None: + ctx = Context(multiline_strings, {}) + for chunk in gen_table_chunks(__obj, ctx, name=""): + __fp.write(chunk.encode()) -def dumps(obj: Dict[str, Any], *, multiline_strings: bool = False) -> str: - opts = Opts(multiline_strings) - return "".join(gen_table_chunks(obj, opts, name="")) +def dumps(__obj: dict[str, Any], *, multiline_strings: bool = False) -> str: + ctx = Context(multiline_strings, {}) + return "".join(gen_table_chunks(__obj, ctx, name="")) -class Opts(NamedTuple): +class Context(NamedTuple): allow_multiline: bool + # cache rendered inline tables (mapping from object id to rendered inline table) + inline_table_cache: dict[int, str] def gen_table_chunks( table: Mapping[str, Any], - opts: Opts, + ctx: Context, *, name: str, inside_aot: bool = False, ) -> Generator[str, None, None]: yielded = False literals = [] - tables: List[Tuple[str, Any, bool]] = [] # => [(key, value, inside_aot)] + tables: list[tuple[str, Any, bool]] = [] # => [(key, value, inside_aot)] for k, v in table.items(): if isinstance(v, dict): tables.append((k, v, False)) - elif is_aot(v) and not all(is_suitable_inline_table(t, opts) for t in v): + elif is_aot(v) and not all(is_suitable_inline_table(t, ctx) for t in v): tables.extend((k, t, True) for t in v) else: literals.append((k, v)) @@ -73,7 +70,7 @@ if literals: yielded = True for k, v in literals: - yield f"{format_key_part(k)} = {format_literal(v, opts)}\n" + yield f"{format_key_part(k)} = {format_literal(v, ctx)}\n" for k, v, in_aot in tables: if yielded: @@ -82,10 +79,10 @@ yielded = True key_part = format_key_part(k) display_name = f"{name}.{key_part}" if name else key_part - yield from gen_table_chunks(v, opts, name=display_name, inside_aot=in_aot) + yield from gen_table_chunks(v, ctx, name=display_name, inside_aot=in_aot) -def format_literal(obj: object, opts: Opts, *, nest_level: int = 0) -> str: +def format_literal(obj: object, ctx: Context, *, nest_level: int = 0) -> str: if isinstance(obj, bool): return "true" if obj else "false" if isinstance(obj, (int, float, date, datetime)): @@ -97,11 +94,11 @@ raise ValueError("TOML does not support offset times") return str(obj) if isinstance(obj, str): - return format_string(obj, allow_multiline=opts.allow_multiline) + return format_string(obj, allow_multiline=ctx.allow_multiline) if isinstance(obj, ARRAY_TYPES): - return format_inline_array(obj, opts, nest_level) + return format_inline_array(obj, ctx, nest_level) if isinstance(obj, dict): - return format_inline_table(obj, opts) + return format_inline_table(obj, ctx) raise TypeError(f"Object of type {type(obj)} is not TOML serializable") @@ -115,19 +112,28 @@ return str(obj) -def format_inline_table(obj: dict, opts: Opts) -> str: +def format_inline_table(obj: dict, ctx: Context) -> str: + # check cache first + obj_id = id(obj) + if obj_id in ctx.inline_table_cache: + return ctx.inline_table_cache[obj_id] + if not obj: - return "{}" - return ( - "{ " - + ", ".join( - f"{format_key_part(k)} = {format_literal(v, opts)}" for k, v in obj.items() + rendered = "{}" + else: + rendered = ( + "{ " + + ", ".join( + f"{format_key_part(k)} = {format_literal(v, ctx)}" + for k, v in obj.items() + ) + + " }" ) - + " }" - ) + ctx.inline_table_cache[obj_id] = rendered + return rendered -def format_inline_array(obj: Union[tuple, list], opts: Opts, nest_level: int) -> str: +def format_inline_array(obj: tuple | list, ctx: Context, nest_level: int) -> str: if not obj: return "[]" item_indent = ARRAY_INDENT * (1 + nest_level) @@ -135,7 +141,7 @@ return ( "[\n" + ",\n".join( - item_indent + format_literal(item, opts, nest_level=nest_level + 1) + item_indent + format_literal(item, ctx, nest_level=nest_level + 1) for item in obj ) + f",\n{closing_bracket_indent}]" @@ -179,24 +185,15 @@ def is_aot(obj: Any) -> bool: - """Decides if object behaves as an array of tables (i.e. list of dicts). - - See: https://toml.io/en/v1.0.0#array-of-tables. - """ + """Decides if an object behaves as an array of tables (i.e. a nonempty list + of dicts).""" return bool( isinstance(obj, ARRAY_TYPES) and obj and all(isinstance(v, dict) for v in obj) ) -def is_suitable_inline_table(obj: dict, opts: Opts) -> bool: - """Uses heuristics to decide if the inline-style representation is a good - choice for a given dict. - - For example, the spec strongly discourages inline tables that - contain line breaks. See: https://toml.io/en/v1.0.0#inline-table - """ - if any(isinstance(v, ARRAY_TYPES + (dict,)) for v in obj.values()): - # Tomli-W will automatically introduce line breaks when converting lists. - # It also prefers to not have nested inline tables. - return False - return len(f"{ARRAY_INDENT}{format_inline_table(obj, opts)},") <= MAX_LINE_LENGTH +def is_suitable_inline_table(obj: dict, ctx: Context) -> bool: + """Use heuristics to decide if the inline-style representation is a good + choice for a given table.""" + rendered_inline = f"{ARRAY_INDENT}{format_inline_table(obj, ctx)}," + return len(rendered_inline) <= MAX_LINE_LENGTH and "\n" not in rendered_inline