Skip to content

Conversation

@askumar27
Copy link
Contributor

Use Looker APIs to get fields for a Views

Current Approach

  • We use lkml parser to get View attributes, and this has limitation of when Views are very complex

Proposed Approach

  • Use Looker APIs to get fields of a View, this API need to be called via the Explore name.
  • As many Views exist within an Explore - number of API calls <= number of Views with local LRU cache
  • The code tries to groups as many Views to the same Explore as possible, so as to minimize the number of API calls. As a single View can exist in many Explore
  • Add reliability towards lineage extraction in the next step where SQL query is prepared based on the fields

Other changes

  • More debug logging
  • Automatic fallback to view_context based fields extraction with better error handling
  • Best effort fields extraction - continue on failure (atleast one field is required) instead of falling back to different upstream strategy

@github-actions github-actions bot added the ingestion PR or Issue related to the ingestion of metadata label Oct 21, 2025
@askumar27 askumar27 changed the title Feature/acr 6601/get fields from api feat(lookml): Use Looker API to get fields of a View Oct 21, 2025
@codecov
Copy link

codecov bot commented Oct 21, 2025

❌ 21 Tests Failed:

Tests completed Failed Passed Skipped
4977 21 4956 38
View the top 3 failed test(s) by shortest run time
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_api_failure_fallback
Stack Traces | 0.01s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c3e20>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664546232976'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664548719152'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664545017376'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664548719392'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664549614688'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664572475376'>
view_to_explore_map = {'test_view': 'test_explore'}

    def test_api_failure_fallback(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Test that API failures are handled gracefully."""
        # Mock the Looker client to raise an exception
        mock_looker_client.generate_sql_query.side_effect = Exception("API call failed")
    
        # This should not raise an exception, but should be handled by the fallback mechanism
        # in the factory function
        with pytest.raises(Exception, match="API call failed"):
>           LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:498: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664545017376'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError

During handling of the above exception, another exception occurred:

self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c3e20>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664546232976'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664548719152'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664545017376'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664548719392'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664549614688'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664572475376'>
view_to_explore_map = {'test_view': 'test_explore'}

    def test_api_failure_fallback(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Test that API failures are handled gracefully."""
        # Mock the Looker client to raise an exception
        mock_looker_client.generate_sql_query.side_effect = Exception("API call failed")
    
        # This should not raise an exception, but should be handled by the fallback mechanism
        # in the factory function
        with pytest.raises(Exception, match="API call failed"):
>           LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )
E           AssertionError: Regex pattern did not match.
E            Regex: 'API call failed'
E            Input: "Mock object has no attribute 'field_threshold_for_splitting'"

.../unit/lookml/test_lookml_api_based_view_upstream.py:498: AssertionError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_sql_write_query_success
Stack Traces | 0.01s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56ab430>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664546473392'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664499482336'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664499662656'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664499564400'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664499662320'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664499572800'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664499662656'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_create_fields_no_column_lineage
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56abe80>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664469780032'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664468809136'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664468844800'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664468809568'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664468844896'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664469027136'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664468844800'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_duration_dimension_group_handling
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c34c0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664546472864'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664399122544'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664399091360'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664399150960'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664399090016'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664399081424'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664399091360'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_duration_dimension_group_without_intervals
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c3670>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664499651152'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664489914672'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664572430176'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664462431904'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664595970368'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664489952640'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664572430176'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_execute_query_invalid_response_format
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b51f0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664544688592'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664585816096'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664580948368'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664506259344'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664580949472'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664540442144'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664580948368'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_execute_query_no_sql_response
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56abe50>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664537414240'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664537399408'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664537204816'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664537391216'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664537203232'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664537273392'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664537204816'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_field_name_from_looker_api_field_name
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c3d30>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664534184576'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664534137104'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664534250784'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664534135760'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664534278880'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664534103040'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664534250784'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_field_name_from_looker_api_field_name_mismatch
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56ab0d0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664549951040'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664549796400'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664506409312'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664549796160'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664506410176'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664506359472'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664506409312'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_looker_api_field_name
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c39d0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664579363072'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664537044256'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664545282416'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664546769168'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664545280304'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664589132848'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664545282416'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_spr_column_error
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b58b0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664551951952'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664550676272'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664550743968'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664550676608'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664550656848'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664550720272'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664550743968'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_spr_table_error
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b5550>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664498541808'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664579812128'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664579675328'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664579811744'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664579675280'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664579717200'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664579675328'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_sql_write_query_no_fields
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56ab790>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664533429120'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664537251312'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664537246400'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664537250544'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664537245584'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664537355408'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664537246400'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_upstream_column_ref
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b5f70>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664399159456'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664399263248'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664399115072'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664399262816'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664399078784'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664399103024'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664399115072'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_upstream_column_ref_dimension_group
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b7310>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664532946416'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664532937216'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664532774720'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664532937456'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664532823872'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664532594352'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664532774720'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_get_upstream_dataset_urn
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b5c10>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664549702240'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664595289232'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664490510800'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664595289472'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664579911936'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664533368784'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664490510800'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_latency_tracking
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b7880>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664561528304'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664561420272'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664561562192'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664561420848'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664561561808'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664561539248'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664561562192'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_time_dimension_group_without_timeframes
Stack Traces | 0.011s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56c3160>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664506346416'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664498553520'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664498594864'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664498554336'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664498593904'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664499521952'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664498594864'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_execute_query_success
Stack Traces | 0.012s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56abaf0>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664546555264'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664530204416'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664462430464'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664530204512'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664489982272'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664489954752'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664462430464'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_time_dimension_group_handling
Stack Traces | 0.012s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56acd60>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664537399056'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664498616448'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664498636016'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664498615104'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664498578144'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664498627392'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664498636016'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError
tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream::test_create_fields
Stack Traces | 0.626s run time
self = <tests.unit.lookml.test_lookml_api_based_view_upstream.TestLookMLAPIBasedViewUpstream object at 0x7fefb56b7670>
mock_view_context = <MagicMock spec='LookerViewContext' id='140664521969568'>
mock_looker_view_id_cache = <MagicMock spec='LookerViewIdCache' id='140664530157136'>
mock_config = <MagicMock spec='LookMLSourceConfig' id='140664530251936'>
mock_reporter = <MagicMock spec='LookMLSourceReport' id='140664530367344'>
mock_ctx = <MagicMock spec='PipelineContext' id='140664530252416'>
mock_looker_client = <MagicMock spec='LookerAPI' id='140664530169712'>
view_to_explore_map = {'test_view': 'test_explore'}

    @pytest.fixture
    def upstream_instance(
        self,
        mock_view_context,
        mock_looker_view_id_cache,
        mock_config,
        mock_reporter,
        mock_ctx,
        mock_looker_client,
        view_to_explore_map,
    ):
        """Create a LookerQueryAPIBasedViewUpstream instance for testing."""
        # Mock the API response to prevent initialization errors
        mock_looker_client.generate_sql_query.return_value = [
            {"sql": "SELECT test_view.user_id FROM test_table"}
        ]
    
        # Mock the view ID cache
        mock_view_id = MagicMock(spec=LookerViewId)
        mock_view_id.get_urn.return_value = "urn:li:dataset:test"
        mock_looker_view_id_cache.get_looker_view_id.return_value = mock_view_id
    
        with patch(
            "datahub.ingestion.source.looker.view_upstream.create_lineage_sql_parsed_result"
        ) as mock_create_lineage:
            # Mock successful SQL parsing
            mock_spr = create_mock_sql_parsing_result()
            mock_create_lineage.return_value = mock_spr
    
>           return LookerQueryAPIBasedViewUpstream(
                view_context=mock_view_context,
                looker_view_id_cache=mock_looker_view_id_cache,
                config=mock_config,
                reporter=mock_reporter,
                ctx=mock_ctx,
                looker_client=mock_looker_client,
                view_to_explore_map=view_to_explore_map,
            )

.../unit/lookml/test_lookml_api_based_view_upstream.py:162: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../source/looker/view_upstream.py:420: in __init__
    self._get_spr()
.../source/looker/view_upstream.py:468: in __get_spr
    if len(view_fields) > self.config.field_threshold_for_splitting:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock spec='LookMLSourceConfig' id='140664530251936'>
name = 'field_threshold_for_splitting'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'field_threshold_for_splitting'

.../hostedtoolcache/Python/3.9.23.../x64/lib/python3.9/unittest/mock.py:630: AttributeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@sgomezvillamor
Copy link
Contributor

  • We use lkml parser to get View attributes, and this has limitation of when Views are very complex
  • Use Looker APIs to get fields of a View, this API need to be called via the Explore name.

Are we adding a fallback strategy? or do we replace with new strategy?
If the second, that could lead to some breaking change. If so, how can we address or mitigate it?

@sgomezvillamor
Copy link
Contributor

Looker/LookML ingestion is becoming a very complex code.
That's noted in the amount of debug logging required.

Given the amount of detail we require for troubleshooting, I wonder if there is python tooling that we can use to trace execution and no require explicit logger debug lines. 🤔

Not a blocker, just thinking loudly.

@askumar27
Copy link
Contributor Author

  • We use lkml parser to get View attributes, and this has limitation of when Views are very complex
  • Use Looker APIs to get fields of a View, this API need to be called via the Explore name.

Are we adding a fallback strategy? or do we replace with new strategy? If the second, that could lead to some breaking change. If so, how can we address or mitigate it?

There is a fallback strategy in place if there are any issues with Looker APIs to get the fields, it falls back to view_context based solution (current). There should not be any backward incompatibility issues here with either of the strategy. Worst case scenarios is no CLL or partial CLL (same as today)

  • Automatic fallback to view_context based fields extraction with better error handling
  • Best effort fields extraction - continue on failure (atleast one field is required) instead of falling back to different upstream strategy

@askumar27
Copy link
Contributor Author

Looker/LookML ingestion is becoming a very complex code. That's noted in the amount of debug logging required.

Given the amount of detail we require for troubleshooting, I wonder if there is python tooling that we can use to trace execution and no require explicit logger debug lines. 🤔

Not a blocker, just thinking loudly.

This is a great suggestion for tracing, we should certainly explore options.
Regarding the debug logs, Sorry I forgot to mention in the PR - this is also a dev build to be used for a customer debug. I will then trim out the debug logs before merging.

…l and caching for explores

- Updated `lookml_model_explore` method to accept an optional `fields` parameter for optimized API calls.
- Introduced `get_explore_fields_from_looker_api` method to fetch fields directly from the Looker API, improving performance and reducing unnecessary API calls.
- Implemented fallback mechanisms to retrieve fields from view context if API calls fail.
- Added logging for better traceability of API interactions and field retrieval processes.
- Implemented a greedy algorithm to minimize API calls by grouping views with common explores, improving overall efficiency.
…ing various scenarios including edge cases and performance with large datasets.
…ew upstream processing

- Updated logic to ensure only one field per dimension group is added, improving clarity and maintainability of the code.
- Removed redundant checks and added comments for better understanding of the dimension group handling process.
…ing to improve performance and error isolation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ingestion PR or Issue related to the ingestion of metadata

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants