diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 456f20551..e3da3ea5e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,7 @@ on: [push, pull_request] jobs: build: runs-on: ubuntu-latest - name: black-flake8-mypy + name: black-ruff-mypy steps: - uses: actions/checkout@v3 - name: Set up Python 3.10 @@ -21,9 +21,10 @@ jobs: run: | black --diff --color . black --check . - - name: Lint with flake8 - run: | - flake8 . --statistics + - name: Lint with ruff + run: | + ruff check . + - name: Lint with mypy run: | mypy altair tests diff --git a/Makefile b/Makefile index cd2511d4c..670946e0c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ install: test : black --diff --color --check . - flake8 . --statistics + ruff check . mypy altair tests python -m pytest --pyargs --doctest-modules tests diff --git a/altair/__init__.py b/altair/__init__.py index 9913585b1..2ac4ff065 100644 --- a/altair/__init__.py +++ b/altair/__init__.py @@ -1,4 +1,4 @@ -# flake8: noqa +# ruff: noqa __version__ = "5.0.0dev" from typing import Any diff --git a/altair/_magics.py b/altair/_magics.py index a5edbb260..7fe613118 100644 --- a/altair/_magics.py +++ b/altair/_magics.py @@ -44,7 +44,7 @@ def _prepare_data(data, data_transformers): elif isinstance(data, str): return {"url": data} else: - warnings.warn("data of type {} not recognized".format(type(data))) + warnings.warn("data of type {} not recognized".format(type(data)), stacklevel=1) return data @@ -94,11 +94,11 @@ def vegalite(line, cell): elif not YAML_AVAILABLE: try: spec = json.loads(cell) - except json.JSONDecodeError: + except json.JSONDecodeError as err: raise ValueError( "%%vegalite: spec is not valid JSON. " "Install pyyaml to parse spec as yaml" - ) + ) from err else: spec = yaml.load(cell, Loader=yaml.SafeLoader) diff --git a/altair/expr/__init__.py b/altair/expr/__init__.py index 962a271ea..6ba7f8b8b 100644 --- a/altair/expr/__init__.py +++ b/altair/expr/__init__.py @@ -1,5 +1,5 @@ """Tools for creating transform & filter expressions with a python syntax""" -# flake8: noqa +# ruff: noqa from typing import Any from .core import datum, Expression diff --git a/altair/utils/core.py b/altair/utils/core.py index 94033cd31..640d337f3 100644 --- a/altair/utils/core.py +++ b/altair/utils/core.py @@ -227,7 +227,8 @@ def infer_vegalite_type(data): else: warnings.warn( "I don't know how to infer vegalite type from '{}'. " - "Defaulting to nominal.".format(typ) + "Defaulting to nominal.".format(typ), + stacklevel=1, ) return "nominal" @@ -483,15 +484,15 @@ def parse_shorthand( valid_typecodes = list(TYPECODE_MAP) + list(INV_TYPECODE_MAP) - units = dict( - field="(?P.*)", - type="(?P{})".format("|".join(valid_typecodes)), - agg_count="(?Pcount)", - op_count="(?Pcount)", - aggregate="(?P{})".format("|".join(AGGREGATES)), - window_op="(?P{})".format("|".join(AGGREGATES + WINDOW_AGGREGATES)), - timeUnit="(?P{})".format("|".join(TIMEUNITS)), - ) + units = { + "field": "(?P.*)", + "type": "(?P{})".format("|".join(valid_typecodes)), + "agg_count": "(?Pcount)", + "op_count": "(?Pcount)", + "aggregate": "(?P{})".format("|".join(AGGREGATES)), + "window_op": "(?P{})".format("|".join(AGGREGATES + WINDOW_AGGREGATES)), + "timeUnit": "(?P{})".format("|".join(TIMEUNITS)), + } patterns = [] @@ -710,7 +711,9 @@ def _wrap_in_channel_class(obj, encoding): return [_wrap_in_channel_class(subobj, encoding) for subobj in obj] if encoding not in name_to_channel: - warnings.warn("Unrecognized encoding channel '{}'".format(encoding)) + warnings.warn( + "Unrecognized encoding channel '{}'".format(encoding), stacklevel=1 + ) return obj classes = name_to_channel[encoding] diff --git a/altair/utils/data.py b/altair/utils/data.py index 7990b63e3..28e66bfab 100644 --- a/altair/utils/data.py +++ b/altair/utils/data.py @@ -258,6 +258,7 @@ def pipe(data, *funcs): "alt.pipe() is deprecated, and will be removed in a future release. " "Use toolz.curried.pipe() instead.", AltairDeprecationWarning, + stacklevel=1, ) return curried.pipe(data, *funcs) @@ -271,6 +272,7 @@ def curry(*args, **kwargs): "alt.curry() is deprecated, and will be removed in a future release. " "Use toolz.curried.curry() instead.", AltairDeprecationWarning, + stacklevel=1, ) return curried.curry(*args, **kwargs) @@ -284,14 +286,14 @@ def import_pyarrow_interchange(): import pyarrow.interchange as pi return pi - except pkg_resources.DistributionNotFound: + except pkg_resources.DistributionNotFound as err: # The package is not installed raise ImportError( "Usage of the DataFrame Interchange Protocol requires the package 'pyarrow', but it is not installed." - ) - except pkg_resources.VersionConflict: + ) from err + except pkg_resources.VersionConflict as err: # The package is installed but does not meet the minimum version requirement raise ImportError( "The installed version of 'pyarrow' does not meet the minimum requirement of version 11.0.0. " "Please update 'pyarrow' to use the DataFrame Interchange Protocol." - ) + ) from err diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py index 54e5159a1..f0ed26ae9 100644 --- a/altair/utils/deprecation.py +++ b/altair/utils/deprecation.py @@ -62,7 +62,7 @@ def _deprecate(obj, name=None, message=None): @functools.wraps(obj) def new_obj(*args, **kwargs): - warnings.warn(message, AltairDeprecationWarning) + warnings.warn(message, AltairDeprecationWarning, stacklevel=1) return obj(*args, **kwargs) new_obj._deprecated = True diff --git a/altair/utils/html.py b/altair/utils/html.py index 6be18139c..07d59fd24 100644 --- a/altair/utils/html.py +++ b/altair/utils/html.py @@ -251,14 +251,14 @@ def spec_to_html( if mode == "vega-lite" and vegalite_version is None: raise ValueError("must specify vega-lite version for mode='vega-lite'") - render_kwargs = dict() + render_kwargs = {} if template == "inline": try: from altair_viewer import get_bundled_script - except ImportError: + except ImportError as err: raise ImportError( "The altair_viewer package is required to convert to HTML with inline=True" - ) + ) from err render_kwargs["vega_script"] = get_bundled_script("vega", vega_version) render_kwargs["vegalite_script"] = get_bundled_script( "vega-lite", vegalite_version diff --git a/altair/utils/plugin_registry.py b/altair/utils/plugin_registry.py index 29772f5fa..37d3db222 100644 --- a/altair/utils/plugin_registry.py +++ b/altair/utils/plugin_registry.py @@ -157,11 +157,11 @@ def _enable(self, name: str, **options) -> None: for ep in importlib_metadata_get(self.entry_point_group) if ep.name == name ] - except ValueError: + except ValueError as err: if name in self.entrypoint_err_messages: - raise ValueError(self.entrypoint_err_messages[name]) + raise ValueError(self.entrypoint_err_messages[name]) from err else: - raise NoSuchEntryPoint(self.entry_point_group, name) + raise NoSuchEntryPoint(self.entry_point_group, name) from err value = cast(PluginType, ep.load()) self.register(name, value) self._active_name = name diff --git a/altair/utils/save.py b/altair/utils/save.py index c3cd11759..77ffd3dc7 100644 --- a/altair/utils/save.py +++ b/altair/utils/save.py @@ -30,7 +30,7 @@ def set_inspect_format_argument(format, fp, inline): ) if format != "html" and inline: - warnings.warn("inline argument ignored for non HTML formats.") + warnings.warn("inline argument ignored for non HTML formats.", stacklevel=1) return format diff --git a/altair/utils/schemapi.py b/altair/utils/schemapi.py index 03b5fc1ee..76fbcb059 100644 --- a/altair/utils/schemapi.py +++ b/altair/utils/schemapi.py @@ -564,11 +564,22 @@ def to_dict(self, validate=True, ignore=None, context=None): try: self.validate(result) except jsonschema.ValidationError as err: - raise SchemaValidationError(self, err) + # We do not raise `from err` as else the resulting + # traceback is very long as it contains part + # of the Vega-Lite schema. It would also first + # show the less helpful ValidationError instead of + # the more user friendly SchemaValidationError + raise SchemaValidationError(self, err) from None return result def to_json( - self, validate=True, ignore=[], context={}, indent=2, sort_keys=True, **kwargs + self, + validate=True, + ignore=None, + context=None, + indent=2, + sort_keys=True, + **kwargs, ): """Emit the JSON representation for this object as a string. @@ -577,7 +588,7 @@ def to_json( validate : boolean If True (default), then validate the output dictionary against the schema. - ignore : list + ignore : list (optional) A list of keys to ignore. This will *not* passed to child to_dict function calls. context : dict (optional) @@ -595,6 +606,10 @@ def to_json( spec : string The JSON specification of the chart object. """ + if ignore is None: + ignore = [] + if context is None: + context = {} dct = self.to_dict(validate=validate, ignore=ignore, context=context) return json.dumps(dct, indent=indent, sort_keys=sort_keys, **kwargs) diff --git a/altair/vegalite/__init__.py b/altair/vegalite/__init__.py index eec9f692d..690d64e63 100644 --- a/altair/vegalite/__init__.py +++ b/altair/vegalite/__init__.py @@ -1,2 +1,2 @@ -# flake8: noqa +# ruff: noqa from .v5 import * diff --git a/altair/vegalite/api.py b/altair/vegalite/api.py index a898d5ff9..6602986fe 100644 --- a/altair/vegalite/api.py +++ b/altair/vegalite/api.py @@ -1,2 +1,2 @@ -# flake8: noqa +# ruff: noqa from .v5.api import * diff --git a/altair/vegalite/schema.py b/altair/vegalite/schema.py index ebb3b4d08..e94c3d199 100644 --- a/altair/vegalite/schema.py +++ b/altair/vegalite/schema.py @@ -1,3 +1,3 @@ """Altair schema wrappers""" -# flake8: noqa +# ruff: noqa from .v5.schema import * diff --git a/altair/vegalite/v5/__init__.py b/altair/vegalite/v5/__init__.py index 62c05b398..5d05c1e0a 100644 --- a/altair/vegalite/v5/__init__.py +++ b/altair/vegalite/v5/__init__.py @@ -1,4 +1,4 @@ -# flake8: noqa +# ruff: noqa from .schema import * from .api import * diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py index 4ab47e774..8b9f80de0 100644 --- a/altair/vegalite/v5/api.py +++ b/altair/vegalite/v5/api.py @@ -112,7 +112,7 @@ def _prepare_data(data, context=None): # if data is still not a recognized type, then return if not isinstance(data, (dict, core.Data)): - warnings.warn("data of type {} not recognized".format(type(data))) + warnings.warn("data of type {} not recognized".format(type(data)), stacklevel=1) return data @@ -355,12 +355,14 @@ def param( warnings.warn( """The value of 'empty' should be True or False.""", utils.AltairDeprecationWarning, + stacklevel=1, ) parameter.empty = False elif parameter.empty == "all": warnings.warn( """The value of 'empty' should be True or False.""", utils.AltairDeprecationWarning, + stacklevel=1, ) parameter.empty = True elif (parameter.empty is False) or (parameter.empty is True): @@ -372,6 +374,7 @@ def param( warnings.warn( """Use 'value' instead of 'init'.""", utils.AltairDeprecationWarning, + stacklevel=1, ) if value is Undefined: kwds["value"] = kwds.pop("init") @@ -416,6 +419,7 @@ def _selection(type=Undefined, **kwds): """The types 'single' and 'multi' are now combined and should be specified using "selection_point()".""", utils.AltairDeprecationWarning, + stacklevel=1, ) else: raise ValueError("""'type' must be 'point' or 'interval'""") @@ -2261,11 +2265,11 @@ def show(self, embed_opt=None, open_browser=None): """ try: import altair_viewer # type: ignore - except ImportError: + except ImportError as err: raise ValueError( "'show' method requires the altair_viewer package. " "See http://github.com/altair-viz/altair_viewer" - ) + ) from err altair_viewer.show(self, embed_opt=embed_opt, open_browser=open_browser) @utils.use_signature(core.Resolve) diff --git a/altair/vegalite/v5/schema/__init__.py b/altair/vegalite/v5/schema/__init__.py index 131be341b..d00e0f816 100644 --- a/altair/vegalite/v5/schema/__init__.py +++ b/altair/vegalite/v5/schema/__init__.py @@ -1,4 +1,4 @@ -# flake8: noqa +# ruff: noqa from .core import * from .channels import * SCHEMA_VERSION = 'v5.6.1' diff --git a/doc/releases/changes.rst b/doc/releases/changes.rst index ab08579b7..09e731e92 100644 --- a/doc/releases/changes.rst +++ b/doc/releases/changes.rst @@ -52,6 +52,11 @@ Backward-Incompatible Changes - Removed the Vega-Lite 3 and 4 wrappers (#2847). - In regards to the grammar changes listed above, the old terminology will still work in many basic cases. On the other hand, if that old terminology gets used at a lower level, then it most likely will not work. For example, in the current version of :ref:`gallery_scatter_with_minimap`, two instances of the key ``param`` are used in dictionaries to specify axis domains. Those used to be ``selection``, but that usage is not compatible with the current Vega-Lite schema. +Maintenance +~~~~~~~~~~~ + +- Vega-Altair now uses ``ruff`` for linting. + Version 4.2.2 (released Jan 27, 2023) ------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 0afd3ccd2..354e75fc3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,37 @@ exclude = ''' requires = ["setuptools >= 40.6.0, <64", "wheel"] build-backend = "setuptools.build_meta" +[tool.ruff] +target-version = "py310" +line-length = 88 +select = [ + # flake8-bugbear + "B", + # flake8-comprehensions + "C4", + # pycodestyle-error + "E", + # pycodestyle-warning + "W", + # pyflakes + "F", + # flake8-tidy-imports + "TID" +] +# ignore = ["E203", "E266", "E501", "W503"] see https://github.com/charliermarsh/ruff/issues/2402 +ignore = ["E501", "TID252", "B905"] +exclude = [ + ".git", + "build", + "__pycache__", + "tests/examples_arguments_syntax", + "tests/examples_methods_syntax", + "altair/vegalite/v?/schema", +] + +[tool.ruff.mccabe] +max-complexity = 18 + [tool.pytest.ini_options] markers = [ "save_engine: marks some of the tests which are using an external package to save a chart to e.g. a png file. This mark is used to run those tests selectively in the build GitHub Action.", diff --git a/requirements_dev.txt b/requirements_dev.txt index f8ccff938..177acafd5 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,6 +1,6 @@ black<24 ipython -flake8 +ruff pytest pytest-cov m2r diff --git a/setup.cfg b/setup.cfg index fc33fc4c6..46c5b3cab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,3 @@ -[flake8] -max-line-length = 88 -ignore = E203, E266, E501, W503 -max-complexity = 18 -select = B,C,E,F,W,T4,B9 -exclude = - .git, - build, - __pycache__, - tests/examples_arguments_syntax, - tests/examples_methods_syntax, - altair/vegalite/v?/schema - [metadata] description-file = README.md -license_file = LICENSE +license_file = LICENSE \ No newline at end of file diff --git a/sphinxext/altairgallery.py b/sphinxext/altairgallery.py index e3a145d02..94f4066aa 100644 --- a/sphinxext/altairgallery.py +++ b/sphinxext/altairgallery.py @@ -179,7 +179,7 @@ def save_example_pngs(examples, image_dir, make_thumbnails=True): chart.save(image_file) hashes[filename] = example_hash except ImportError: - warnings.warn("Unable to save image: using generic image") + warnings.warn("Unable to save image: using generic image", stacklevel=1) create_generic_image(image_file) with open(hash_file, "w") as f: diff --git a/sphinxext/altairplot.py b/sphinxext/altairplot.py index d7bc68c58..6c4faa359 100644 --- a/sphinxext/altairplot.py +++ b/sphinxext/altairplot.py @@ -241,15 +241,15 @@ def html_visit_altair_plot(self, node): with contextlib.redirect_stdout(f): chart = eval_block(node["code"], namespace) stdout = f.getvalue() - except Exception as e: + except Exception as err: message = "altair-plot: {}:{} Code Execution failed:" "{}: {}".format( - node["rst_source"], node["rst_lineno"], e.__class__.__name__, str(e) + node["rst_source"], node["rst_lineno"], err.__class__.__name__, str(err) ) if node["strict"]: - raise ValueError(message) from e + raise ValueError(message) from err else: - warnings.warn(message) - raise nodes.SkipNode + warnings.warn(message, stacklevel=1) + raise nodes.SkipNode from err chart_name = node["chart-var-name"] if chart_name is not None: @@ -283,8 +283,8 @@ def html_visit_altair_plot(self, node): # Last line should be a chart; convert to spec dict try: spec = chart.to_dict() - except alt.utils.schemapi.SchemaValidationError: - raise ValueError("Invalid chart: {0}".format(node["code"])) + except alt.utils.schemapi.SchemaValidationError as err: + raise ValueError("Invalid chart: {0}".format(node["code"])) from err actions = node["links"] # TODO: add an option to save chart specs to file & load from there. @@ -313,7 +313,8 @@ def html_visit_altair_plot(self, node): warnings.warn( "altair-plot: {}:{} Malformed block. Last line of " "code block should define a valid altair Chart object." - "".format(node["rst_source"], node["rst_lineno"]) + "".format(node["rst_source"], node["rst_lineno"]), + stacklevel=1, ) raise nodes.SkipNode diff --git a/sphinxext/schematable.py b/sphinxext/schematable.py index 336b4c189..c70060b32 100644 --- a/sphinxext/schematable.py +++ b/sphinxext/schematable.py @@ -37,7 +37,8 @@ def type_description(schema): ) else: warnings.warn( - "cannot infer type for schema with keys {}" "".format(schema.keys()) + "cannot infer type for schema with keys {}" "".format(schema.keys()), + stacklevel=1, ) return "--" diff --git a/tests/expr/test_expr.py b/tests/expr/test_expr.py index f57a1f3c9..a5b6870bd 100644 --- a/tests/expr/test_expr.py +++ b/tests/expr/test_expr.py @@ -99,8 +99,9 @@ def test_datum_getattr(): x = datum["foo"] assert repr(x) == "datum['foo']" + magic_attr = "__magic__" with pytest.raises(AttributeError): - datum.__magic__ + getattr(datum, magic_attr) def test_expression_getitem(): diff --git a/tests/test_examples.py b/tests/test_examples.py index 4dcfee36b..578f38303 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -19,7 +19,7 @@ def iter_examples_filenames(syntax_module): - for importer, modname, ispkg in pkgutil.iter_modules(syntax_module.__path__): + for _importer, modname, ispkg in pkgutil.iter_modules(syntax_module.__path__): if ispkg or modname.startswith("_"): continue yield modname + ".py" @@ -41,11 +41,11 @@ def test_render_examples_to_chart(syntax_module): try: assert isinstance(chart.to_dict(), dict) - except Exception as e: + except Exception as err: raise AssertionError( f"Example file {filename} raised an exception when " - f"converting to a dict: {e}" - ) + f"converting to a dict: {err}" + ) from err # We do not apply the save_engine mark to this test. This mark is used in diff --git a/tests/test_toplevel.py b/tests/test_toplevel.py index 0da425ada..a8b0673e1 100644 --- a/tests/test_toplevel.py +++ b/tests/test_toplevel.py @@ -17,4 +17,4 @@ def test_completeness_of__all__(): # If the assert statement fails below, there are probably either new objects # in the top-level Altair namespace or some were removed. # In that case, run tools/update_init_file.py to update __all__ - assert getattr(alt, "__all__") == relevant_attributes + assert alt.__all__ == relevant_attributes diff --git a/tests/utils/test_core.py b/tests/utils/test_core.py index 91dd93eac..376c72a32 100644 --- a/tests/utils/test_core.py +++ b/tests/utils/test_core.py @@ -226,11 +226,11 @@ def _getargs(*args, **kwargs): def test_infer_encoding_types(channels): - expected = dict( - x=channels.X("xval"), - y=channels.YValue("yval"), - strokeWidth=channels.StrokeWidthValue(value=4), - ) + expected = { + "x": channels.X("xval"), + "y": channels.YValue("yval"), + "strokeWidth": channels.StrokeWidthValue(value=4), + } # All positional args args, kwds = _getargs( @@ -258,20 +258,20 @@ def test_infer_encoding_types_with_condition(): opacity=alt.condition("pred3", "ofield:N", alt.value(0.2)), ) - expected = dict( - size=channels.SizeValue( + expected = { + "size": channels.SizeValue( 2, condition=alt.ConditionalPredicateValueDefnumberExprRef( value=1, test=alt.Predicate("pred1") ), ), - color=channels.Color( + "color": channels.Color( "cfield:N", condition=alt.ConditionalPredicateValueDefGradientstringnullExprRef( value="red", test=alt.Predicate("pred2") ), ), - opacity=channels.OpacityValue( + "opacity": channels.OpacityValue( 0.2, condition=alt.ConditionalPredicateMarkPropFieldOrDatumDef( field=alt.FieldName("ofield"), @@ -279,7 +279,7 @@ def test_infer_encoding_types_with_condition(): type=alt.StandardType("nominal"), ), ), - ) + } assert infer_encoding_types(args, kwds, channels) == expected diff --git a/tests/utils/test_schemapi.py b/tests/utils/test_schemapi.py index 8bc11b09b..2b5b96501 100644 --- a/tests/utils/test_schemapi.py +++ b/tests/utils/test_schemapi.py @@ -335,8 +335,9 @@ def test_copy_module(dct): def test_attribute_error(): m = MySchema() + invalid_attr = "invalid_attribute" with pytest.raises(AttributeError) as err: - m.invalid_attribute + getattr(m, invalid_attr) assert str(err.value) == ( "'MySchema' object has no attribute " "'invalid_attribute'" ) @@ -418,7 +419,9 @@ def chart_example_hconcat(): text = ( alt.Chart(source) .mark_text(align="right") - .encode(alt.Text("Horsepower:N", title=dict(text="Horsepower", align="right"))) + .encode( + alt.Text("Horsepower:N", title={"text": "Horsepower", "align": "right"}) + ) ) return points | text diff --git a/tests/vegalite/v5/test_alias.py b/tests/vegalite/v5/test_alias.py index b9051f13a..f39ab57cc 100644 --- a/tests/vegalite/v5/test_alias.py +++ b/tests/vegalite/v5/test_alias.py @@ -9,8 +9,8 @@ def test_aliases(): # this test pass if the alias can resolve to its real name try: getattr(alt, alias) - except AttributeError as e: - assert False, f"cannot resolve '{alias}':, {e}" + except AttributeError as err: + raise AssertionError(f"cannot resolve '{alias}':, {err}") from err # this test fails if the alias match a colliding name in core with pytest.raises(AttributeError): diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py index 437956f23..e3d27d14a 100644 --- a/tests/vegalite/v5/test_api.py +++ b/tests/vegalite/v5/test_api.py @@ -285,8 +285,9 @@ def test_selection_expression(): assert isinstance(selection["value"], alt.expr.Expression) assert selection["value"].to_dict() == "{0}['value']".format(selection.name) + magic_attr = "__magic__" with pytest.raises(AttributeError): - selection.__magic__ + getattr(selection, magic_attr) @pytest.mark.save_engine @@ -481,7 +482,7 @@ def test_selection(): # test adding to chart chart = alt.Chart().add_params(single) chart = chart.add_params(multi, interval) - assert set(x.name for x in chart.params) == {"selec_1", "selec_2", "selec_3"} + assert {x.name for x in chart.params} == {"selec_1", "selec_2", "selec_3"} # test logical operations assert isinstance(single & multi, alt.SelectionPredicateComposition) @@ -504,7 +505,7 @@ def test_transforms(): agg1 = alt.AggregatedFieldDef(**{"as": "x1", "op": "mean", "field": "y"}) agg2 = alt.AggregatedFieldDef(**{"as": "x2", "op": "median", "field": "z"}) chart = alt.Chart().transform_aggregate([agg1], ["foo"], x2="median(z)") - kwds = dict(aggregate=[agg1, agg2], groupby=["foo"]) + kwds = {"aggregate": [agg1, agg2], "groupby": ["foo"]} assert chart.transform == [alt.AggregateTransform(**kwds)] # bin transform diff --git a/tests/vegalite/v5/test_geo_interface.py b/tests/vegalite/v5/test_geo_interface.py index 613cf0065..bc3dc6444 100644 --- a/tests/vegalite/v5/test_geo_interface.py +++ b/tests/vegalite/v5/test_geo_interface.py @@ -7,7 +7,7 @@ class Geom: pass geom_obj = Geom() - setattr(geom_obj, "__geo_interface__", geom) + geom_obj.__geo_interface__ = geom return geom_obj @@ -74,13 +74,11 @@ def test_geo_interface_serializing_arrays_tuples(): "bbox": arr.array("d", [1, 2, 3, 4]), "geometry": { "coordinates": [ - tuple( - ( - tuple((6.90, 53.48)), - tuple((5.98, 51.85)), - tuple((6.07, 53.51)), - tuple((6.90, 53.48)), - ) + ( + (6.90, 53.48), + (5.98, 51.85), + (6.07, 53.51), + (6.90, 53.48), ) ], "type": "Polygon", diff --git a/tests/vegalite/v5/test_renderers.py b/tests/vegalite/v5/test_renderers.py index 94717e629..8a62829e1 100644 --- a/tests/vegalite/v5/test_renderers.py +++ b/tests/vegalite/v5/test_renderers.py @@ -25,7 +25,7 @@ def assert_has_options(chart, **opts): with alt.renderers.enable(renderer): assert_has_options(chart, mode="vega-lite") - with alt.renderers.enable(embed_options=dict(actions={"export": True})): + with alt.renderers.enable(embed_options={"actions": {"export": True}}): assert_has_options(chart, mode="vega-lite", actions={"export": True}) with alt.renderers.set_embed_options(actions=True): diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py index cd895bd54..794fc31f1 100644 --- a/tools/generate_schema_wrapper.py +++ b/tools/generate_schema_wrapper.py @@ -581,7 +581,7 @@ def vegalite_main(skip_download=False): outfile = join(schemapath, "__init__.py") print("Writing {}".format(outfile)) with open(outfile, "w", encoding="utf8") as f: - f.write("# flake8: noqa\n") + f.write("# ruff: noqa\n") f.write("from .core import *\nfrom .channels import *\n") f.write( "SCHEMA_VERSION = {!r}\n" "".format(SCHEMA_VERSION[library][version]) diff --git a/tools/schemapi/codegen.py b/tools/schemapi/codegen.py index f7b387305..acf99d88b 100644 --- a/tools/schemapi/codegen.py +++ b/tools/schemapi/codegen.py @@ -244,7 +244,7 @@ def get_args(self, si): contents = ["self"] props = [] if si.is_anyOf(): - props = sorted(list({p for si_sub in si.anyOf for p in si_sub.properties})) + props = sorted({p for si_sub in si.anyOf for p in si_sub.properties}) elif si.properties: props = si.properties diff --git a/tools/schemapi/schemapi.py b/tools/schemapi/schemapi.py index 799cf0b16..a79655e9c 100644 --- a/tools/schemapi/schemapi.py +++ b/tools/schemapi/schemapi.py @@ -562,11 +562,22 @@ def to_dict(self, validate=True, ignore=None, context=None): try: self.validate(result) except jsonschema.ValidationError as err: - raise SchemaValidationError(self, err) + # We do not raise `from err` as else the resulting + # traceback is very long as it contains part + # of the Vega-Lite schema. It would also first + # show the less helpful ValidationError instead of + # the more user friendly SchemaValidationError + raise SchemaValidationError(self, err) from None return result def to_json( - self, validate=True, ignore=[], context={}, indent=2, sort_keys=True, **kwargs + self, + validate=True, + ignore=None, + context=None, + indent=2, + sort_keys=True, + **kwargs, ): """Emit the JSON representation for this object as a string. @@ -575,7 +586,7 @@ def to_json( validate : boolean If True (default), then validate the output dictionary against the schema. - ignore : list + ignore : list (optional) A list of keys to ignore. This will *not* passed to child to_dict function calls. context : dict (optional) @@ -593,6 +604,10 @@ def to_json( spec : string The JSON specification of the chart object. """ + if ignore is None: + ignore = [] + if context is None: + context = {} dct = self.to_dict(validate=validate, ignore=ignore, context=context) return json.dumps(dct, indent=indent, sort_keys=sort_keys, **kwargs) diff --git a/tools/schemapi/utils.py b/tools/schemapi/utils.py index b76731771..61529718b 100644 --- a/tools/schemapi/utils.py +++ b/tools/schemapi/utils.py @@ -230,7 +230,10 @@ def medium_description(self): elif not self.type: import warnings - warnings.warn("no short_description for schema\n{}" "".format(self.schema)) + warnings.warn( + "no short_description for schema\n{}" "".format(self.schema), + stacklevel=1, + ) return "any" @property