diff --git a/pyproject.toml b/pyproject.toml index 51549fd..0cf1e67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,21 +90,6 @@ exclude = ''' [tool.ruff] line-length = 120 -src = ["src/thoughtspot_tml"] -exclude = [ - "__pycache__", # ignore compiled bytecode - ".venv*", # ignore virtual environments - ".nox", # ignore virtual environments - - # project specific ignores - "__init__.py", # ignore __init__.py - "_version.py", # ignore __version__ - "*compat.py", # ignore compatibles - "_scriptability.py", # ignore auto-generated files -] - -[tool.ruff.lint] -ignore-init-module-imports = true select = [ "A", # flake8-builtins: https://pypi.org/project/flake8-builtins/ "ARG", # flake8-unused-arguments: https://pypi.org/project/flake8-unused-arguments/ @@ -121,11 +106,19 @@ select = [ "T20", # flake8-print: https://pypi.org/project/flake8-print/ "TCH", # flake8-type-checking: https://pypi.org/project/flake8-type-checking/ ] +src = ["src/thoughtspot_tml"] +exclude = [ + "__pycache__", # ignore compiled bytecode + ".venv*", # ignore virtual environments + ".nox", # ignore virtual environments -[tool.ruff.lint.flake8-import-conventions.aliases] -# Declare the default aliases. -datetime = "dt" -sqlalchemy = "sa" + # project specific ignores + "__init__.py", # ignore __init__.py + "_version.py", # ignore __version__ + "*compat.py", # ignore compatibles + "_scriptability.py", # ignore auto-generated files +] +ignore-init-module-imports = true [tool.ruff.lint.flake8-type-checking] runtime-evaluated-base-classes = [ @@ -140,9 +133,6 @@ runtime-evaluated-base-classes = [ "sqlmodel.SQLModel", ] -[flake8] -type-checking-pydantic-enabled = true - [tool.ruff.lint.isort] combine-as-imports = true force-wrap-aliases = true diff --git a/src/thoughtspot_tml/_tml.py b/src/thoughtspot_tml/_tml.py index d271520..2686591 100644 --- a/src/thoughtspot_tml/_tml.py +++ b/src/thoughtspot_tml/_tml.py @@ -4,6 +4,7 @@ from dataclasses import asdict, dataclass, fields, is_dataclass from typing import TYPE_CHECKING import json +import keyword import pathlib import re import typing @@ -67,6 +68,7 @@ def recursive_complex_attrs_to_dataclasses(instance: Any) -> None: for item in value: if is_dataclass(field_type) and isinstance(item, dict): + item = _sanitize_reserved_keyword_keys(item) item = field_type(**item) new_value.append(item) # type: ignore[attr-defined] @@ -84,6 +86,13 @@ def recursive_complex_attrs_to_dataclasses(instance: Any) -> None: setattr(instance, field.name, new_value) +def _sanitize_reserved_keyword_keys(mapping: Dict[str, Any]) -> Dict[str, Any]: + """ + Replace reserved keywords with a trailing sunder. + """ + return {(f"{k}_" if keyword.iskeyword(k) else k): v for k, v in mapping.items()} + + def _recursive_remove_null(mapping: Dict[str, Any]) -> Dict[str, Any]: """ Drop all keys with null values, they're optional. diff --git a/src/thoughtspot_tml/_version.py b/src/thoughtspot_tml/_version.py index 2ae4933..5015444 100644 --- a/src/thoughtspot_tml/_version.py +++ b/src/thoughtspot_tml/_version.py @@ -1 +1 @@ -__version__ = "2.0.14" +__version__ = "2.0.15" diff --git a/tests/test_tml.py b/tests/test_tml.py index 7234f85..dfcfd77 100644 --- a/tests/test_tml.py +++ b/tests/test_tml.py @@ -1,13 +1,13 @@ +from __future__ import annotations + +import warnings + +from thoughtspot_tml import Answer, Connection, Liveboard, Pinboard, SQLView, Table, View, Worksheet from thoughtspot_tml.exceptions import TMLExtensionWarning -from thoughtspot_tml import Connection -from thoughtspot_tml import Table, View, SQLView, Worksheet -from thoughtspot_tml import Answer, Liveboard, Pinboard from ward import test -import warnings from . import _const - for tml_cls, tml_type_name in ( (Connection, "connection"), (Table, "table"), @@ -22,9 +22,7 @@ @test("{tml_cls.__name__} roundtrips") def _(tml_cls=tml_cls, tml_type_name=tml_type_name): path = _const.DATA_DIR / f"DUMMY.{tml_type_name}.tml" - temp = _const.TEMP_DIR / f"DUMMY.{tml_type_name}.tml" - tml_cls.load(path).dump(temp) - assert path.read_text() == temp.read_text() + assert path.read_text() == tml_cls.load(path).dumps() @test("{tml_cls.__name__} type name is '{tml_type_name}'") def _(tml_cls=tml_cls, tml_type_name=tml_type_name):