From 4660e9e06999fb3b026bc6d260f6eee04150baf3 Mon Sep 17 00:00:00 2001 From: WilliamBinquet Date: Fri, 5 Jun 2026 10:32:53 +0200 Subject: [PATCH 1/4] Pollux #12098 Update pybana to python 3.10 --- .gitignore | 2 +- .python-version | 1 + .travis.yml | 2 +- requirements-dev.txt | 8 ++++---- 4 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 .python-version diff --git a/.gitignore b/.gitignore index 53be168..d701132 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,7 @@ profile_default/ ipython_config.py # pyenv -.python-version +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..9dfc796 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.14 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b1d82c6..37a3c1d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ jobs: include: - stage: 'Tests' language: python - python: 3.8 + python: 3.10 before_install: - sudo docker network create elastic8 - > diff --git a/requirements-dev.txt b/requirements-dev.txt index 813fbac..a73546c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,11 +1,11 @@ -r requirements.txt -black==18.6b4 +black==26.1.0 coverage==4.5.3 -flake8==3.7.7 +flake8==7.3.0 pytest-cov==2.10.1 -pytest==8.3.5 +pytest==9.0.2 recommonmark==0.6.0 -sphinx==1.8.5 +sphinx==5.2.3 twine==3.0.0 wheel>=0.31.0 #pluggy==1.1.0 \ No newline at end of file From 4cba493ef8f6cd6b55ef533acee09e902fdc6eb0 Mon Sep 17 00:00:00 2001 From: WilliamBinquet Date: Fri, 5 Jun 2026 10:42:46 +0200 Subject: [PATCH 2/4] Applied new Black --- pybana/elastic/fixes_for_v8.py | 8 +- pybana/helpers/datasweet.py | 16 ++-- pybana/helpers/datetime.py | 2 +- pybana/translators/vega/vega.py | 100 ++++++++++++++--------- pybana/translators/vega/visualization.py | 9 +- tests/test_client.py | 16 ++-- 6 files changed, 87 insertions(+), 64 deletions(-) diff --git a/pybana/elastic/fixes_for_v8.py b/pybana/elastic/fixes_for_v8.py index 5248f15..4e330a1 100644 --- a/pybana/elastic/fixes_for_v8.py +++ b/pybana/elastic/fixes_for_v8.py @@ -250,9 +250,11 @@ def fix_histogram(self, params: Union[List, Dict]): # cf https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-datehistogram-aggregation interval = v["interval"] v[ - "calendar_interval" - if _is_calendar_interval(interval) - else "fixed_interval" + ( + "calendar_interval" + if _is_calendar_interval(interval) + else "fixed_interval" + ) ] = v["interval"] del v["interval"] elif isinstance(v, dict) or isinstance(v, list): diff --git a/pybana/helpers/datasweet.py b/pybana/helpers/datasweet.py index d804417..50c8883 100644 --- a/pybana/helpers/datasweet.py +++ b/pybana/helpers/datasweet.py @@ -69,11 +69,11 @@ def ds_if(cond, yes, no): out.append( ds_if( cond_item, - yes - if not isinstance(yes, list) - else yes[i] - if len(yes) > i - else None, + ( + yes + if not isinstance(yes, list) + else yes[i] if len(yes) > i else None + ), no if not isinstance(no, list) else no[i] if len(no) > i else None, ) ) @@ -149,9 +149,9 @@ def datasweet_eval(expr, bucket): val = ( value["std_deviation"] if "std_deviation" in value - else value["values"]["50.0"] - if "values" in value - else value["value"] + else ( + value["values"]["50.0"] if "values" in value else value["value"] + ) ) scope[f"agg{key}"] = float("nan") if val is None else val try: diff --git a/pybana/helpers/datetime.py b/pybana/helpers/datetime.py index 7c56726..2801338 100644 --- a/pybana/helpers/datetime.py +++ b/pybana/helpers/datetime.py @@ -143,7 +143,7 @@ def get_scaled_date_format(config, interval): scaled_date_formats = json.loads(config.get("dateFormat:scaled", "[]")) scaled_date_formats.reverse() default_date_format = config.get("dateFormat") - for (duration, date_format) in scaled_date_formats: + for duration, date_format in scaled_date_formats: if not duration or interval >= pendulum.parse(duration): return date_format return default_date_format diff --git a/pybana/translators/vega/vega.py b/pybana/translators/vega/vega.py index 72b1571..d520250 100644 --- a/pybana/translators/vega/vega.py +++ b/pybana/translators/vega/vega.py @@ -31,11 +31,15 @@ def __init__(self, using): def conf(self, state): return { "$schema": "https://vega.github.io/schema/vega/v5.json", - "width": DEFAULT_PIE_WIDTH - if state.type() == "pie" - else DEFAULT_GAUGE_WIDTH - if state.type() in ["gauge", "goal"] - else DEFAULT_WIDTH, + "width": ( + DEFAULT_PIE_WIDTH + if state.type() == "pie" + else ( + DEFAULT_GAUGE_WIDTH + if state.type() in ["gauge", "goal"] + else DEFAULT_WIDTH + ) + ), "height": DEFAULT_HEIGHT, "padding": DEFAULT_PADDING, } @@ -187,9 +191,11 @@ def _iter_response( ) tooltip = { childpoint.get("x_label", "x"): childpoint["x"], - childpoint["metric"]: self._format_duration(y) - if self._is_duration_bucket(state, metric_agg, metric) - else y, + childpoint["metric"]: ( + self._format_duration(y) + if self._is_duration_bucket(state, metric_agg, metric) + else y + ), } if childpoint["group"]: tooltip["group"] = childpoint["group"] @@ -248,9 +254,11 @@ def data_line_bar(self, conf, state, response, scope): "groupby": ["x"], "field": state.y(ax), "as": [state.y(ax) + "|0", state.y(ax) + "|1"], - "offset": "normalize" - if ax["scale"]["mode"] == "percentage" - else "zero", + "offset": ( + "normalize" + if ax["scale"]["mode"] == "percentage" + else "zero" + ), } ] elif state.metrics_stacked(ax): @@ -261,9 +269,11 @@ def data_line_bar(self, conf, state, response, scope): "field": state.y(ax), "as": [state.y(ax) + "|0", state.y(ax) + "|1"], "sort": {"field": "metric"}, - "offset": "normalize" - if ax["scale"]["mode"] == "percentage" - else "zero", + "offset": ( + "normalize" + if ax["scale"]["mode"] == "percentage" + else "zero" + ), } ] @@ -376,9 +386,9 @@ def _scales_y(self, state): else: domain = { "data": "table", - "field": state.y(ax) + "|1" - if state.stacked_applied(ax) - else state.y(ax), + "field": ( + state.y(ax) + "|1" if state.stacked_applied(ax) else state.y(ax) + ), } nice = True zero = True @@ -480,9 +490,11 @@ def legends_line_bar(self, conf, state): { "fill": "groupcolor", "title": "", - "columns": 1 - if state._state["params"]["legendPosition"] in ("left", "right") - else 10, + "columns": ( + 1 + if state._state["params"]["legendPosition"] in ("left", "right") + else 10 + ), "orient": state._state["params"]["legendPosition"], } ] @@ -491,9 +503,11 @@ def legends_line_bar(self, conf, state): { "fill": "metriccolor", "title": "", - "columns": 1 - if state._state["params"]["legendPosition"] in ("left", "right") - else 10, + "columns": ( + 1 + if state._state["params"]["legendPosition"] in ("left", "right") + else 10 + ), "orient": state._state["params"]["legendPosition"], } ] @@ -521,9 +535,9 @@ def marks_pie(self, conf, state, response): "y": {"signal": "height / 2"}, "startAngle": {"field": "startAngle"}, "endAngle": {"field": "endAngle"}, - "innerRadius": {"signal": "width * .35"} - if donut - else {"value": 0}, + "innerRadius": ( + {"signal": "width * .35"} if donut else {"value": 0} + ), "outerRadius": {"signal": "width / 2"}, **( {"tooltip": {"field": "tooltip"}} @@ -729,9 +743,11 @@ def get_color(value): }, "update": { "text": { - "signal": "format(mainValue*100/(maxValue - minValue), '.0f') + '%'" - if percentage_mode - else f"'{formatted_value}'" + "signal": ( + "format(mainValue*100/(maxValue - minValue), '.0f') + '%'" + if percentage_mode + else f"'{formatted_value}'" + ) }, "y": {"signal": "centerY - 14*fontFactor"}, "fontSize": {"signal": "fontFactor*18"}, @@ -832,11 +848,11 @@ def _marks_histogram(self, state, ax): x2 = ( "axis" if state.multi_axis() - else "group" - if state.groups_side_by_side(ax) - else "metric" - if state.metrics_side_by_side(ax) - else "x" + else ( + "group" + if state.groups_side_by_side(ax) + else "metric" if state.metrics_side_by_side(ax) else "x" + ) ) ret = [ { @@ -933,9 +949,11 @@ def _marks_line(self, state, ax): "x": {"scale": "xscale", "field": "x"}, "y": { "scale": ax["id"], - "field": state.y(ax) + "|1" - if state.stacked_applied(ax) - else state.y(ax), + "field": ( + state.y(ax) + "|1" + if state.stacked_applied(ax) + else state.y(ax) + ), }, "stroke": {"scale": stylescale, "field": stackgroupfield}, "strokeWidth": strokesizes, @@ -952,9 +970,11 @@ def _marks_line(self, state, ax): "x": {"scale": "xscale", "field": "x"}, "y": { "scale": ax["id"], - "field": state.y(ax) + "|1" - if state.stacked_applied(ax) - else state.y(ax), + "field": ( + state.y(ax) + "|1" + if state.stacked_applied(ax) + else state.y(ax) + ), }, "fill": {"scale": stylescale, "field": stackgroupfield}, "size": circlesizes, diff --git a/pybana/translators/vega/visualization.py b/pybana/translators/vega/visualization.py index e7c31c7..216af44 100644 --- a/pybana/translators/vega/visualization.py +++ b/pybana/translators/vega/visualization.py @@ -2,7 +2,6 @@ import json - __all__ = ("ContextVisualization",) @@ -105,9 +104,11 @@ def metric_label(self, agg): return "Count" return "%s - %s" % ( agg["type"], - agg["params"]["field"] - if "field" in agg["params"] - else "Formula %(id)s" % agg, + ( + agg["params"]["field"] + if "field" in agg["params"] + else "Formula %(id)s" % agg + ), ) return self.series_params(agg)["data"]["label"] diff --git a/tests/test_client.py b/tests/test_client.py index 40ce040..84e4518 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -194,7 +194,7 @@ def test_fusion_mappings_v8(self): ), f"FAILED: {test_name}" def test_fix_dynamic_template(self): - for (origin, expected, title) in [ + for origin, expected, title in [ (None, None, "none"), ({"a": 1}, {"a": 1}, "case 0"), ({"a": {}}, {"a": {}}, "case 1"), @@ -256,7 +256,7 @@ def test_fix_dynamic_template(self): assert fixed == expected, title def test_fix_dynamic_templates(self): - for (origin, expected, title) in [ + for origin, expected, title in [ (None, None, "none"), ([{"a": {}}], [{"a": {}}], "case 1"), ({"a": {}}, {"a": {}}, "case 2"), @@ -293,7 +293,7 @@ def test_fix_dynamic_templates(self): assert fixed == expected, title def test_fix_mappings(self): - for (origin, expected, title) in [ + for origin, expected, title in [ (None, {}, "none"), ( {"docx": {"properties": {"a": "b"}}}, @@ -315,7 +315,7 @@ def test_fix_histogram(self): calendar_dest = {"date_histogram": {"calendar_interval": "1m"}} invalid_origin = {"date_histogram": {"interval": 1}} invalid_dest = {"date_histogram": {"fixed_interval": 1}} - for (origin, expected, title) in [ + for origin, expected, title in [ (None, None, "none"), (1, 1, "invalid format"), (invalid_origin, invalid_dest, "invalid_origin 1"), @@ -330,7 +330,7 @@ def test_fix_histogram(self): self.assertEqual(found, expected, title) def test_fix_search_body(self): - for (origin, expected, title) in [ + for origin, expected, title in [ (None, None, "none"), ({}, {}, "empty"), (1, 1, "invalid format"), @@ -342,7 +342,7 @@ def test_fix_search_body(self): assert found == expected, title def test_fix_search_params(self): - for (origin, expected, title) in [ + for origin, expected, title in [ ({}, {}, "empty"), ( {"doc_type": "a", "another": "toto"}, @@ -369,7 +369,7 @@ def test_fix_search_params(self): assert found == expected, title def test_fix_transport_error_args(self): - for (origin, expected, title) in [ + for origin, expected, title in [ ((), (), "empty"), (("a", "b"), ("a", "b"), "2 items"), ( @@ -401,7 +401,7 @@ def test_fix_transport_error_args(self): assert found == expected, title def test_fix_template(self): - for (origin, expected, title) in [ + for origin, expected, title in [ (None, {}, "none"), ( {"a": {}}, From 920d33ba590619e7de33ddf6cd3fb8ee03debdd7 Mon Sep 17 00:00:00 2001 From: Diane Elzaabi Date: Wed, 17 Jun 2026 11:57:34 +0200 Subject: [PATCH 3/4] #12098 fixing test --- pybana/client.py | 19 ++++++++++++++++++- pybana/models.py | 16 ++++++++++------ requirements.txt | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/pybana/client.py b/pybana/client.py index a8210ff..49b2253 100644 --- a/pybana/client.py +++ b/pybana/client.py @@ -81,7 +81,24 @@ def config(self, using=None): """ Return the config associated to the current version of elastic """ - return self._get(Config, self.config_id(using), using=using) + config_id = self.config_id(using) + try: + return self._get(Config, config_id, using=using) + except NotFoundError: + # Kibana config ids are versioned (e.g. config:8.18.4). In tests the + # seeded config may exist for another patch version, so fallback to + # any config document for the same major version. + major_version = config_id.split(":")[-1].split(".")[0] + hits = ( + self.objects("config", using=using) + .extra(size=50) + .execute() + ) + for hit in hits: + hit_id = hit.meta.id + if hit_id.startswith(f"config:{major_version}."): + return self._get(Config, hit_id, using=using) + raise def is_v8(self, using=None): elastic = self.get_es(using) diff --git a/pybana/models.py b/pybana/models.py index 3d042e6..0788702 100644 --- a/pybana/models.py +++ b/pybana/models.py @@ -130,7 +130,7 @@ class DataView(BaseDocument): class Search(KibanaSavedObjectReferencesMixin, BaseDocument): _type = "search" - def index(self, using): + def index(self, using, raise_error=True): """ Returns the index-pattern associated to the visualization. Go through the search if needed. @@ -139,9 +139,11 @@ def index(self, using): refs = getattr(self, "_kibana_references", []) doc_id = resolve_index_pattern_document_id(search_source, refs) if not doc_id: - raise ValueError( - "Could not resolve data source from searchSourceJSON (missing index / references)" - ) + if raise_error: + raise ValueError( + "Could not resolve data source from searchSourceJSON (missing index / references)" + ) + return None return get_index_pattern_or_data_view(doc_id, self.meta.index, using=using) @@ -167,7 +169,9 @@ def index(self, using, raise_error=True): search if needed. """ if hasattr(self.visualization, "savedSearchId"): - return self.related_search(using=using).index(using=using) + return self.related_search(using=using).index( + using=using, raise_error=raise_error + ) search_source = self.visualization.kibanaSavedObjectMeta.searchSourceJSON refs = getattr(self, "_kibana_references", []) doc_id = resolve_index_pattern_document_id(search_source, refs) @@ -185,7 +189,7 @@ def index(self, using, raise_error=True): "Could not resolve data source from searchSourceJSON, references, " "or input control visState (missing index / indexPattern)" ) - return + return None def filters(self): """ diff --git a/requirements.txt b/requirements.txt index 76d9db0..ad5fe12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ elasticsearch==6.4.0 elasticsearch-dsl==6.4.0 hjson==3.0.1 pendulum==2.1.2 -pytz>=2019.1 +pytz==2024.1 pynumeral==0.1.2 sentry-sdk==0.14.3 vl-convert-python==1.9.0.post1 From 2645551f38f4956be79183aea8ec799cf62efe5e Mon Sep 17 00:00:00 2001 From: WilliamBinquet Date: Wed, 17 Jun 2026 15:56:44 +0200 Subject: [PATCH 4/4] Pollux #12098 Applied pybana --- pybana/client.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pybana/client.py b/pybana/client.py index 49b2253..8a6c170 100644 --- a/pybana/client.py +++ b/pybana/client.py @@ -89,11 +89,7 @@ def config(self, using=None): # seeded config may exist for another patch version, so fallback to # any config document for the same major version. major_version = config_id.split(":")[-1].split(".")[0] - hits = ( - self.objects("config", using=using) - .extra(size=50) - .execute() - ) + hits = self.objects("config", using=using).extra(size=50).execute() for hit in hits: hit_id = hit.meta.id if hit_id.startswith(f"config:{major_version}."):