From dd35256cf05b8aacd0ac4cf01a5d94c846e057c1 Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Sun, 30 Nov 2025 00:48:29 +0000 Subject: [PATCH 1/7] isolate request and session in request handler --- src/crawlee/_types.py | 37 ++++++++++++++++- .../_adaptive_playwright_crawler.py | 40 ++++--------------- src/crawlee/crawlers/_basic/_basic_crawler.py | 22 +++++++--- .../test_adaptive_playwright_crawler.py | 2 + 4 files changed, 61 insertions(+), 40 deletions(-) diff --git a/src/crawlee/_types.py b/src/crawlee/_types.py index da11adae5d..a1404f80de 100644 --- a/src/crawlee/_types.py +++ b/src/crawlee/_types.py @@ -2,8 +2,9 @@ import dataclasses from collections.abc import Callable, Iterator, Mapping +from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Annotated, Any, Literal, Protocol, TypedDict, TypeVar, cast, overload +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, Protocol, TypedDict, TypeVar, cast, overload from pydantic import ConfigDict, Field, PlainValidator, RootModel @@ -260,12 +261,27 @@ async def get_value(self, key: str, default_value: T | None = None) -> T | None: class RequestHandlerRunResult: """Record of calls to storage-related context helpers.""" - def __init__(self, *, key_value_store_getter: GetKeyValueStoreFunction) -> None: + _REQUEST_SYNC_FIELDS: ClassVar[frozenset[str]] = frozenset({'headers', 'user_data'}) + _SESSION_SYNC_FIELDS: ClassVar[frozenset[str]] = frozenset( + {'_user_data', '_usage_count', '_error_score', '_cookies'} + ) + + def __init__( + self, + *, + key_value_store_getter: GetKeyValueStoreFunction, + request: Request, + session: Session | None = None, + ) -> None: self._key_value_store_getter = key_value_store_getter self.add_requests_calls = list[AddRequestsKwargs]() self.push_data_calls = list[PushDataFunctionCall]() self.key_value_store_changes = dict[tuple[str | None, str | None, str | None], KeyValueStoreChangeRecords]() + # Isolated copies for handler execution + self.request = deepcopy(request) + self.session = deepcopy(session) if session else None + async def add_requests( self, requests: Sequence[str | Request], @@ -315,6 +331,23 @@ async def get_key_value_store( return self.key_value_store_changes[id, name, alias] + def sync_request(self, sync_request: Request) -> None: + """Sync request state from copies back to originals.""" + for field in self._REQUEST_SYNC_FIELDS: + value = getattr(self.request, field) + original_value = getattr(sync_request, field) + if value != original_value: + object.__setattr__(sync_request, field, value) + + def sync_session(self, sync_session: Session | None = None) -> None: + """Sync session state from copies back to originals.""" + if self.session and sync_session: + for field in self._SESSION_SYNC_FIELDS: + value = getattr(self.session, field) + original_value = getattr(sync_session, field) + if value != original_value: + object.__setattr__(sync_session, field, value) + @docs_group('Functions') class AddRequestsFunction(Protocol): diff --git a/src/crawlee/crawlers/_adaptive_playwright/_adaptive_playwright_crawler.py b/src/crawlee/crawlers/_adaptive_playwright/_adaptive_playwright_crawler.py index c51180e1fc..ee6fb12b4e 100644 --- a/src/crawlee/crawlers/_adaptive_playwright/_adaptive_playwright_crawler.py +++ b/src/crawlee/crawlers/_adaptive_playwright/_adaptive_playwright_crawler.py @@ -290,10 +290,12 @@ async def get_input_state( use_state_function = context.use_state # New result is created and injected to newly created context. This is done to ensure isolation of sub crawlers. - result = RequestHandlerRunResult(key_value_store_getter=self.get_key_value_store) + result = RequestHandlerRunResult( + key_value_store_getter=self.get_key_value_store, request=context.request, session=context.session + ) context_linked_to_result = BasicCrawlingContext( - request=deepcopy(context.request), - session=deepcopy(context.session), + request=result.request, + session=result.session, proxy_info=deepcopy(context.proxy_info), send_request=context.send_request, add_requests=result.add_requests, @@ -314,7 +316,7 @@ async def get_input_state( ), logger=self._logger, ) - return SubCrawlerRun(result=result, run_context=context_linked_to_result) + return SubCrawlerRun(result=result) except Exception as e: return SubCrawlerRun(exception=e) @@ -370,8 +372,7 @@ async def _run_request_handler(self, context: BasicCrawlingContext) -> None: self.track_http_only_request_handler_runs() static_run = await self._crawl_one(rendering_type='static', context=context) - if static_run.result and static_run.run_context and self.result_checker(static_run.result): - self._update_context_from_copy(context, static_run.run_context) + if static_run.result and self.result_checker(static_run.result): self._context_result_map[context] = static_run.result return if static_run.exception: @@ -402,7 +403,7 @@ async def _run_request_handler(self, context: BasicCrawlingContext) -> None: if pw_run.exception is not None: raise pw_run.exception - if pw_run.result and pw_run.run_context: + if pw_run.result: if should_detect_rendering_type: detection_result: RenderingType static_run = await self._crawl_one('static', context=context, state=old_state_copy) @@ -414,7 +415,6 @@ async def _run_request_handler(self, context: BasicCrawlingContext) -> None: context.log.debug(f'Detected rendering type {detection_result} for {context.request.url}') self.rendering_type_predictor.store_result(context.request, detection_result) - self._update_context_from_copy(context, pw_run.run_context) self._context_result_map[context] = pw_run.result def pre_navigation_hook( @@ -451,32 +451,8 @@ def track_browser_request_handler_runs(self) -> None: def track_rendering_type_mispredictions(self) -> None: self.statistics.state.rendering_type_mispredictions += 1 - def _update_context_from_copy(self, context: BasicCrawlingContext, context_copy: BasicCrawlingContext) -> None: - """Update mutable fields of `context` from `context_copy`. - - Uses object.__setattr__ to bypass frozen dataclass restrictions, - allowing state synchronization after isolated crawler execution. - """ - updating_attributes = { - 'request': ('headers', 'user_data'), - 'session': ('_user_data', '_usage_count', '_error_score', '_cookies'), - } - - for attr, sub_attrs in updating_attributes.items(): - original_sub_obj = getattr(context, attr) - copy_sub_obj = getattr(context_copy, attr) - - # Check that both sub objects are not None - if original_sub_obj is None or copy_sub_obj is None: - continue - - for sub_attr in sub_attrs: - new_value = getattr(copy_sub_obj, sub_attr) - object.__setattr__(original_sub_obj, sub_attr, new_value) - @dataclass(frozen=True) class SubCrawlerRun: result: RequestHandlerRunResult | None = None exception: Exception | None = None - run_context: BasicCrawlingContext | None = None diff --git a/src/crawlee/crawlers/_basic/_basic_crawler.py b/src/crawlee/crawlers/_basic/_basic_crawler.py index 79027aeba0..510fca2b57 100644 --- a/src/crawlee/crawlers/_basic/_basic_crawler.py +++ b/src/crawlee/crawlers/_basic/_basic_crawler.py @@ -1312,7 +1312,12 @@ async def _add_requests( return await request_manager.add_requests(context_aware_requests) - async def _commit_request_handler_result(self, context: BasicCrawlingContext) -> None: + async def _commit_request_handler_result( + self, + context: BasicCrawlingContext, + original_request: Request, + original_session: Session | None = None, + ) -> None: """Commit request handler result for the input `context`. Result is taken from `_context_result_map`.""" result = self._context_result_map[context] @@ -1324,6 +1329,9 @@ async def _commit_request_handler_result(self, context: BasicCrawlingContext) -> await self._commit_key_value_store_changes(result, get_kvs=self.get_key_value_store) + result.sync_session(sync_session=original_session) + result.sync_request(sync_request=original_request) + @staticmethod async def _commit_key_value_store_changes( result: RequestHandlerRunResult, get_kvs: GetKeyValueStoreFromRequestHandlerFunction @@ -1389,11 +1397,13 @@ async def __run_task_function(self) -> None: else: session = await self._get_session() proxy_info = await self._get_proxy_info(request, session) - result = RequestHandlerRunResult(key_value_store_getter=self.get_key_value_store) + result = RequestHandlerRunResult( + key_value_store_getter=self.get_key_value_store, request=request, session=session + ) context = BasicCrawlingContext( - request=request, - session=session, + request=result.request, + session=result.session, proxy_info=proxy_info, send_request=self._prepare_send_request_function(session, proxy_info), add_requests=result.add_requests, @@ -1416,9 +1426,9 @@ async def __run_task_function(self) -> None: except asyncio.TimeoutError as e: raise RequestHandlerError(e, context) from e - await self._commit_request_handler_result(context) + await self._commit_request_handler_result(context, original_request=request, original_session=session) await wait_for( - lambda: request_manager.mark_request_as_handled(context.request), + lambda: request_manager.mark_request_as_handled(request), timeout=self._internal_timeout, timeout_message='Marking request as handled timed out after ' f'{self._internal_timeout.total_seconds()} seconds', diff --git a/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py b/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py index b2e3a41853..7655b9b53d 100644 --- a/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py +++ b/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py @@ -802,6 +802,8 @@ async def request_handler(context: AdaptivePlaywrightCrawlingContext) -> None: assert session is not None assert check_request is not None + + print('Test Session', check_request) assert session.user_data.get('session_state') is True # Check that request user data was updated in the handler and only onse. assert check_request.user_data.get('request_state') == ['initial', 'handler'] From 6b014a7b1aa27d2381e1ee3159960491ba8e2c3d Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Wed, 3 Dec 2025 00:52:55 +0000 Subject: [PATCH 2/7] replace result request and session in context by originals --- src/crawlee/_types.py | 54 +++++++++++-------- src/crawlee/crawlers/_basic/_basic_crawler.py | 32 +++++------ src/crawlee/crawlers/_basic/_context_utils.py | 27 ++++++++++ 3 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 src/crawlee/crawlers/_basic/_context_utils.py diff --git a/src/crawlee/_types.py b/src/crawlee/_types.py index a1404f80de..8886feea17 100644 --- a/src/crawlee/_types.py +++ b/src/crawlee/_types.py @@ -4,7 +4,7 @@ from collections.abc import Callable, Iterator, Mapping from copy import deepcopy from dataclasses import dataclass -from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, Protocol, TypedDict, TypeVar, cast, overload +from typing import TYPE_CHECKING, Annotated, Any, Literal, Protocol, TypedDict, TypeVar, cast, overload from pydantic import ConfigDict, Field, PlainValidator, RootModel @@ -261,11 +261,6 @@ async def get_value(self, key: str, default_value: T | None = None) -> T | None: class RequestHandlerRunResult: """Record of calls to storage-related context helpers.""" - _REQUEST_SYNC_FIELDS: ClassVar[frozenset[str]] = frozenset({'headers', 'user_data'}) - _SESSION_SYNC_FIELDS: ClassVar[frozenset[str]] = frozenset( - {'_user_data', '_usage_count', '_error_score', '_cookies'} - ) - def __init__( self, *, @@ -279,8 +274,16 @@ def __init__( self.key_value_store_changes = dict[tuple[str | None, str | None, str | None], KeyValueStoreChangeRecords]() # Isolated copies for handler execution - self.request = deepcopy(request) - self.session = deepcopy(session) if session else None + self._request = deepcopy(request) + self._session = deepcopy(session) if session else None + + @property + def request(self) -> Request: + return self._request + + @property + def session(self) -> Session | None: + return self._session async def add_requests( self, @@ -331,22 +334,29 @@ async def get_key_value_store( return self.key_value_store_changes[id, name, alias] - def sync_request(self, sync_request: Request) -> None: - """Sync request state from copies back to originals.""" - for field in self._REQUEST_SYNC_FIELDS: - value = getattr(self.request, field) - original_value = getattr(sync_request, field) - if value != original_value: - object.__setattr__(sync_request, field, value) - - def sync_session(self, sync_session: Session | None = None) -> None: - """Sync session state from copies back to originals.""" - if self.session and sync_session: - for field in self._SESSION_SYNC_FIELDS: + def apply_request_changes(self, target: Request) -> None: + """Apply tracked changes from handler copy to original request.""" + if self.request.user_data != target.user_data: + target.user_data.update(self.request.user_data) + + if self.request.headers != target.headers: + target.headers = target.headers | self.request.headers + + def apply_session_changes(self, target: Session | None = None) -> None: + """Apply tracked changes from handler copy to original session.""" + simple_fields: set[str] = {'_usage_count', '_error_score'} + + if self.session and target: + if self.session.user_data != target.user_data: + target.user_data.update(self.session.user_data) + + if self.session.cookies != target.cookies: + target.cookies.set_cookies(self.session.cookies.get_cookies_as_dicts()) + for field in simple_fields: value = getattr(self.session, field) - original_value = getattr(sync_session, field) + original_value = getattr(target, field) if value != original_value: - object.__setattr__(sync_session, field, value) + object.__setattr__(target, field, value) @docs_group('Functions') diff --git a/src/crawlee/crawlers/_basic/_basic_crawler.py b/src/crawlee/crawlers/_basic/_basic_crawler.py index 510fca2b57..660a8f57b4 100644 --- a/src/crawlee/crawlers/_basic/_basic_crawler.py +++ b/src/crawlee/crawlers/_basic/_basic_crawler.py @@ -65,6 +65,7 @@ from crawlee.storages import Dataset, KeyValueStore, RequestQueue from ._context_pipeline import ContextPipeline +from ._context_utils import swaped_context from ._logging_utils import ( get_one_line_error_summary_if_possible, reduce_asyncio_timeout_error_to_relevant_traceback_parts, @@ -1315,8 +1316,6 @@ async def _add_requests( async def _commit_request_handler_result( self, context: BasicCrawlingContext, - original_request: Request, - original_session: Session | None = None, ) -> None: """Commit request handler result for the input `context`. Result is taken from `_context_result_map`.""" result = self._context_result_map[context] @@ -1329,8 +1328,8 @@ async def _commit_request_handler_result( await self._commit_key_value_store_changes(result, get_kvs=self.get_key_value_store) - result.sync_session(sync_session=original_session) - result.sync_request(sync_request=original_request) + result.apply_session_changes(target=context.session) + result.apply_request_changes(target=context.request) @staticmethod async def _commit_key_value_store_changes( @@ -1419,14 +1418,14 @@ async def __run_task_function(self) -> None: try: request.state = RequestState.REQUEST_HANDLER - self._check_request_collision(context.request, context.session) - try: - await self._run_request_handler(context=context) + with swaped_context(context, request, session): + self._check_request_collision(request, session) + await self._run_request_handler(context=context) except asyncio.TimeoutError as e: raise RequestHandlerError(e, context) from e - await self._commit_request_handler_result(context, original_request=request, original_session=session) + await self._commit_request_handler_result(context) await wait_for( lambda: request_manager.mark_request_as_handled(request), timeout=self._internal_timeout, @@ -1438,13 +1437,13 @@ async def __run_task_function(self) -> None: request.state = RequestState.DONE - if context.session and context.session.is_usable: - context.session.mark_good() + if session and session.is_usable: + session.mark_good() self._statistics.record_request_processing_finish(request.unique_key) except RequestCollisionError as request_error: - context.request.no_retry = True + request.no_retry = True await self._handle_request_error(context, request_error) except RequestHandlerError as primary_error: @@ -1459,7 +1458,7 @@ async def __run_task_function(self) -> None: await self._handle_request_error(primary_error.crawling_context, primary_error.wrapped_exception) except SessionError as session_error: - if not context.session: + if not session: raise RuntimeError('SessionError raised in a crawling context without a session') from session_error if self._error_handler: @@ -1469,16 +1468,17 @@ async def __run_task_function(self) -> None: exc_only = ''.join(traceback.format_exception_only(session_error)).strip() self._logger.warning('Encountered "%s", rotating session and retrying...', exc_only) - context.session.retire() + if session: + session.retire() # Increment session rotation count. - context.request.session_rotation_count = (context.request.session_rotation_count or 0) + 1 + request.session_rotation_count = (request.session_rotation_count or 0) + 1 await request_manager.reclaim_request(request) await self._statistics.error_tracker_retry.add(error=session_error, context=context) else: await wait_for( - lambda: request_manager.mark_request_as_handled(context.request), + lambda: request_manager.mark_request_as_handled(request), timeout=self._internal_timeout, timeout_message='Marking request as handled timed out after ' f'{self._internal_timeout.total_seconds()} seconds', @@ -1493,7 +1493,7 @@ async def __run_task_function(self) -> None: self._logger.debug('The context pipeline was interrupted', exc_info=interrupted_error) await wait_for( - lambda: request_manager.mark_request_as_handled(context.request), + lambda: request_manager.mark_request_as_handled(request), timeout=self._internal_timeout, timeout_message='Marking request as handled timed out after ' f'{self._internal_timeout.total_seconds()} seconds', diff --git a/src/crawlee/crawlers/_basic/_context_utils.py b/src/crawlee/crawlers/_basic/_context_utils.py new file mode 100644 index 0000000000..d682e44cc2 --- /dev/null +++ b/src/crawlee/crawlers/_basic/_context_utils.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from contextlib import contextmanager +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator + + from crawlee._request import Request + from crawlee.sessions import Session + + from ._basic_crawling_context import BasicCrawlingContext + + +@contextmanager +def swaped_context( + context: BasicCrawlingContext, + request: Request, + session: Session | None, +) -> Iterator[None]: + """Replace context's isolated copies with originals after handler execution.""" + try: + yield + finally: + # Restore original context state to avoid side effects between different handlers. + object.__setattr__(context, 'request', request) + object.__setattr__(context, 'session', session) From 22a81ba94fc9b241bc421f477483589b44854a82 Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Wed, 3 Dec 2025 01:22:10 +0000 Subject: [PATCH 3/7] add test --- .../crawlers/_basic/test_basic_crawler.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/unit/crawlers/_basic/test_basic_crawler.py b/tests/unit/crawlers/_basic/test_basic_crawler.py index c711d0c9d2..d5f6759c20 100644 --- a/tests/unit/crawlers/_basic/test_basic_crawler.py +++ b/tests/unit/crawlers/_basic/test_basic_crawler.py @@ -1768,3 +1768,35 @@ async def handler(_: BasicCrawlingContext) -> None: # Wait for crawler to finish await crawler_task + + +async def test_protect_request_and_session_in_run_handlers() -> None: + """Test that session and request in crawling context are protected in run handlers.""" + request_queue = await RequestQueue.open(name='state-test') + + async with SessionPool(max_pool_size=1) as session_pool: + session = await session_pool.get_session() + session.user_data['session_state'] = ['initial'] + + request = Request.from_url('https://test.url/', user_data={'request_state': ['initial']}, session_id=session.id) + + crawler = BasicCrawler(session_pool=session_pool, request_manager=request_queue, max_request_retries=0) + + @crawler.router.default_handler + async def handler(context: BasicCrawlingContext) -> None: + if context.session: + context.session.user_data['session_state'].append('modified') + if isinstance(context.request.user_data['request_state'], list): + context.request.user_data['request_state'].append('modified') + raise ValueError('Simulated error after modifying request and session') + + await crawler.run([request]) + + check_request = await request_queue.get_request(request.unique_key) + assert check_request is not None + assert check_request.user_data['request_state'] == ['initial'] + + check_session = await session_pool.get_session() + assert check_session.user_data['session_state'] == ['initial'] + + await request_queue.drop() From d0c9a7dd8591a31f8dfeaf0332bb9cb0b290d15f Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Tue, 9 Dec 2025 22:21:20 +0000 Subject: [PATCH 4/7] remove extra prints --- .../_adaptive_playwright/test_adaptive_playwright_crawler.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py b/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py index 7655b9b53d..c2d0f153b1 100644 --- a/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py +++ b/tests/unit/crawlers/_adaptive_playwright/test_adaptive_playwright_crawler.py @@ -803,7 +803,6 @@ async def request_handler(context: AdaptivePlaywrightCrawlingContext) -> None: assert session is not None assert check_request is not None - print('Test Session', check_request) assert session.user_data.get('session_state') is True # Check that request user data was updated in the handler and only onse. assert check_request.user_data.get('request_state') == ['initial', 'handler'] From b91ef31b70cee41974aa3261a34479dad2069344 Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Wed, 10 Dec 2025 17:31:06 +0000 Subject: [PATCH 5/7] fix headers in `apply_request_changes` --- src/crawlee/_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crawlee/_types.py b/src/crawlee/_types.py index a746a880af..a98664d02d 100644 --- a/src/crawlee/_types.py +++ b/src/crawlee/_types.py @@ -334,7 +334,7 @@ def apply_request_changes(self, target: Request) -> None: target.user_data = self.request.user_data if self.request.headers != target.headers: - target.headers = target.headers | self.request.headers + target.headers = self.request.headers @docs_group('Functions') From 851ca5158b8648bce87b3cba22129fb98ba34a3f Mon Sep 17 00:00:00 2001 From: Max Bohomolov Date: Wed, 10 Dec 2025 23:40:17 +0000 Subject: [PATCH 6/7] update test docs --- .../crawlers/_basic/test_basic_crawler.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/tests/unit/crawlers/_basic/test_basic_crawler.py b/tests/unit/crawlers/_basic/test_basic_crawler.py index 112c259ccb..d4ac09ff85 100644 --- a/tests/unit/crawlers/_basic/test_basic_crawler.py +++ b/tests/unit/crawlers/_basic/test_basic_crawler.py @@ -1826,25 +1826,24 @@ async def handler(_: BasicCrawlingContext) -> None: async def test_protect_request_in_run_handlers() -> None: - """Test that session and request in crawling context are protected in run handlers.""" + """Test that request in crawling context are protected in run handlers.""" request_queue = await RequestQueue.open(name='state-test') - async with SessionPool(max_pool_size=1) as session_pool: - request = Request.from_url('https://test.url/', user_data={'request_state': ['initial']}) + request = Request.from_url('https://test.url/', user_data={'request_state': ['initial']}) - crawler = BasicCrawler(session_pool=session_pool, request_manager=request_queue, max_request_retries=0) + crawler = BasicCrawler(request_manager=request_queue, max_request_retries=0) - @crawler.router.default_handler - async def handler(context: BasicCrawlingContext) -> None: - if isinstance(context.request.user_data['request_state'], list): - context.request.user_data['request_state'].append('modified') - raise ValueError('Simulated error after modifying request and session') + @crawler.router.default_handler + async def handler(context: BasicCrawlingContext) -> None: + if isinstance(context.request.user_data['request_state'], list): + context.request.user_data['request_state'].append('modified') + raise ValueError('Simulated error after modifying request') - await crawler.run([request]) + await crawler.run([request]) - check_request = await request_queue.get_request(request.unique_key) - assert check_request is not None - assert check_request.user_data['request_state'] == ['initial'] + check_request = await request_queue.get_request(request.unique_key) + assert check_request is not None + assert check_request.user_data['request_state'] == ['initial'] await request_queue.drop() From f9bd1e2f0b41caae7e54538931f347cf0b1dd566 Mon Sep 17 00:00:00 2001 From: Vlada Dusek Date: Mon, 15 Dec 2025 10:55:24 +0100 Subject: [PATCH 7/7] Update src/crawlee/crawlers/_basic/_basic_crawler.py --- src/crawlee/crawlers/_basic/_basic_crawler.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/crawlee/crawlers/_basic/_basic_crawler.py b/src/crawlee/crawlers/_basic/_basic_crawler.py index ae45b071b4..5cc2993c1c 100644 --- a/src/crawlee/crawlers/_basic/_basic_crawler.py +++ b/src/crawlee/crawlers/_basic/_basic_crawler.py @@ -1310,10 +1310,7 @@ async def _add_requests( return await request_manager.add_requests(context_aware_requests) - async def _commit_request_handler_result( - self, - context: BasicCrawlingContext, - ) -> None: + async def _commit_request_handler_result(self, context: BasicCrawlingContext) -> None: """Commit request handler result for the input `context`. Result is taken from `_context_result_map`.""" result = self._context_result_map[context]