Skip to content

Draft: remove sentry.snuba.metrics.query_builder from untyped module lists#117601

Draft
cursor[bot] wants to merge 1 commit into
masterfrom
cursor/untyped-module-cleanup-99f3
Draft

Draft: remove sentry.snuba.metrics.query_builder from untyped module lists#117601
cursor[bot] wants to merge 1 commit into
masterfrom
cursor/untyped-module-cleanup-99f3

Conversation

@cursor

@cursor cursor Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • remove sentry.snuba.metrics.query_builder from the mypy "typing issues" allowlist and weaklist in pyproject.toml
  • add explicit annotations, type narrowing, and safe casts in src/sentry/snuba/metrics/query_builder.py so it passes strict mypy checks
  • keep runtime behavior for metric params while satisfying typed call signatures

Testing

  • .venv/bin/mypy --show-error-codes src/sentry/snuba/metrics/query_builder.py
  • DATABASE_URL="postgres://postgres:postgres@127.0.0.1/sentry" SENTRY_SKIP_SERVICE_VALIDATION=1 .venv/bin/pytest -n3 -svv --reuse-db tests/sentry/snuba/metrics/test_query_builder.py
  • .venv/bin/prek run -q --files src/sentry/snuba/metrics/query_builder.py pyproject.toml

@armenzg please review.

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

Open in Web View Automation 

Co-authored-by: Armen Zambrano G. <armenzg@users.noreply.github.com>
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jun 13, 2026
@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on d955692 in this run:

tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_order_by_aggregate_top_events_graph_different_aggregatelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:2163: in test_order_by_aggregate_top_events_graph_different_aggregate
    assert len(response.data) == 3
E   assert 5 == 3
E    +  where 5 = len({'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...})
E    +    where {'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...} = <Response status_code=200, "application/json">.data
tests/sentry/search/events/builder/test_metrics.py::TimeseriesMetricQueryBuilderTest::test_run_top_timeseries_query_with_on_demand_columnslog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
src/sentry/search/events/builder/metrics.py:1886: in run_query
    get_series(
src/sentry/snuba/metrics/datasource.py:835: in get_series
    ).get_snuba_queries()
      ^^^^^^^^^^^^^^^^^^^
src/sentry/snuba/metrics/query_builder.py:1273: in get_snuba_queries
    queries_dict[entity] = self.__build_totals_and_series_queries(
src/sentry/snuba/metrics/query_builder.py:1107: in __build_totals_and_series_queries
    series_limit = self._metrics_query.max_limit.limit
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   AttributeError: 'int' object has no attribute 'limit'

During handling of the above exception, another exception occurred:
tests/sentry/search/events/builder/test_metrics.py:2605: in test_run_top_timeseries_query_with_on_demand_columns
    result = query.run_query("test_query")
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/search/events/builder/metrics.py:1899: in run_query
    raise IncompatibleMetricsQuery(err)
E   sentry.exceptions.IncompatibleMetricsQuery: 'int' object has no attribute 'limit'
tests/sentry/search/events/builder/test_metrics.py::TimeseriesMetricQueryBuilderTest::test_on_demand_top_timeseries_simple_metric_spec_with_environment_setlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
src/sentry/search/events/builder/metrics.py:1886: in run_query
    get_series(
src/sentry/snuba/metrics/datasource.py:835: in get_series
    ).get_snuba_queries()
      ^^^^^^^^^^^^^^^^^^^
src/sentry/snuba/metrics/query_builder.py:1273: in get_snuba_queries
    queries_dict[entity] = self.__build_totals_and_series_queries(
src/sentry/snuba/metrics/query_builder.py:1107: in __build_totals_and_series_queries
    series_limit = self._metrics_query.max_limit.limit
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   AttributeError: 'int' object has no attribute 'limit'

During handling of the above exception, another exception occurred:
tests/sentry/search/events/builder/test_metrics.py:2703: in test_on_demand_top_timeseries_simple_metric_spec_with_environment_set
    empty_result = query_builder.run_query("test_query")
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/search/events/builder/metrics.py:1899: in run_query
    raise IncompatibleMetricsQuery(err)
E   sentry.exceptions.IncompatibleMetricsQuery: 'int' object has no attribute 'limit'
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_with_transaction_on_demand_passing_widget_id_unsaved_transaction_onlylog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1444: in test_top_events_with_transaction_on_demand_passing_widget_id_unsaved_transaction_only
    assert saved_widget.discover_widget_split == DashboardWidgetTypes.TRANSACTION_LIKE
E   AssertionError: assert 100 == 101
E    +  where 100 = <DashboardWidget at 0x7f3b98243cb0: id=25, dashboard=<Dashboard at 0x7f3b98c0f8b0: id=35, organization=<Organization at 0x7f3b88ad4310: id=4558306181644304, owner_id=None, name='baz', slug='baz'>, title='Dashboard'>, title=''>.discover_widget_split
E    +  and   101 = DashboardWidgetTypes.TRANSACTION_LIKE
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_with_taglog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:2277: in test_top_events_with_tag
    assert response.status_code == 200, response.content
E   AssertionError: b'{"detail":"\'int\' object has no attribute \'limit\'"}'
E   assert 400 == 200
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_with_transaction_on_demand_passing_widget_id_savedlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1692: in test_top_events_with_transaction_on_demand_passing_widget_id_saved
    assert len(response.data.keys()) == 2
E   assert 1 == 2
E    +  where 1 = len(dict_keys(['']))
E    +    where dict_keys(['']) = <built-in method keys of dict object at 0x7f671c59ff00>()
E    +      where <built-in method keys of dict object at 0x7f671c59ff00> = {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}}.keys
E    +        where {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}} = <Response status_code=200, "application/json">.data
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_works_without_on_demand_typelog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1228: in test_top_events_works_without_on_demand_type
    assert response.status_code == 200, response.content
E   AssertionError: b'{"detail":"\'int\' object has no attribute \'limit\'"}'
E   assert 400 == 200
E    +  where 400 = <Response status_code=400, "application/json">.status_code
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_glob_http_referer_on_demandlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1895: in test_glob_http_referer_on_demand
    assert datum["meta"] == {
           ^^^^^^^^^^^^^
E   TypeError: list indices must be integers or slices, not str
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_order_by_aggregate_top_events_desclog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:2077: in test_order_by_aggregate_top_events_desc
    assert len(response.data) == 3
E   assert 5 == 3
E    +  where 5 = len({'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...})
E    +    where {'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...} = <Response status_code=200, "application/json">.data
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_with_transaction_on_demandlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1297: in test_top_events_with_transaction_on_demand
    assert len(response.data.keys()) == 2
E   assert 1 == 2
E    +  where 1 = len(dict_keys(['']))
E    +    where dict_keys(['']) = <built-in method keys of dict object at 0x7f1c1bc83c80>()
E    +      where <built-in method keys of dict object at 0x7f1c1bc83c80> = {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}}.keys
E    +        where {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}} = <Response status_code=200, "application/json">.data
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_top_events_with_transaction_on_demand_and_no_environmentlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1372: in test_top_events_with_transaction_on_demand_and_no_environment
    assert len(response.data.keys()) == 2
E   assert 1 == 2
E    +  where 1 = len(dict_keys(['']))
E    +    where dict_keys(['']) = <built-in method keys of dict object at 0x7f2563db8280>()
E    +      where <built-in method keys of dict object at 0x7f2563db8280> = {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}}.keys
E    +        where {'': {'count()': {'data': [(1781258400, [{...}]), (1781262000, [{...}])], 'end': 1781265600, 'isMetricsData': False, '... 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...}}} = <Response status_code=200, "application/json">.data
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_order_by_aggregate_top_events_asclog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:2120: in test_order_by_aggregate_top_events_asc
    assert len(response.data) == 3
E   assert 5 == 3
E    +  where 5 = len({'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...})
E    +    where {'data': [(1781222400, [{'count': 0}])], 'end': 1781222400, 'isMetricsData': False, 'meta': {'dataScanned': 'full', 'dataset': 'metricsEnhanced', 'datasetReason': 'unchanged', 'fields': {}, ...}, ...} = <Response status_code=200, "application/json">.data
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_group_by_transactionlog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:2006: in test_group_by_transaction
    assert response.data["/performance"]["meta"]["isMetricsExtractedData"] is True
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   KeyError: '/performance'
tests/snuba/api/endpoints/test_organization_events_stats_mep.py::OrganizationEventsStatsMetricsEnhancedPerformanceEndpointTestWithOnDemandWidgets::test_apdex_issuelog
[gw1] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/snuba/api/endpoints/test_organization_events_stats_mep.py:1817: in test_apdex_issue
    assert response.data["group_one"]["meta"]["isMetricsExtractedData"] is True
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
E   KeyError: 'group_one'
tests/sentry/search/events/builder/test_metrics.py::TimeseriesMetricQueryBuilderTest::test_on_demand_top_timeseries_dynamic_metric_spec_with_environment_setlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
src/sentry/search/events/builder/metrics.py:1886: in run_query
    get_series(
src/sentry/snuba/metrics/datasource.py:835: in get_series
    ).get_snuba_queries()
      ^^^^^^^^^^^^^^^^^^^
src/sentry/snuba/metrics/query_builder.py:1273: in get_snuba_queries
    queries_dict[entity] = self.__build_totals_and_series_queries(
src/sentry/snuba/metrics/query_builder.py:1107: in __build_totals_and_series_queries
    series_limit = self._metrics_query.max_limit.limit
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   AttributeError: 'int' object has no attribute 'limit'

During handling of the above exception, another exception occurred:
tests/sentry/search/events/builder/test_metrics.py:2813: in test_on_demand_top_timeseries_dynamic_metric_spec_with_environment_set
    empty_result = query_builder.run_query("test_query")
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/search/events/builder/metrics.py:1899: in run_query
    raise IncompatibleMetricsQuery(err)
E   sentry.exceptions.IncompatibleMetricsQuery: 'int' object has no attribute 'limit'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant