From e4abc1f6ddb5a5166e356e2efc0075c9acd007d3 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Wed, 17 Jun 2026 19:26:34 +0100 Subject: [PATCH 01/12] x --- README.md | 5 +- docs/sources/api-overview.rst | 9 +- docs/sources/usage.rst | 68 +++--- src/ch_api/__init__.py | 21 +- src/ch_api/api.py | 206 ++++++++---------- src/ch_api/exc.py | 22 ++ src/ch_api/types/pagination/__init__.py | 13 +- src/ch_api/types/pagination/types.py | 89 +++++--- src/ch_api/types/public_data/search.py | 11 +- .../types/public_data/search_companies.py | 4 +- tests/integration/conftest.py | 19 ++ tests/integration/test_company_info.py | 2 +- tests/integration/test_filing_history.py | 4 +- .../integration/test_officer_appointments.py | 2 +- tests/integration/test_psc.py | 8 +- tests/integration/test_search_api.py | 30 +-- tests/unit/test_api_branch_coverage.py | 74 +++++-- 17 files changed, 351 insertions(+), 236 deletions(-) diff --git a/README.md b/README.md index d7412c7..c9957c8 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,11 @@ Example of getting company information: ## Pagination -List endpoints return `MultipageList[T]` with `.data` (list) and `.pagination` metadata: +List endpoints return a single-page `MultipageList[T]` with `.data` (list) and +`.pagination` metadata. Advance one page at a time with `.get_next()`: >>> async def search_example(client): - ... results = await client.search_companies("tech", result_count=1) + ... results = await client.search_companies("tech") ... return len(results.data) >= 1 >>> run_async_func(search_example) True diff --git a/docs/sources/api-overview.rst b/docs/sources/api-overview.rst index 2068849..fd08f1c 100644 --- a/docs/sources/api-overview.rst +++ b/docs/sources/api-overview.rst @@ -66,11 +66,12 @@ Key Endpoints Pagination ========== -List endpoints return ``MultipageList[T]`` with ``data`` (list) and ``pagination`` metadata. -Pass ``result_count`` to control how many items are fetched, and ``next_page`` to continue: +List endpoints return a single-page ``MultipageList[T]`` with ``data`` (list) and +``pagination`` metadata. Pass ``page_size`` to control how many items a page holds, +and advance with ``get_next`` (or ``next_page``): >>> async def pagination_example(client): - ... page = await client.search_companies("tech", result_count=1) + ... page = await client.search_companies("tech") ... print(f"Company: {page.data[0].title}") ... print(f"Has more: {page.pagination.has_next}") ... return True @@ -115,7 +116,7 @@ All calls are async and must be awaited: >>> async def async_example(client): ... company = await client.get_company_profile("09370755") ... print(f"Company: {company.company_name}") - ... results = await client.search_companies("Apple", result_count=1) + ... results = await client.search_companies("Apple") ... if results.data: ... print(f"Found: {results.data[0].title}") ... return True diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index 7cd65b1..744ed0f 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -80,7 +80,7 @@ Officers Get information about company officers (directors, secretaries, etc.):: >>> async def officers_example(client): - ... officers = await client.get_officer_list("09370755", result_count=1) + ... officers = await client.get_officer_list("09370755") ... return len(officers.data) >= 1 >>> run_async_func(officers_example) True @@ -103,8 +103,8 @@ Access a company's filing history: .. code:: python - # Get filing history - filings = await client.get_company_filing_history("09370755", result_count=100) + # Get one page of filing history (use page_size / get_next for more) + filings = await client.get_company_filing_history("09370755", page_size=100) for filing in filings.data: print(f"Description: {filing.description}") print(f"Date: {filing.date}") @@ -117,9 +117,9 @@ Get information about charges registered against a company: .. code:: python - # Get charges - charges = await client.get_company_charges("09370755", result_count=100) - for charge in charges.data: + # Get charges (single response, not paginated) + charges = await client.get_company_charges("09370755") + for charge in charges.items or []: print(f"Charge Number: {charge.charge_number}") print(f"Created: {charge.created_on}") print(f"Status: {charge.status}") @@ -136,7 +136,7 @@ Company Search Search for companies by name:: >>> async def search_companies_example(client): - ... results = await client.search_companies("Apple", result_count=1) + ... results = await client.search_companies("Apple") ... return len(results.data) >= 1 >>> run_async_func(search_companies_example) True @@ -156,7 +156,7 @@ Use advanced search with multiple criteria: company_status="active", company_type="ltd", location="London", - result_count=100, + page_size=100, ) for company in results.data: print(f"{company.company_name} ({company.company_number})") @@ -168,7 +168,7 @@ Search for officers across all companies: .. code:: python - results = await client.search_officers("John Smith", result_count=100) + results = await client.search_officers("John Smith", page_size=100) for officer in results.data: print(f"Name: {officer.title}") print(f"Date of Birth: {officer.date_of_birth}") @@ -182,7 +182,7 @@ Search for disqualified officers: .. code:: python - results = await client.search_disqualified_officers("Smith", result_count=100) + results = await client.search_disqualified_officers("Smith", page_size=100) for officer in results.data: print(f"Name: {officer.title}") print(f"Date of Birth: {officer.date_of_birth}") @@ -192,12 +192,12 @@ Search for disqualified officers: Working with Pagination ======================= -Many API endpoints return paginated results as :class:`ch_api.types.pagination.types.MultipageList`, a simple value object with ``data`` (list of items) and ``pagination`` (cursor metadata). +Many API endpoints return paginated results as :class:`ch_api.types.pagination.types.MultipageList`, a minimal value object holding a single API page: ``data`` (the items on this page), ``pagination`` (cursor metadata), and a ``get_next`` handle to fetch the next page. It never accumulates a unified list across pages — you advance one page at a time. Fetching a page:: >>> async def lazy_loading_example(client): - ... results = await client.search_companies("tech", result_count=1) + ... results = await client.search_companies("tech") ... return len(results.data) >= 1 >>> run_async_func(lazy_loading_example) True @@ -205,22 +205,27 @@ Fetching a page:: Fetching multiple pages ----------------------- -Use ``result_count`` to fetch more items in one call, or loop with ``next_page``: +Each call returns one page. Use ``page_size`` to control how many items a page +holds, and walk the result set with ``get_next``: .. code:: python - # Fetch at least 100 items (may make multiple underlying requests) - page = await client.search_companies("tech", result_count=100) - for company in page.data: - print(company.title) - - # Manual cursor-based paging - page = await client.search_companies("tech", result_count=25) + # Walk every page with the bound get_next handle + page = await client.search_companies("tech", page_size=100) + while True: + for company in page.data: + print(company.title) + if not page.pagination.has_next: + break + page = await page.get_next() + + # Or resume statelessly with the opaque cursor token + page = await client.search_companies("tech", page_size=25) while page.pagination.has_next: page = await client.search_companies( "tech", + page_size=25, next_page=page.pagination.next_page, - result_count=25, ) .. _usage.rate-limiting: @@ -373,7 +378,7 @@ All Pydantic models support conversion to dictionaries: company_json = company.model_dump_json() # For paginated results - results = await client.search_companies("Apple", result_count=25) + results = await client.search_companies("Apple", page_size=25) # Convert all items to dictionaries companies_list = [c.model_dump() for c in results.data] @@ -397,7 +402,7 @@ Find Companies and Their Officers client = Client(credentials=auth) # Search for companies - companies = await client.search_companies("Technology Ltd", result_count=5) + companies = await client.search_companies("Technology Ltd", page_size=5) for company in companies.data: print(f"\\nCompany: {company.title} ({company.company_number})") @@ -405,7 +410,7 @@ Find Companies and Their Officers # Get officers for each company try: - officers = await client.get_officer_list(company.company_number, result_count=100) + officers = await client.get_officer_list(company.company_number, page_size=100) print("Officers:") for officer in officers.data: print(f" - {officer.name} ({officer.officer_role})") @@ -437,15 +442,15 @@ Export Company Data data['profile'] = (await client.get_company_profile(company_number)).model_dump() # Officers - officers = await client.get_officer_list(company_number, result_count=200) + officers = await client.get_officer_list(company_number, page_size=200) data['officers'] = [o.model_dump() for o in officers.data] # PSCs - psc_result = await client.get_company_psc_list(company_number, result_count=200) + psc_result = await client.get_company_psc_list(company_number, page_size=200) data['pscs'] = [p.model_dump() for p in psc_result.data] # Filing history (first 100) - filings = await client.get_company_filing_history(company_number, result_count=100) + filings = await client.get_company_filing_history(company_number, page_size=100) data['filing_history'] = [f.model_dump() for f in filings.data] # Write to file @@ -473,7 +478,7 @@ Monitor Company Changes client = Client(credentials=auth) # Get filing history - filings = await client.get_company_filing_history(company_number, result_count=100) + filings = await client.get_company_filing_history(company_number, page_size=100) # Filter for recent filings (last 30 days) cutoff_date = datetime.now().date() - timedelta(days=30) @@ -541,6 +546,7 @@ Pagination Issues If pagination isn't working as expected: -- Use a regular ``for`` loop over ``result.data`` (it's a plain list) -- Pass ``result_count`` to fetch more than one page's worth of items -- Use ``result.pagination.next_page`` to fetch subsequent pages manually +- Use a regular ``for`` loop over ``result.data`` (it's a plain list for one page) +- Pass ``page_size`` to control how many items each page holds +- Call ``await result.get_next()`` (or pass ``result.pagination.next_page`` back to + the endpoint) to fetch the next page diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index 8358790..34ba3d3 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -21,7 +21,7 @@ >>> # Search for companies >>> @run_async_func ... async def search_companies_example(client): - ... results = await client.search_companies("Apple", result_count=25) + ... results = await client.search_companies("Apple") ... for company in results.data: ... print(f"Found: {company.title}") ... @@ -30,7 +30,7 @@ >>> # Get officers >>> @run_async_func ... async def get_officers_example(client): - ... officers = await client.get_officer_list("09370755", result_count=100) + ... officers = await client.get_officer_list("09370755") ... for officer in officers.data: ... print(f"Officer: {officer.name}") Officer: ... @@ -142,22 +142,19 @@ Pagination ---------- -Search and list endpoints return ``MultipageList[T]`` with a ``data`` list -and ``pagination`` metadata. Pass ``result_count`` to fetch more items in one -call, and ``next_page`` to continue from a previous response:: +Search and list endpoints return a single-page ``MultipageList[T]`` with a +``data`` list and ``pagination`` metadata. Each call fetches exactly one API +page; advance one page at a time with ``get_next`` (or pass ``next_page`` back +to the same endpoint). Use ``page_size`` to control how many items a page holds:: >>> @run_async_func ... async def pagination_example(client): - ... page = await client.search_companies("Apple", result_count=25) + ... page = await client.search_companies("Apple") ... # page.data is a plain list — iterate with a regular for loop ... assert len(page.data) >= 1 - ... # Fetch next page using the cursor + ... # Fetch the next page with the bound get_next handle ... if page.pagination.has_next: - ... page2 = await client.search_companies( - ... "Apple", - ... next_page=page.pagination.next_page, - ... result_count=25, - ... ) + ... page2 = await page.get_next() ... Exception Handling diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 9184e4c..a2fdbda 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -81,14 +81,18 @@ class Client: ... # Fetch a company's profile ... profile = await client.get_company_profile("09370755") ... print(f"{profile.company_name} - Status: {profile.company_status}") - ... # Fetch company officers + ... # Fetch one page of company officers ... officers = await client.get_officer_list("09370755") - ... async for officer in officers: + ... for officer in officers.data: ... print(f"Officer: {officer.name}") - ... # Search for companies + ... # Search for companies; walk every page with get_next ... results = await client.search_companies("Apple") - ... async for result in results: - ... print(f"Found: {result.title} ({result.company_number})") + ... while True: + ... for result in results.data: + ... print(f"Found: {result.title} ({result.company_number})") + ... if not results.pagination.has_next: + ... break + ... results = await results.get_next() ... ... # doctest: +SKIP @@ -340,51 +344,38 @@ async def _fetch_paginated( self, fetch_page_fn: typing.Callable[[int], typing.Awaitable[tuple[list, typing.Optional[int]]]], next_page: typing.Optional[types.pagination.types.NextPageToken], - result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch one or more offset-based API pages and return a MultipageList. + """Fetch a single offset-based API page and return a MultipageList. + + Fetches exactly one API page (no accumulation). When more pages exist, + the returned list carries a ``get_next`` handle that fetches the next + single page via the same ``fetch_page_fn``. Args: fetch_page_fn: Callable taking ``start_index`` (int), returning a tuple of ``(items, total_count)``. ``total_count`` may be None - if unknown; in that case no further pages will be fetched. + if unknown; in that case no further pages will be reported. next_page: Cursor from a previous call, or None to start from offset 0. - result_count: Minimum number of items to collect. The method fetches - at least one page regardless of this value. Returns: - A MultipageList with the collected items and pagination metadata. + A MultipageList holding one page of items and pagination metadata. """ page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) current_start = page_state.start_index - items: list = [] - total_count: typing.Optional[int] = None - has_next = False - last_page_len = 0 - - while True: - page_items, page_total = await fetch_page_fn(current_start) - last_page_len = len(page_items) - if page_total is not None: - total_count = page_total - items.extend(page_items) - - has_next = bool(total_count is not None and page_items and (current_start + last_page_len) < total_count) - - if not has_next or len(items) >= result_count: - break - current_start += last_page_len + page_items, total_count = await fetch_page_fn(current_start) + next_start = current_start + len(page_items) + has_next = bool(total_count is not None and page_items and next_start < total_count) next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next: - next_state = types.pagination.types._PageState(start_index=current_start + last_page_len) + next_state = types.pagination.types._PageState(start_index=next_start) next_page_out = self._encode_next_page(next_state) - return types.pagination.types.MultipageList( - data=items, + result = types.pagination.types.MultipageList( + data=page_items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -392,16 +383,27 @@ async def _fetch_paginated( ), ) + if has_next and next_page_out is not None: + captured_next_page = next_page_out + + async def _fetch_next() -> types.pagination.types.MultipageList: + return await self._fetch_paginated(fetch_page_fn, captured_next_page) + + result._fetch_next_page = _fetch_next + + return result + async def _fetch_paginated_cursor( self, fetch_page_fn: typing.Callable[[typing.Optional[str]], typing.Awaitable[tuple[list, typing.Optional[str]]]], next_page: typing.Optional[types.pagination.types.NextPageToken], - result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch one or more cursor-based API pages and return a MultipageList. + """Fetch a single cursor-based API page and return a MultipageList. Used for endpoints that paginate via ``search_below`` / ``search_above`` cursors (e.g. alphabetical company search) rather than ``start_index``. + Fetches exactly one API page; the returned list carries a ``get_next`` + handle to advance. Args: fetch_page_fn: Callable taking the current ``search_below`` cursor @@ -409,37 +411,26 @@ async def _fetch_paginated_cursor( ``next_cursor`` is None when no further pages exist. next_page: Cursor from a previous call, or None to start from the beginning. - result_count: Minimum number of items to collect. Returns: - A MultipageList with the collected items and pagination metadata. + A MultipageList holding one page of items and pagination metadata. ``pagination.size`` is always None for cursor-based endpoints. """ page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) cursor = page_state.search_below - items: list = [] - has_next = False - next_cursor: typing.Optional[str] = None - while True: - page_items, next_cursor = await fetch_page_fn(cursor) - items.extend(page_items) - has_next = next_cursor is not None - - if not has_next or len(items) >= result_count: - break - - cursor = next_cursor + page_items, next_cursor = await fetch_page_fn(cursor) + has_next = next_cursor is not None next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next and next_cursor is not None: next_state = types.pagination.types._PageState(search_below=next_cursor) next_page_out = self._encode_next_page(next_state) - return types.pagination.types.MultipageList( - data=items, + result = types.pagination.types.MultipageList( + data=page_items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -447,6 +438,16 @@ async def _fetch_paginated_cursor( ), ) + if has_next and next_page_out is not None: + captured_next_page = next_page_out + + async def _fetch_next() -> types.pagination.types.MultipageList: + return await self._fetch_paginated_cursor(fetch_page_fn, captured_next_page) + + result._fetch_next_page = _fetch_next + + return result + @pydantic.validate_call async def create_test_company( self, company: types.test_data_generator.CreateTestCompanyRequest @@ -528,19 +529,19 @@ async def get_officer_list( ] ] = None, order_by: typing.Literal["appointed_on", "resigned_on", "surname"] = "appointed_on", + page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.company_officers.OfficerSummary]: - """Fetch the list of company officers for a given company. + """Fetch one page of company officers for a given company. Parameters ---------- company_number: str The company number to fetch the officers for. + page_size: int + Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). Returns ------- @@ -553,7 +554,7 @@ async def get_officer_list( base_url = f"{self._settings.api_url}/company/{company_number}/officers" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = query_params | {"start_index": start_index, "items_per_page": 200} + params = query_params | {"start_index": start_index, "items_per_page": page_size} url = f"{base_url}?{urllib.parse.urlencode(params, doseq=True)}" try: result = await self._get_resource( @@ -570,7 +571,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def get_officer_appointment( @@ -593,8 +594,8 @@ async def get_company_registers( async def search( self, query: str, + page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.AnySearchResultT]: """Search for companies using the Companies House search API. @@ -602,17 +603,15 @@ async def search( ---------- query: str The search query string. + page_size: int + Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/search" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = ( - f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': 200})}" - ) + url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 try: result = await self._get_resource( url, @@ -628,7 +627,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def advanced_company_search( # noqa: C901 @@ -645,10 +644,19 @@ async def advanced_company_search( # noqa: C901 incorporated_to: typing.Optional[datetime.date] = None, location: typing.Optional[str] = None, sic_codes: typing.Optional[typing.Sequence[str]] = None, + page_size: typing.Optional[typing.Annotated[int, pydantic.conint(ge=1, le=5000)]] = None, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AdvancedCompany]: - """Perform an advanced search for companies using the Companies House search API.""" + """Perform an advanced search for companies using the Companies House search API. + + Parameters + ---------- + page_size: int, optional + Number of items per API page (``size`` query parameter, 1-5000). + When omitted, the API's own default page size is used. + next_page: str, optional + Cursor from a previous call to continue pagination. + """ query_params: dict = {} if company_name_includes: query_params["company_name_includes"] = company_name_includes @@ -678,6 +686,8 @@ async def advanced_company_search( # noqa: C901 query_params["location"] = location if sic_codes: query_params["sic_codes"] = list(sic_codes) + if page_size is not None: + query_params["size"] = page_size base_url = f"{self._settings.api_url}/advanced-search/companies" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: @@ -698,7 +708,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.hits - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def alphabetical_companies_search( @@ -706,7 +716,6 @@ async def alphabetical_companies_search( query: str, page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 10, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AlphabeticalCompany]: """Search for companies alphabetically using the Companies House search API. @@ -718,8 +727,6 @@ async def alphabetical_companies_search( Number of results per API page (1-100, default 10). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/alphabetical-search/companies" @@ -742,7 +749,7 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page, result_count) + return await self._fetch_paginated_cursor(_fetch, next_page) @pydantic.validate_call async def search_dissolved_companies( @@ -751,7 +758,6 @@ async def search_dissolved_companies( page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 10, type: typing.Literal["alphabetical", "best-match", "previous-name-dissolved"] = "alphabetical", # noqa: A002 next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.DissolvedCompany]: """Search for dissolved companies using the Companies House search API. @@ -765,8 +771,6 @@ async def search_dissolved_companies( Search type (alphabetical, best-match, previous-name-dissolved). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/dissolved-search/companies" @@ -789,14 +793,14 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page, result_count) + return await self._fetch_paginated_cursor(_fetch, next_page) @pydantic.validate_call async def search_companies( self, query: str, + page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.CompanySearchItem]: """Search for companies using the Companies House search API. @@ -804,17 +808,15 @@ async def search_companies( ---------- query: str The search query string. + page_size: int + Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/search/companies" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = ( - f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': 200})}" - ) + url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 try: result = await self._get_resource( url, @@ -830,14 +832,14 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def search_officers( self, query: str, + page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.OfficerSearchItem]: """Search for officers using the Companies House search API. @@ -845,17 +847,15 @@ async def search_officers( ---------- query: str The search query string. + page_size: int + Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/search/officers" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = ( - f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': 200})}" - ) + url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 try: result = await self._get_resource( url, @@ -871,14 +871,14 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def search_disqualified_officers( self, query: str, + page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.DisqualifiedOfficerSearchItem]: """Search for disqualified officers using the Companies House search API. @@ -886,17 +886,15 @@ async def search_disqualified_officers( ---------- query: str The search query string. + page_size: int + Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/search/disqualified-officers" async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = ( - f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': 200})}" - ) + url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 try: result = await self._get_resource( url, @@ -912,7 +910,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def get_company_charges( @@ -984,7 +982,6 @@ async def get_company_filing_history( | None = None, page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.filing_history.FilingHistoryItem]: """Fetch the filing history for a given company. @@ -998,8 +995,6 @@ async def get_company_filing_history( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_query_params: dict = {} if categories is not None: @@ -1019,7 +1014,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_count - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def get_filing_history_item( @@ -1312,7 +1307,6 @@ async def get_officer_appointments( filter: typing.Optional[typing.Literal["active"]] = None, # noqa: A002 page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.officer_appointments.OfficerAppointmentSummary]: """Fetch the officer appointments for a given officer. @@ -1326,8 +1320,6 @@ async def get_officer_appointments( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/officers/{officer_id}/appointments" @@ -1346,7 +1338,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def get_company_uk_establishments( @@ -1377,7 +1369,6 @@ async def get_company_psc_list( register_view: bool = False, page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.ListSummary]: """Fetch the list of persons with significant control for a given company. @@ -1391,8 +1382,6 @@ async def get_company_psc_list( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control" register_view_str = "true" if register_view else "false" @@ -1414,7 +1403,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) @pydantic.validate_call async def get_company_psc_statements( @@ -1423,7 +1412,6 @@ async def get_company_psc_statements( register_view: bool = False, page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, - result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.Statement]: """Fetch the PSC statements for a given company. @@ -1437,8 +1425,6 @@ async def get_company_psc_statements( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. - result_count: int - Minimum number of results to return (default 1 = one API page). """ base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control-statements" register_view_str = "true" if register_view else "false" @@ -1460,7 +1446,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, next_page) async def _get_psc_by_type( self, diff --git a/src/ch_api/exc.py b/src/ch_api/exc.py index 127f76a..d98b872 100644 --- a/src/ch_api/exc.py +++ b/src/ch_api/exc.py @@ -21,6 +21,7 @@ __all__ = [ "CompaniesHouseApiError", "UnexpectedApiResponseError", + "NoMorePagesError", ] @@ -83,3 +84,24 @@ class UnexpectedApiResponseError(CompaniesHouseApiError): -------- CompaniesHouseApiError : Base exception class """ + + +class NoMorePagesError(CompaniesHouseApiError): + """Raised when :meth:`MultipageList.get_next` is called on the last page. + + Indicates the caller asked for a page beyond the end of the result set. + Check ``MultipageList.pagination.has_next`` before calling ``get_next`` + to avoid this exception. + + Example + ------- + Iterate safely to exhaustion:: + + page = await client.search_companies("Apple") + while page.pagination.has_next: + page = await page.get_next() + + See Also + -------- + CompaniesHouseApiError : Base exception class + """ diff --git a/src/ch_api/types/pagination/__init__.py b/src/ch_api/types/pagination/__init__.py index 86559cf..3f64aac 100644 --- a/src/ch_api/types/pagination/__init__.py +++ b/src/ch_api/types/pagination/__init__.py @@ -1,19 +1,16 @@ """Pagination support for Companies House API list endpoints. -All paginated endpoints on ``Client`` accept ``next_page`` and ``result_count`` -and return ``MultipageList[T]``:: +All paginated endpoints on ``Client`` accept ``next_page`` and ``page_size`` +and return a single-page ``MultipageList[T]``. Advance one page at a time with +``get_next``:: page = await client.search_companies("Apple") while page.pagination.has_next: - page = await client.search_companies( - "Apple", - next_page=page.pagination.next_page, - result_count=25, - ) + page = await page.get_next() Key Classes ----------- -- :class:`types.MultipageList` - Simple paginated result container +- :class:`types.MultipageList` - Single-page result container with ``get_next`` - :class:`types.PaginationInfo` - Pagination metadata - :class:`types.NextPageToken` - Opaque cursor type - :class:`types.PageTokenSerializer` - Optional token encryption protocol diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index ee50ace..b21a83b 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -16,6 +16,8 @@ import pydantic +from ... import exc + _ItemT = typing.TypeVar("_ItemT", bound=pydantic.BaseModel) @@ -137,19 +139,15 @@ class PaginationInfo(pydantic.BaseModel): """Pagination state for a result set returned by the CH API. Returned alongside every page of results from the async client. Use - ``next_page`` in a subsequent call to the same endpoint to retrieve - the next batch of items. + ``MultipageList.get_next`` (or pass ``next_page`` back to the same + endpoint) to retrieve the next page of items. Example:: - page = await client.search_companies("Apple", result_count=25) + page = await client.search_companies("Apple") while page.pagination.has_next: - page = await client.search_companies( - "Apple", - next_page=page.pagination.next_page, - result_count=25, - ) + page = await page.get_next() """ model_config = pydantic.ConfigDict(frozen=True) @@ -173,35 +171,35 @@ class PaginationInfo(pydantic.BaseModel): class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): - """A page of typed results from a paginated CH API endpoint. + """A single page of typed results from a paginated CH API endpoint. - Contains the fetched data items and the pagination metadata needed to - retrieve subsequent pages. Returned by all paginated methods on - ``Client``. + Contains exactly one API page of data items plus the pagination metadata + needed to retrieve the next page. Returned by all paginated methods on + ``Client``. Each instance holds one page only — it never accumulates a + unified list across pages. Advance one page at a time with :meth:`get_next` + (or by passing ``pagination.next_page`` back to the originating endpoint). Type Parameters: _ItemT: The type of items in ``data``. - Fetching pages:: + Walking every page with ``get_next``:: - # First page page = await client.search_companies("Apple") - print(f"Got {len(page.data)} of ~{page.pagination.size} total results") + while True: + for company in page.data: + ... # process this page's items + if not page.pagination.has_next: + break + page = await page.get_next() - # Subsequent pages - while page.pagination.has_next: - page = await client.search_companies( + Resuming statelessly with a cursor token:: + + page = await client.search_companies("Apple") + if page.pagination.has_next: + page2 = await client.search_companies( "Apple", next_page=page.pagination.next_page, - result_count=25, ) - # process page.data ... - - Fetching a larger batch in one call:: - - # Request at least 100 items (may trigger multiple underlying API calls) - page = await client.search_companies("Apple", result_count=100) - # page.data has >= 100 items (or all available if fewer exist) """ model_config = pydantic.ConfigDict(frozen=True, arbitrary_types_allowed=True) @@ -210,3 +208,42 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): pagination: PaginationInfo = pydantic.Field( description="Pagination state, including whether more results exist and how to fetch them." ) + + _fetch_next_page: typing.Optional[typing.Callable[[], typing.Awaitable["MultipageList[_ItemT]"]]] = ( + pydantic.PrivateAttr(default=None) + ) + """Bound closure that fetches the next page using the same endpoint and + arguments as the call that produced this list. Set by the client when the + list is produced; ``None`` on manually-constructed instances. + """ + + async def get_next(self) -> "MultipageList[_ItemT]": + """Fetch the next page from the same endpoint with the same arguments. + + Returns a new ``MultipageList`` carrying the next page of results. + The returned list itself has ``get_next`` bound for further iteration. + + Raises: + NoMorePagesError: If ``pagination.has_next`` is ``False`` — this + list is already the last page. + RuntimeError: If this list was constructed manually (e.g. in a + test or via deserialization) and has no fetcher attached. + + Example: + Walk every page:: + + page = await client.search_companies("Apple") + while page.pagination.has_next: + page = await page.get_next() + for company in page.data: + ... + """ + if not self.pagination.has_next: + raise exc.NoMorePagesError("This is the last page; no more results to fetch.") + if self._fetch_next_page is None: + raise RuntimeError( + "MultipageList has no next-page fetcher attached — it was likely " + "constructed manually or deserialized. Call the originating endpoint " + "with `next_page=` instead." + ) + return await self._fetch_next_page() diff --git a/src/ch_api/types/public_data/search.py b/src/ch_api/types/public_data/search.py index 98451de..1c04f07 100644 --- a/src/ch_api/types/public_data/search.py +++ b/src/ch_api/types/public_data/search.py @@ -53,24 +53,25 @@ auth = api_settings.AuthSettings(api_key="your-key") client = Client(credentials=auth) - # Simple search + # Simple search (one page; call results.get_next() for more) results = await client.search_companies("Apple") - async for company in results: + for company in results.data: print(f"{company.title} ({company.company_number})") # Search all types results = await client.search("Barclays") - async for result in results: + for result in results.data: print(f"{result.title}") # Search officers officers = await client.search_officers("Smith") - async for officer in officers: + for officer in officers.data: print(f"{officer.title}") Pagination ----- -Search results are returned as :class:`ch_api.types.pagination.types.MultipageList`. +Search results are returned as a single-page :class:`ch_api.types.pagination.types.MultipageList`. +Each call returns one page; advance with ``get_next`` (or pass ``next_page`` back to the endpoint). See Also -------- diff --git a/src/ch_api/types/public_data/search_companies.py b/src/ch_api/types/public_data/search_companies.py index 194db7e..263f24f 100644 --- a/src/ch_api/types/public_data/search_companies.py +++ b/src/ch_api/types/public_data/search_companies.py @@ -73,8 +73,8 @@ Pagination ----- -All searches are paginated. Use the client's search methods which handle -pagination automatically through MultipageList interface. +All searches are paginated. The client's search methods return a single-page +MultipageList; advance one page at a time with ``get_next`` (or ``next_page``). Example Usage ----- diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 0ed42c3..51222fd 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -19,3 +19,22 @@ def lloyds_company_number(): @pytest.fixture def tesco_company_number(): return "00445790" # Tesco PLC + + +@pytest.fixture +def collect_pages(): + """Walk a MultipageList via ``get_next``, accumulating items. + + Returns an async helper ``collect(page, minimum=None)`` that follows the + ``get_next`` chain until at least ``minimum`` items are gathered (or every + page is exhausted when ``minimum`` is None). + """ + + async def _collect(page, minimum=None): + items = list(page.data) + while (minimum is None or len(items) < minimum) and page.pagination.has_next: + page = await page.get_next() + items.extend(page.data) + return items + + return _collect diff --git a/tests/integration/test_company_info.py b/tests/integration/test_company_info.py index 986cd72..f3fe531 100644 --- a/tests/integration/test_company_info.py +++ b/tests/integration/test_company_info.py @@ -20,7 +20,7 @@ async def test_get_registered_office_address(live_env_test_client: ch_api.api.Cl @pytest.mark.asyncio async def test_get_officer_list(live_env_test_client: ch_api.api.Client, r5e_company_number): - officer_list = await live_env_test_client.get_officer_list(r5e_company_number, result_count=100) + officer_list = await live_env_test_client.get_officer_list(r5e_company_number) assert officer_list is not None assert len(officer_list.data) == 1 assert officer_list.data[0].name == "ORLOVS, Ilja" # Hey there, it's me! diff --git a/tests/integration/test_filing_history.py b/tests/integration/test_filing_history.py index e7f8363..8c47866 100644 --- a/tests/integration/test_filing_history.py +++ b/tests/integration/test_filing_history.py @@ -5,7 +5,7 @@ @pytest.mark.asyncio async def test_get_r5e_company_filing_history(live_env_test_client: ch_api.api.Client, r5e_company_number): - response = await live_env_test_client.get_company_filing_history(r5e_company_number, result_count=100) + response = await live_env_test_client.get_company_filing_history(r5e_company_number) assert len(response.data) >= 8 # Check that the name change filing is present for filing in response.data: @@ -36,7 +36,7 @@ async def test_get_r5e_company_filing_history_w_filter( exp_result_count, ): response = await live_env_test_client.get_company_filing_history( - r5e_company_number, categories=cetegory_filter, result_count=100 + r5e_company_number, categories=cetegory_filter ) assert len(response.data) == exp_result_count diff --git a/tests/integration/test_officer_appointments.py b/tests/integration/test_officer_appointments.py index 1e47ed7..3f605fa 100644 --- a/tests/integration/test_officer_appointments.py +++ b/tests/integration/test_officer_appointments.py @@ -5,6 +5,6 @@ @pytest.mark.asyncio async def test_get_appointments(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU", result_count=100) + result = await live_env_test_client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU") assert result assert len(result.data) > 0 diff --git a/tests/integration/test_psc.py b/tests/integration/test_psc.py index 8dd8d10..36add21 100644 --- a/tests/integration/test_psc.py +++ b/tests/integration/test_psc.py @@ -5,7 +5,7 @@ @pytest.mark.asyncio async def test_get_psc_list(live_env_test_client: ch_api.api.Client, r5e_company_number): - result = await live_env_test_client.get_company_psc_list(r5e_company_number, result_count=100) + result = await live_env_test_client.get_company_psc_list(r5e_company_number) assert result assert len(result.data) == 1 assert result.data[0].name == "Mr Ilja Orlovs" @@ -13,7 +13,7 @@ async def test_get_psc_list(live_env_test_client: ch_api.api.Client, r5e_company @pytest.mark.asyncio async def test_get_lloyds_psc_list(live_env_test_client: ch_api.api.Client, lloyds_company_number): - result = await live_env_test_client.get_company_psc_list(lloyds_company_number, result_count=100) + result = await live_env_test_client.get_company_psc_list(lloyds_company_number) assert result assert len(result.data) == 1 assert result.data[0].name == "Lloyds Banking Group Plc" @@ -21,7 +21,7 @@ async def test_get_lloyds_psc_list(live_env_test_client: ch_api.api.Client, lloy @pytest.mark.asyncio async def test_get_r5e_statements(live_env_test_client: ch_api.api.Client, r5e_company_number): - result = await live_env_test_client.get_company_psc_statements(r5e_company_number, result_count=100) + result = await live_env_test_client.get_company_psc_statements(r5e_company_number) assert result # no statements for R5E assert len(result.data) == 0 @@ -29,7 +29,7 @@ async def test_get_r5e_statements(live_env_test_client: ch_api.api.Client, r5e_c @pytest.mark.asyncio async def test_someones_psc_statements(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.get_company_psc_statements("SC549056", result_count=100) + result = await live_env_test_client.get_company_psc_statements("SC549056") assert result assert len(result.data) == 1 assert result.data[0].statement == "no-individual-or-entity-with-signficant-control" diff --git a/tests/integration/test_search_api.py b/tests/integration/test_search_api.py index 4a08198..5cb76d9 100644 --- a/tests/integration/test_search_api.py +++ b/tests/integration/test_search_api.py @@ -7,12 +7,13 @@ class TestGenericSearch: @pytest.mark.asyncio - async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e_company_number): - search_response = await live_env_test_client.search("R5E ART LIMITED", result_count=300) - assert len(search_response.data) >= 300 + async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e_company_number, collect_pages): + first_page = await live_env_test_client.search("R5E ART LIMITED") + items = await collect_pages(first_page, minimum=300) + assert len(items) >= 300 one_found = False - for el in search_response.data: + for el in items: if el.company_number == r5e_company_number: one_found = True break @@ -20,7 +21,7 @@ async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e @pytest.mark.asyncio async def test_search_director(self, live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search("Orlovs", result_count=50) + search_response = await live_env_test_client.search("Orlovs") assert len(search_response.data) > 50 one_found = False @@ -65,25 +66,26 @@ class TestAdvancedSearch: ], ) async def test_simple(self, live_env_test_client: ch_api.api.Client, query, expected_count): - search_response = await live_env_test_client.advanced_company_search(**query, result_count=100) + search_response = await live_env_test_client.advanced_company_search(**query) assert len(search_response.data) == expected_count @pytest.mark.asyncio async def test_alphabetical_companies_search(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.alphabetical_companies_search("Barclays", page_size=100, result_count=100) + result = await live_env_test_client.alphabetical_companies_search("Barclays", page_size=100) assert len(result.data) >= 100 all_names = [el.company_name for el in result.data] assert all("BARCLAY" in name.upper() for name in all_names) @pytest.mark.asyncio -async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_company_number): - search_response = await live_env_test_client.search_companies("R5E ART LIMITED", result_count=300) - assert len(search_response.data) >= 300 +async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_company_number, collect_pages): + first_page = await live_env_test_client.search_companies("R5E ART LIMITED") + items = await collect_pages(first_page, minimum=300) + assert len(items) >= 300 one_found = False - for el in search_response.data: + for el in items: assert isinstance(el, ch_api.types.public_data.search.CompanySearchItem) if el.company_number == r5e_company_number: one_found = True @@ -93,7 +95,7 @@ async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_com @pytest.mark.asyncio async def test_search_officers(live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search_officers("Ilja Orlovs", result_count=10) + search_response = await live_env_test_client.search_officers("Ilja Orlovs") assert len(search_response.data) > 10 one_found = False @@ -107,7 +109,7 @@ async def test_search_officers(live_env_test_client: ch_api.api.Client): @pytest.mark.asyncio async def test_search_disqualified_officers(live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search_disqualified_officers("bob", result_count=10) + search_response = await live_env_test_client.search_disqualified_officers("bob") assert len(search_response.data) > 0 one_found = False @@ -129,5 +131,5 @@ async def test_search_disqualified_officers(live_env_test_client: ch_api.api.Cli ], ) async def test_search_dissolved_companies(live_env_test_client: ch_api.api.Client, query_type, exp_company_name): - search_response = await live_env_test_client.search_dissolved_companies("bob", type=query_type, result_count=10) + search_response = await live_env_test_client.search_dissolved_companies("bob", type=query_type) assert len(search_response.data) >= 10 diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index 5fd51c5..12d6901 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -7,7 +7,7 @@ import pydantic import pytest -from ch_api import api, api_settings +from ch_api import api, api_settings, exc from ch_api.types.pagination import types as pagination_types from ch_api.types.public_data import search_companies as sc @@ -73,12 +73,36 @@ def test_decode_calls_deserialize(self): serializer.deserialize.assert_called_once_with("ENCRYPTED") +class TestMultipageListGetNext: + """MultipageList.get_next error paths on manually-constructed instances.""" + + @pytest.mark.asyncio + async def test_get_next_on_last_page_raises_no_more_pages(self): + """has_next is False → NoMorePagesError.""" + page = pagination_types.MultipageList( + data=[], + pagination=pagination_types.PaginationInfo(has_next=False), + ) + with pytest.raises(exc.NoMorePagesError): + await page.get_next() + + @pytest.mark.asyncio + async def test_get_next_without_fetcher_raises_runtime_error(self): + """has_next True but no bound fetcher (e.g. deserialized) → RuntimeError.""" + page = pagination_types.MultipageList( + data=[], + pagination=pagination_types.PaginationInfo(has_next=True, next_page="tok"), + ) + with pytest.raises(RuntimeError, match="no next-page fetcher"): + await page.get_next() + + class TestCursorPaginationContinuation: - """Line 422 — cursor=next_cursor loop continuation.""" + """Single cursor page + get_next advances via the bound fetcher.""" @pytest.mark.asyncio - async def test_cursor_loop_continues(self): - """Line 422: second iteration sets cursor = next_cursor.""" + async def test_get_next_advances_cursor(self): + """First page reports has_next; get_next fetches the next cursor page.""" client = _make_client() call_count = 0 @@ -94,8 +118,14 @@ async def fetch_fn(cursor): assert cursor == "CURSOR_A" return [_Item(val=2)], None - page = await client._fetch_paginated_cursor(fetch_fn, None, 2) - assert len(page.data) == 2 + page = await client._fetch_paginated_cursor(fetch_fn, None) + assert [i.val for i in page.data] == [1] + assert page.pagination.has_next + assert call_count == 1 + + page2 = await page.get_next() + assert [i.val for i in page2.data] == [2] + assert not page2.pagination.has_next assert call_count == 2 @@ -144,27 +174,31 @@ async def fake_get_resource(url, result_type): assert page.data == [] @pytest.mark.asyncio - async def test_cursor_loop_via_alphabetical_search(self): - """Line 422 via alphabetical_companies_search: second call uses search_below.""" + async def test_get_next_via_alphabetical_search_uses_search_below(self): + """alphabetical_companies_search: get_next carries the search_below cursor.""" client = _make_client() call_count = 0 item = _alpha_company("KEY_ALPHA:00000001") + urls_seen = [] async def fake_get_resource(url, result_type): nonlocal call_count call_count += 1 - if call_count == 1: - result = MagicMock() - result.items = [item] - return result + urls_seen.append(url) result = MagicMock() - result.items = [] + result.items = [item] if call_count == 1 else [] return result client._get_resource = fake_get_resource - page = await client.alphabetical_companies_search("test", page_size=1, result_count=2) + page = await client.alphabetical_companies_search("test", page_size=1) assert len(page.data) == 1 + assert page.pagination.has_next + assert call_count == 1 + + page2 = await page.get_next() + assert page2.data == [] assert call_count == 2 + assert any("search_below=KEY_ALPHA" in u for u in urls_seen) class TestDissolvedSearchBranches: @@ -382,6 +416,18 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource await client.advanced_company_search(sic_codes=["62012"]) + @pytest.mark.asyncio + async def test_page_size_adds_size_param(self): + """page_size is forwarded as the ``size`` query parameter.""" + client = _make_client() + + async def fake_get_resource(url, result_type): + assert "size=50" in url + return MagicMock(items=[], hits=0) + + client._get_resource = fake_get_resource + await client.advanced_company_search(company_name_includes="test", page_size=50) + @pytest.mark.asyncio async def test_416_returns_empty(self): """Lines 681-684: 416 → return [], None.""" From b43717ce8ec89f9439e87f186d175c8f69cfa933 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Wed, 17 Jun 2026 19:32:45 +0100 Subject: [PATCH 02/12] bugfixes --- .pdm-python | 2 +- src/ch_api/api.py | 24 ++++++++++++------------ tests/unit/test_api_branch_coverage.py | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/.pdm-python b/.pdm-python index 57c08da..a748949 100644 --- a/.pdm-python +++ b/.pdm-python @@ -1 +1 @@ -/Users/ilo/Devel/ch-api/.venv/bin/python \ No newline at end of file +/Users/ilo/Devel/Release.art/ch-api/.venv/bin/python \ No newline at end of file diff --git a/src/ch_api/api.py b/src/ch_api/api.py index a2fdbda..910559d 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -529,7 +529,7 @@ async def get_officer_list( ] ] = None, order_by: typing.Literal["appointed_on", "resigned_on", "surname"] = "appointed_on", - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.company_officers.OfficerSummary]: """Fetch one page of company officers for a given company. @@ -594,7 +594,7 @@ async def get_company_registers( async def search( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search.AnySearchResultT]: """Search for companies using the Companies House search API. @@ -644,7 +644,7 @@ async def advanced_company_search( # noqa: C901 incorporated_to: typing.Optional[datetime.date] = None, location: typing.Optional[str] = None, sic_codes: typing.Optional[typing.Sequence[str]] = None, - page_size: typing.Optional[typing.Annotated[int, pydantic.conint(ge=1, le=5000)]] = None, + page_size: typing.Optional[typing.Annotated[int, pydantic.Field(ge=1, le=5000)]] = None, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AdvancedCompany]: """Perform an advanced search for companies using the Companies House search API. @@ -714,7 +714,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: async def alphabetical_companies_search( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 10, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AlphabeticalCompany]: """Search for companies alphabetically using the Companies House search API. @@ -755,7 +755,7 @@ async def _fetch( async def search_dissolved_companies( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 10, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, type: typing.Literal["alphabetical", "best-match", "previous-name-dissolved"] = "alphabetical", # noqa: A002 next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.DissolvedCompany]: @@ -799,7 +799,7 @@ async def _fetch( async def search_companies( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search.CompanySearchItem]: """Search for companies using the Companies House search API. @@ -838,7 +838,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: async def search_officers( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search.OfficerSearchItem]: """Search for officers using the Companies House search API. @@ -877,7 +877,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: async def search_disqualified_officers( self, query: str, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=200)] = 200, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.search.DisqualifiedOfficerSearchItem]: """Search for disqualified officers using the Companies House search API. @@ -980,7 +980,7 @@ async def get_company_filing_history( ..., ] | None = None, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.filing_history.FilingHistoryItem]: """Fetch the filing history for a given company. @@ -1305,7 +1305,7 @@ async def get_officer_appointments( self, officer_id: OfficerIdStrT, filter: typing.Optional[typing.Literal["active"]] = None, # noqa: A002 - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.officer_appointments.OfficerAppointmentSummary]: """Fetch the officer appointments for a given officer. @@ -1367,7 +1367,7 @@ async def get_company_psc_list( self, company_number: CompanyNumberStrT, register_view: bool = False, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.psc.ListSummary]: """Fetch the list of persons with significant control for a given company. @@ -1410,7 +1410,7 @@ async def get_company_psc_statements( self, company_number: CompanyNumberStrT, register_view: bool = False, - page_size: typing.Annotated[int, pydantic.conint(ge=1, le=100)] = 25, + page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, ) -> types.pagination.types.MultipageList[types.public_data.psc.Statement]: """Fetch the PSC statements for a given company. diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index 12d6901..b485198 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -73,6 +73,32 @@ def test_decode_calls_deserialize(self): serializer.deserialize.assert_called_once_with("ENCRYPTED") +class TestPageSizeBoundsEnforced: + """page_size bounds must actually be enforced (regression: conint nested in + Annotated silently dropped the constraint — Field is required).""" + + @pytest.mark.asyncio + @pytest.mark.parametrize("page_size", [0, 999]) + async def test_search_companies_rejects_out_of_range(self, page_size): + client = _make_client() + with pytest.raises(pydantic.ValidationError): + await client.search_companies("x", page_size=page_size) + + @pytest.mark.asyncio + @pytest.mark.parametrize("page_size", [0, 101]) + async def test_filing_history_rejects_out_of_range(self, page_size): + client = _make_client() + with pytest.raises(pydantic.ValidationError): + await client.get_company_filing_history("12345678", page_size=page_size) + + @pytest.mark.asyncio + @pytest.mark.parametrize("page_size", [0, 5001]) + async def test_advanced_search_rejects_out_of_range(self, page_size): + client = _make_client() + with pytest.raises(pydantic.ValidationError): + await client.advanced_company_search(company_name_includes="x", page_size=page_size) + + class TestMultipageListGetNext: """MultipageList.get_next error paths on manually-constructed instances.""" From 8ad75b8247e14e388323911f36e7b21da8a0fc64 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Wed, 17 Jun 2026 19:51:06 +0100 Subject: [PATCH 03/12] x --- README.md | 5 +- docs/sources/api-overview.rst | 7 +- docs/sources/usage.rst | 47 +++--- src/ch_api/__init__.py | 9 +- src/ch_api/api.py | 144 ++++++++++++++---- src/ch_api/types/pagination/__init__.py | 11 +- src/ch_api/types/pagination/types.py | 26 ++-- src/ch_api/types/public_data/search.py | 5 +- .../types/public_data/search_companies.py | 5 +- tests/integration/conftest.py | 19 --- tests/integration/test_company_info.py | 2 +- tests/integration/test_filing_history.py | 4 +- .../integration/test_officer_appointments.py | 2 +- tests/integration/test_psc.py | 8 +- tests/integration/test_search_api.py | 30 ++-- tests/unit/test_api_branch_coverage.py | 58 ++++++- 16 files changed, 254 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index c9957c8..d1b04ac 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,9 @@ Example of getting company information: ## Pagination -List endpoints return a single-page `MultipageList[T]` with `.data` (list) and -`.pagination` metadata. Advance one page at a time with `.get_next()`: +List endpoints return a `MultipageList[T]` with `.data` (list) and `.pagination` +metadata. Pass `result_count` to collect more items per call, and advance with +`.get_next()`: >>> async def search_example(client): ... results = await client.search_companies("tech") diff --git a/docs/sources/api-overview.rst b/docs/sources/api-overview.rst index fd08f1c..df090e8 100644 --- a/docs/sources/api-overview.rst +++ b/docs/sources/api-overview.rst @@ -66,9 +66,10 @@ Key Endpoints Pagination ========== -List endpoints return a single-page ``MultipageList[T]`` with ``data`` (list) and -``pagination`` metadata. Pass ``page_size`` to control how many items a page holds, -and advance with ``get_next`` (or ``next_page``): +List endpoints return a ``MultipageList[T]`` with ``data`` (list) and ``pagination`` +metadata. Pass ``result_count`` to collect at least that many items in one call, +advance with ``get_next`` (or ``next_page``), and use ``page_size`` to control the +underlying per-request size: >>> async def pagination_example(client): ... page = await client.search_companies("tech") diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index 744ed0f..6702c81 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -103,8 +103,8 @@ Access a company's filing history: .. code:: python - # Get one page of filing history (use page_size / get_next for more) - filings = await client.get_company_filing_history("09370755", page_size=100) + # Get up to 100 filings (issues several requests as needed) + filings = await client.get_company_filing_history("09370755", result_count=100) for filing in filings.data: print(f"Description: {filing.description}") print(f"Date: {filing.date}") @@ -156,7 +156,7 @@ Use advanced search with multiple criteria: company_status="active", company_type="ltd", location="London", - page_size=100, + result_count=100, ) for company in results.data: print(f"{company.company_name} ({company.company_number})") @@ -168,7 +168,7 @@ Search for officers across all companies: .. code:: python - results = await client.search_officers("John Smith", page_size=100) + results = await client.search_officers("John Smith", result_count=100) for officer in results.data: print(f"Name: {officer.title}") print(f"Date of Birth: {officer.date_of_birth}") @@ -182,7 +182,7 @@ Search for disqualified officers: .. code:: python - results = await client.search_disqualified_officers("Smith", page_size=100) + results = await client.search_disqualified_officers("Smith", result_count=100) for officer in results.data: print(f"Name: {officer.title}") print(f"Date of Birth: {officer.date_of_birth}") @@ -192,7 +192,7 @@ Search for disqualified officers: Working with Pagination ======================= -Many API endpoints return paginated results as :class:`ch_api.types.pagination.types.MultipageList`, a minimal value object holding a single API page: ``data`` (the items on this page), ``pagination`` (cursor metadata), and a ``get_next`` handle to fetch the next page. It never accumulates a unified list across pages — you advance one page at a time. +Many API endpoints return paginated results as :class:`ch_api.types.pagination.types.MultipageList`, a minimal value object with ``data`` (the items collected by this call), ``pagination`` (cursor metadata), and a ``get_next`` handle to fetch the next batch. Pass ``result_count`` to collect at least that many items in one call (the client issues multiple underlying requests of ``page_size`` as needed); call ``get_next`` to fetch the next batch. Fetching a page:: @@ -205,26 +205,26 @@ Fetching a page:: Fetching multiple pages ----------------------- -Each call returns one page. Use ``page_size`` to control how many items a page -holds, and walk the result set with ``get_next``: +Pass ``result_count`` to collect more items in one call, then walk the rest of +the result set with ``get_next``: .. code:: python - # Walk every page with the bound get_next handle - page = await client.search_companies("tech", page_size=100) + # Collect at least 100 items (may issue several underlying requests) + page = await client.search_companies("tech", result_count=100) while True: for company in page.data: print(company.title) if not page.pagination.has_next: break - page = await page.get_next() + page = await page.get_next() # fetches the next batch of result_count items # Or resume statelessly with the opaque cursor token - page = await client.search_companies("tech", page_size=25) + page = await client.search_companies("tech", result_count=25) while page.pagination.has_next: page = await client.search_companies( "tech", - page_size=25, + result_count=25, next_page=page.pagination.next_page, ) @@ -378,7 +378,7 @@ All Pydantic models support conversion to dictionaries: company_json = company.model_dump_json() # For paginated results - results = await client.search_companies("Apple", page_size=25) + results = await client.search_companies("Apple", result_count=25) # Convert all items to dictionaries companies_list = [c.model_dump() for c in results.data] @@ -402,7 +402,7 @@ Find Companies and Their Officers client = Client(credentials=auth) # Search for companies - companies = await client.search_companies("Technology Ltd", page_size=5) + companies = await client.search_companies("Technology Ltd", result_count=5) for company in companies.data: print(f"\\nCompany: {company.title} ({company.company_number})") @@ -410,7 +410,7 @@ Find Companies and Their Officers # Get officers for each company try: - officers = await client.get_officer_list(company.company_number, page_size=100) + officers = await client.get_officer_list(company.company_number, result_count=100) print("Officers:") for officer in officers.data: print(f" - {officer.name} ({officer.officer_role})") @@ -442,15 +442,15 @@ Export Company Data data['profile'] = (await client.get_company_profile(company_number)).model_dump() # Officers - officers = await client.get_officer_list(company_number, page_size=200) + officers = await client.get_officer_list(company_number, result_count=200) data['officers'] = [o.model_dump() for o in officers.data] # PSCs - psc_result = await client.get_company_psc_list(company_number, page_size=200) + psc_result = await client.get_company_psc_list(company_number, result_count=200) data['pscs'] = [p.model_dump() for p in psc_result.data] # Filing history (first 100) - filings = await client.get_company_filing_history(company_number, page_size=100) + filings = await client.get_company_filing_history(company_number, result_count=100) data['filing_history'] = [f.model_dump() for f in filings.data] # Write to file @@ -478,7 +478,7 @@ Monitor Company Changes client = Client(credentials=auth) # Get filing history - filings = await client.get_company_filing_history(company_number, page_size=100) + filings = await client.get_company_filing_history(company_number, result_count=100) # Filter for recent filings (last 30 days) cutoff_date = datetime.now().date() - timedelta(days=30) @@ -546,7 +546,8 @@ Pagination Issues If pagination isn't working as expected: -- Use a regular ``for`` loop over ``result.data`` (it's a plain list for one page) -- Pass ``page_size`` to control how many items each page holds +- Use a regular ``for`` loop over ``result.data`` (it's a plain list) +- Pass ``result_count`` to collect more items per call; ``page_size`` controls the + underlying per-request size - Call ``await result.get_next()`` (or pass ``result.pagination.next_page`` back to - the endpoint) to fetch the next page + the endpoint) to fetch the next batch diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index 34ba3d3..beb5174 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -142,10 +142,11 @@ Pagination ---------- -Search and list endpoints return a single-page ``MultipageList[T]`` with a -``data`` list and ``pagination`` metadata. Each call fetches exactly one API -page; advance one page at a time with ``get_next`` (or pass ``next_page`` back -to the same endpoint). Use ``page_size`` to control how many items a page holds:: +Search and list endpoints return a ``MultipageList[T]`` with a ``data`` list and +``pagination`` metadata. Pass ``result_count`` to collect at least that many +items in one call (issuing multiple underlying requests if needed), advance with +``get_next`` (or ``next_page``), and use ``page_size`` to control the underlying +per-request size:: >>> @run_async_func ... async def pagination_example(client): diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 910559d..1afd39d 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -344,38 +344,57 @@ async def _fetch_paginated( self, fetch_page_fn: typing.Callable[[int], typing.Awaitable[tuple[list, typing.Optional[int]]]], next_page: typing.Optional[types.pagination.types.NextPageToken], + result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch a single offset-based API page and return a MultipageList. + """Fetch offset-based API pages until ``result_count`` items are collected. - Fetches exactly one API page (no accumulation). When more pages exist, - the returned list carries a ``get_next`` handle that fetches the next - single page via the same ``fetch_page_fn``. + Issues one or more underlying API requests (each of the endpoint's + ``page_size``) until at least ``result_count`` items are gathered or no + further pages exist. The returned list carries a ``get_next`` handle that + fetches the next batch of ``result_count`` items via the same + ``fetch_page_fn``. Args: fetch_page_fn: Callable taking ``start_index`` (int), returning a tuple of ``(items, total_count)``. ``total_count`` may be None - if unknown; in that case no further pages will be reported. + if unknown; in that case no further pages will be fetched. next_page: Cursor from a previous call, or None to start from offset 0. + result_count: Minimum number of items to collect. At least one + underlying page is always fetched regardless of this value. Returns: - A MultipageList holding one page of items and pagination metadata. + A MultipageList holding the collected items and pagination metadata. """ page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) current_start = page_state.start_index + items: list = [] + total_count: typing.Optional[int] = None + has_next = False + last_page_len = 0 - page_items, total_count = await fetch_page_fn(current_start) - next_start = current_start + len(page_items) - has_next = bool(total_count is not None and page_items and next_start < total_count) + while True: + page_items, page_total = await fetch_page_fn(current_start) + last_page_len = len(page_items) + if page_total is not None: + total_count = page_total + items.extend(page_items) + + has_next = bool(total_count is not None and page_items and (current_start + last_page_len) < total_count) + + if not has_next or len(items) >= result_count: + break + + current_start += last_page_len next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next: - next_state = types.pagination.types._PageState(start_index=next_start) + next_state = types.pagination.types._PageState(start_index=current_start + last_page_len) next_page_out = self._encode_next_page(next_state) result = types.pagination.types.MultipageList( - data=page_items, + data=items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -387,7 +406,7 @@ async def _fetch_paginated( captured_next_page = next_page_out async def _fetch_next() -> types.pagination.types.MultipageList: - return await self._fetch_paginated(fetch_page_fn, captured_next_page) + return await self._fetch_paginated(fetch_page_fn, captured_next_page, result_count) result._fetch_next_page = _fetch_next @@ -397,13 +416,15 @@ async def _fetch_paginated_cursor( self, fetch_page_fn: typing.Callable[[typing.Optional[str]], typing.Awaitable[tuple[list, typing.Optional[str]]]], next_page: typing.Optional[types.pagination.types.NextPageToken], + result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch a single cursor-based API page and return a MultipageList. + """Fetch cursor-based API pages until ``result_count`` items are collected. Used for endpoints that paginate via ``search_below`` / ``search_above`` cursors (e.g. alphabetical company search) rather than ``start_index``. - Fetches exactly one API page; the returned list carries a ``get_next`` - handle to advance. + Issues one or more underlying API requests until at least ``result_count`` + items are gathered or no further pages exist. The returned list carries a + ``get_next`` handle that fetches the next batch. Args: fetch_page_fn: Callable taking the current ``search_below`` cursor @@ -411,18 +432,29 @@ async def _fetch_paginated_cursor( ``next_cursor`` is None when no further pages exist. next_page: Cursor from a previous call, or None to start from the beginning. + result_count: Minimum number of items to collect. Returns: - A MultipageList holding one page of items and pagination metadata. + A MultipageList holding the collected items and pagination metadata. ``pagination.size`` is always None for cursor-based endpoints. """ page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) cursor = page_state.search_below + items: list = [] + has_next = False + next_cursor: typing.Optional[str] = None + + while True: + page_items, next_cursor = await fetch_page_fn(cursor) + items.extend(page_items) + has_next = next_cursor is not None + + if not has_next or len(items) >= result_count: + break - page_items, next_cursor = await fetch_page_fn(cursor) - has_next = next_cursor is not None + cursor = next_cursor next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next and next_cursor is not None: @@ -430,7 +462,7 @@ async def _fetch_paginated_cursor( next_page_out = self._encode_next_page(next_state) result = types.pagination.types.MultipageList( - data=page_items, + data=items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -442,7 +474,7 @@ async def _fetch_paginated_cursor( captured_next_page = next_page_out async def _fetch_next() -> types.pagination.types.MultipageList: - return await self._fetch_paginated_cursor(fetch_page_fn, captured_next_page) + return await self._fetch_paginated_cursor(fetch_page_fn, captured_next_page, result_count) result._fetch_next_page = _fetch_next @@ -531,6 +563,7 @@ async def get_officer_list( order_by: typing.Literal["appointed_on", "resigned_on", "surname"] = "appointed_on", page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.company_officers.OfficerSummary]: """Fetch one page of company officers for a given company. @@ -542,6 +575,9 @@ async def get_officer_list( Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). Returns ------- @@ -571,7 +607,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def get_officer_appointment( @@ -596,6 +632,7 @@ async def search( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.AnySearchResultT]: """Search for companies using the Companies House search API. @@ -607,6 +644,9 @@ async def search( Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/search" @@ -627,7 +667,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def advanced_company_search( # noqa: C901 @@ -646,6 +686,7 @@ async def advanced_company_search( # noqa: C901 sic_codes: typing.Optional[typing.Sequence[str]] = None, page_size: typing.Optional[typing.Annotated[int, pydantic.Field(ge=1, le=5000)]] = None, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AdvancedCompany]: """Perform an advanced search for companies using the Companies House search API. @@ -656,6 +697,9 @@ async def advanced_company_search( # noqa: C901 When omitted, the API's own default page size is used. next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ query_params: dict = {} if company_name_includes: @@ -708,7 +752,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.hits - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def alphabetical_companies_search( @@ -716,6 +760,7 @@ async def alphabetical_companies_search( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AlphabeticalCompany]: """Search for companies alphabetically using the Companies House search API. @@ -727,6 +772,9 @@ async def alphabetical_companies_search( Number of results per API page (1-100, default 10). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/alphabetical-search/companies" @@ -749,7 +797,7 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page) + return await self._fetch_paginated_cursor(_fetch, next_page, result_count) @pydantic.validate_call async def search_dissolved_companies( @@ -758,6 +806,7 @@ async def search_dissolved_companies( page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, type: typing.Literal["alphabetical", "best-match", "previous-name-dissolved"] = "alphabetical", # noqa: A002 next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.DissolvedCompany]: """Search for dissolved companies using the Companies House search API. @@ -771,6 +820,9 @@ async def search_dissolved_companies( Search type (alphabetical, best-match, previous-name-dissolved). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/dissolved-search/companies" @@ -793,7 +845,7 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page) + return await self._fetch_paginated_cursor(_fetch, next_page, result_count) @pydantic.validate_call async def search_companies( @@ -801,6 +853,7 @@ async def search_companies( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.CompanySearchItem]: """Search for companies using the Companies House search API. @@ -812,6 +865,9 @@ async def search_companies( Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/search/companies" @@ -832,7 +888,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def search_officers( @@ -840,6 +896,7 @@ async def search_officers( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.OfficerSearchItem]: """Search for officers using the Companies House search API. @@ -851,6 +908,9 @@ async def search_officers( Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/search/officers" @@ -871,7 +931,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def search_disqualified_officers( @@ -879,6 +939,7 @@ async def search_disqualified_officers( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.DisqualifiedOfficerSearchItem]: """Search for disqualified officers using the Companies House search API. @@ -890,6 +951,9 @@ async def search_disqualified_officers( Number of items per API page (1-200, default 200). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/search/disqualified-officers" @@ -910,7 +974,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def get_company_charges( @@ -982,6 +1046,7 @@ async def get_company_filing_history( | None = None, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.filing_history.FilingHistoryItem]: """Fetch the filing history for a given company. @@ -995,6 +1060,9 @@ async def get_company_filing_history( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_query_params: dict = {} if categories is not None: @@ -1014,7 +1082,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_count - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def get_filing_history_item( @@ -1307,6 +1375,7 @@ async def get_officer_appointments( filter: typing.Optional[typing.Literal["active"]] = None, # noqa: A002 page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.officer_appointments.OfficerAppointmentSummary]: """Fetch the officer appointments for a given officer. @@ -1320,6 +1389,9 @@ async def get_officer_appointments( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/officers/{officer_id}/appointments" @@ -1338,7 +1410,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def get_company_uk_establishments( @@ -1369,6 +1441,7 @@ async def get_company_psc_list( register_view: bool = False, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.ListSummary]: """Fetch the list of persons with significant control for a given company. @@ -1382,6 +1455,9 @@ async def get_company_psc_list( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control" register_view_str = "true" if register_view else "false" @@ -1403,7 +1479,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) @pydantic.validate_call async def get_company_psc_statements( @@ -1412,6 +1488,7 @@ async def get_company_psc_statements( register_view: bool = False, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, next_page: typing.Optional[types.pagination.types.NextPageToken] = None, + result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.Statement]: """Fetch the PSC statements for a given company. @@ -1425,6 +1502,9 @@ async def get_company_psc_statements( Number of items per API page (1-100, default 25). next_page: str, optional Cursor from a previous call to continue pagination. + result_count: int + Minimum number of items to collect, issuing multiple underlying + requests of ``page_size`` if needed (default 1 = one page). """ base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control-statements" register_view_str = "true" if register_view else "false" @@ -1446,7 +1526,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page) + return await self._fetch_paginated(_fetch, next_page, result_count) async def _get_psc_by_type( self, diff --git a/src/ch_api/types/pagination/__init__.py b/src/ch_api/types/pagination/__init__.py index 3f64aac..cce5a3e 100644 --- a/src/ch_api/types/pagination/__init__.py +++ b/src/ch_api/types/pagination/__init__.py @@ -1,16 +1,17 @@ """Pagination support for Companies House API list endpoints. -All paginated endpoints on ``Client`` accept ``next_page`` and ``page_size`` -and return a single-page ``MultipageList[T]``. Advance one page at a time with -``get_next``:: +All paginated endpoints on ``Client`` accept ``page_size``, ``next_page``, and +``result_count``, and return a ``MultipageList[T]``. ``result_count`` sets the +minimum number of items to collect in one call; ``get_next`` fetches the next +batch:: - page = await client.search_companies("Apple") + page = await client.search_companies("Apple", result_count=50) while page.pagination.has_next: page = await page.get_next() Key Classes ----------- -- :class:`types.MultipageList` - Single-page result container with ``get_next`` +- :class:`types.MultipageList` - Result-batch container with a ``get_next`` handle - :class:`types.PaginationInfo` - Pagination metadata - :class:`types.NextPageToken` - Opaque cursor type - :class:`types.PageTokenSerializer` - Optional token encryption protocol diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index b21a83b..d2c2de7 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -4,7 +4,7 @@ NextPageToken: Opaque string cursor passed between calls to page through results. PageTokenSerializer: Protocol for encrypting/decrypting pagination tokens. PaginationInfo: Pagination metadata returned alongside each page of results. - MultipageList: Generic value object containing one page of results. + MultipageList: Generic value object holding one batch of results plus a get_next handle. Internal types (not part of the public API): _PageState: Encodes CH API pagination state (start_index / search_below cursor). @@ -171,27 +171,35 @@ class PaginationInfo(pydantic.BaseModel): class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): - """A single page of typed results from a paginated CH API endpoint. + """A batch of typed results from a paginated CH API endpoint. - Contains exactly one API page of data items plus the pagination metadata - needed to retrieve the next page. Returned by all paginated methods on - ``Client``. Each instance holds one page only — it never accumulates a - unified list across pages. Advance one page at a time with :meth:`get_next` - (or by passing ``pagination.next_page`` back to the originating endpoint). + Contains the items collected by one client call — at least ``result_count`` + items, or all remaining items if fewer exist — plus the pagination metadata + needed to retrieve the next batch. Returned by all paginated methods on + ``Client``. ``MultipageList`` itself is a plain value object: it holds the + already-fetched ``data`` and a single :meth:`get_next` handle, and does not + fetch lazily or merge across calls. Advance with :meth:`get_next` (or by + passing ``pagination.next_page`` back to the originating endpoint). Type Parameters: _ItemT: The type of items in ``data``. - Walking every page with ``get_next``:: + Walking the whole result set with ``get_next``:: page = await client.search_companies("Apple") while True: for company in page.data: - ... # process this page's items + ... # process this batch's items if not page.pagination.has_next: break page = await page.get_next() + Fetching a larger batch in one call:: + + # Collect at least 100 items (may issue several underlying requests) + page = await client.search_companies("Apple", result_count=100) + # page.data has >= 100 items (or all available if fewer exist) + Resuming statelessly with a cursor token:: page = await client.search_companies("Apple") diff --git a/src/ch_api/types/public_data/search.py b/src/ch_api/types/public_data/search.py index 1c04f07..34253e1 100644 --- a/src/ch_api/types/public_data/search.py +++ b/src/ch_api/types/public_data/search.py @@ -70,8 +70,9 @@ Pagination ----- -Search results are returned as a single-page :class:`ch_api.types.pagination.types.MultipageList`. -Each call returns one page; advance with ``get_next`` (or pass ``next_page`` back to the endpoint). +Search results are returned as a :class:`ch_api.types.pagination.types.MultipageList`. +Pass ``result_count`` to collect at least that many items in one call; advance with +``get_next`` (or pass ``next_page`` back to the endpoint). See Also -------- diff --git a/src/ch_api/types/public_data/search_companies.py b/src/ch_api/types/public_data/search_companies.py index 263f24f..a6198d5 100644 --- a/src/ch_api/types/public_data/search_companies.py +++ b/src/ch_api/types/public_data/search_companies.py @@ -73,8 +73,9 @@ Pagination ----- -All searches are paginated. The client's search methods return a single-page -MultipageList; advance one page at a time with ``get_next`` (or ``next_page``). +All searches are paginated. The client's search methods return a MultipageList; +pass ``result_count`` to collect more items per call and advance with ``get_next`` +(or ``next_page``). Example Usage ----- diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 51222fd..0ed42c3 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -19,22 +19,3 @@ def lloyds_company_number(): @pytest.fixture def tesco_company_number(): return "00445790" # Tesco PLC - - -@pytest.fixture -def collect_pages(): - """Walk a MultipageList via ``get_next``, accumulating items. - - Returns an async helper ``collect(page, minimum=None)`` that follows the - ``get_next`` chain until at least ``minimum`` items are gathered (or every - page is exhausted when ``minimum`` is None). - """ - - async def _collect(page, minimum=None): - items = list(page.data) - while (minimum is None or len(items) < minimum) and page.pagination.has_next: - page = await page.get_next() - items.extend(page.data) - return items - - return _collect diff --git a/tests/integration/test_company_info.py b/tests/integration/test_company_info.py index f3fe531..986cd72 100644 --- a/tests/integration/test_company_info.py +++ b/tests/integration/test_company_info.py @@ -20,7 +20,7 @@ async def test_get_registered_office_address(live_env_test_client: ch_api.api.Cl @pytest.mark.asyncio async def test_get_officer_list(live_env_test_client: ch_api.api.Client, r5e_company_number): - officer_list = await live_env_test_client.get_officer_list(r5e_company_number) + officer_list = await live_env_test_client.get_officer_list(r5e_company_number, result_count=100) assert officer_list is not None assert len(officer_list.data) == 1 assert officer_list.data[0].name == "ORLOVS, Ilja" # Hey there, it's me! diff --git a/tests/integration/test_filing_history.py b/tests/integration/test_filing_history.py index 8c47866..e7f8363 100644 --- a/tests/integration/test_filing_history.py +++ b/tests/integration/test_filing_history.py @@ -5,7 +5,7 @@ @pytest.mark.asyncio async def test_get_r5e_company_filing_history(live_env_test_client: ch_api.api.Client, r5e_company_number): - response = await live_env_test_client.get_company_filing_history(r5e_company_number) + response = await live_env_test_client.get_company_filing_history(r5e_company_number, result_count=100) assert len(response.data) >= 8 # Check that the name change filing is present for filing in response.data: @@ -36,7 +36,7 @@ async def test_get_r5e_company_filing_history_w_filter( exp_result_count, ): response = await live_env_test_client.get_company_filing_history( - r5e_company_number, categories=cetegory_filter + r5e_company_number, categories=cetegory_filter, result_count=100 ) assert len(response.data) == exp_result_count diff --git a/tests/integration/test_officer_appointments.py b/tests/integration/test_officer_appointments.py index 3f605fa..1e47ed7 100644 --- a/tests/integration/test_officer_appointments.py +++ b/tests/integration/test_officer_appointments.py @@ -5,6 +5,6 @@ @pytest.mark.asyncio async def test_get_appointments(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU") + result = await live_env_test_client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU", result_count=100) assert result assert len(result.data) > 0 diff --git a/tests/integration/test_psc.py b/tests/integration/test_psc.py index 36add21..8dd8d10 100644 --- a/tests/integration/test_psc.py +++ b/tests/integration/test_psc.py @@ -5,7 +5,7 @@ @pytest.mark.asyncio async def test_get_psc_list(live_env_test_client: ch_api.api.Client, r5e_company_number): - result = await live_env_test_client.get_company_psc_list(r5e_company_number) + result = await live_env_test_client.get_company_psc_list(r5e_company_number, result_count=100) assert result assert len(result.data) == 1 assert result.data[0].name == "Mr Ilja Orlovs" @@ -13,7 +13,7 @@ async def test_get_psc_list(live_env_test_client: ch_api.api.Client, r5e_company @pytest.mark.asyncio async def test_get_lloyds_psc_list(live_env_test_client: ch_api.api.Client, lloyds_company_number): - result = await live_env_test_client.get_company_psc_list(lloyds_company_number) + result = await live_env_test_client.get_company_psc_list(lloyds_company_number, result_count=100) assert result assert len(result.data) == 1 assert result.data[0].name == "Lloyds Banking Group Plc" @@ -21,7 +21,7 @@ async def test_get_lloyds_psc_list(live_env_test_client: ch_api.api.Client, lloy @pytest.mark.asyncio async def test_get_r5e_statements(live_env_test_client: ch_api.api.Client, r5e_company_number): - result = await live_env_test_client.get_company_psc_statements(r5e_company_number) + result = await live_env_test_client.get_company_psc_statements(r5e_company_number, result_count=100) assert result # no statements for R5E assert len(result.data) == 0 @@ -29,7 +29,7 @@ async def test_get_r5e_statements(live_env_test_client: ch_api.api.Client, r5e_c @pytest.mark.asyncio async def test_someones_psc_statements(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.get_company_psc_statements("SC549056") + result = await live_env_test_client.get_company_psc_statements("SC549056", result_count=100) assert result assert len(result.data) == 1 assert result.data[0].statement == "no-individual-or-entity-with-signficant-control" diff --git a/tests/integration/test_search_api.py b/tests/integration/test_search_api.py index 5cb76d9..4a08198 100644 --- a/tests/integration/test_search_api.py +++ b/tests/integration/test_search_api.py @@ -7,13 +7,12 @@ class TestGenericSearch: @pytest.mark.asyncio - async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e_company_number, collect_pages): - first_page = await live_env_test_client.search("R5E ART LIMITED") - items = await collect_pages(first_page, minimum=300) - assert len(items) >= 300 + async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e_company_number): + search_response = await live_env_test_client.search("R5E ART LIMITED", result_count=300) + assert len(search_response.data) >= 300 one_found = False - for el in items: + for el in search_response.data: if el.company_number == r5e_company_number: one_found = True break @@ -21,7 +20,7 @@ async def test_search_company(self, live_env_test_client: ch_api.api.Client, r5e @pytest.mark.asyncio async def test_search_director(self, live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search("Orlovs") + search_response = await live_env_test_client.search("Orlovs", result_count=50) assert len(search_response.data) > 50 one_found = False @@ -66,26 +65,25 @@ class TestAdvancedSearch: ], ) async def test_simple(self, live_env_test_client: ch_api.api.Client, query, expected_count): - search_response = await live_env_test_client.advanced_company_search(**query) + search_response = await live_env_test_client.advanced_company_search(**query, result_count=100) assert len(search_response.data) == expected_count @pytest.mark.asyncio async def test_alphabetical_companies_search(live_env_test_client: ch_api.api.Client): - result = await live_env_test_client.alphabetical_companies_search("Barclays", page_size=100) + result = await live_env_test_client.alphabetical_companies_search("Barclays", page_size=100, result_count=100) assert len(result.data) >= 100 all_names = [el.company_name for el in result.data] assert all("BARCLAY" in name.upper() for name in all_names) @pytest.mark.asyncio -async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_company_number, collect_pages): - first_page = await live_env_test_client.search_companies("R5E ART LIMITED") - items = await collect_pages(first_page, minimum=300) - assert len(items) >= 300 +async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_company_number): + search_response = await live_env_test_client.search_companies("R5E ART LIMITED", result_count=300) + assert len(search_response.data) >= 300 one_found = False - for el in items: + for el in search_response.data: assert isinstance(el, ch_api.types.public_data.search.CompanySearchItem) if el.company_number == r5e_company_number: one_found = True @@ -95,7 +93,7 @@ async def test_search_companies(live_env_test_client: ch_api.api.Client, r5e_com @pytest.mark.asyncio async def test_search_officers(live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search_officers("Ilja Orlovs") + search_response = await live_env_test_client.search_officers("Ilja Orlovs", result_count=10) assert len(search_response.data) > 10 one_found = False @@ -109,7 +107,7 @@ async def test_search_officers(live_env_test_client: ch_api.api.Client): @pytest.mark.asyncio async def test_search_disqualified_officers(live_env_test_client: ch_api.api.Client): - search_response = await live_env_test_client.search_disqualified_officers("bob") + search_response = await live_env_test_client.search_disqualified_officers("bob", result_count=10) assert len(search_response.data) > 0 one_found = False @@ -131,5 +129,5 @@ async def test_search_disqualified_officers(live_env_test_client: ch_api.api.Cli ], ) async def test_search_dissolved_companies(live_env_test_client: ch_api.api.Client, query_type, exp_company_name): - search_response = await live_env_test_client.search_dissolved_companies("bob", type=query_type) + search_response = await live_env_test_client.search_dissolved_companies("bob", type=query_type, result_count=10) assert len(search_response.data) >= 10 diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index b485198..53a4594 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -123,12 +123,64 @@ async def test_get_next_without_fetcher_raises_runtime_error(self): await page.get_next() +class TestOffsetPagination: + """Offset accumulation to result_count + get_next advances the batch.""" + + @pytest.mark.asyncio + async def test_get_next_offset(self): + """get_next fetches the next result_count batch from the next offset.""" + client = _make_client() + calls = [] + + class _Item(pydantic.BaseModel): + val: int = 0 + + async def fetch(start): + calls.append(start) + if start == 0: + return [_Item(val=0), _Item(val=1)], 4 + return [_Item(val=2), _Item(val=3)], 4 + + page = await client._fetch_paginated(fetch, None, 1) + assert [i.val for i in page.data] == [0, 1] + assert page.pagination.has_next + assert calls == [0] + + page2 = await page.get_next() + assert [i.val for i in page2.data] == [2, 3] + assert not page2.pagination.has_next + assert calls == [0, 2] + + class TestCursorPaginationContinuation: - """Single cursor page + get_next advances via the bound fetcher.""" + """Cursor accumulation loop continuation + get_next via the bound fetcher.""" + + @pytest.mark.asyncio + async def test_cursor_loop_continues(self): + """result_count spanning pages drives a second loop iteration (cursor = next_cursor).""" + client = _make_client() + call_count = 0 + + class _Item(pydantic.BaseModel): + val: int = 0 + + async def fetch_fn(cursor): + nonlocal call_count + call_count += 1 + if call_count == 1: + assert cursor is None + return [_Item(val=1)], "CURSOR_A" + assert cursor == "CURSOR_A" + return [_Item(val=2)], None + + page = await client._fetch_paginated_cursor(fetch_fn, None, 2) + assert [i.val for i in page.data] == [1, 2] + assert call_count == 2 + assert not page.pagination.has_next @pytest.mark.asyncio async def test_get_next_advances_cursor(self): - """First page reports has_next; get_next fetches the next cursor page.""" + """With result_count=1 each batch is one page; get_next fetches the next.""" client = _make_client() call_count = 0 @@ -144,7 +196,7 @@ async def fetch_fn(cursor): assert cursor == "CURSOR_A" return [_Item(val=2)], None - page = await client._fetch_paginated_cursor(fetch_fn, None) + page = await client._fetch_paginated_cursor(fetch_fn, None, 1) assert [i.val for i in page.data] == [1] assert page.pagination.has_next assert call_count == 1 From abe1af8adf915a9ba203f876c4636f3f0d4af017 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 11:47:52 +0100 Subject: [PATCH 04/12] api --- README.md | 6 + docs/sources/usage.rst | 24 +++ src/ch_api/__init__.py | 7 + src/ch_api/_paginate.py | 82 +++++++++ src/ch_api/api.py | 126 +++++++++---- src/ch_api/types/pagination/__init__.py | 6 + src/ch_api/types/pagination/types.py | 111 +++++++----- tests/unit/test_api_branch_coverage.py | 228 +++++++++++++++++------- 8 files changed, 448 insertions(+), 142 deletions(-) create mode 100644 src/ch_api/_paginate.py diff --git a/README.md b/README.md index d1b04ac..b7d168e 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ metadata. Pass `result_count` to collect more items per call, and advance with >>> run_async_func(search_example) True +`pagination.next_page` is a **self-contained** cursor — it embeds the endpoint and +its arguments, so a fresh process can resume statelessly with just the token via +`await client.fetch_next_page(token)`. This makes pagination restartable across +requests (e.g. for an async server or AI-agent tool) without walking the chain or +re-supplying the query. + ## Rate Limiting The API allows 600 requests per 5 minutes. Use an async rate limiter: diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index 6702c81..742d160 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -228,6 +228,30 @@ the result set with ``get_next``: next_page=page.pagination.next_page, ) +Restarting from a token (servers / agent tools) +----------------------------------------------- + +``pagination.next_page`` is **self-contained**: it embeds the originating endpoint +and its arguments, so a separate process — with no in-memory state and without +re-supplying the query — can resume from just the token via +:meth:`~ch_api.api.Client.fetch_next_page`. This is the building block for an async +service or AI-agent tool that returns one page plus an opaque cursor, then continues +on a later, independent request: + +.. code:: python + + # First request: return a page and a cursor to the caller + page = await client.search_companies("tech", page_size=20) + payload = {"items": [c.model_dump() for c in page.data], + "next": page.pagination.next_page} # opaque token + + # ... later, a fresh request arrives carrying only `next` ... + page2 = await client.fetch_next_page(payload["next"]) + +Only the 12 paginated endpoints can be resumed this way; a token naming anything +else is rejected. Configure a :class:`~ch_api.types.pagination.types.PageTokenSerializer` +on the client to sign or encrypt the token before it leaves your service. + .. _usage.rate-limiting: Rate Limiting diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index beb5174..6310c9d 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -158,6 +158,13 @@ ... page2 = await page.get_next() ... +``pagination.next_page`` is a **self-contained** cursor: it embeds the originating +endpoint and its arguments, so a fresh process can resume with only the token via +``client.fetch_next_page(token)`` — no need to walk the chain or re-supply the +query. This makes pagination restartable across requests, e.g. for an async server +or AI-agent tool that returns one page plus an opaque cursor and resumes later. +Pair it with a ``PageTokenSerializer`` to sign/encrypt the token on the wire. + Exception Handling ------------------ Handle API errors with custom exceptions:: diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py new file mode 100644 index 0000000..7473fc9 --- /dev/null +++ b/src/ch_api/_paginate.py @@ -0,0 +1,82 @@ +"""Internal runtime for paginated ``Client`` endpoints. + +Houses the :func:`paginated` decorator and the task-local channel it uses to +hand ``(endpoint_name, call_params)`` to the fetch helpers in :mod:`ch_api.api`, +which stamp them into self-contained, replayable ``next_page`` tokens. Kept out +of ``api.py`` to keep that module focused on the endpoint surface. + +Not part of the public API. +""" + +import contextvars +import functools +import inspect +import typing + +import pydantic + +_PaginatedFn = typing.TypeVar("_PaginatedFn", bound=typing.Callable[..., typing.Awaitable[typing.Any]]) + +#: Task-local channel carrying ``(endpoint_name, call_params)`` from the +#: :func:`paginated` decorator down to the fetch helpers, which stamp them into +#: the outgoing ``next_page`` token. Task-local (not instance state) so concurrent +#: paginated calls on the same client never clash. +_resume_ctx: contextvars.ContextVar[typing.Optional[typing.Tuple[str, dict]]] = contextvars.ContextVar( + "ch_api_resume_ctx", default=None +) + + +def current_resume_identity() -> typing.Tuple[str, dict]: + """Return the ``(endpoint, params)`` published by the active ``@paginated`` call. + + Returns ``("", {})`` when called outside a paginated method (e.g. a fetch + helper invoked directly in a test) — in that case the produced token is + position-only and not replayable via :meth:`ch_api.api.Client.fetch_next_page`. + """ + return _resume_ctx.get() or ("", {}) + + +def paginated( + *, exclude: typing.Collection[str] = ("self", "next_page") +) -> typing.Callable[[_PaginatedFn], _PaginatedFn]: + """Decorator for async ``Client`` methods that return a ``MultipageList``. + + Folds three concerns together so endpoint bodies stay minimal: + + * Applies :func:`pydantic.validate_call` — its schema is derived from the + method's own annotations, so no separate argument spec is needed. + * Captures the endpoint name (``func.__name__``) and the call's arguments and + publishes them on a task-local context var that the fetch helpers read to + build a self-contained, replayable ``next_page`` token (see + :meth:`ch_api.api.Client.fetch_next_page`). + * Binds the originating client to the returned list so + :meth:`~ch_api.types.pagination.types.MultipageList.get_next` works. + + Args: + exclude: Argument names omitted from the resume token's ``params``. + Defaults to ``self`` and ``next_page`` (cursor position is tracked + separately by the fetch helpers). + """ + excluded = set(exclude) + + def decorate(func: _PaginatedFn) -> _PaginatedFn: + endpoint = func.__name__ + sig = inspect.signature(func) + validated = pydantic.validate_call(func) + + @functools.wraps(func) + async def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + bound = sig.bind(*args, **kwargs) + bound.apply_defaults() + params = {name: value for name, value in bound.arguments.items() if name not in excluded} + ctx_token = _resume_ctx.set((endpoint, params)) + try: + result = await validated(*args, **kwargs) + finally: + _resume_ctx.reset(ctx_token) + result._client = args[0] # bind the Client (self) for get_next() + return result + + return typing.cast(_PaginatedFn, wrapper) + + return decorate diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 1afd39d..bdb0ed3 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -26,6 +26,7 @@ import pydantic from . import api_settings, exc, types +from ._paginate import current_resume_identity, paginated logger = logging.getLogger(__name__) @@ -113,6 +114,27 @@ class Client: _session_auth: typing.Optional[httpx.BasicAuth] # Stored to allow session restart _page_token_serializer: typing.Optional[types.pagination.types.PageTokenSerializer] + #: Paginated methods that :meth:`fetch_next_page` may re-dispatch to. A + #: self-contained ``next_page`` token names the method that produced it; this + #: allowlist ensures a tampered or malformed token can only ever resume a real + #: paginated endpoint, never an arbitrary client method. + _RESUMABLE_ENDPOINTS: typing.ClassVar[frozenset[str]] = frozenset( + { + "get_officer_list", + "search", + "advanced_company_search", + "alphabetical_companies_search", + "search_dissolved_companies", + "search_companies", + "search_officers", + "search_disqualified_officers", + "get_company_filing_history", + "get_officer_appointments", + "get_company_psc_list", + "get_company_psc_statements", + } + ) + def __init__( self, credentials: typing.Union[ @@ -350,9 +372,12 @@ async def _fetch_paginated( Issues one or more underlying API requests (each of the endpoint's ``page_size``) until at least ``result_count`` items are gathered or no - further pages exist. The returned list carries a ``get_next`` handle that - fetches the next batch of ``result_count`` items via the same - ``fetch_page_fn``. + further pages exist. The ``next_page`` token on the returned list is + self-contained — the originating endpoint name and call arguments are read + from the :func:`~ch_api._paginate.current_resume_identity` helper (populated by + embedded, so the request can be resumed via :meth:`fetch_next_page` from a + fresh process. (Called outside a ``paginated`` method — e.g. directly in a + test — the context is empty and a position-only token is produced.) Args: fetch_page_fn: Callable taking ``start_index`` (int), returning a @@ -365,6 +390,7 @@ async def _fetch_paginated( Returns: A MultipageList holding the collected items and pagination metadata. """ + endpoint, params = current_resume_identity() page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) @@ -390,10 +416,12 @@ async def _fetch_paginated( next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next: - next_state = types.pagination.types._PageState(start_index=current_start + last_page_len) + next_state = types.pagination.types._PageState( + start_index=current_start + last_page_len, endpoint=endpoint, params=params + ) next_page_out = self._encode_next_page(next_state) - result = types.pagination.types.MultipageList( + return types.pagination.types.MultipageList( data=items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, @@ -402,16 +430,6 @@ async def _fetch_paginated( ), ) - if has_next and next_page_out is not None: - captured_next_page = next_page_out - - async def _fetch_next() -> types.pagination.types.MultipageList: - return await self._fetch_paginated(fetch_page_fn, captured_next_page, result_count) - - result._fetch_next_page = _fetch_next - - return result - async def _fetch_paginated_cursor( self, fetch_page_fn: typing.Callable[[typing.Optional[str]], typing.Awaitable[tuple[list, typing.Optional[str]]]], @@ -423,8 +441,10 @@ async def _fetch_paginated_cursor( Used for endpoints that paginate via ``search_below`` / ``search_above`` cursors (e.g. alphabetical company search) rather than ``start_index``. Issues one or more underlying API requests until at least ``result_count`` - items are gathered or no further pages exist. The returned list carries a - ``get_next`` handle that fetches the next batch. + items are gathered or no further pages exist. The originating endpoint name + and call arguments come from :func:`~ch_api._paginate.current_resume_identity` (populated by + :func:`paginated`) and embedded in the ``next_page`` token, so the request + can be resumed via :meth:`fetch_next_page`. Args: fetch_page_fn: Callable taking the current ``search_below`` cursor @@ -438,6 +458,7 @@ async def _fetch_paginated_cursor( A MultipageList holding the collected items and pagination metadata. ``pagination.size`` is always None for cursor-based endpoints. """ + endpoint, params = current_resume_identity() page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) @@ -458,10 +479,12 @@ async def _fetch_paginated_cursor( next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next and next_cursor is not None: - next_state = types.pagination.types._PageState(search_below=next_cursor) + next_state = types.pagination.types._PageState( + search_below=next_cursor, endpoint=endpoint, params=params + ) next_page_out = self._encode_next_page(next_state) - result = types.pagination.types.MultipageList( + return types.pagination.types.MultipageList( data=items, pagination=types.pagination.types.PaginationInfo( has_next=has_next, @@ -470,15 +493,44 @@ async def _fetch_paginated_cursor( ), ) - if has_next and next_page_out is not None: - captured_next_page = next_page_out + async def fetch_next_page( + self, next_page: types.pagination.types.NextPageToken + ) -> types.pagination.types.MultipageList: + """Resume a paginated request from a self-contained ``next_page`` token. - async def _fetch_next() -> types.pagination.types.MultipageList: - return await self._fetch_paginated_cursor(fetch_page_fn, captured_next_page, result_count) + The token (taken from ``MultipageList.pagination.next_page``) embeds the + originating endpoint and its arguments, so a fresh process can fetch the + next batch with only the token — there is no need to walk the page chain + or reconstruct the original query. This is the building block for stateless + services such as an AI-agent tool that returns one page plus an opaque + cursor, then resumes on a later, independent request. - result._fetch_next_page = _fetch_next + Args: + next_page: A ``pagination.next_page`` token from a prior result. + + Returns: + The next ``MultipageList``, itself bound for further iteration. + + Raises: + ValueError: If the token does not name a known, resumable endpoint — + e.g. a position-only token, or a malformed / tampered value. - return result + Example:: + + page = await client.search_companies("Apple", page_size=20) + token = page.pagination.next_page # hand this to the caller + + # ... later, in a new request with only `token` in hand ... + page2 = await client.fetch_next_page(token) + """ + state = self._decode_next_page(next_page) + if state.endpoint not in self._RESUMABLE_ENDPOINTS: + raise ValueError( + f"next_page token does not identify a resumable endpoint (got {state.endpoint!r}); " + "it may be position-only, malformed, or tampered." + ) + method = getattr(self, state.endpoint) + return await method(**state.params, next_page=next_page) @pydantic.validate_call async def create_test_company( @@ -549,7 +601,7 @@ async def registered_office_address( types.public_data.registered_office.RegisteredOfficeAddress, ) - @pydantic.validate_call + @paginated() async def get_officer_list( self, company_number: CompanyNumberStrT, @@ -626,7 +678,7 @@ async def get_company_registers( url = f"{self._settings.api_url}/company/{company_number}/registers" return await self._get_resource(url, types.public_data.company_registers.CompanyRegister) - @pydantic.validate_call + @paginated() async def search( self, query: str, @@ -669,7 +721,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return await self._fetch_paginated(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def advanced_company_search( # noqa: C901 self, /, @@ -754,7 +806,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return await self._fetch_paginated(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def alphabetical_companies_search( self, query: str, @@ -799,7 +851,7 @@ async def _fetch( return await self._fetch_paginated_cursor(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def search_dissolved_companies( self, query: str, @@ -847,7 +899,7 @@ async def _fetch( return await self._fetch_paginated_cursor(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def search_companies( self, query: str, @@ -890,7 +942,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return await self._fetch_paginated(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def search_officers( self, query: str, @@ -933,7 +985,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return await self._fetch_paginated(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def search_disqualified_officers( self, query: str, @@ -1023,7 +1075,7 @@ async def get_company_charge_details( types.public_data.charges.ChargeDetails, ) - @pydantic.validate_call + @paginated() async def get_company_filing_history( self, company_number: CompanyNumberStrT, @@ -1368,7 +1420,7 @@ async def get_natural_officer_disqualification( types.public_data.disqualifications.NaturalDisqualification, ) - @pydantic.validate_call + @paginated() async def get_officer_appointments( self, officer_id: OfficerIdStrT, @@ -1434,7 +1486,7 @@ async def get_company_uk_establishments( types.public_data.uk_establishments.CompanyUKEstablishments, ) - @pydantic.validate_call + @paginated() async def get_company_psc_list( self, company_number: CompanyNumberStrT, @@ -1481,7 +1533,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return await self._fetch_paginated(_fetch, next_page, result_count) - @pydantic.validate_call + @paginated() async def get_company_psc_statements( self, company_number: CompanyNumberStrT, diff --git a/src/ch_api/types/pagination/__init__.py b/src/ch_api/types/pagination/__init__.py index cce5a3e..414f9ae 100644 --- a/src/ch_api/types/pagination/__init__.py +++ b/src/ch_api/types/pagination/__init__.py @@ -9,9 +9,15 @@ while page.pagination.has_next: page = await page.get_next() +``pagination.next_page`` is a self-contained cursor (it embeds the endpoint and +its arguments), so a fresh process can resume statelessly with only the token:: + + page2 = await client.fetch_next_page(token) + Key Classes ----------- - :class:`types.MultipageList` - Result-batch container with a ``get_next`` handle +- :class:`types.NextPageToken` - Self-contained, restartable opaque cursor - :class:`types.PaginationInfo` - Pagination metadata - :class:`types.NextPageToken` - Opaque cursor type - :class:`types.PageTokenSerializer` - Optional token encryption protocol diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index d2c2de7..0703dd3 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -10,8 +10,6 @@ _PageState: Encodes CH API pagination state (start_index / search_below cursor). """ -import dataclasses -import json import typing import pydantic @@ -26,33 +24,38 @@ # --------------------------------------------------------------------------- -@dataclasses.dataclass(frozen=True) -class _PageState: - """Encodes CH API pagination state as a portable JSON string. +class _PageState(pydantic.BaseModel, frozen=True): + """A self-contained, restartable pagination cursor encoded as JSON. Not part of the public API — callers only ever see ``NextPageToken`` (str). - For offset-based endpoints (most CH API endpoints), ``start_index`` stores - the next offset to request. For cursor-based endpoints (alphabetical - search), ``search_below`` stores the ordered_alpha_key_with_id cursor. + The state captures everything needed to resume a paginated request from a + fresh process with no in-memory context: + + * ``endpoint`` — the name of the ``Client`` method that produced the page + (used to re-dispatch on resume; validated against an allowlist). + * ``params`` — the originating call's keyword arguments (query, filters, + ``page_size``, ``result_count``, path parameters, …), as JSON-safe values. + * ``start_index`` — next offset for offset-based endpoints. + * ``search_below`` — ``ordered_alpha_key_with_id`` cursor for cursor-based + endpoints (alphabetical / dissolved search). + + A position-only state (``endpoint``/``params`` empty) is still valid: the + originating endpoint resumes from the position, it just cannot be replayed + blindly via :meth:`Client.fetch_next_page`. """ start_index: int = 0 search_below: typing.Optional[str] = None + endpoint: str = "" + params: typing.Dict[str, typing.Any] = pydantic.Field(default_factory=dict) def encode(self) -> str: - data: dict[str, typing.Any] = {"start_index": self.start_index} - if self.search_below is not None: - data["search_below"] = self.search_below - return json.dumps(data) + return self.model_dump_json() @classmethod def decode(cls, token: str) -> "_PageState": - data = json.loads(token) - return cls( - start_index=data.get("start_index", 0), - search_below=data.get("search_below"), - ) + return cls.model_validate_json(token) @classmethod def first(cls) -> "_PageState": @@ -165,6 +168,22 @@ class PaginationInfo(pydantic.BaseModel): ) +# --------------------------------------------------------------------------- +# Internal: client handle used by MultipageList.get_next +# --------------------------------------------------------------------------- + + +@typing.runtime_checkable +class _NextPageFetcher(typing.Protocol): + """Minimal interface :meth:`MultipageList.get_next` needs from a client. + + ``Client`` satisfies this structurally; kept here to avoid importing the + client into the types package. + """ + + async def fetch_next_page(self, next_page: str) -> typing.Any: ... + + # --------------------------------------------------------------------------- # Public: result page model # --------------------------------------------------------------------------- @@ -200,14 +219,14 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): page = await client.search_companies("Apple", result_count=100) # page.data has >= 100 items (or all available if fewer exist) - Resuming statelessly with a cursor token:: + Resuming statelessly from a self-contained token (e.g. a fresh request in an + async server or agent tool — only the token is needed, not the query):: page = await client.search_companies("Apple") - if page.pagination.has_next: - page2 = await client.search_companies( - "Apple", - next_page=page.pagination.next_page, - ) + token = page.pagination.next_page # opaque, restartable cursor + + # ... later, in a new process with only `token` ... + page2 = await client.fetch_next_page(token) """ model_config = pydantic.ConfigDict(frozen=True, arbitrary_types_allowed=True) @@ -217,28 +236,38 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): description="Pagination state, including whether more results exist and how to fetch them." ) - _fetch_next_page: typing.Optional[typing.Callable[[], typing.Awaitable["MultipageList[_ItemT]"]]] = ( - pydantic.PrivateAttr(default=None) - ) - """Bound closure that fetches the next page using the same endpoint and - arguments as the call that produced this list. Set by the client when the - list is produced; ``None`` on manually-constructed instances. + _client: typing.Optional[_NextPageFetcher] = pydantic.PrivateAttr(default=None) + """Client used to fetch the next batch via the self-contained ``next_page`` + token. Set by the client when the list is produced; ``None`` on + manually-constructed or deserialized instances. Bind one with + :meth:`with_client` to re-enable :meth:`get_next` on a reconstructed list. """ + def with_client(self, client: _NextPageFetcher) -> "MultipageList[_ItemT]": + """Bind a client so :meth:`get_next` works on this (e.g. deserialized) list. + + Returns ``self`` for chaining. The token itself is self-contained, so + the bound client only supplies the HTTP session — any ``Client`` will do. + """ + self._client = client + return self + async def get_next(self) -> "MultipageList[_ItemT]": - """Fetch the next page from the same endpoint with the same arguments. + """Fetch the next batch from the same endpoint with the same arguments. - Returns a new ``MultipageList`` carrying the next page of results. - The returned list itself has ``get_next`` bound for further iteration. + Convenience wrapper over :meth:`Client.fetch_next_page` using this + list's self-contained ``pagination.next_page`` token. The returned + ``MultipageList`` is itself bound for further iteration. Raises: NoMorePagesError: If ``pagination.has_next`` is ``False`` — this - list is already the last page. - RuntimeError: If this list was constructed manually (e.g. in a - test or via deserialization) and has no fetcher attached. + list is already the last batch. + RuntimeError: If no client is bound (e.g. a manually-constructed or + deserialized list). Either call ``client.fetch_next_page(token)`` + directly, or bind one first via :meth:`with_client`. Example: - Walk every page:: + Walk every batch:: page = await client.search_companies("Apple") while page.pagination.has_next: @@ -248,10 +277,10 @@ async def get_next(self) -> "MultipageList[_ItemT]": """ if not self.pagination.has_next: raise exc.NoMorePagesError("This is the last page; no more results to fetch.") - if self._fetch_next_page is None: + if self._client is None or self.pagination.next_page is None: raise RuntimeError( - "MultipageList has no next-page fetcher attached — it was likely " - "constructed manually or deserialized. Call the originating endpoint " - "with `next_page=` instead." + "MultipageList has no client bound — it was likely constructed manually " + "or deserialized. Call `client.fetch_next_page(pagination.next_page)` " + "directly, or bind a client with `.with_client(client)` first." ) - return await self._fetch_next_page() + return await self._client.fetch_next_page(self.pagination.next_page) diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index 53a4594..963cd04 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -1,6 +1,7 @@ """Branch coverage tests for api.py — covers all remaining uncovered lines.""" import datetime +import json from unittest.mock import MagicMock import httpx @@ -9,7 +10,10 @@ from ch_api import api, api_settings, exc from ch_api.types.pagination import types as pagination_types -from ch_api.types.public_data import search_companies as sc +from ch_api.types.public_data import ( + search as search_types, + search_companies as sc, +) def _make_client(serializer=None): @@ -99,6 +103,134 @@ async def test_advanced_search_rejects_out_of_range(self, page_size): await client.advanced_company_search(company_name_includes="x", page_size=page_size) +class TestFetchNextPage: + """Self-contained next_page token: stateless resume via Client.fetch_next_page.""" + + @pytest.mark.asyncio + async def test_token_is_self_contained_and_resumes(self): + """Token embeds endpoint + params; fetch_next_page resumes from token alone.""" + client = _make_client() + urls = [] + + async def fake(url, result_type): + urls.append(url) + result = MagicMock() + result.items = [search_types.CompanySearchItem.model_construct() for _ in range(2)] + result.total_results = 4 + return result + + client._get_resource = fake + page = await client.search_companies("Apple", page_size=2) + token = page.pagination.next_page + decoded = json.loads(token) + assert decoded["endpoint"] == "search_companies" + assert decoded["params"]["query"] == "Apple" + assert decoded["params"]["page_size"] == 2 + assert decoded["start_index"] == 2 + + urls.clear() + # a fresh call with ONLY the token — no query re-supplied + page2 = await client.fetch_next_page(token) + assert len(page2.data) == 2 + assert any("start_index=2" in u and "q=Apple" in u for u in urls) + + @pytest.mark.asyncio + async def test_cursor_token_is_self_contained(self): + """Cursor endpoints embed endpoint + params + search_below.""" + client = _make_client() + calls = 0 + + async def fake(url, result_type): + nonlocal calls + calls += 1 + result = MagicMock() + result.items = [_alpha_company("KEY:1")] if calls == 1 else [] + return result + + client._get_resource = fake + page = await client.alphabetical_companies_search("Barclays", page_size=1) + decoded = json.loads(page.pagination.next_page) + assert decoded["endpoint"] == "alphabetical_companies_search" + assert decoded["search_below"] == "KEY:1" + assert decoded["params"]["query"] == "Barclays" + + @pytest.mark.asyncio + async def test_date_params_round_trip(self): + """datetime.date args serialise to ISO and coerce back on resume.""" + client = _make_client() + urls = [] + + async def fake(url, result_type): + urls.append(url) + result = MagicMock() + result.items = [sc.AdvancedCompany.model_construct() for _ in range(2)] + result.hits = 4 + return result + + client._get_resource = fake + page = await client.advanced_company_search( + company_name_includes="x", dissolved_from=datetime.date(2020, 1, 1), page_size=2 + ) + assert json.loads(page.pagination.next_page)["params"]["dissolved_from"] == "2020-01-01" + + urls.clear() + await client.fetch_next_page(page.pagination.next_page) # must coerce "2020-01-01" -> date + assert any("dissolved_from=2020-01-01" in u for u in urls) + + @pytest.mark.asyncio + async def test_tampered_endpoint_rejected(self): + """A token naming a non-resumable method is rejected (no arbitrary dispatch).""" + client = _make_client() + bad = json.dumps({"endpoint": "aclose", "params": {}, "start_index": 0}) + with pytest.raises(ValueError, match="resumable endpoint"): + await client.fetch_next_page(bad) + + @pytest.mark.asyncio + async def test_resume_via_serializer(self): + """fetch_next_page works through a PageTokenSerializer (opaque/encrypted token).""" + serializer = MagicMock() + serializer.serialize = lambda t: "ENC:" + t + serializer.deserialize = lambda t: t[len("ENC:") :] + client = _make_client(serializer=serializer) + urls = [] + + async def fake(url, result_type): + urls.append(url) + result = MagicMock() + result.items = [search_types.CompanySearchItem.model_construct() for _ in range(2)] + result.total_results = 4 + return result + + client._get_resource = fake + page = await client.search_companies("Apple", page_size=2) + assert page.pagination.next_page.startswith("ENC:") + + urls.clear() + await client.fetch_next_page(page.pagination.next_page) + assert any("start_index=2" in u for u in urls) + + @pytest.mark.asyncio + async def test_with_client_rebinds_get_next(self): + """A page with no client can be re-bound so get_next works again.""" + client = _make_client() + + async def fake(url, result_type): + result = MagicMock() + result.items = [search_types.CompanySearchItem.model_construct() for _ in range(2)] + result.total_results = 4 + return result + + client._get_resource = fake + page = await client.search_companies("Apple", page_size=2) + # simulate a reconstructed page that lost its client binding + page._client = None + with pytest.raises(RuntimeError, match="no client bound"): + await page.get_next() + page.with_client(client) + page2 = await page.get_next() + assert len(page2.data) == 2 + + class TestMultipageListGetNext: """MultipageList.get_next error paths on manually-constructed instances.""" @@ -113,98 +245,66 @@ async def test_get_next_on_last_page_raises_no_more_pages(self): await page.get_next() @pytest.mark.asyncio - async def test_get_next_without_fetcher_raises_runtime_error(self): - """has_next True but no bound fetcher (e.g. deserialized) → RuntimeError.""" + async def test_get_next_without_client_raises_runtime_error(self): + """has_next True but no bound client (e.g. deserialized) → RuntimeError.""" page = pagination_types.MultipageList( data=[], pagination=pagination_types.PaginationInfo(has_next=True, next_page="tok"), ) - with pytest.raises(RuntimeError, match="no next-page fetcher"): + with pytest.raises(RuntimeError, match="no client bound"): await page.get_next() class TestOffsetPagination: - """Offset accumulation to result_count + get_next advances the batch.""" + """Offset accumulation to result_count + get_next advances the batch via fetch_next_page.""" @pytest.mark.asyncio async def test_get_next_offset(self): - """get_next fetches the next result_count batch from the next offset.""" + """get_next fetches the next batch from the next offset (page_size 2, total 4).""" client = _make_client() - calls = [] + urls = [] - class _Item(pydantic.BaseModel): - val: int = 0 - - async def fetch(start): - calls.append(start) - if start == 0: - return [_Item(val=0), _Item(val=1)], 4 - return [_Item(val=2), _Item(val=3)], 4 + async def fake(url, result_type): + urls.append(url) + result = MagicMock() + result.items = [search_types.CompanySearchItem.model_construct() for _ in range(2)] + result.total_results = 4 + return result - page = await client._fetch_paginated(fetch, None, 1) - assert [i.val for i in page.data] == [0, 1] + client._get_resource = fake + page = await client.search_companies("x", page_size=2) + assert len(page.data) == 2 assert page.pagination.has_next - assert calls == [0] + assert any("start_index=0" in u for u in urls) + urls.clear() page2 = await page.get_next() - assert [i.val for i in page2.data] == [2, 3] + assert len(page2.data) == 2 assert not page2.pagination.has_next - assert calls == [0, 2] + assert any("start_index=2" in u for u in urls) class TestCursorPaginationContinuation: - """Cursor accumulation loop continuation + get_next via the bound fetcher.""" + """Cursor accumulation loop continuation (result_count spanning pages).""" @pytest.mark.asyncio async def test_cursor_loop_continues(self): """result_count spanning pages drives a second loop iteration (cursor = next_cursor).""" client = _make_client() - call_count = 0 + calls = 0 - class _Item(pydantic.BaseModel): - val: int = 0 - - async def fetch_fn(cursor): - nonlocal call_count - call_count += 1 - if call_count == 1: - assert cursor is None - return [_Item(val=1)], "CURSOR_A" - assert cursor == "CURSOR_A" - return [_Item(val=2)], None - - page = await client._fetch_paginated_cursor(fetch_fn, None, 2) - assert [i.val for i in page.data] == [1, 2] - assert call_count == 2 - assert not page.pagination.has_next - - @pytest.mark.asyncio - async def test_get_next_advances_cursor(self): - """With result_count=1 each batch is one page; get_next fetches the next.""" - client = _make_client() - call_count = 0 - - class _Item(pydantic.BaseModel): - val: int = 0 + async def fake(url, result_type): + nonlocal calls + calls += 1 + result = MagicMock() + result.items = [_alpha_company(f"KEY:{calls}")] if calls <= 2 else [] + return result - async def fetch_fn(cursor): - nonlocal call_count - call_count += 1 - if call_count == 1: - assert cursor is None - return [_Item(val=1)], "CURSOR_A" - assert cursor == "CURSOR_A" - return [_Item(val=2)], None - - page = await client._fetch_paginated_cursor(fetch_fn, None, 1) - assert [i.val for i in page.data] == [1] + client._get_resource = fake + page = await client.alphabetical_companies_search("q", page_size=1, result_count=2) + assert len(page.data) == 2 # accumulated across two cursor pages + assert calls == 2 assert page.pagination.has_next - assert call_count == 1 - - page2 = await page.get_next() - assert [i.val for i in page2.data] == [2] - assert not page2.pagination.has_next - assert call_count == 2 class TestAlphabeticalSearchBranches: From c546486a77b6551c8ba42188b86849d0ee774137 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 11:54:58 +0100 Subject: [PATCH 05/12] resume --- src/ch_api/_paginate.py | 37 ++++++++++++++++++++++++++----------- src/ch_api/api.py | 26 ++++++++++++++------------ 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index 7473fc9..0a43558 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -9,6 +9,7 @@ """ import contextvars +import dataclasses import functools import inspect import typing @@ -17,23 +18,37 @@ _PaginatedFn = typing.TypeVar("_PaginatedFn", bound=typing.Callable[..., typing.Awaitable[typing.Any]]) -#: Task-local channel carrying ``(endpoint_name, call_params)`` from the -#: :func:`paginated` decorator down to the fetch helpers, which stamp them into -#: the outgoing ``next_page`` token. Task-local (not instance state) so concurrent + +@dataclasses.dataclass(frozen=True, slots=True) +class _ResumeState: + """The originating endpoint and call arguments of an active ``@paginated`` call. + + Read by the fetch helpers to stamp a self-contained ``next_page`` token. An + empty instance (``endpoint == ""``) means there is no active paginated call. + """ + + endpoint: str = "" + params: typing.Dict[str, typing.Any] = dataclasses.field(default_factory=dict) + + +#: Task-local channel carrying the active call's :class:`_ResumeState` from the +#: :func:`paginated` decorator down to the fetch helpers, which stamp it into the +#: outgoing ``next_page`` token. Task-local (not instance state) so concurrent #: paginated calls on the same client never clash. -_resume_ctx: contextvars.ContextVar[typing.Optional[typing.Tuple[str, dict]]] = contextvars.ContextVar( +_resume_ctx: contextvars.ContextVar[typing.Optional[_ResumeState]] = contextvars.ContextVar( "ch_api_resume_ctx", default=None ) -def current_resume_identity() -> typing.Tuple[str, dict]: - """Return the ``(endpoint, params)`` published by the active ``@paginated`` call. +def current_resume_state() -> _ResumeState: + """Return the :class:`_ResumeState` published by the active ``@paginated`` call. - Returns ``("", {})`` when called outside a paginated method (e.g. a fetch - helper invoked directly in a test) — in that case the produced token is - position-only and not replayable via :meth:`ch_api.api.Client.fetch_next_page`. + Returns an empty ``_ResumeState`` (``endpoint == ""``) when called outside a + paginated method (e.g. a fetch helper invoked directly in a test) — in that + case the produced token is position-only and not replayable via + :meth:`ch_api.api.Client.fetch_next_page`. """ - return _resume_ctx.get() or ("", {}) + return _resume_ctx.get() or _ResumeState() def paginated( @@ -69,7 +84,7 @@ async def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: bound = sig.bind(*args, **kwargs) bound.apply_defaults() params = {name: value for name, value in bound.arguments.items() if name not in excluded} - ctx_token = _resume_ctx.set((endpoint, params)) + ctx_token = _resume_ctx.set(_ResumeState(endpoint=endpoint, params=params)) try: result = await validated(*args, **kwargs) finally: diff --git a/src/ch_api/api.py b/src/ch_api/api.py index bdb0ed3..d213e58 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -26,7 +26,7 @@ import pydantic from . import api_settings, exc, types -from ._paginate import current_resume_identity, paginated +from ._paginate import current_resume_state, paginated logger = logging.getLogger(__name__) @@ -374,10 +374,11 @@ async def _fetch_paginated( ``page_size``) until at least ``result_count`` items are gathered or no further pages exist. The ``next_page`` token on the returned list is self-contained — the originating endpoint name and call arguments are read - from the :func:`~ch_api._paginate.current_resume_identity` helper (populated by - embedded, so the request can be resumed via :meth:`fetch_next_page` from a - fresh process. (Called outside a ``paginated`` method — e.g. directly in a - test — the context is empty and a position-only token is produced.) + from :func:`~ch_api._paginate.current_resume_state` (populated by the + :func:`~ch_api._paginate.paginated` decorator) and embedded, so the request + can be resumed via :meth:`fetch_next_page` from a fresh process. (Called + outside a ``paginated`` method — e.g. directly in a test — the resume state + is empty and a position-only token is produced.) Args: fetch_page_fn: Callable taking ``start_index`` (int), returning a @@ -390,7 +391,7 @@ async def _fetch_paginated( Returns: A MultipageList holding the collected items and pagination metadata. """ - endpoint, params = current_resume_identity() + resume = current_resume_state() page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) @@ -417,7 +418,7 @@ async def _fetch_paginated( next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next: next_state = types.pagination.types._PageState( - start_index=current_start + last_page_len, endpoint=endpoint, params=params + start_index=current_start + last_page_len, endpoint=resume.endpoint, params=resume.params ) next_page_out = self._encode_next_page(next_state) @@ -442,9 +443,10 @@ async def _fetch_paginated_cursor( cursors (e.g. alphabetical company search) rather than ``start_index``. Issues one or more underlying API requests until at least ``result_count`` items are gathered or no further pages exist. The originating endpoint name - and call arguments come from :func:`~ch_api._paginate.current_resume_identity` (populated by - :func:`paginated`) and embedded in the ``next_page`` token, so the request - can be resumed via :meth:`fetch_next_page`. + and call arguments come from :func:`~ch_api._paginate.current_resume_state` + (populated by the :func:`~ch_api._paginate.paginated` decorator) and are + embedded in the ``next_page`` token, so the request can be resumed via + :meth:`fetch_next_page`. Args: fetch_page_fn: Callable taking the current ``search_below`` cursor @@ -458,7 +460,7 @@ async def _fetch_paginated_cursor( A MultipageList holding the collected items and pagination metadata. ``pagination.size`` is always None for cursor-based endpoints. """ - endpoint, params = current_resume_identity() + resume = current_resume_state() page_state = ( self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() ) @@ -480,7 +482,7 @@ async def _fetch_paginated_cursor( next_page_out: typing.Optional[types.pagination.types.NextPageToken] = None if has_next and next_cursor is not None: next_state = types.pagination.types._PageState( - search_below=next_cursor, endpoint=endpoint, params=params + search_below=next_cursor, endpoint=resume.endpoint, params=resume.params ) next_page_out = self._encode_next_page(next_state) From 5a120cbd24fd2cf2a76ee99481a5902a3a506b5e Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 12:21:10 +0100 Subject: [PATCH 06/12] x --- docs/sources/api-overview.rst | 4 +- docs/sources/usage.rst | 13 +- src/ch_api/__init__.py | 3 +- src/ch_api/__version__.py | 2 +- src/ch_api/_paginate.py | 8 +- src/ch_api/api.py | 115 +++++++----------- src/ch_api/types/pagination/__init__.py | 14 +-- src/ch_api/types/pagination/types.py | 20 +-- src/ch_api/types/public_data/search.py | 2 +- .../types/public_data/search_companies.py | 2 +- tests/unit/test_api_branch_coverage.py | 32 +++-- 11 files changed, 95 insertions(+), 120 deletions(-) diff --git a/docs/sources/api-overview.rst b/docs/sources/api-overview.rst index df090e8..17fedba 100644 --- a/docs/sources/api-overview.rst +++ b/docs/sources/api-overview.rst @@ -68,8 +68,8 @@ Pagination List endpoints return a ``MultipageList[T]`` with ``data`` (list) and ``pagination`` metadata. Pass ``result_count`` to collect at least that many items in one call, -advance with ``get_next`` (or ``next_page``), and use ``page_size`` to control the -underlying per-request size: +advance with ``get_next`` (or ``Client.fetch_next_page`` for stateless resume), and +use ``page_size`` to control the underlying per-request size: >>> async def pagination_example(client): ... page = await client.search_companies("tech") diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index 742d160..ffb1c0e 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -219,14 +219,11 @@ the result set with ``get_next``: break page = await page.get_next() # fetches the next batch of result_count items - # Or resume statelessly with the opaque cursor token + # Or resume statelessly with the opaque cursor token (see "Restarting from a + # token" below) — endpoints themselves take no next_page argument page = await client.search_companies("tech", result_count=25) while page.pagination.has_next: - page = await client.search_companies( - "tech", - result_count=25, - next_page=page.pagination.next_page, - ) + page = await client.fetch_next_page(page.pagination.next_page) Restarting from a token (servers / agent tools) ----------------------------------------------- @@ -573,5 +570,5 @@ If pagination isn't working as expected: - Use a regular ``for`` loop over ``result.data`` (it's a plain list) - Pass ``result_count`` to collect more items per call; ``page_size`` controls the underlying per-request size -- Call ``await result.get_next()`` (or pass ``result.pagination.next_page`` back to - the endpoint) to fetch the next batch +- Call ``await result.get_next()`` (or ``await client.fetch_next_page(result.pagination.next_page)`` + for stateless resume) to fetch the next batch diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index 6310c9d..4a545f7 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -145,8 +145,7 @@ Search and list endpoints return a ``MultipageList[T]`` with a ``data`` list and ``pagination`` metadata. Pass ``result_count`` to collect at least that many items in one call (issuing multiple underlying requests if needed), advance with -``get_next`` (or ``next_page``), and use ``page_size`` to control the underlying -per-request size:: +``get_next``, and use ``page_size`` to control the underlying per-request size:: >>> @run_async_func ... async def pagination_example(client): diff --git a/src/ch_api/__version__.py b/src/ch_api/__version__.py index 10aa336..8c0d5d5 100644 --- a/src/ch_api/__version__.py +++ b/src/ch_api/__version__.py @@ -1 +1 @@ -__version__ = "1.2.3" +__version__ = "2.0.0" diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index 0a43558..340fb4a 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -51,9 +51,7 @@ def current_resume_state() -> _ResumeState: return _resume_ctx.get() or _ResumeState() -def paginated( - *, exclude: typing.Collection[str] = ("self", "next_page") -) -> typing.Callable[[_PaginatedFn], _PaginatedFn]: +def paginated(*, exclude: typing.Collection[str] = ("self",)) -> typing.Callable[[_PaginatedFn], _PaginatedFn]: """Decorator for async ``Client`` methods that return a ``MultipageList``. Folds three concerns together so endpoint bodies stay minimal: @@ -69,8 +67,8 @@ def paginated( Args: exclude: Argument names omitted from the resume token's ``params``. - Defaults to ``self`` and ``next_page`` (cursor position is tracked - separately by the fetch helpers). + Defaults to just ``self`` (cursor position is tracked separately by + :meth:`ch_api.api.Client.fetch_next_page`). """ excluded = set(exclude) diff --git a/src/ch_api/api.py b/src/ch_api/api.py index d213e58..c0789a4 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -17,6 +17,7 @@ """ import contextlib +import contextvars import datetime import logging import typing @@ -30,6 +31,14 @@ logger = logging.getLogger(__name__) +#: Task-local channel carrying the position to resume from, published by +#: :meth:`Client.fetch_next_page` and read by the fetch helpers. Empty (``None``) +#: for fresh calls — endpoints no longer accept a ``next_page`` parameter, so +#: ``fetch_next_page`` is the sole resume path. +_resume_from_ctx: contextvars.ContextVar[typing.Optional[types.pagination.types._PageState]] = ( + contextvars.ContextVar("ch_api_resume_from", default=None) +) + LimiterContextT = typing.Callable[[], typing.AsyncContextManager[None]] ModelT = typing.TypeVar("ModelT", bound=types.base.BaseModel) @@ -365,26 +374,24 @@ def _decode_next_page(self, token: types.pagination.types.NextPageToken) -> type async def _fetch_paginated( self, fetch_page_fn: typing.Callable[[int], typing.Awaitable[tuple[list, typing.Optional[int]]]], - next_page: typing.Optional[types.pagination.types.NextPageToken], result_count: int, ) -> types.pagination.types.MultipageList: """Fetch offset-based API pages until ``result_count`` items are collected. Issues one or more underlying API requests (each of the endpoint's ``page_size``) until at least ``result_count`` items are gathered or no - further pages exist. The ``next_page`` token on the returned list is - self-contained — the originating endpoint name and call arguments are read - from :func:`~ch_api._paginate.current_resume_state` (populated by the - :func:`~ch_api._paginate.paginated` decorator) and embedded, so the request - can be resumed via :meth:`fetch_next_page` from a fresh process. (Called - outside a ``paginated`` method — e.g. directly in a test — the resume state - is empty and a position-only token is produced.) + further pages exist. Starts from offset 0 for a fresh call, or from the + position published on :data:`_resume_from_ctx` when invoked via + :meth:`fetch_next_page`. The ``next_page`` token on the returned list is + self-contained — the endpoint name and call arguments come from + :func:`~ch_api._paginate.current_resume_state` (populated by the + :func:`~ch_api._paginate.paginated` decorator) and are embedded so the + request can be resumed from a fresh process. Args: fetch_page_fn: Callable taking ``start_index`` (int), returning a tuple of ``(items, total_count)``. ``total_count`` may be None if unknown; in that case no further pages will be fetched. - next_page: Cursor from a previous call, or None to start from offset 0. result_count: Minimum number of items to collect. At least one underlying page is always fetched regardless of this value. @@ -392,9 +399,7 @@ async def _fetch_paginated( A MultipageList holding the collected items and pagination metadata. """ resume = current_resume_state() - page_state = ( - self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() - ) + page_state = _resume_from_ctx.get() or types.pagination.types._PageState.first() current_start = page_state.start_index items: list = [] total_count: typing.Optional[int] = None @@ -434,7 +439,6 @@ async def _fetch_paginated( async def _fetch_paginated_cursor( self, fetch_page_fn: typing.Callable[[typing.Optional[str]], typing.Awaitable[tuple[list, typing.Optional[str]]]], - next_page: typing.Optional[types.pagination.types.NextPageToken], result_count: int, ) -> types.pagination.types.MultipageList: """Fetch cursor-based API pages until ``result_count`` items are collected. @@ -442,18 +446,17 @@ async def _fetch_paginated_cursor( Used for endpoints that paginate via ``search_below`` / ``search_above`` cursors (e.g. alphabetical company search) rather than ``start_index``. Issues one or more underlying API requests until at least ``result_count`` - items are gathered or no further pages exist. The originating endpoint name - and call arguments come from :func:`~ch_api._paginate.current_resume_state` - (populated by the :func:`~ch_api._paginate.paginated` decorator) and are - embedded in the ``next_page`` token, so the request can be resumed via - :meth:`fetch_next_page`. + items are gathered or no further pages exist. Starts from the beginning for + a fresh call, or from the cursor published on :data:`_resume_from_ctx` when + invoked via :meth:`fetch_next_page`. The endpoint name and call arguments + come from :func:`~ch_api._paginate.current_resume_state` (populated by the + :func:`~ch_api._paginate.paginated` decorator) and are embedded in the + ``next_page`` token so the request can be resumed. Args: fetch_page_fn: Callable taking the current ``search_below`` cursor (None for the first page), returning ``(items, next_cursor)``. ``next_cursor`` is None when no further pages exist. - next_page: Cursor from a previous call, or None to start from the - beginning. result_count: Minimum number of items to collect. Returns: @@ -461,9 +464,7 @@ async def _fetch_paginated_cursor( ``pagination.size`` is always None for cursor-based endpoints. """ resume = current_resume_state() - page_state = ( - self._decode_next_page(next_page) if next_page is not None else types.pagination.types._PageState.first() - ) + page_state = _resume_from_ctx.get() or types.pagination.types._PageState.first() cursor = page_state.search_below items: list = [] has_next = False @@ -532,7 +533,13 @@ async def fetch_next_page( "it may be position-only, malformed, or tampered." ) method = getattr(self, state.endpoint) - return await method(**state.params, next_page=next_page) + # Publish the resume position for the fetch helpers; the endpoint is called + # fresh (no next_page parameter) with the token's captured arguments. + ctx_token = _resume_from_ctx.set(state) + try: + return await method(**state.params) + finally: + _resume_from_ctx.reset(ctx_token) @pydantic.validate_call async def create_test_company( @@ -616,7 +623,6 @@ async def get_officer_list( ] = None, order_by: typing.Literal["appointed_on", "resigned_on", "surname"] = "appointed_on", page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.company_officers.OfficerSummary]: """Fetch one page of company officers for a given company. @@ -627,8 +633,6 @@ async def get_officer_list( The company number to fetch the officers for. page_size: int Number of items per API page (1-200, default 200). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -661,7 +665,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @pydantic.validate_call async def get_officer_appointment( @@ -685,7 +689,6 @@ async def search( self, query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.AnySearchResultT]: """Search for companies using the Companies House search API. @@ -696,8 +699,6 @@ async def search( The search query string. page_size: int Number of items per API page (1-200, default 200). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -721,7 +722,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @paginated() async def advanced_company_search( # noqa: C901 @@ -739,7 +740,6 @@ async def advanced_company_search( # noqa: C901 location: typing.Optional[str] = None, sic_codes: typing.Optional[typing.Sequence[str]] = None, page_size: typing.Optional[typing.Annotated[int, pydantic.Field(ge=1, le=5000)]] = None, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AdvancedCompany]: """Perform an advanced search for companies using the Companies House search API. @@ -749,8 +749,6 @@ async def advanced_company_search( # noqa: C901 page_size: int, optional Number of items per API page (``size`` query parameter, 1-5000). When omitted, the API's own default page size is used. - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -806,14 +804,13 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.hits - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @paginated() async def alphabetical_companies_search( self, query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.AlphabeticalCompany]: """Search for companies alphabetically using the Companies House search API. @@ -824,8 +821,6 @@ async def alphabetical_companies_search( The search query string. page_size: int Number of results per API page (1-100, default 10). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -851,7 +846,7 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page, result_count) + return await self._fetch_paginated_cursor(_fetch, result_count) @paginated() async def search_dissolved_companies( @@ -859,7 +854,6 @@ async def search_dissolved_companies( query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 10, type: typing.Literal["alphabetical", "best-match", "previous-name-dissolved"] = "alphabetical", # noqa: A002 - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search_companies.DissolvedCompany]: """Search for dissolved companies using the Companies House search API. @@ -872,8 +866,6 @@ async def search_dissolved_companies( Number of results per API page (1-100, default 10). type: str Search type (alphabetical, best-match, previous-name-dissolved). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -899,14 +891,13 @@ async def _fetch( next_cursor = items[-1].ordered_alpha_key_with_id return items, next_cursor - return await self._fetch_paginated_cursor(_fetch, next_page, result_count) + return await self._fetch_paginated_cursor(_fetch, result_count) @paginated() async def search_companies( self, query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.CompanySearchItem]: """Search for companies using the Companies House search API. @@ -917,8 +908,6 @@ async def search_companies( The search query string. page_size: int Number of items per API page (1-200, default 200). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -942,14 +931,13 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @paginated() async def search_officers( self, query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.OfficerSearchItem]: """Search for officers using the Companies House search API. @@ -960,8 +948,6 @@ async def search_officers( The search query string. page_size: int Number of items per API page (1-200, default 200). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -985,14 +971,13 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @paginated() async def search_disqualified_officers( self, query: str, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=200)] = 200, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.search.DisqualifiedOfficerSearchItem]: """Search for disqualified officers using the Companies House search API. @@ -1003,8 +988,6 @@ async def search_disqualified_officers( The search query string. page_size: int Number of items per API page (1-200, default 200). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -1028,7 +1011,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @pydantic.validate_call async def get_company_charges( @@ -1099,7 +1082,6 @@ async def get_company_filing_history( ] | None = None, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.filing_history.FilingHistoryItem]: """Fetch the filing history for a given company. @@ -1112,8 +1094,6 @@ async def get_company_filing_history( Filter by filing categories. page_size: int Number of items per API page (1-100, default 25). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -1136,7 +1116,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_count - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @pydantic.validate_call async def get_filing_history_item( @@ -1428,7 +1408,6 @@ async def get_officer_appointments( officer_id: OfficerIdStrT, filter: typing.Optional[typing.Literal["active"]] = None, # noqa: A002 page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.officer_appointments.OfficerAppointmentSummary]: """Fetch the officer appointments for a given officer. @@ -1441,8 +1420,6 @@ async def get_officer_appointments( Filter appointments (e.g. 'active'). page_size: int Number of items per API page (1-100, default 25). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -1464,7 +1441,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @pydantic.validate_call async def get_company_uk_establishments( @@ -1494,7 +1471,6 @@ async def get_company_psc_list( company_number: CompanyNumberStrT, register_view: bool = False, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.ListSummary]: """Fetch the list of persons with significant control for a given company. @@ -1507,8 +1483,6 @@ async def get_company_psc_list( If True, only show PSCs active or terminated during election period. page_size: int Number of items per API page (1-100, default 25). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -1533,7 +1507,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) @paginated() async def get_company_psc_statements( @@ -1541,7 +1515,6 @@ async def get_company_psc_statements( company_number: CompanyNumberStrT, register_view: bool = False, page_size: typing.Annotated[int, pydantic.Field(ge=1, le=100)] = 25, - next_page: typing.Optional[types.pagination.types.NextPageToken] = None, result_count: int = 1, ) -> types.pagination.types.MultipageList[types.public_data.psc.Statement]: """Fetch the PSC statements for a given company. @@ -1554,8 +1527,6 @@ async def get_company_psc_statements( If True, only show PSCs active or terminated during election period. page_size: int Number of items per API page (1-100, default 25). - next_page: str, optional - Cursor from a previous call to continue pagination. result_count: int Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). @@ -1580,7 +1551,7 @@ async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: return [], None return result.items or [], result.total_results - return await self._fetch_paginated(_fetch, next_page, result_count) + return await self._fetch_paginated(_fetch, result_count) async def _get_psc_by_type( self, diff --git a/src/ch_api/types/pagination/__init__.py b/src/ch_api/types/pagination/__init__.py index 414f9ae..350e977 100644 --- a/src/ch_api/types/pagination/__init__.py +++ b/src/ch_api/types/pagination/__init__.py @@ -1,25 +1,25 @@ """Pagination support for Companies House API list endpoints. -All paginated endpoints on ``Client`` accept ``page_size``, ``next_page``, and -``result_count``, and return a ``MultipageList[T]``. ``result_count`` sets the -minimum number of items to collect in one call; ``get_next`` fetches the next -batch:: +All paginated endpoints on ``Client`` accept ``page_size`` and ``result_count`` +and return a ``MultipageList[T]``. ``result_count`` sets the minimum number of +items to collect in one call; ``get_next`` fetches the next batch:: page = await client.search_companies("Apple", result_count=50) while page.pagination.has_next: page = await page.get_next() ``pagination.next_page`` is a self-contained cursor (it embeds the endpoint and -its arguments), so a fresh process can resume statelessly with only the token:: +its arguments), so a fresh process can resume statelessly with only the token — +endpoints do not take a ``next_page`` argument; resume goes through +``fetch_next_page``:: page2 = await client.fetch_next_page(token) Key Classes ----------- - :class:`types.MultipageList` - Result-batch container with a ``get_next`` handle -- :class:`types.NextPageToken` - Self-contained, restartable opaque cursor - :class:`types.PaginationInfo` - Pagination metadata -- :class:`types.NextPageToken` - Opaque cursor type +- :class:`types.NextPageToken` - Self-contained, restartable opaque cursor - :class:`types.PageTokenSerializer` - Optional token encryption protocol """ diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index 0703dd3..9b1cb98 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -70,17 +70,17 @@ def first(cls) -> "_PageState": str, pydantic.Field( description=( - "Opaque pagination cursor. Pass this value unchanged to the same endpoint " - "to retrieve the next page of results. Treat it as an opaque string — " - "do not construct, parse, or modify it." + "Opaque pagination cursor. Pass this value unchanged to " + "``Client.fetch_next_page`` to retrieve the next page of results. Treat " + "it as an opaque string — do not construct, parse, or modify it." ) ), ] """An opaque string cursor for retrieving the next page of results. -Returned in ``PaginationInfo.next_page`` when more results exist. Pass it -back to the same endpoint method (as the ``next_page`` argument) to fetch -the next batch. +Returned in ``PaginationInfo.next_page`` when more results exist. Pass it to +``Client.fetch_next_page`` to fetch the next batch (or call +``MultipageList.get_next``, which does this for you). The internal format is an implementation detail and may change. Always treat this value as opaque. @@ -142,8 +142,8 @@ class PaginationInfo(pydantic.BaseModel): """Pagination state for a result set returned by the CH API. Returned alongside every page of results from the async client. Use - ``MultipageList.get_next`` (or pass ``next_page`` back to the same - endpoint) to retrieve the next page of items. + ``MultipageList.get_next`` (or pass ``next_page`` to + ``Client.fetch_next_page``) to retrieve the next page of items. Example:: @@ -158,7 +158,7 @@ class PaginationInfo(pydantic.BaseModel): has_next: bool = pydantic.Field(description="True if more results are available beyond this page.") next_page: typing.Optional[NextPageToken] = pydantic.Field( default=None, - description="Cursor to pass to the same endpoint to fetch the next page. None when has_next is False.", + description="Cursor to pass to Client.fetch_next_page to fetch the next page. None when has_next is False.", ) size: typing.Optional[int] = pydantic.Field( default=None, @@ -198,7 +198,7 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): ``Client``. ``MultipageList`` itself is a plain value object: it holds the already-fetched ``data`` and a single :meth:`get_next` handle, and does not fetch lazily or merge across calls. Advance with :meth:`get_next` (or by - passing ``pagination.next_page`` back to the originating endpoint). + passing ``pagination.next_page`` to :meth:`Client.fetch_next_page`). Type Parameters: _ItemT: The type of items in ``data``. diff --git a/src/ch_api/types/public_data/search.py b/src/ch_api/types/public_data/search.py index 34253e1..1765bfe 100644 --- a/src/ch_api/types/public_data/search.py +++ b/src/ch_api/types/public_data/search.py @@ -72,7 +72,7 @@ ----- Search results are returned as a :class:`ch_api.types.pagination.types.MultipageList`. Pass ``result_count`` to collect at least that many items in one call; advance with -``get_next`` (or pass ``next_page`` back to the endpoint). +``get_next`` (or ``Client.fetch_next_page`` for stateless resume). See Also -------- diff --git a/src/ch_api/types/public_data/search_companies.py b/src/ch_api/types/public_data/search_companies.py index a6198d5..4cb2d92 100644 --- a/src/ch_api/types/public_data/search_companies.py +++ b/src/ch_api/types/public_data/search_companies.py @@ -75,7 +75,7 @@ ----- All searches are paginated. The client's search methods return a MultipageList; pass ``result_count`` to collect more items per call and advance with ``get_next`` -(or ``next_page``). +(or ``Client.fetch_next_page`` for stateless resume). Example Usage ----- diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index 963cd04..dd9bb86 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -311,11 +311,16 @@ class TestAlphabeticalSearchBranches: """Lines 719, 729 — search_below param + empty items in alphabetical search.""" @pytest.mark.asyncio - async def test_search_below_added_from_next_page_token(self): - """Line 719: search_below query param added when cursor from token.""" - client = _make_client() - state = pagination_types._PageState(search_below="KEY:12345678") - next_page_token = state.encode() + async def test_search_below_added_via_fetch_next_page(self): + """Resuming a cursor token via fetch_next_page adds the search_below param.""" + client = _make_client() + token = client._encode_next_page( + pagination_types._PageState( + search_below="KEY:12345678", + endpoint="alphabetical_companies_search", + params={"query": "test", "page_size": 10, "result_count": 1}, + ) + ) urls_seen = [] async def fake_get_resource(url, result_type): @@ -323,7 +328,7 @@ async def fake_get_resource(url, result_type): return MagicMock(items=[]) client._get_resource = fake_get_resource - await client.alphabetical_companies_search("test", next_page=next_page_token) + await client.fetch_next_page(token) assert any("search_below=KEY%3A12345678" in u or "search_below=KEY:12345678" in u for u in urls_seen) @pytest.mark.asyncio @@ -383,11 +388,16 @@ class TestDissolvedSearchBranches: """Lines 766, 776 — search_below param + empty items in dissolved search.""" @pytest.mark.asyncio - async def test_search_below_added_from_next_page_token(self): - """Line 766: search_below query param added when cursor from token.""" + async def test_search_below_added_via_fetch_next_page(self): + """Resuming a cursor token via fetch_next_page adds the search_below param.""" client = _make_client() - state = pagination_types._PageState(search_below="OLD:12345678") - next_page_token = state.encode() + token = client._encode_next_page( + pagination_types._PageState( + search_below="OLD:12345678", + endpoint="search_dissolved_companies", + params={"query": "test", "page_size": 10, "type": "alphabetical", "result_count": 1}, + ) + ) urls_seen = [] async def fake_get_resource(url, result_type): @@ -395,7 +405,7 @@ async def fake_get_resource(url, result_type): return MagicMock(items=[]) client._get_resource = fake_get_resource - await client.search_dissolved_companies("test", next_page=next_page_token) + await client.fetch_next_page(token) assert any("search_below" in u for u in urls_seen) @pytest.mark.asyncio From 78d2d6face3f1629faf05547687660df6b0fe8e5 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 12:30:18 +0100 Subject: [PATCH 07/12] pagination --- README.md | 6 +- docs/sources/usage.rst | 13 ++-- src/ch_api/__init__.py | 10 ++- src/ch_api/_paginate.py | 51 +++++----------- src/ch_api/api.py | 91 ++++++++++------------------ src/ch_api/types/pagination/types.py | 76 +++++++---------------- 6 files changed, 82 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index b7d168e..b571fd6 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,8 @@ metadata. Pass `result_count` to collect more items per call, and advance with True `pagination.next_page` is a **self-contained** cursor — it embeds the endpoint and -its arguments, so a fresh process can resume statelessly with just the token via -`await client.fetch_next_page(token)`. This makes pagination restartable across -requests (e.g. for an async server or AI-agent tool) without walking the chain or -re-supplying the query. +its arguments, so a fresh process can resume with just the token via +`await client.fetch_next_page(token)` (ideal for stateless servers or agent tools). ## Rate Limiting diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index ffb1c0e..a822cbc 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -192,7 +192,7 @@ Search for disqualified officers: Working with Pagination ======================= -Many API endpoints return paginated results as :class:`ch_api.types.pagination.types.MultipageList`, a minimal value object with ``data`` (the items collected by this call), ``pagination`` (cursor metadata), and a ``get_next`` handle to fetch the next batch. Pass ``result_count`` to collect at least that many items in one call (the client issues multiple underlying requests of ``page_size`` as needed); call ``get_next`` to fetch the next batch. +Many endpoints return a :class:`ch_api.types.pagination.types.MultipageList`: a value object with ``data`` (this call's items), ``pagination`` (cursor metadata), and a ``get_next`` handle. Pass ``result_count`` to collect at least that many items in one call (the client issues multiple ``page_size`` requests as needed). Fetching a page:: @@ -228,12 +228,11 @@ the result set with ``get_next``: Restarting from a token (servers / agent tools) ----------------------------------------------- -``pagination.next_page`` is **self-contained**: it embeds the originating endpoint -and its arguments, so a separate process — with no in-memory state and without -re-supplying the query — can resume from just the token via -:meth:`~ch_api.api.Client.fetch_next_page`. This is the building block for an async -service or AI-agent tool that returns one page plus an opaque cursor, then continues -on a later, independent request: +``pagination.next_page`` is **self-contained**: it embeds the endpoint and its +arguments, so a separate process can resume from just the token via +:meth:`~ch_api.api.Client.fetch_next_page` — no in-memory state, no re-supplying the +query. Ideal for an async service or agent tool that returns a page plus a cursor +and continues on a later, independent request: .. code:: python diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index 4a545f7..0e3e4a2 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -157,12 +157,10 @@ ... page2 = await page.get_next() ... -``pagination.next_page`` is a **self-contained** cursor: it embeds the originating -endpoint and its arguments, so a fresh process can resume with only the token via -``client.fetch_next_page(token)`` — no need to walk the chain or re-supply the -query. This makes pagination restartable across requests, e.g. for an async server -or AI-agent tool that returns one page plus an opaque cursor and resumes later. -Pair it with a ``PageTokenSerializer`` to sign/encrypt the token on the wire. +``pagination.next_page`` is a **self-contained** cursor: it embeds the endpoint and +its arguments, so a fresh process can resume with only the token via +``client.fetch_next_page(token)`` — ideal for stateless servers or agent tools. Pair +it with a ``PageTokenSerializer`` to sign or encrypt the token on the wire. Exception Handling ------------------ diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index 340fb4a..1deb3ae 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -1,11 +1,6 @@ -"""Internal runtime for paginated ``Client`` endpoints. +"""The ``@paginated`` decorator and the task-local channel it feeds. -Houses the :func:`paginated` decorator and the task-local channel it uses to -hand ``(endpoint_name, call_params)`` to the fetch helpers in :mod:`ch_api.api`, -which stamp them into self-contained, replayable ``next_page`` tokens. Kept out -of ``api.py`` to keep that module focused on the endpoint surface. - -Not part of the public API. +Internal; not part of the public API. """ import contextvars @@ -21,54 +16,40 @@ @dataclasses.dataclass(frozen=True, slots=True) class _ResumeState: - """The originating endpoint and call arguments of an active ``@paginated`` call. + """Endpoint name and call arguments of the active ``@paginated`` call. - Read by the fetch helpers to stamp a self-contained ``next_page`` token. An - empty instance (``endpoint == ""``) means there is no active paginated call. + The fetch helpers read it to build the ``next_page`` token; an empty + ``endpoint`` means no paginated call is active. """ endpoint: str = "" params: typing.Dict[str, typing.Any] = dataclasses.field(default_factory=dict) -#: Task-local channel carrying the active call's :class:`_ResumeState` from the -#: :func:`paginated` decorator down to the fetch helpers, which stamp it into the -#: outgoing ``next_page`` token. Task-local (not instance state) so concurrent -#: paginated calls on the same client never clash. +#: Task-local channel from :func:`paginated` to the fetch helpers. Task-local so +#: concurrent paginated calls on one client don't clash. _resume_ctx: contextvars.ContextVar[typing.Optional[_ResumeState]] = contextvars.ContextVar( "ch_api_resume_ctx", default=None ) def current_resume_state() -> _ResumeState: - """Return the :class:`_ResumeState` published by the active ``@paginated`` call. - - Returns an empty ``_ResumeState`` (``endpoint == ""``) when called outside a - paginated method (e.g. a fetch helper invoked directly in a test) — in that - case the produced token is position-only and not replayable via - :meth:`ch_api.api.Client.fetch_next_page`. - """ + """The active ``@paginated`` call's :class:`_ResumeState`, or an empty one.""" return _resume_ctx.get() or _ResumeState() def paginated(*, exclude: typing.Collection[str] = ("self",)) -> typing.Callable[[_PaginatedFn], _PaginatedFn]: - """Decorator for async ``Client`` methods that return a ``MultipageList``. - - Folds three concerns together so endpoint bodies stay minimal: + """Decorate an async ``Client`` method that returns a ``MultipageList``. - * Applies :func:`pydantic.validate_call` — its schema is derived from the - method's own annotations, so no separate argument spec is needed. - * Captures the endpoint name (``func.__name__``) and the call's arguments and - publishes them on a task-local context var that the fetch helpers read to - build a self-contained, replayable ``next_page`` token (see - :meth:`ch_api.api.Client.fetch_next_page`). - * Binds the originating client to the returned list so - :meth:`~ch_api.types.pagination.types.MultipageList.get_next` works. + * Validates arguments via :func:`pydantic.validate_call` (schema from the + method's annotations). + * Publishes the endpoint name and arguments on :data:`_resume_ctx` so the + fetch helpers can build a replayable ``next_page`` token. + * Binds the client to the result so :meth:`MultipageList.get_next` works. Args: - exclude: Argument names omitted from the resume token's ``params``. - Defaults to just ``self`` (cursor position is tracked separately by - :meth:`ch_api.api.Client.fetch_next_page`). + exclude: Argument names left out of the resume token's ``params`` + (cursor position is tracked separately). Defaults to ``self``. """ excluded = set(exclude) diff --git a/src/ch_api/api.py b/src/ch_api/api.py index c0789a4..3784094 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -31,10 +31,9 @@ logger = logging.getLogger(__name__) -#: Task-local channel carrying the position to resume from, published by -#: :meth:`Client.fetch_next_page` and read by the fetch helpers. Empty (``None``) -#: for fresh calls — endpoints no longer accept a ``next_page`` parameter, so -#: ``fetch_next_page`` is the sole resume path. +#: Task-local channel for the position to resume from, set by +#: :meth:`Client.fetch_next_page` and read by the fetch helpers. ``None`` for a +#: fresh call (start from the beginning). _resume_from_ctx: contextvars.ContextVar[typing.Optional[types.pagination.types._PageState]] = ( contextvars.ContextVar("ch_api_resume_from", default=None) ) @@ -123,10 +122,9 @@ class Client: _session_auth: typing.Optional[httpx.BasicAuth] # Stored to allow session restart _page_token_serializer: typing.Optional[types.pagination.types.PageTokenSerializer] - #: Paginated methods that :meth:`fetch_next_page` may re-dispatch to. A - #: self-contained ``next_page`` token names the method that produced it; this - #: allowlist ensures a tampered or malformed token can only ever resume a real - #: paginated endpoint, never an arbitrary client method. + #: Methods :meth:`fetch_next_page` may re-dispatch to. Allowlisting the + #: endpoint named in a token stops a tampered token from invoking an arbitrary + #: client method. _RESUMABLE_ENDPOINTS: typing.ClassVar[frozenset[str]] = frozenset( { "get_officer_list", @@ -376,27 +374,16 @@ async def _fetch_paginated( fetch_page_fn: typing.Callable[[int], typing.Awaitable[tuple[list, typing.Optional[int]]]], result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch offset-based API pages until ``result_count`` items are collected. - - Issues one or more underlying API requests (each of the endpoint's - ``page_size``) until at least ``result_count`` items are gathered or no - further pages exist. Starts from offset 0 for a fresh call, or from the - position published on :data:`_resume_from_ctx` when invoked via - :meth:`fetch_next_page`. The ``next_page`` token on the returned list is - self-contained — the endpoint name and call arguments come from - :func:`~ch_api._paginate.current_resume_state` (populated by the - :func:`~ch_api._paginate.paginated` decorator) and are embedded so the - request can be resumed from a fresh process. + """Collect offset-based API pages until ``result_count`` items are gathered. - Args: - fetch_page_fn: Callable taking ``start_index`` (int), returning a - tuple of ``(items, total_count)``. ``total_count`` may be None - if unknown; in that case no further pages will be fetched. - result_count: Minimum number of items to collect. At least one - underlying page is always fetched regardless of this value. + Fetches successive pages from the resume position (offset 0 for a fresh + call) until ``result_count`` items are collected or pages run out, and + embeds a self-contained ``next_page`` token in the result. - Returns: - A MultipageList holding the collected items and pagination metadata. + Args: + fetch_page_fn: Takes a ``start_index`` and returns ``(items, total_count)``; + ``total_count`` may be None, which stops pagination. + result_count: Minimum items to collect; at least one page is fetched. """ resume = current_resume_state() page_state = _resume_from_ctx.get() or types.pagination.types._PageState.first() @@ -441,27 +428,17 @@ async def _fetch_paginated_cursor( fetch_page_fn: typing.Callable[[typing.Optional[str]], typing.Awaitable[tuple[list, typing.Optional[str]]]], result_count: int, ) -> types.pagination.types.MultipageList: - """Fetch cursor-based API pages until ``result_count`` items are collected. - - Used for endpoints that paginate via ``search_below`` / ``search_above`` - cursors (e.g. alphabetical company search) rather than ``start_index``. - Issues one or more underlying API requests until at least ``result_count`` - items are gathered or no further pages exist. Starts from the beginning for - a fresh call, or from the cursor published on :data:`_resume_from_ctx` when - invoked via :meth:`fetch_next_page`. The endpoint name and call arguments - come from :func:`~ch_api._paginate.current_resume_state` (populated by the - :func:`~ch_api._paginate.paginated` decorator) and are embedded in the - ``next_page`` token so the request can be resumed. + """Collect cursor-based API pages until ``result_count`` items are gathered. - Args: - fetch_page_fn: Callable taking the current ``search_below`` cursor - (None for the first page), returning ``(items, next_cursor)``. - ``next_cursor`` is None when no further pages exist. - result_count: Minimum number of items to collect. + For endpoints paginating by a ``search_below`` cursor (e.g. alphabetical + search) rather than an offset. Like :meth:`_fetch_paginated`, but tracks a + cursor instead of an index; ``pagination.size`` is always None. - Returns: - A MultipageList holding the collected items and pagination metadata. - ``pagination.size`` is always None for cursor-based endpoints. + Args: + fetch_page_fn: Takes the current ``search_below`` cursor (None for the + first page) and returns ``(items, next_cursor)``; ``next_cursor`` is + None when no further pages exist. + result_count: Minimum items to collect. """ resume = current_resume_state() page_state = _resume_from_ctx.get() or types.pagination.types._PageState.first() @@ -499,24 +476,23 @@ async def _fetch_paginated_cursor( async def fetch_next_page( self, next_page: types.pagination.types.NextPageToken ) -> types.pagination.types.MultipageList: - """Resume a paginated request from a self-contained ``next_page`` token. + """Resume a paginated request from a ``next_page`` token. - The token (taken from ``MultipageList.pagination.next_page``) embeds the - originating endpoint and its arguments, so a fresh process can fetch the - next batch with only the token — there is no need to walk the page chain - or reconstruct the original query. This is the building block for stateless - services such as an AI-agent tool that returns one page plus an opaque - cursor, then resumes on a later, independent request. + The token (from ``MultipageList.pagination.next_page``) embeds the endpoint + and its arguments, so a fresh process can fetch the next batch from the + token alone — no need to keep the original query. This is the basis for + stateless resume, e.g. an agent tool that returns a page plus a cursor and + continues on a later, independent request. Args: next_page: A ``pagination.next_page`` token from a prior result. Returns: - The next ``MultipageList``, itself bound for further iteration. + The next ``MultipageList``, bound for further iteration. Raises: - ValueError: If the token does not name a known, resumable endpoint — - e.g. a position-only token, or a malformed / tampered value. + ValueError: If the token does not name a resumable endpoint + (e.g. malformed or tampered). Example:: @@ -533,8 +509,7 @@ async def fetch_next_page( "it may be position-only, malformed, or tampered." ) method = getattr(self, state.endpoint) - # Publish the resume position for the fetch helpers; the endpoint is called - # fresh (no next_page parameter) with the token's captured arguments. + # Publish the resume position, then call the endpoint with the token's args. ctx_token = _resume_from_ctx.set(state) try: return await method(**state.params) diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index 9b1cb98..23f5843 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -7,7 +7,7 @@ MultipageList: Generic value object holding one batch of results plus a get_next handle. Internal types (not part of the public API): - _PageState: Encodes CH API pagination state (start_index / search_below cursor). + _PageState: Self-contained, restartable pagination cursor (the encoded NextPageToken). """ import typing @@ -25,24 +25,15 @@ class _PageState(pydantic.BaseModel, frozen=True): - """A self-contained, restartable pagination cursor encoded as JSON. + """A self-contained, restartable pagination cursor, encoded as the JSON ``NextPageToken``. - Not part of the public API — callers only ever see ``NextPageToken`` (str). + Internal — callers only ever see the opaque ``NextPageToken`` (str). Holds + everything needed to resume from a fresh process: - The state captures everything needed to resume a paginated request from a - fresh process with no in-memory context: - - * ``endpoint`` — the name of the ``Client`` method that produced the page - (used to re-dispatch on resume; validated against an allowlist). - * ``params`` — the originating call's keyword arguments (query, filters, - ``page_size``, ``result_count``, path parameters, …), as JSON-safe values. + * ``endpoint`` / ``params`` — the ``Client`` method and its arguments to + re-dispatch (params are JSON-safe values). * ``start_index`` — next offset for offset-based endpoints. - * ``search_below`` — ``ordered_alpha_key_with_id`` cursor for cursor-based - endpoints (alphabetical / dissolved search). - - A position-only state (``endpoint``/``params`` empty) is still valid: the - originating endpoint resumes from the position, it just cannot be replayed - blindly via :meth:`Client.fetch_next_page`. + * ``search_below`` — cursor for cursor-based endpoints (alphabetical / dissolved). """ start_index: int = 0 @@ -175,11 +166,7 @@ class PaginationInfo(pydantic.BaseModel): @typing.runtime_checkable class _NextPageFetcher(typing.Protocol): - """Minimal interface :meth:`MultipageList.get_next` needs from a client. - - ``Client`` satisfies this structurally; kept here to avoid importing the - client into the types package. - """ + """Minimal client interface :meth:`MultipageList.get_next` needs (``Client`` satisfies it).""" async def fetch_next_page(self, next_page: str) -> typing.Any: ... @@ -192,13 +179,10 @@ async def fetch_next_page(self, next_page: str) -> typing.Any: ... class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): """A batch of typed results from a paginated CH API endpoint. - Contains the items collected by one client call — at least ``result_count`` - items, or all remaining items if fewer exist — plus the pagination metadata - needed to retrieve the next batch. Returned by all paginated methods on - ``Client``. ``MultipageList`` itself is a plain value object: it holds the - already-fetched ``data`` and a single :meth:`get_next` handle, and does not - fetch lazily or merge across calls. Advance with :meth:`get_next` (or by - passing ``pagination.next_page`` to :meth:`Client.fetch_next_page`). + Holds the items from one client call — at least ``result_count``, or all + remaining if fewer — plus pagination metadata. A plain value object: advance + with :meth:`get_next` (or pass ``pagination.next_page`` to + :meth:`Client.fetch_next_page`); it does not fetch lazily or merge batches. Type Parameters: _ItemT: The type of items in ``data``. @@ -237,43 +221,25 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): ) _client: typing.Optional[_NextPageFetcher] = pydantic.PrivateAttr(default=None) - """Client used to fetch the next batch via the self-contained ``next_page`` - token. Set by the client when the list is produced; ``None`` on - manually-constructed or deserialized instances. Bind one with - :meth:`with_client` to re-enable :meth:`get_next` on a reconstructed list. - """ + """Client used by :meth:`get_next`. Set when the list is produced; ``None`` on + deserialized instances (rebind with :meth:`with_client`).""" def with_client(self, client: _NextPageFetcher) -> "MultipageList[_ItemT]": - """Bind a client so :meth:`get_next` works on this (e.g. deserialized) list. + """Bind a client so :meth:`get_next` works (e.g. on a deserialized list); returns ``self``. - Returns ``self`` for chaining. The token itself is self-contained, so - the bound client only supplies the HTTP session — any ``Client`` will do. + The token is self-contained, so any ``Client`` will do — it only supplies + the HTTP session. """ self._client = client return self async def get_next(self) -> "MultipageList[_ItemT]": - """Fetch the next batch from the same endpoint with the same arguments. - - Convenience wrapper over :meth:`Client.fetch_next_page` using this - list's self-contained ``pagination.next_page`` token. The returned - ``MultipageList`` is itself bound for further iteration. + """Fetch the next batch via :meth:`Client.fetch_next_page` and this list's token. Raises: - NoMorePagesError: If ``pagination.has_next`` is ``False`` — this - list is already the last batch. - RuntimeError: If no client is bound (e.g. a manually-constructed or - deserialized list). Either call ``client.fetch_next_page(token)`` - directly, or bind one first via :meth:`with_client`. - - Example: - Walk every batch:: - - page = await client.search_companies("Apple") - while page.pagination.has_next: - page = await page.get_next() - for company in page.data: - ... + NoMorePagesError: If ``pagination.has_next`` is ``False``. + RuntimeError: If no client is bound (e.g. a deserialized list); call + ``client.fetch_next_page(token)`` or :meth:`with_client` first. """ if not self.pagination.has_next: raise exc.NoMorePagesError("This is the last page; no more results to fetch.") From 31c03cc9616c2638e303b11583ae0b9148ed0617 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 12:58:01 +0100 Subject: [PATCH 08/12] x --- pyproject.toml | 2 +- src/ch_api/_paginate.py | 4 + src/ch_api/api.py | 516 ++++++++---------- .../types/public_data/search_companies.py | 108 +--- ...er_view_false_start_index_0_91b32762.json} | 4 +- ...s_per_page_25_start_index_0_b8ccbabc.json} | 4 +- ...er_view_false_start_index_0_a3877b80.json} | 4 +- ...er_view_false_start_index_0_1731111d.json} | 4 +- ...er_view_false_start_index_0_f05c66d6.json} | 4 +- ...er_view_false_start_index_0_bcf9d086.json} | 4 +- tests/unit/test_api_branch_coverage.py | 8 + 11 files changed, 258 insertions(+), 404 deletions(-) rename tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/{company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_4f5e3bfa.json => company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_91b32762.json} (93%) rename tests/resources/tests/test_officer_appointments.py/test_get_appointments/{_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_9186729a.json => _y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json} (94%) rename tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/{company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_6054a354.json => company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json} (93%) rename tests/resources/tests/test_psc.py/test_get_psc_list/{company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_3ff2c5ab.json => company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json} (94%) rename tests/resources/tests/test_psc.py/test_get_r5e_statements/{company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_0d564235.json => company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json} (87%) rename tests/resources/tests/test_psc.py/test_someones_psc_statements/{sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_1fc1d812.json => sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json} (90%) diff --git a/pyproject.toml b/pyproject.toml index 3ee0d99..51f765f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Operating System :: POSIX :: Linux", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index 1deb3ae..da2b16f 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -71,6 +71,10 @@ async def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: result._client = args[0] # bind the Client (self) for get_next() return result + #: Marks the method as a resumable paginated endpoint. ``Client.fetch_next_page`` + #: only re-dispatches to methods carrying this flag, so the allowlist can never + #: drift out of sync with the set of ``@paginated`` methods. + wrapper._ch_paginated = True # type: ignore[attr-defined] return typing.cast(_PaginatedFn, wrapper) return decorate diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 3784094..68cc558 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -46,15 +46,15 @@ pydantic.StringConstraints(min_length=1, pattern="^[A-Za-z0-9]{1,8}$"), ] -OfficerIdStrT = typing.Annotated[ +#: A non-empty string identifier. Officer and PSC ids share the same constraint; +#: the distinct aliases document intent at the call sites. +_NonEmptyStrT = typing.Annotated[ str, pydantic.StringConstraints(min_length=1), ] -PscIdStrT = typing.Annotated[ - str, - pydantic.StringConstraints(min_length=1), -] +OfficerIdStrT = _NonEmptyStrT +PscIdStrT = _NonEmptyStrT @contextlib.asynccontextmanager @@ -122,26 +122,6 @@ class Client: _session_auth: typing.Optional[httpx.BasicAuth] # Stored to allow session restart _page_token_serializer: typing.Optional[types.pagination.types.PageTokenSerializer] - #: Methods :meth:`fetch_next_page` may re-dispatch to. Allowlisting the - #: endpoint named in a token stops a tampered token from invoking an arbitrary - #: client method. - _RESUMABLE_ENDPOINTS: typing.ClassVar[frozenset[str]] = frozenset( - { - "get_officer_list", - "search", - "advanced_company_search", - "alphabetical_companies_search", - "search_dissolved_companies", - "search_companies", - "search_officers", - "search_disqualified_officers", - "get_company_filing_history", - "get_officer_appointments", - "get_company_psc_list", - "get_company_psc_statements", - } - ) - def __init__( self, credentials: typing.Union[ @@ -250,6 +230,23 @@ def _new_session(self) -> httpx.AsyncClient: headers={"ACCEPT": "application/json"}, ) + async def _send(self, request: httpx.Request) -> httpx.Response: + """Send ``request`` under the rate limiter, reopening a closed owned session. + + If the underlying session was closed out from under a client that owns it, + transparently rebuild the session and retry once. Sessions supplied by the + caller are left untouched (the ``RuntimeError`` propagates). + """ + async with self._api_limiter(): + try: + return await self._api_session.send(request) + except RuntimeError as err: + if self._owns_session and "has been closed" in str(err): + logger.warning("HTTP session was closed; reopening and retrying.") + self._api_session = self._new_session() + return await self._api_session.send(request) + raise + async def __aenter__(self) -> "Client": """Enter async context manager.""" return self @@ -299,17 +296,14 @@ async def _execute_request( request: httpx.Request, expected_out: typing.Type[ModelT] | None, ) -> ModelT | None: - """Placeholder for request execution logic.""" - async with self._api_limiter(): - try: - response = await self._api_session.send(request) - except RuntimeError as err: - if self._owns_session and "has been closed" in str(err): - logger.warning("HTTP session was closed; reopening and retrying.") - self._api_session = self._new_session() - response = await self._api_session.send(request) - else: - raise + """Send a request through the rate limiter and validate the response. + + Applies the configured rate limiter, sends the request (reopening a + client-owned session if it was closed underneath us), maps ``404`` to + ``None``, raises for other error statuses, and validates the body against + ``expected_out`` when one is given. + """ + response = await self._send(request) if response.status_code == 404: # Resource not found return None @@ -327,7 +321,7 @@ async def _get_resource( self, url: str, result_type: typing.Type[ModelT], - ) -> typing.Optional[ModelT]: # noqa: C901 + ) -> typing.Optional[ModelT]: """Helper method for simple GET requests. Reduces duplication for endpoints that just need to fetch a resource. @@ -473,6 +467,86 @@ async def _fetch_paginated_cursor( ), ) + async def _fetch_offset_endpoint( + self, + *, + base_url: str, + result_type: typing.Any, + result_count: int, + params: typing.Optional[dict] = None, + total_attr: str = "total_results", + page_size: typing.Optional[int] = None, + page_size_param: str = "items_per_page", + ) -> types.pagination.types.MultipageList: + """Collect pages from an offset-based list endpoint. + + Builds the per-page ``_fetch`` closure shared by every offset endpoint — + URL assembly, the ``416 Range Not Satisfiable`` end-of-results guard, and + reading ``items``/total off the response — and feeds it to + :meth:`_fetch_paginated`. + + Args: + base_url: Endpoint URL without query string. + result_type: Response model exposing ``items`` and ``total_attr``. + params: Static query params common to every page. + total_attr: Attribute holding the total result count (``total_results``, + ``total_count`` or ``hits`` depending on the endpoint). + page_size: Items per request; omitted from the query when ``None``. + page_size_param: Query parameter name for ``page_size`` (the search + endpoints use ``items_per_page``; advanced search uses ``size``). + """ + base_params = params or {} + + async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: + page_params = {**base_params, "start_index": start_index} + if page_size is not None: + page_params[page_size_param] = page_size + url = f"{base_url}?{urllib.parse.urlencode(page_params, doseq=True)}" + try: + result = await self._get_resource(url, result_type) + except httpx.HTTPStatusError as e: + if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: + return [], None + raise + if result is None: + return [], None + return result.items or [], getattr(result, total_attr) + + return await self._fetch_paginated(_fetch, result_count) + + async def _fetch_cursor_endpoint( + self, + *, + base_url: str, + result_type: typing.Any, + result_count: int, + params: dict, + ) -> types.pagination.types.MultipageList: + """Collect pages from a ``search_below`` cursor endpoint. + + Shared body for the alphabetical and dissolved searches: the next cursor is + the last item's ``ordered_alpha_key_with_id``, and an empty page ends + pagination. + + Args: + base_url: Endpoint URL without query string. + result_type: Response model exposing an ``items`` list. + params: Static query params common to every page. + """ + + async def _fetch(search_below: typing.Optional[str]) -> tuple[list, typing.Optional[str]]: + page_params = dict(params) + if search_below is not None: + page_params["search_below"] = search_below + url = f"{base_url}?{urllib.parse.urlencode(page_params)}" + result = await self._get_resource(url, result_type) + items = result.items if result is not None else [] + if not items: + return [], None + return items, items[-1].ordered_alpha_key_with_id + + return await self._fetch_paginated_cursor(_fetch, result_count) + async def fetch_next_page( self, next_page: types.pagination.types.NextPageToken ) -> types.pagination.types.MultipageList: @@ -503,12 +577,15 @@ async def fetch_next_page( page2 = await client.fetch_next_page(token) """ state = self._decode_next_page(next_page) - if state.endpoint not in self._RESUMABLE_ENDPOINTS: + # Only ``@paginated`` methods carry ``_ch_paginated``; gating on it stops a + # tampered token from re-dispatching to an arbitrary client method, and the + # allowlist can never drift out of sync with the decorated endpoints. + method = getattr(self, state.endpoint, None) + if method is None or not getattr(method, "_ch_paginated", False): raise ValueError( f"next_page token does not identify a resumable endpoint (got {state.endpoint!r}); " "it may be position-only, malformed, or tampered." ) - method = getattr(self, state.endpoint) # Publish the resume position, then call the endpoint with the token's args. ctx_token = _resume_from_ctx.set(state) try: @@ -620,27 +697,15 @@ async def get_officer_list( query_params: dict[str, typing.Union[str, list[str]]] = {"order_by": order_by} if only_type is not None: query_params |= {"register_type": only_type, "register_view": "true"} - base_url = f"{self._settings.api_url}/company/{company_number}/officers" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = query_params | {"start_index": start_index, "items_per_page": page_size} - url = f"{base_url}?{urllib.parse.urlencode(params, doseq=True)}" - try: - result = await self._get_resource( - url, - types.public_data.search_companies.GenericSearchResult[ # type: ignore[arg-type] - types.public_data.company_officers.OfficerSummary - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/company/{company_number}/officers", + result_type=types.public_data.search_companies.GenericSearchResult[ + types.public_data.company_officers.OfficerSummary + ], + result_count=result_count, + params=query_params, + page_size=page_size, + ) @pydantic.validate_call async def get_officer_appointment( @@ -678,26 +743,15 @@ async def search( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/search" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 - try: - result = await self._get_resource( - url, - types.public_data.search_companies.GenericSearchResult[ # type: ignore[arg-type] - types.public_data.search.AnySearchResultT - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/search", + result_type=types.public_data.search_companies.GenericSearchResult[ + types.public_data.search.AnySearchResultT + ], + result_count=result_count, + params={"q": query}, + page_size=page_size, + ) @paginated() async def advanced_company_search( # noqa: C901 @@ -757,29 +811,17 @@ async def advanced_company_search( # noqa: C901 query_params["location"] = location if sic_codes: query_params["sic_codes"] = list(sic_codes) - if page_size is not None: - query_params["size"] = page_size - base_url = f"{self._settings.api_url}/advanced-search/companies" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = query_params | {"start_index": start_index} - url = f"{base_url}?{urllib.parse.urlencode(params, doseq=True)}" - try: - result = await self._get_resource( - url, - types.public_data.search_companies.AdvancedSearchResult[ # type: ignore[arg-type] - types.public_data.search_companies.AdvancedCompany - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.hits - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/advanced-search/companies", + result_type=types.public_data.search_companies.AdvancedSearchResult[ + types.public_data.search_companies.AdvancedCompany + ], + result_count=result_count, + params=query_params, + total_attr="hits", + page_size=page_size, + page_size_param="size", + ) @paginated() async def alphabetical_companies_search( @@ -800,28 +842,14 @@ async def alphabetical_companies_search( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/alphabetical-search/companies" - - async def _fetch( - search_below: typing.Optional[str], - ) -> tuple[list, typing.Optional[str]]: - params: dict = {"q": query, "size": str(page_size)} - if search_below is not None: - params["search_below"] = search_below - url = f"{base_url}?{urllib.parse.urlencode(params)}" - result = await self._get_resource( - url, - types.public_data.search_companies.AlphabeticalCompanySearchResult[ # type: ignore[arg-type] - types.public_data.search_companies.AlphabeticalCompany - ], - ) - items = result.items if result is not None else [] - if not items: - return [], None - next_cursor = items[-1].ordered_alpha_key_with_id - return items, next_cursor - - return await self._fetch_paginated_cursor(_fetch, result_count) + return await self._fetch_cursor_endpoint( + base_url=f"{self._settings.api_url}/alphabetical-search/companies", + result_type=types.public_data.search_companies.AlphabeticalCompanySearchResult[ + types.public_data.search_companies.AlphabeticalCompany + ], + result_count=result_count, + params={"q": query, "size": str(page_size)}, + ) @paginated() async def search_dissolved_companies( @@ -845,28 +873,14 @@ async def search_dissolved_companies( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/dissolved-search/companies" - - async def _fetch( - search_below: typing.Optional[str], - ) -> tuple[list, typing.Optional[str]]: - params: dict = {"q": query, "size": str(page_size), "search_type": type} - if search_below is not None: - params["search_below"] = search_below - url = f"{base_url}?{urllib.parse.urlencode(params)}" - result = await self._get_resource( - url, - types.public_data.search_companies.AlphabeticalCompanySearchResult[ # type: ignore[arg-type] - types.public_data.search_companies.DissolvedCompany - ], - ) - items = result.items if result is not None else [] - if not items: - return [], None - next_cursor = items[-1].ordered_alpha_key_with_id - return items, next_cursor - - return await self._fetch_paginated_cursor(_fetch, result_count) + return await self._fetch_cursor_endpoint( + base_url=f"{self._settings.api_url}/dissolved-search/companies", + result_type=types.public_data.search_companies.AlphabeticalCompanySearchResult[ + types.public_data.search_companies.DissolvedCompany + ], + result_count=result_count, + params={"q": query, "size": str(page_size), "search_type": type}, + ) @paginated() async def search_companies( @@ -887,26 +901,15 @@ async def search_companies( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/search/companies" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 - try: - result = await self._get_resource( - url, - types.public_data.search_companies.GenericSearchResult[ # type: ignore[arg-type] - types.public_data.search.CompanySearchItem - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/search/companies", + result_type=types.public_data.search_companies.GenericSearchResult[ + types.public_data.search.CompanySearchItem + ], + result_count=result_count, + params={"q": query}, + page_size=page_size, + ) @paginated() async def search_officers( @@ -927,26 +930,15 @@ async def search_officers( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/search/officers" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 - try: - result = await self._get_resource( - url, - types.public_data.search_companies.GenericSearchResult[ # type: ignore[arg-type] - types.public_data.search.OfficerSearchItem - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/search/officers", + result_type=types.public_data.search_companies.GenericSearchResult[ + types.public_data.search.OfficerSearchItem + ], + result_count=result_count, + params={"q": query}, + page_size=page_size, + ) @paginated() async def search_disqualified_officers( @@ -967,26 +959,15 @@ async def search_disqualified_officers( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/search/disqualified-officers" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - url = f"{base_url}?{urllib.parse.urlencode({'q': query, 'start_index': start_index, 'items_per_page': page_size})}" # noqa: E501 - try: - result = await self._get_resource( - url, - types.public_data.search_companies.GenericSearchResult[ # type: ignore[arg-type] - types.public_data.search.DisqualifiedOfficerSearchItem - ], - ) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/search/disqualified-officers", + result_type=types.public_data.search_companies.GenericSearchResult[ + types.public_data.search.DisqualifiedOfficerSearchItem + ], + result_count=result_count, + params={"q": query}, + page_size=page_size, + ) @pydantic.validate_call async def get_company_charges( @@ -1076,22 +1057,14 @@ async def get_company_filing_history( base_query_params: dict = {} if categories is not None: base_query_params["category"] = ",".join(categories) - base_url = f"{self._settings.api_url}/company/{company_number}/filing-history" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = base_query_params | {"start_index": start_index, "items_per_page": page_size} - url = f"{base_url}?{urllib.parse.urlencode(params)}" - try: - result = await self._get_resource(url, types.public_data.filing_history.FilingHistoryList) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_count - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/company/{company_number}/filing-history", + result_type=types.public_data.filing_history.FilingHistoryList, + result_count=result_count, + params=base_query_params, + total_attr="total_count", + page_size=page_size, + ) @pydantic.validate_call async def get_filing_history_item( @@ -1203,22 +1176,13 @@ async def get_document_url( print(url) """ url = f"{self._settings.document_api_url}/document/{document_id}/content" + # follow_redirects=False is the httpx default; the redirect is resolved here by hand. request = self._api_session.build_request( method="GET", url=url, headers={"Accept": content_type}, ) - async with self._api_limiter(): - try: - # follow_redirects=False is the httpx default; stated explicitly for clarity - response = await self._api_session.send(request) - except RuntimeError as err: - if self._owns_session and "has been closed" in str(err): - logger.warning("HTTP session was closed; reopening and retrying.") - self._api_session = self._new_session() - response = await self._api_session.send(request) - else: - raise + response = await self._send(request) if response.status_code == httpx.codes.NOT_FOUND: return None if response.status_code in (httpx.codes.FOUND, httpx.codes.MOVED_PERMANENTLY): @@ -1399,24 +1363,16 @@ async def get_officer_appointments( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/officers/{officer_id}/appointments" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params: dict = {"items_per_page": str(page_size), "start_index": str(start_index)} - if filter is not None: - params["filter"] = filter - url = f"{base_url}?{urllib.parse.urlencode(params)}" - try: - result = await self._get_resource(url, types.public_data.officer_appointments.AppointmentList) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + base_query_params: dict = {} + if filter is not None: + base_query_params["filter"] = filter + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/officers/{officer_id}/appointments", + result_type=types.public_data.officer_appointments.AppointmentList, + result_count=result_count, + params=base_query_params, + page_size=page_size, + ) @pydantic.validate_call async def get_company_uk_establishments( @@ -1462,27 +1418,13 @@ async def get_company_psc_list( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control" - register_view_str = "true" if register_view else "false" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = { - "items_per_page": str(page_size), - "start_index": str(start_index), - "register_view": register_view_str, - } - url = f"{base_url}?{urllib.parse.urlencode(params)}" - try: - result = await self._get_resource(url, types.public_data.psc.PSCList) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control", + result_type=types.public_data.psc.PSCList, + result_count=result_count, + params={"register_view": "true" if register_view else "false"}, + page_size=page_size, + ) @paginated() async def get_company_psc_statements( @@ -1506,27 +1448,13 @@ async def get_company_psc_statements( Minimum number of items to collect, issuing multiple underlying requests of ``page_size`` if needed (default 1 = one page). """ - base_url = f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control-statements" - register_view_str = "true" if register_view else "false" - - async def _fetch(start_index: int) -> tuple[list, typing.Optional[int]]: - params = { - "items_per_page": str(page_size), - "start_index": str(start_index), - "register_view": register_view_str, - } - url = f"{base_url}?{urllib.parse.urlencode(params)}" - try: - result = await self._get_resource(url, types.public_data.psc.StatementList) - except httpx.HTTPStatusError as e: - if e.response.status_code == httpx.codes.REQUESTED_RANGE_NOT_SATISFIABLE: - return [], None - raise - if result is None: - return [], None - return result.items or [], result.total_results - - return await self._fetch_paginated(_fetch, result_count) + return await self._fetch_offset_endpoint( + base_url=f"{self._settings.api_url}/company/{company_number}/persons-with-significant-control-statements", + result_type=types.public_data.psc.StatementList, + result_count=result_count, + params={"register_view": "true" if register_view else "false"}, + page_size=page_size, + ) async def _get_psc_by_type( self, diff --git a/src/ch_api/types/public_data/search_companies.py b/src/ch_api/types/public_data/search_companies.py index 4cb2d92..2385164 100644 --- a/src/ch_api/types/public_data/search_companies.py +++ b/src/ch_api/types/public_data/search_companies.py @@ -85,31 +85,21 @@ ... results = await client.advanced_company_search( ... company_name_includes="Apple" ... ) - ... return len(results.items) if results.items else 0 + ... return len(results.data) >>> count = await run_async_func(search_companies_example) # doctest: +SKIP Alphabetical search:: >>> async def alphabetical_search_example(client): ... results = await client.alphabetical_companies_search("BBC") - ... count = 0 - ... async for company in results: - ... count += 1 - ... if count >= 1: - ... break - ... return count + ... return len(results.data) >>> count = await run_async_func(alphabetical_search_example) # doctest: +SKIP Dissolved company search:: >>> async def dissolved_search_example(client): ... dissolved = await client.search_dissolved_companies("Enron") - ... count = 0 - ... async for company in dissolved: - ... count += 1 - ... if count >= 1: - ... break - ... return count + ... return len(dissolved.data) >>> count = await run_async_func(dissolved_search_example) # doctest: +SKIP See Also @@ -166,8 +156,8 @@ class PreviousCompanyName(base.BaseModel): ] -class DissolvedCompanyRegisteredOfficeAddress(base.BaseModel): - """Registered Office Address for dissolved companies. +class _SearchRegisteredOfficeAddress(base.BaseModel): + """Registered Office Address fields common to search-result companies. This will only appear if there are ROA details in the company record. """ @@ -205,43 +195,12 @@ class DissolvedCompanyRegisteredOfficeAddress(base.BaseModel): ] -class AdvancedCompanyRegisteredOfficeAddress(base.BaseModel): - """Registered Office Address for advanced search results. +class DissolvedCompanyRegisteredOfficeAddress(_SearchRegisteredOfficeAddress): + """Registered Office Address for dissolved companies.""" - This will only appear if there are ROA details in the company record. - """ - - address_line_1: typing.Annotated[ - str | None, - pydantic.Field( - description="The first line of the address e.g Crown Way", - default=None, - ), - ] - - address_line_2: typing.Annotated[ - str | None, - pydantic.Field( - description="The second line of the address", - default=None, - ), - ] - - locality: typing.Annotated[ - str | None, - pydantic.Field( - description="The town associated to the ROA e.g Cardiff", - default=None, - ), - ] - postal_code: typing.Annotated[ - str | None, - pydantic.Field( - description="The postal code e.g CF14 3UZ", - default=None, - ), - ] +class AdvancedCompanyRegisteredOfficeAddress(_SearchRegisteredOfficeAddress): + """Registered Office Address for advanced search results.""" region: typing.Annotated[ str | None, @@ -414,7 +373,8 @@ class AlphabeticalCompany(base.BaseModel): "search-results#company", "searchresults#alphabetical-search", "searchresults#company", - ], + ] + | None, pydantic.Field( description="The type of search result", default=None, @@ -586,52 +546,6 @@ class AdvancedCompany(base.BaseModel): ] -# class DissolvedCompanySearch(base.BaseModel): -# """List of dissolved companies.""" - -# etag: typing.Annotated[ -# str | None, -# pydantic.Field( -# default=None, -# ), -# ] - -# items: typing.Annotated[ -# list[DissolvedCompany] | None, -# pydantic.Field( -# default=None, -# ), -# ] - -# kind: typing.Annotated[ -# typing.Literal[ -# "search#alphabetical-dissolved", -# "search#dissolved", -# "search#previous-name-dissolved", -# ] -# | None, -# pydantic.Field( -# default=None, -# ), -# ] - -# top_hit: typing.Annotated[ -# DissolvedCompany | None, -# pydantic.Field( -# description="The best matching company in dissolved search results", -# default=None, -# ), -# ] - -# hits: typing.Annotated[ -# str | None, -# pydantic.Field( -# description="The number of hits returned on a best-match or previous-company-names search", -# default=None, -# ), -# ] - - class AlphabeticalCompanySearchResult(base.BaseModel, typing.Generic[T]): """List of companies from alphabetical search.""" diff --git a/tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_4f5e3bfa.json b/tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_91b32762.json similarity index 93% rename from tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_4f5e3bfa.json rename to tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_91b32762.json index 1f418f6..55cf818 100644 --- a/tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_4f5e3bfa.json +++ b/tests/resources/doctests/ch-api/docs/sources/usage.rst/usage.rst/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_91b32762.json @@ -78,8 +78,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/company/09370755/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/09370755/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" }, "status_code": 200, - "url": "https://api.company-information.service.gov.uk/company/09370755/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/09370755/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_9186729a.json b/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json similarity index 94% rename from tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_9186729a.json rename to tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json index b7c2563..a3b3272 100644 --- a/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_9186729a.json +++ b/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json @@ -81,8 +81,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/officers/_y4370DCOaJgIqvAlmHtJ7HdiqU/appointments?items_per_page=25&start_index=0" + "url": "https://api.company-information.service.gov.uk/officers/_y4370DCOaJgIqvAlmHtJ7HdiqU/appointments?start_index=0&items_per_page=25" }, "status_code": 200, - "url": "https://api.company-information.service.gov.uk/officers/_y4370DCOaJgIqvAlmHtJ7HdiqU/appointments?items_per_page=25&start_index=0" + "url": "https://api.company-information.service.gov.uk/officers/_y4370DCOaJgIqvAlmHtJ7HdiqU/appointments?start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_6054a354.json b/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json similarity index 93% rename from tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_6054a354.json rename to tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json index f9bd4f1..c8a6142 100644 --- a/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_6054a354.json +++ b/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json @@ -71,8 +71,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/company/00002065/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/00002065/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" }, "status_code": 200, - "url": "https://api.company-information.service.gov.uk/company/00002065/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/00002065/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_3ff2c5ab.json b/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json similarity index 94% rename from tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_3ff2c5ab.json rename to tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json index 8a236bc..c7d0780 100644 --- a/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_3ff2c5ab.json +++ b/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json @@ -80,8 +80,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" }, "status_code": 200, - "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control?register_view=false&start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_0d564235.json b/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json similarity index 87% rename from tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_0d564235.json rename to tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json index 1c1e525..691bc1f 100644 --- a/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_0d564235.json +++ b/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json @@ -31,8 +31,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control-statements?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control-statements?register_view=false&start_index=0&items_per_page=25" }, "status_code": 404, - "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control-statements?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/14200734/persons-with-significant-control-statements?register_view=false&start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_1fc1d812.json b/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json similarity index 90% rename from tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_1fc1d812.json rename to tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json index 982e85a..f80d85f 100644 --- a/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_1fc1d812.json +++ b/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json @@ -52,8 +52,8 @@ "user-agent": "python-httpx/0.28.1" }, "params": null, - "url": "https://api.company-information.service.gov.uk/company/SC549056/persons-with-significant-control-statements?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/SC549056/persons-with-significant-control-statements?register_view=false&start_index=0&items_per_page=25" }, "status_code": 200, - "url": "https://api.company-information.service.gov.uk/company/SC549056/persons-with-significant-control-statements?items_per_page=25&start_index=0®ister_view=false" + "url": "https://api.company-information.service.gov.uk/company/SC549056/persons-with-significant-control-statements?register_view=false&start_index=0&items_per_page=25" } \ No newline at end of file diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index dd9bb86..84f444a 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -185,6 +185,14 @@ async def test_tampered_endpoint_rejected(self): with pytest.raises(ValueError, match="resumable endpoint"): await client.fetch_next_page(bad) + @pytest.mark.asyncio + async def test_unknown_endpoint_rejected(self): + """A token naming a method that does not exist is rejected, not an AttributeError.""" + client = _make_client() + bad = json.dumps({"endpoint": "does_not_exist", "params": {}, "start_index": 0}) + with pytest.raises(ValueError, match="resumable endpoint"): + await client.fetch_next_page(bad) + @pytest.mark.asyncio async def test_resume_via_serializer(self): """fetch_next_page works through a PageTokenSerializer (opaque/encrypted token).""" From 5a41414930d7ef2648ed7daf44fefcfeed274d1d Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 13:01:32 +0100 Subject: [PATCH 09/12] refetched test data --- ...6jnb-wvl9vxkruzlm_get_data_0_94caf8fe.json | 8 +- .../company_charges_get_data_0_637baa55.json | 8 +- .../company_charges_get_data_0_d5301d41.json | 8 +- .../company_get_data_0_7ef9c0b7.json | 8 +- ...company_registers_get_data_0_b9901ae3.json | 8 +- ...company_registers_get_data_0_6bbb5d54.json | 8 +- ...sjtytjkacoku1yapy_get_data_0_6a268900.json | 8 +- ...y_appointed_on_start_index_0_11470a0f.json | 8 +- ...ed-office-address_get_data_0_7a9f9ca3.json | 6 +- ..._n3rszcl92rd4rwom_get_data_0_6e2213e3.json | 8 +- ...utsfta5vre6yzla4s_get_data_0_b0dd6b2e.json | 8 +- ...zotizogfkaxf6a2n4_get_data_0_c7622319.json | 8 +- ...ms_per_page_25_start_index_0_d3be38bb.json | 8 +- ...ms_per_page_25_start_index_0_d3be38bb.json | 8 +- ...ms_per_page_25_start_index_0_9349de23.json | 8 +- ...ms_per_page_25_start_index_0_1a803b34.json | 8 +- ...uk-establishments_get_data_0_d3707bbe.json | 10 +- ...uk-establishments_get_data_0_b00b4ed3.json | 10 +- ...ompany_exemptions_get_data_0_bc785c86.json | 8 +- ...ompany_exemptions_get_data_0_9b591d3e.json | 8 +- ...ompany_insolvency_get_data_0_dc8fc0ac.json | 8 +- ...ompany_insolvency_get_data_0_c723ff1c.json | 10 +- ...ms_per_page_25_start_index_0_b8ccbabc.json | 8 +- ..._bpo0rwnj0bzoc9cm_get_data_0_72e95c87.json | 8 +- ...4nqu_grhl-rueb6z0_get_data_0_20da23d3.json | 8 +- ...ifosktldkjt9t_kcg_get_data_0_bdaca913.json | 8 +- ...wi0_og6okoabbbyve_get_data_0_945686c9.json | 8 +- ...afxiamsuujylzo4su_get_data_0_80d9d133.json | 8 +- ...ter_view_false_start_index_0_a3877b80.json | 8 +- ...ter_view_false_start_index_0_1731111d.json | 8 +- ...ter_view_false_start_index_0_f05c66d6.json | 8 +- ...7fxyhb9cdnimutsss_get_data_0_fdf449d3.json | 8 +- ...ter_view_false_start_index_0_bcf9d086.json | 8 +- ...SE_ART_LIMITED_start_index_0_45444972.json | 10 +- ...stent_Company__start_index_0_d0679e99.json | 8 +- ...SE_ART_LIMITED_start_index_0_5285fd53.json | 10 +- ...rom_2000-01-01_start_index_0_d8a13ce5.json | 10 +- ..._to_2001-01-01_start_index_0_2a0d4914.json | 8 +- ...5E_ART_LIMITED_start_index_0_da3ea323.json | 1419 +++++++-------- ..._ART_LIMITED_start_index_100_0df271dc.json | 1545 ++++++++--------- ..._ART_LIMITED_start_index_200_36e11bb5.json | 1250 +++++++------ ...e_200_q_Orlovs_start_index_0_3b2666a9.json | 57 +- ...t_data_0_q_Barclays_size_100_7492c371.json | 8 +- ...5E_ART_LIMITED_start_index_0_02317c7a.json | 168 +- ..._ART_LIMITED_start_index_100_7c6d9a11.json | 372 ++-- ..._ART_LIMITED_start_index_200_fe007d92.json | 404 ++--- ...page_200_q_bob_start_index_0_8747c50e.json | 8 +- ...ch_type_alphabetical_size_10_7d6a56cd.json | 10 +- ...arch_type_best-match_size_10_ef34428a.json | 10 +- ...previous-name-dissol_size_10_3f156bc2.json | 18 +- ..._q_Ilja_Orlovs_start_index_0_f9033425.json | 107 +- 51 files changed, 2853 insertions(+), 2837 deletions(-) diff --git a/tests/resources/tests/test_charges.py/test_get_ipc_company_charge_details/charges_hvtzfirvvr6jnb-wvl9vxkruzlm_get_data_0_94caf8fe.json b/tests/resources/tests/test_charges.py/test_get_ipc_company_charge_details/charges_hvtzfirvvr6jnb-wvl9vxkruzlm_get_data_0_94caf8fe.json index 02d6daf..3a43458 100644 --- a/tests/resources/tests/test_charges.py/test_get_ipc_company_charge_details/charges_hvtzfirvvr6jnb-wvl9vxkruzlm_get_data_0_94caf8fe.json +++ b/tests/resources/tests/test_charges.py/test_get_ipc_company_charge_details/charges_hvtzfirvvr6jnb-wvl9vxkruzlm_get_data_0_94caf8fe.json @@ -43,16 +43,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=2FE16FDDF64D488726810339604E4B1B; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=0E33B7D2D2E5B0A4362C67B8DEC106FF; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "595", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "597", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_charges.py/test_get_ipc_company_charges/company_charges_get_data_0_637baa55.json b/tests/resources/tests/test_charges.py/test_get_ipc_company_charges/company_charges_get_data_0_637baa55.json index 82d255f..fc18fe0 100644 --- a/tests/resources/tests/test_charges.py/test_get_ipc_company_charges/company_charges_get_data_0_637baa55.json +++ b/tests/resources/tests/test_charges.py/test_get_ipc_company_charges/company_charges_get_data_0_637baa55.json @@ -535,16 +535,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:12 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=1584A6B12E7A7E60FC01F6CF38B883C5; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=E14010EE6140A3E90B7E1846EFAD0C27; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "596", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "598", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_charges.py/test_get_r5e_company_charges/company_charges_get_data_0_d5301d41.json b/tests/resources/tests/test_charges.py/test_get_r5e_company_charges/company_charges_get_data_0_d5301d41.json index 43de057..3b3f066 100644 --- a/tests/resources/tests/test_charges.py/test_get_r5e_company_charges/company_charges_get_data_0_d5301d41.json +++ b/tests/resources/tests/test_charges.py/test_get_r5e_company_charges/company_charges_get_data_0_d5301d41.json @@ -17,16 +17,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:12 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=D09998D2BB7DD86D12509F4BFEA92965; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=35F9DCE20FBDF305BE0699DFD210B83D; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "597", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "599", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_company_profile/company_get_data_0_7ef9c0b7.json b/tests/resources/tests/test_company_info.py/test_get_company_profile/company_get_data_0_7ef9c0b7.json index 828bbee..2ded325 100644 --- a/tests/resources/tests/test_company_info.py/test_get_company_profile/company_get_data_0_7ef9c0b7.json +++ b/tests/resources/tests/test_company_info.py/test_get_company_profile/company_get_data_0_7ef9c0b7.json @@ -73,16 +73,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=421AC955B511F0B452149A54ED1F8100; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=6C79C7C59AE3F536B25463F9BE5F51BD; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "594", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "596", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_company_registers/company_registers_get_data_0_b9901ae3.json b/tests/resources/tests/test_company_info.py/test_get_company_registers/company_registers_get_data_0_b9901ae3.json index 77c8080..baa98ba 100644 --- a/tests/resources/tests/test_company_info.py/test_get_company_registers/company_registers_get_data_0_b9901ae3.json +++ b/tests/resources/tests/test_company_info.py/test_get_company_registers/company_registers_get_data_0_b9901ae3.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=8F1B8EA3BA4E69E1C7493B1A52307258; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=0DDCED62C19920519A3901CDC23C3405; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "590", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "592", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_company_registers_barclays/company_registers_get_data_0_6bbb5d54.json b/tests/resources/tests/test_company_info.py/test_get_company_registers_barclays/company_registers_get_data_0_6bbb5d54.json index 9f07f9e..f55ec49 100644 --- a/tests/resources/tests/test_company_info.py/test_get_company_registers_barclays/company_registers_get_data_0_6bbb5d54.json +++ b/tests/resources/tests/test_company_info.py/test_get_company_registers_barclays/company_registers_get_data_0_6bbb5d54.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4964BA566790D6AED359475B3519813F; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=FA6683FB6990AAA6D391EB712676D8B1; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "589", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "591", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_officer_appointment/appointments_undjhrgedksjtytjkacoku1yapy_get_data_0_6a268900.json b/tests/resources/tests/test_company_info.py/test_get_officer_appointment/appointments_undjhrgedksjtytjkacoku1yapy_get_data_0_6a268900.json index 16d8036..abab8ba 100644 --- a/tests/resources/tests/test_company_info.py/test_get_officer_appointment/appointments_undjhrgedksjtytjkacoku1yapy_get_data_0_6a268900.json +++ b/tests/resources/tests/test_company_info.py/test_get_officer_appointment/appointments_undjhrgedksjtytjkacoku1yapy_get_data_0_6a268900.json @@ -39,16 +39,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=7BCCAA168FFC18DDBD022237CB70779F; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=32FFBD018EA348AC27B296D322DF316B; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "591", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "593", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_officer_list/company_officers_get_data_0_items_per_page_200_order_by_appointed_on_start_index_0_11470a0f.json b/tests/resources/tests/test_company_info.py/test_get_officer_list/company_officers_get_data_0_items_per_page_200_order_by_appointed_on_start_index_0_11470a0f.json index 5630ac3..76d29f0 100644 --- a/tests/resources/tests/test_company_info.py/test_get_officer_list/company_officers_get_data_0_items_per_page_200_order_by_appointed_on_start_index_0_11470a0f.json +++ b/tests/resources/tests/test_company_info.py/test_get_officer_list/company_officers_get_data_0_items_per_page_200_order_by_appointed_on_start_index_0_11470a0f.json @@ -54,16 +54,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=5A74AC8578CC87C73FC9533C30DBD03B; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=BC4E2EFF2D64E5CD37DD5F89363ECB9C; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "592", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "594", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_company_info.py/test_get_registered_office_address/company_registered-office-address_get_data_0_7a9f9ca3.json b/tests/resources/tests/test_company_info.py/test_get_registered_office_address/company_registered-office-address_get_data_0_7a9f9ca3.json index 228e81f..066bb7c 100644 --- a/tests/resources/tests/test_company_info.py/test_get_registered_office_address/company_registered-office-address_get_data_0_7a9f9ca3.json +++ b/tests/resources/tests/test_company_info.py/test_get_registered_office_address/company_registered-office-address_get_data_0_7a9f9ca3.json @@ -20,10 +20,10 @@ "connection": "keep-alive", "content-length": "254", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:13 GMT", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "593", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "595", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m" }, "request": { diff --git a/tests/resources/tests/test_disqualification.py/test_corporate_disq/corporate_dal_5s20vd_n3rszcl92rd4rwom_get_data_0_6e2213e3.json b/tests/resources/tests/test_disqualification.py/test_corporate_disq/corporate_dal_5s20vd_n3rszcl92rd4rwom_get_data_0_6e2213e3.json index 4eeae12..2c4fab7 100644 --- a/tests/resources/tests/test_disqualification.py/test_corporate_disq/corporate_dal_5s20vd_n3rszcl92rd4rwom_get_data_0_6e2213e3.json +++ b/tests/resources/tests/test_disqualification.py/test_corporate_disq/corporate_dal_5s20vd_n3rszcl92rd4rwom_get_data_0_6e2213e3.json @@ -35,16 +35,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=65DA6C8A9294981E8D3A493EB7E99C8B; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=94823702C3413B02AEB1648DE7E81AFB; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "587", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "589", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_disqualification.py/test_natural_disq/natural_z5bd9sanuiutsfta5vre6yzla4s_get_data_0_b0dd6b2e.json b/tests/resources/tests/test_disqualification.py/test_natural_disq/natural_z5bd9sanuiutsfta5vre6yzla4s_get_data_0_b0dd6b2e.json index b1f5805..18be8ae 100644 --- a/tests/resources/tests/test_disqualification.py/test_natural_disq/natural_z5bd9sanuiutsfta5vre6yzla4s_get_data_0_b0dd6b2e.json +++ b/tests/resources/tests/test_disqualification.py/test_natural_disq/natural_z5bd9sanuiutsfta5vre6yzla4s_get_data_0_b0dd6b2e.json @@ -48,16 +48,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=ADA73D19DD50AAC544D02E0EC548BC11; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=C5A3396E8A8604202F083B1101A293FD; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "588", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "590", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_filing_history.py/test_get_filing_history_item/filing-history_mzq4ntmzotizogfkaxf6a2n4_get_data_0_c7622319.json b/tests/resources/tests/test_filing_history.py/test_get_filing_history_item/filing-history_mzq4ntmzotizogfkaxf6a2n4_get_data_0_c7622319.json index 70aa977..049a670 100644 --- a/tests/resources/tests/test_filing_history.py/test_get_filing_history_item/filing-history_mzq4ntmzotizogfkaxf6a2n4_get_data_0_c7622319.json +++ b/tests/resources/tests/test_filing_history.py/test_get_filing_history_item/filing-history_mzq4ntmzotizogfkaxf6a2n4_get_data_0_c7622319.json @@ -26,16 +26,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=A6BE3311DC6ADE3F3D406A627AE426D4; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=FEFE7071BF81AB551043499B443DF241; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "582", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "584", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json index a299b10..bb9b3be 100644 --- a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json +++ b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json @@ -186,16 +186,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:13 GMT", + "date": "Thu, 18 Jun 2026 12:00:14 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=9D4BD8DE2C09C4B3C5448B7769AF587E; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=505B19B66C99804BDE3BF3C1EAD314FF; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "586", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "588", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[None-8]/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[None-8]/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json index 3503f73..7cd33ab 100644 --- a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[None-8]/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json +++ b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[None-8]/company_filing-history_get_data_0_items_per_page_25_start_index_0_d3be38bb.json @@ -186,16 +186,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:35 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=6743C5351DAD29DA042773772886DB27; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=BF4D56EDB149182370D975F62E1977CD; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "585", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "548", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter1-1]/company_filing-history_get_data_0_category_change-of-name_items_per_page_25_start_index_0_9349de23.json b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter1-1]/company_filing-history_get_data_0_category_change-of-name_items_per_page_25_start_index_0_9349de23.json index b9c61ea..6e628a1 100644 --- a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter1-1]/company_filing-history_get_data_0_category_change-of-name_items_per_page_25_start_index_0_9349de23.json +++ b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter1-1]/company_filing-history_get_data_0_category_change-of-name_items_per_page_25_start_index_0_9349de23.json @@ -53,16 +53,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:19 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=FBFA7533971CD9FBF530C7960BC9A4BC; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=BE8D8908807A9A37646B7B01BDFF5D3B; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "584", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "586", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter2-0]/company_filing-history_get_data_0_category_miscellaneous_items_per_page_25_start_index_0_1a803b34.json b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter2-0]/company_filing-history_get_data_0_category_miscellaneous_items_per_page_25_start_index_0_1a803b34.json index ed7e1b9..724416f 100644 --- a/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter2-0]/company_filing-history_get_data_0_category_miscellaneous_items_per_page_25_start_index_0_1a803b34.json +++ b/tests/resources/tests/test_filing_history.py/test_get_r5e_company_filing_history_w_filter[cetegory_filter2-0]/company_filing-history_get_data_0_category_miscellaneous_items_per_page_25_start_index_0_1a803b34.json @@ -16,16 +16,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:19 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=9BFDB774E7FC489BB9306EBC4E7DD4A1; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=BFB8ACEB4DF78B9BDA8EF12C2BABC6C2; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "583", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "585", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_company_uk_establishments.py/test_get_r5e_establishments/company_uk-establishments_get_data_0_d3707bbe.json b/tests/resources/tests/test_get_company_uk_establishments.py/test_get_r5e_establishments/company_uk-establishments_get_data_0_d3707bbe.json index 3dc1a74..be032bf 100644 --- a/tests/resources/tests/test_get_company_uk_establishments.py/test_get_r5e_establishments/company_uk-establishments_get_data_0_d3707bbe.json +++ b/tests/resources/tests/test_get_company_uk_establishments.py/test_get_r5e_establishments/company_uk-establishments_get_data_0_d3707bbe.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "764536682dd47f57e48d3238d8c6f6c6b9a14919", + "etag": "b8e02fb57d48ac34e02a57a467695e1ab4c82f45", "items": [], "kind": "related-companies", "links": { @@ -17,16 +17,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4836B389323BA837BAD06C166346C622; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=3299F8F2495D62F2AD6E02176C5C733F; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "581", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "583", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_company_uk_establishments.py/test_get_someones_establishments/fc030084_uk-establishments_get_data_0_b00b4ed3.json b/tests/resources/tests/test_get_company_uk_establishments.py/test_get_someones_establishments/fc030084_uk-establishments_get_data_0_b00b4ed3.json index 81d4b8f..2dd8b24 100644 --- a/tests/resources/tests/test_get_company_uk_establishments.py/test_get_someones_establishments/fc030084_uk-establishments_get_data_0_b00b4ed3.json +++ b/tests/resources/tests/test_get_company_uk_establishments.py/test_get_someones_establishments/fc030084_uk-establishments_get_data_0_b00b4ed3.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "5c0dc116051bf6e1a99d3c57c12a397c23f398a2", + "etag": "f2acc7eac829530b55bef857d2394827c5db608d", "items": [ { "company_name": "EBI", @@ -27,16 +27,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=D5326ACA1BB80F4FD9863833F15E9319; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=BA7CABDFF4A1ED1C89B79E9366C450DE; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "580", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "582", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_exemptions.py/test_get_barclays_exemptions/company_exemptions_get_data_0_bc785c86.json b/tests/resources/tests/test_get_exemptions.py/test_get_barclays_exemptions/company_exemptions_get_data_0_bc785c86.json index 25cbe65..fd2eb0a 100644 --- a/tests/resources/tests/test_get_exemptions.py/test_get_barclays_exemptions/company_exemptions_get_data_0_bc785c86.json +++ b/tests/resources/tests/test_get_exemptions.py/test_get_barclays_exemptions/company_exemptions_get_data_0_bc785c86.json @@ -39,16 +39,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=B48D0E7031A15D033A5D32E34CCE9463; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=8308CA42EA0D1C59D144B184695C6909; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "578", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "580", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_exemptions.py/test_get_r5e_company_exemptions/company_exemptions_get_data_0_9b591d3e.json b/tests/resources/tests/test_get_exemptions.py/test_get_r5e_company_exemptions/company_exemptions_get_data_0_9b591d3e.json index 139cb59..e125b77 100644 --- a/tests/resources/tests/test_get_exemptions.py/test_get_r5e_company_exemptions/company_exemptions_get_data_0_9b591d3e.json +++ b/tests/resources/tests/test_get_exemptions.py/test_get_r5e_company_exemptions/company_exemptions_get_data_0_9b591d3e.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=BDC924630BAD45745495208D43C44315; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=7738F3631CD7D3360970485058D3E841; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "579", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "581", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_insolvency.py/test_get_john_insolvency/company_insolvency_get_data_0_dc8fc0ac.json b/tests/resources/tests/test_get_insolvency.py/test_get_john_insolvency/company_insolvency_get_data_0_dc8fc0ac.json index de28e4e..f6d0696 100644 --- a/tests/resources/tests/test_get_insolvency.py/test_get_john_insolvency/company_insolvency_get_data_0_dc8fc0ac.json +++ b/tests/resources/tests/test_get_insolvency.py/test_get_john_insolvency/company_insolvency_get_data_0_dc8fc0ac.json @@ -55,16 +55,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=DCE7DBE64D9B1389E6B8D145B46A47B4; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=68E4D2455123578A37E7DA7F5A3EB3AE; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "576", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "578", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_get_insolvency.py/test_get_r5e_company_insolvency/company_insolvency_get_data_0_c723ff1c.json b/tests/resources/tests/test_get_insolvency.py/test_get_r5e_company_insolvency/company_insolvency_get_data_0_c723ff1c.json index 7212dcd..6f3ef41 100644 --- a/tests/resources/tests/test_get_insolvency.py/test_get_r5e_company_insolvency/company_insolvency_get_data_0_c723ff1c.json +++ b/tests/resources/tests/test_get_insolvency.py/test_get_r5e_company_insolvency/company_insolvency_get_data_0_c723ff1c.json @@ -4,7 +4,7 @@ "error": "Not Found", "path": "/company/14200734/insolvency", "status": 404, - "timestamp": "2026-04-21" + "timestamp": "2026-06-18" } }, "headers": { @@ -15,16 +15,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:14 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=874FBB1EF0FFF03EBDD1F46724D69036; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=41F9DA73B57BE5CE0288DF0A09E60E7D; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "577", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "579", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json b/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json index a3b3272..a6167ce 100644 --- a/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json +++ b/tests/resources/tests/test_officer_appointments.py/test_get_appointments/_y4370dcoajgiqvalmhtj7hdiqu_appointments_get_data_0_items_per_page_25_start_index_0_b8ccbabc.json @@ -59,16 +59,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=DCEC0199A4DB2BA151AE7BEE6C11A725; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=7A725AEDBF4C3D0839741A63FAFED70B; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "575", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "577", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_corporate_ben_owner/corporate-entity-beneficial-owner_vstvxoctxg_bpo0rwnj0bzoc9cm_get_data_0_72e95c87.json b/tests/resources/tests/test_psc.py/test_get_corporate_ben_owner/corporate-entity-beneficial-owner_vstvxoctxg_bpo0rwnj0bzoc9cm_get_data_0_72e95c87.json index 57a3f0c..f6f1fe8 100644 --- a/tests/resources/tests/test_psc.py/test_get_corporate_ben_owner/corporate-entity-beneficial-owner_vstvxoctxg_bpo0rwnj0bzoc9cm_get_data_0_72e95c87.json +++ b/tests/resources/tests/test_psc.py/test_get_corporate_ben_owner/corporate-entity-beneficial-owner_vstvxoctxg_bpo0rwnj0bzoc9cm_get_data_0_72e95c87.json @@ -44,16 +44,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=C28F7811A5EB163C8C54782303647FB8; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=934547C96598C95F588D4B4416A0ADED; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "568", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "570", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_corporate_psc/corporate-entity_rthhny4-wo4nqu_grhl-rueb6z0_get_data_0_20da23d3.json b/tests/resources/tests/test_psc.py/test_get_corporate_psc/corporate-entity_rthhny4-wo4nqu_grhl-rueb6z0_get_data_0_20da23d3.json index 646816e..120ecf5 100644 --- a/tests/resources/tests/test_psc.py/test_get_corporate_psc/corporate-entity_rthhny4-wo4nqu_grhl-rueb6z0_get_data_0_20da23d3.json +++ b/tests/resources/tests/test_psc.py/test_get_corporate_psc/corporate-entity_rthhny4-wo4nqu_grhl-rueb6z0_get_data_0_20da23d3.json @@ -36,16 +36,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=202A2D25F6F4C21E8303EDB939DE485B; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=04CC60F440C5FE4E568750A36C7C0782; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "570", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "572", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_individual_ben_owner/individual-beneficial-owner_szahw70tvwifosktldkjt9t_kcg_get_data_0_bdaca913.json b/tests/resources/tests/test_psc.py/test_get_individual_ben_owner/individual-beneficial-owner_szahw70tvwifosktldkjt9t_kcg_get_data_0_bdaca913.json index 705d0dd..0cf617d 100644 --- a/tests/resources/tests/test_psc.py/test_get_individual_ben_owner/individual-beneficial-owner_szahw70tvwifosktldkjt9t_kcg_get_data_0_bdaca913.json +++ b/tests/resources/tests/test_psc.py/test_get_individual_ben_owner/individual-beneficial-owner_szahw70tvwifosktldkjt9t_kcg_get_data_0_bdaca913.json @@ -40,16 +40,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4DB345B0EB9DFAC8F50E87E5CDE96301; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=D918E2A81E9780C688148DF2BC3F5806; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "569", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "571", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_individual_psc/individual_okpgmloal1wi0_og6okoabbbyve_get_data_0_945686c9.json b/tests/resources/tests/test_psc.py/test_get_individual_psc/individual_okpgmloal1wi0_og6okoabbbyve_get_data_0_945686c9.json index 9f3b476..3a51c6c 100644 --- a/tests/resources/tests/test_psc.py/test_get_individual_psc/individual_okpgmloal1wi0_og6okoabbbyve_get_data_0_945686c9.json +++ b/tests/resources/tests/test_psc.py/test_get_individual_psc/individual_okpgmloal1wi0_og6okoabbbyve_get_data_0_945686c9.json @@ -45,16 +45,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=D770C2605EFDABBDB5F50335A47090E8; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=EDF8E56AFF045FE9AAB0B99DA785C6B7; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "566", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "568", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_legal_ben_owner/legal-person-beneficial-owner_tc4-moq5xgafxiamsuujylzo4su_get_data_0_80d9d133.json b/tests/resources/tests/test_psc.py/test_get_legal_ben_owner/legal-person-beneficial-owner_tc4-moq5xgafxiamsuujylzo4su_get_data_0_80d9d133.json index 1854afb..591b95b 100644 --- a/tests/resources/tests/test_psc.py/test_get_legal_ben_owner/legal-person-beneficial-owner_tc4-moq5xgafxiamsuujylzo4su_get_data_0_80d9d133.json +++ b/tests/resources/tests/test_psc.py/test_get_legal_ben_owner/legal-person-beneficial-owner_tc4-moq5xgafxiamsuujylzo4su_get_data_0_80d9d133.json @@ -41,16 +41,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=FFD6FAACAB680163A633F53C6708A508; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=611201264D60ED453790B5F345F2A955; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "567", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "569", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json b/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json index c8a6142..9da32ab 100644 --- a/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json +++ b/tests/resources/tests/test_psc.py/test_get_lloyds_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_a3877b80.json @@ -49,16 +49,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=128AC0DDFF784D63C441D6EBDF118956; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=D708291A5ED91EF9A12B172EFFA03110; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "573", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "575", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json b/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json index c7d0780..fef633e 100644 --- a/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json +++ b/tests/resources/tests/test_psc.py/test_get_psc_list/company_persons-with-significant-control_get_data_0_items_per_page_25_register_view_false_start_index_0_1731111d.json @@ -58,16 +58,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:24 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=09CC15154482A512708017DAFF9C5F4D; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=13389A01D4F457F847C70ACC6A4D102A; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "574", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "576", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json b/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json index 691bc1f..b4a5823 100644 --- a/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json +++ b/tests/resources/tests/test_psc.py/test_get_r5e_statements/company_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_f05c66d6.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=2CBF8A5B97BA6C79813E380D1AEDDBF6; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=0C34490A1556491C7EC821E2714722EE; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "572", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "574", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_get_uk_legal_person_psc/legal-person_ilkojwf-p67fxyhb9cdnimutsss_get_data_0_fdf449d3.json b/tests/resources/tests/test_psc.py/test_get_uk_legal_person_psc/legal-person_ilkojwf-p67fxyhb9cdnimutsss_get_data_0_fdf449d3.json index 2c0b03a..110f434 100644 --- a/tests/resources/tests/test_psc.py/test_get_uk_legal_person_psc/legal-person_ilkojwf-p67fxyhb9cdnimutsss_get_data_0_fdf449d3.json +++ b/tests/resources/tests/test_psc.py/test_get_uk_legal_person_psc/legal-person_ilkojwf-p67fxyhb9cdnimutsss_get_data_0_fdf449d3.json @@ -33,16 +33,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:16 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=E9C82935C640A2B3DF1354376A926D96; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=1007EDF043A3A744D4D7BF02EDFCF133; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "565", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "567", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json b/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json index f80d85f..32f8ac5 100644 --- a/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json +++ b/tests/resources/tests/test_psc.py/test_someones_psc_statements/sc549056_persons-with-significant-control-statements_get_data_0_items_per_page_25_register_view_false_start_index_0_bcf9d086.json @@ -30,16 +30,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:15 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=5CABFC73157F4339B202A88A47E55719; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=073ABE57BC3176C30FFC8E790CA268D9; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "571", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "573", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query0-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_start_index_0_45444972.json b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query0-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_start_index_0_45444972.json index 309711c..308cea7 100644 --- a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query0-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_start_index_0_45444972.json +++ b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query0-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_start_index_0_45444972.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "52b5ef457c71674251db3c6d7316c21dbc45ee39", + "etag": "ee3924834119226ec4de0f901ce32817a0b074ff", "hits": 1, "items": [ { @@ -56,16 +56,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:42 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=7530B682B5E72E55B97E87D1A9C3FC0E; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=AA043F16086EB25688D6DB7259D0EEB4; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "560", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "562", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query1-0]/advanced-search_companies_get_data_0_company_name_includes_Nonexistent_Company__start_index_0_d0679e99.json b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query1-0]/advanced-search_companies_get_data_0_company_name_includes_Nonexistent_Company__start_index_0_d0679e99.json index 92c0180..b437148 100644 --- a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query1-0]/advanced-search_companies_get_data_0_company_name_includes_Nonexistent_Company__start_index_0_d0679e99.json +++ b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query1-0]/advanced-search_companies_get_data_0_company_name_includes_Nonexistent_Company__start_index_0_d0679e99.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:07:42 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=DBB4AE22B716D187FCB2A1A05CE98958; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=2C1D1AA37CCACD0594F070B8967DA79D; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "559", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "561", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query2-1]/advanced-search_companies_get_data_0_company_name_excludes_potato_company_name_includes_RELEASE_ART_LIMITED_start_index_0_5285fd53.json b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query2-1]/advanced-search_companies_get_data_0_company_name_excludes_potato_company_name_includes_RELEASE_ART_LIMITED_start_index_0_5285fd53.json index c182a7e..b2682a2 100644 --- a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query2-1]/advanced-search_companies_get_data_0_company_name_excludes_potato_company_name_includes_RELEASE_ART_LIMITED_start_index_0_5285fd53.json +++ b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query2-1]/advanced-search_companies_get_data_0_company_name_excludes_potato_company_name_includes_RELEASE_ART_LIMITED_start_index_0_5285fd53.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "1a2ab881c1aac903e99d62013c3ac2cc59a7d304", + "etag": "266180963655c401aa613c4f99061be9f9111a6e", "hits": 1, "items": [ { @@ -56,16 +56,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:42 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=2F8730E39BC8A32B7135629E586850CA; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=35811B815513930B1BDB678C3AFE3AEA; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "558", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "560", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query3-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_start_index_0_d8a13ce5.json b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query3-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_start_index_0_d8a13ce5.json index 7428604..bad544e 100644 --- a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query3-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_start_index_0_d8a13ce5.json +++ b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query3-1]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_start_index_0_d8a13ce5.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "77c123e613eafcf889297acf5b6e981289ba176c", + "etag": "51c7f1d2336e28e794fb53f1aa91a215be6d7550", "hits": 1, "items": [ { @@ -56,16 +56,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:42 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=02BA112398807F03BB5DC7E7A294668F; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=222DADF017F12F4405CCA2C3B2AB97DC; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "557", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "559", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query4-0]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_incorporated_to_2001-01-01_start_index_0_2a0d4914.json b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query4-0]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_incorporated_to_2001-01-01_start_index_0_2a0d4914.json index f34f2e3..6cd43d5 100644 --- a/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query4-0]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_incorporated_to_2001-01-01_start_index_0_2a0d4914.json +++ b/tests/resources/tests/test_search_api.py/TestAdvancedSearch/test_simple[query4-0]/advanced-search_companies_get_data_0_company_name_includes_RELEASE_ART_LIMITED_incorporated_from_2000-01-01_incorporated_to_2001-01-01_start_index_0_2a0d4914.json @@ -10,15 +10,15 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-length": "0", - "date": "Tue, 21 Apr 2026 11:07:42 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=426734E3BE6BD1D2D0364F44FBE4382A; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=874E8610DE9F6092FC14647E2630E177; Path=/; HttpOnly", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "556", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "558", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_da3ea323.json b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_da3ea323.json index 74de780..359f58e 100644 --- a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_da3ea323.json +++ b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_da3ea323.json @@ -60,37 +60,37 @@ }, { "address": { - "address_line_1": "New Brunswick Street", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "address_line_1": "25 Walbrook", + "country": "England", + "locality": "London", + "postal_code": "EC4N 8AW", + "premises": "The Walbrook Building" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, WF1 5QW", - "appointment_count": 1, - "description": "Total number of appointments 1", + "address_snippet": "The Walbrook Building, 25 Walbrook, London, England, EC4N 8AW", + "appointment_count": 11, + "description": "Total number of appointments 11", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/-YrDPEcuSJJ2Voz7xEGDcno2yAo/appointments" + "self": "/officers/aPl6vnFflkylqSmvLpcplGn_3eU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "ARTHUR J. GALLAGHER (UK) LIMITED" }, { "address": { - "address_line_1": "Marina House", - "address_line_2": "Eden Island", - "country": "Seychelles", - "locality": "Mahe", - "premises": "Tenancy 10" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Tenancy 10, Marina House, Eden Island, Mahe, Seychelles", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -98,23 +98,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/yXqXGmRNrP6EML93zBAmDHmJh6g/appointments" + "self": "/officers/VV0OALKLc_aW3uVSPj1v4gOb9bo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNEAVEU S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Lueneburger Str. 17", + "country": "Germany", + "locality": "Hamburg", + "region": "21073" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Lueneburger Str. 17, Hamburg, 21073, Germany", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -122,23 +121,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/yZSvRSLbS0SuPpc9IqFAFnGWRpE/appointments" + "self": "/officers/W6lL39b7LQLqqrpSilwR5ghunH8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ART- HAUS GMBH" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Crabtree Manorway North", + "country": "United Kingdom", + "locality": "Belvedere", + "postal_code": "DA17 6AX", + "premises": "C5 Oyo Business Units" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "C5 Oyo Business Units, Crabtree Manorway North, Belvedere, United Kingdom, DA17 6AX", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -146,24 +145,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zXCUS48m3bq6UMmHKM-ZE0zT4Hk/appointments" + "self": "/officers/X1rsLd_WQR2lx83KrNbqtspMi1A/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTISAN PROPERTY LTD" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "57 St. James's Street", + "locality": "London", + "postal_code": "SW1A 1LD", + "premises": "Cassini House" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Cassini House, 57 St. James's Street, London, SW1A 1LD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -171,23 +168,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zqUWXs8UBqb7tsDOoiws7WW6lHs/appointments" + "self": "/officers/XNeD2XyCFq_AYD8QZ22CXp9ZWA4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTEMIS FUND MANAGERS LIMITED" }, { "address": { - "address_line_1": "B Rue Rabelais", - "country": "France", - "locality": "Montreuil", - "postal_code": "93100", - "premises": "11" + "address_line_1": "Silver Street", + "address_line_2": "Witcham", + "country": "England", + "locality": "Ely", + "postal_code": "CB6 2LF", + "premises": "The Chapel", + "region": "Cambridgeshire" }, - "address_snippet": "11 B Rue Rabelais, Montreuil, France, 93100", + "address_snippet": "The Chapel, Silver Street, Witcham, Ely, Cambridgeshire, England, CB6 2LF", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -195,23 +194,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ACxq70q0to_i4haJqUNKK7FaIwg/appointments" + "self": "/officers/XS_K36SAF0KP0p-FPZ-JHnzk9sg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTANA SARL" + "title": "CRAFT & ARTISAN GROUP LIMITED" }, { "address": { - "address_line_1": "Urbanizacion Obarrio", - "address_line_2": "Torre Swiss Bank, Piso 16", - "country": "Panama", - "locality": "Panama", - "premises": "Calle 53" + "address_line_1": "Sham Peng Tong Plaza", + "country": "Seychelles", + "locality": "Victoria", + "premises": "103", + "region": "Mahe" }, - "address_snippet": "Calle 53, Urbanizacion Obarrio, Torre Swiss Bank, Piso 16, Panama, Panama", + "address_snippet": "103 Sham Peng Tong Plaza, Victoria, Mahe, Seychelles", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -219,22 +218,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ARgM63-6a0esFXQpFmcLTdnGzPc/appointments" + "self": "/officers/ZIv8T8DaEMyCwngJAmT0c_6yExc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "BABE ART INC" + "title": "ART CULT GROUP LTD." }, { "address": { - "address_line_1": "Plaza 2000, 10th Floor", - "address_line_2": "50th Street", - "country": "Republic Of Panama", - "locality": "Panama" + "address_line_1": "Ottergemsesteenweg-Zuid", + "country": "Belgium", + "locality": "Gent", + "postal_code": "9000", + "premises": "816" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "address_snippet": "816 Ottergemsesteenweg-Zuid, Gent, Belgium, 9000", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -242,46 +242,47 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/BQwLTxOypHlBId79_qllNtnSV-I/appointments" + "self": "/officers/ZhzO15dWnRvdXCDkQ3a6jjVDuo0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTVAL NV" }, { "address": { - "address_line_1": "Webbs Road", + "address_line_1": "1 Station Hill", "country": "United Kingdom", - "locality": "London", - "postal_code": "SW11 6SE", - "premises": "60" + "locality": "Reading", + "postal_code": "RG1 1NB", + "premises": "4th Floor Phoenix House", + "region": "Berkshire" }, - "address_snippet": "60 Webbs Road, London, United Kingdom, SW11 6SE", - "appointment_count": 2, - "description": "Total number of appointments 2", + "address_snippet": "4th Floor Phoenix House, 1 Station Hill, Reading, Berkshire, United Kingdom, RG1 1NB", + "appointment_count": 1, + "description": "Total number of appointments 1", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/J32OofiZ1ieDNZFCFOf2oG24v10/appointments" + "self": "/officers/_WLSgCWV-djDQTjySozeaz2HxcA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTESIAN DEVELOPMENTS IV LIMITED" + "title": "ARTISAN PARTNERS LIMITED" }, { "address": { - "address_line_1": "Floor No 4, Suite No 401", - "country": "Panama", - "locality": "Panama City", - "premises": "Ricardo J. Alfaro Avenue, The Century Tower" + "address_line_1": "Dept 1, Unit 9d1", + "address_line_2": "Carcroft Enterprise Park, Station Road, Carcroft", + "locality": "Doncaster", + "postal_code": "DN6 8DD" }, - "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", + "address_snippet": "Dept 1, Unit 9d1, Carcroft Enterprise Park, Station Road, Carcroft, Doncaster, DN6 8DD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -289,23 +290,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Jzqnb1ZEuBMNKcQzfqJ604nteUY/appointments" + "self": "/officers/a0nXiNosk8-l1PTioCapoG1EyHI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA GROUP S.A." + "title": "ARTFACT PLC" }, { "address": { - "address_line_1": "Hornchurch Road", - "locality": "Hornchurch", - "postal_code": "RM11 1JS", - "premises": "98", - "region": "Essex" + "address_line_1": "10th Floor, 50th Street", + "country": "Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "98 Hornchurch Road, Hornchurch, Essex, RM11 1JS", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -313,23 +313,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/KD9vSnClCK4Z3JAELXNMiinzfNk/appointments" + "self": "/officers/aPmBn050P9naie5DkKXmkPo1KUw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CREATIVE ARTFUL BUSINESS LIMITED" + "title": "ARTONA HOLDING S.A." }, { "address": { - "country": "British Virgin Islands", - "locality": "Road Town", - "postal_code": "PO BOX 146", - "premises": "Trident Chambers", - "region": "Tortola" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "Trident Chambers, Road Town, Tortola, British Virgin Islands, PO BOX 146", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -337,24 +337,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/LqRPnLYwxPE-xnwaKC0JkMDAAb4/appointments" + "self": "/officers/bCvDQlwWXQFw2841DuyAxy7Z7O0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "PADDINGTON ART CONSULTING LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Newhey Road", - "address_line_2": "Milnrow", - "country": "England", - "locality": "Rochdale", - "postal_code": "OL16 3PS", - "premises": "The Old County Police Station" + "address_line_1": "Ne 72nd St", + "country": "United States", + "locality": "Seattle", + "postal_code": "98115", + "premises": "2616" }, - "address_snippet": "The Old County Police Station, Newhey Road, Milnrow, Rochdale, England, OL16 3PS", + "address_snippet": "2616 Ne 72nd St, Seattle, United States, 98115", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -362,13 +361,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/M3NkbXtdwM96ZkcXkDy8o2NFTLM/appointments" + "self": "/officers/FOSoXARcAdfGzJYLjXMiCWjvfps/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "INTERNATIONAL ARTISTS (2013) LIMITED" + "title": "ARTISAN BRANDS INC" }, { "address": { @@ -386,7 +385,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/NlebtX1xVOMpEOEOLS44hwY4WoQ/appointments" + "self": "/officers/GGsFN-Oc27z3fH4_X9eI9XUz0-w/appointments" }, "matches": { "snippet": [] @@ -396,15 +395,13 @@ }, { "address": { - "address_line_1": "Pontefract Road", - "address_line_2": "Cudworth", - "care_of": "Stephen Sutton", - "country": "England", - "locality": "Barnsley", - "postal_code": "S72 8BE", - "premises": "176-178" + "address_line_1": "Webbs Road", + "country": "United Kingdom", + "locality": "London", + "postal_code": "SW11 6SE", + "premises": "60" }, - "address_snippet": "Stephen Sutton, 176-178, Pontefract Road, Cudworth, Barnsley, England, S72 8BE", + "address_snippet": "60 Webbs Road, London, United Kingdom, SW11 6SE", "appointment_count": 2, "description": "Total number of appointments 2", "description_identifiers": [ @@ -412,24 +409,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/OM3JSGgGHFledDAK9s81-84lN9o/appointments" + "self": "/officers/J32OofiZ1ieDNZFCFOf2oG24v10/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "DIVINE ART LIMITED" + "title": "ARTESIAN DEVELOPMENTS IV LIMITED" }, { "address": { - "address_line_1": "Park Lane", - "country": "United Kingdom", - "locality": "Hornchurch", - "postal_code": "RM11 1BH", - "premises": "Park Lane", - "region": "Essex" + "address_line_1": "Floor No 4, Suite No 401", + "country": "Panama", + "locality": "Panama City", + "premises": "Ricardo J. Alfaro Avenue, The Century Tower" }, - "address_snippet": "Park Lane, Park Lane, Hornchurch, Essex, United Kingdom, RM11 1BH", + "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -437,22 +432,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/OtkLyiygSEIz7bw3JqXVJJF4540/appointments" + "self": "/officers/Jzqnb1ZEuBMNKcQzfqJ604nteUY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "HENLY ARTHUR LIMITED" + "title": "ARTONA GROUP S.A." }, { "address": { - "address_line_1": "The Century Tower, Floor No 4, Suite No 401", - "country": "Panama", - "locality": "Panama City", - "premises": "Ricardo J. Alfaro Avenue" + "address_line_1": "Hornchurch Road", + "locality": "Hornchurch", + "postal_code": "RM11 1JS", + "premises": "98", + "region": "Essex" }, - "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", + "address_snippet": "98 Hornchurch Road, Hornchurch, Essex, RM11 1JS", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -460,24 +456,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/PBO3piouLKyhvcmVrFjyYnllgIg/appointments" + "self": "/officers/KD9vSnClCK4Z3JAELXNMiinzfNk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA GROUP S.A." + "title": "CREATIVE ARTFUL BUSINESS LIMITED" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "country": "British Virgin Islands", + "locality": "Road Town", + "postal_code": "PO BOX 146", + "premises": "Trident Chambers", + "region": "Tortola" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Trident Chambers, Road Town, Tortola, British Virgin Islands, PO BOX 146", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -485,13 +480,38 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/PZ8Kgs-51D1TG_JJEIH-gbrJyc8/appointments" + "self": "/officers/LqRPnLYwxPE-xnwaKC0JkMDAAb4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "PADDINGTON ART CONSULTING LIMITED" + }, + { + "address": { + "address_line_1": "Newhey Road", + "address_line_2": "Milnrow", + "country": "England", + "locality": "Rochdale", + "postal_code": "OL16 3PS", + "premises": "The Old County Police Station" + }, + "address_snippet": "The Old County Police Station, Newhey Road, Milnrow, Rochdale, England, OL16 3PS", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/M3NkbXtdwM96ZkcXkDy8o2NFTLM/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "INTERNATIONAL ARTISTS (2013) LIMITED" }, { "address": { @@ -662,30 +682,6 @@ "snippet": "", "title": "FILM ARTWORK ONLINE LIMITED" }, - { - "address": { - "address_line_1": "Latimer Road", - "country": "England", - "locality": "London", - "postal_code": "W10 6RQ", - "premises": "Unit 11 Latimer Studios" - }, - "address_snippet": "Unit 11 Latimer Studios, Latimer Road, London, England, W10 6RQ", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/f95BiZqMaS8VWFWVjiQNPME4Sq4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "LOCKET ARTISTS LIMITED" - }, { "address": { "address_line_1": "153", @@ -862,12 +858,13 @@ }, { "address": { - "address_line_1": "Lueneburger Str. 17", - "country": "Germany", - "locality": "Hamburg", - "region": "21073" + "address_line_1": "New Brunswick Street", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "Lueneburger Str. 17, Hamburg, 21073, Germany", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -875,23 +872,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/W6lL39b7LQLqqrpSilwR5ghunH8/appointments" + "self": "/officers/-YrDPEcuSJJ2Voz7xEGDcno2yAo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ART- HAUS GMBH" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Crabtree Manorway North", - "country": "United Kingdom", - "locality": "Belvedere", - "postal_code": "DA17 6AX", - "premises": "C5 Oyo Business Units" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "C5 Oyo Business Units, Crabtree Manorway North, Belvedere, United Kingdom, DA17 6AX", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -899,22 +896,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/X1rsLd_WQR2lx83KrNbqtspMi1A/appointments" + "self": "/officers/0lApjaMj101pOhxUZW4PbfcdiKI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISAN PROPERTY LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "57 St. James's Street", - "locality": "London", - "postal_code": "SW1A 1LD", - "premises": "Cassini House" + "country": "Switzerland", + "locality": "Del Mont", + "postal_code": "2800", + "premises": "30 Rue De La Malt Re", + "region": "Jura" }, - "address_snippet": "Cassini House, 57 St. James's Street, London, SW1A 1LD", + "address_snippet": "30 Rue De La Malt Re, Del Mont, Jura, Switzerland, 2800", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -922,25 +920,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/XNeD2XyCFq_AYD8QZ22CXp9ZWA4/appointments" + "self": "/officers/1iEM7Ct-_uCYZ1QmTFTfP1bA0Wo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS FUND MANAGERS LIMITED" + "title": "MONBOUAN ART & CARS SA" }, { "address": { - "address_line_1": "Silver Street", - "address_line_2": "Witcham", - "country": "England", - "locality": "Ely", - "postal_code": "CB6 2LF", - "premises": "The Chapel", - "region": "Cambridgeshire" + "address_line_1": "Northwood Avenue", + "address_line_2": "Saltdean", + "country": "United Kingdom", + "locality": "Brighton", + "postal_code": "BN2 8RG", + "premises": "75" }, - "address_snippet": "The Chapel, Silver Street, Witcham, Ely, Cambridgeshire, England, CB6 2LF", + "address_snippet": "75 Northwood Avenue, Saltdean, Brighton, United Kingdom, BN2 8RG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -948,23 +945,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/XS_K36SAF0KP0p-FPZ-JHnzk9sg/appointments" + "self": "/officers/2GTq_oU7LXu9s-5Qa-lZ8hvh55E/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CRAFT & ARTISAN GROUP LIMITED" + "title": "TAMBAR ARTS LIMITED" }, { "address": { - "address_line_1": "Sham Peng Tong Plaza", - "country": "Seychelles", - "locality": "Victoria", - "premises": "103", - "region": "Mahe" + "address_line_1": "18 West Smithfield", + "locality": "London", + "postal_code": "EC1A 9HQ", + "premises": "Haberdashers' Hall" }, - "address_snippet": "103 Sham Peng Tong Plaza, Victoria, Mahe, Seychelles", + "address_snippet": "Haberdashers' Hall, 18 West Smithfield, London, EC1A 9HQ", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -972,23 +968,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ZIv8T8DaEMyCwngJAmT0c_6yExc/appointments" + "self": "/officers/2Uit5cyBF0CF0i-8gXNhc-VhTas/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ART CULT GROUP LTD." + "title": "THE MASTER AND FOUR WARDENS OF THE FRATERNITY OF THE ART OR MYSTERY OF HABERDASHERS IN THE CITY OF LONDON" }, { "address": { - "address_line_1": "Ottergemsesteenweg-Zuid", - "country": "Belgium", - "locality": "Gent", - "postal_code": "9000", - "premises": "816" + "address_line_1": "The Century Tower, 4th Floor, Suite No 401", + "country": "Pan", + "locality": "Panama", + "premises": "Avenida Ricardo J Alfaro" }, - "address_snippet": "816 Ottergemsesteenweg-Zuid, Gent, Belgium, 9000", + "address_snippet": "Avenida Ricardo J Alfaro, The Century Tower, 4th Floor, Suite No 401, Panama, Pan", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -996,24 +991,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ZhzO15dWnRvdXCDkQ3a6jjVDuo0/appointments" + "self": "/officers/3-g8LL8Bvjsu7mYZpH_8YnOdGLI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTVAL NV" + "title": "ARTONA GROUP S.A." }, { "address": { - "address_line_1": "1 Station Hill", - "country": "United Kingdom", - "locality": "Reading", - "postal_code": "RG1 1NB", - "premises": "4th Floor Phoenix House", - "region": "Berkshire" + "address_line_1": "Prospect Avenue", + "country": "United States", + "locality": "Shelter Island Heights", + "postal_code": "11965", + "premises": "33", + "region": "New York" }, - "address_snippet": "4th Floor Phoenix House, 1 Station Hill, Reading, Berkshire, United Kingdom, RG1 1NB", + "address_snippet": "33 Prospect Avenue, Shelter Island Heights, New York, United States, 11965", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1021,22 +1016,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/_WLSgCWV-djDQTjySozeaz2HxcA/appointments" + "self": "/officers/35_9PfWpfK2YbsO_yBV2fVnK2jo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISAN PARTNERS LIMITED" + "title": "ART ADVISORY INTERNATIONAL LLC" }, { "address": { - "address_line_1": "Dept 1, Unit 9d1", - "address_line_2": "Carcroft Enterprise Park, Station Road, Carcroft", - "locality": "Doncaster", - "postal_code": "DN6 8DD" + "address_line_1": "Oak Avenue", + "address_line_2": "Flushing", + "country": "United States", + "locality": "New York", + "postal_code": "11355", + "premises": "142-03" }, - "address_snippet": "Dept 1, Unit 9d1, Carcroft Enterprise Park, Station Road, Carcroft, Doncaster, DN6 8DD", + "address_snippet": "142-03, Oak Avenue, Flushing, New York, United States, 11355", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1044,22 +1041,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/a0nXiNosk8-l1PTioCapoG1EyHI/appointments" + "self": "/officers/4uICcfw2aLaHj8j8Et75w160On4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTFACT PLC" + "title": "5AM ARTIST MANAGEMENT & EVENT PROMOTION LLC" }, { "address": { - "address_line_1": "10th Floor, 50th Street", - "country": "Panama", - "locality": "Panama", - "premises": "Plaza 2000" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1067,23 +1065,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/aPmBn050P9naie5DkKXmkPo1KUw/appointments" + "self": "/officers/6fWaAU3Du1lmYqXgBIb6KvphOCA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" + "address_line_1": "Tavistock Place", + "locality": "Plymouth", + "postal_code": "PL4 8AT", + "region": "Devon" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", + "address_snippet": "Tavistock Place, Plymouth, Devon, PL4 8AT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1091,23 +1088,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/bCvDQlwWXQFw2841DuyAxy7Z7O0/appointments" + "self": "/officers/81HwCYqVpeGUDCYdyFfz29xdJ20/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "PLYMOUTH COLLEGE OF ART" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Trafalgar Court", + "address_line_2": "Admiral Park", + "country": "Guernsey", + "locality": "St Peter Port", + "postal_code": "GY1 3EL", + "premises": "2nd Floor, East Wing" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "2nd Floor, East Wing, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, GY1 3EL", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1115,23 +1113,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/0lApjaMj101pOhxUZW4PbfcdiKI/appointments" + "self": "/officers/8k2UVW20D8fx7z2kBQoiuG9QY2E/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTEMIS SECRETARY LIMITED" }, { "address": { - "country": "Switzerland", - "locality": "Del Mont", - "postal_code": "2800", - "premises": "30 Rue De La Malt Re", - "region": "Jura" + "address_line_1": "Queen Street", + "country": "England", + "locality": "Maidenhead", + "postal_code": "SL6 1LT", + "premises": "61", + "region": "Berkshire" }, - "address_snippet": "30 Rue De La Malt Re, Del Mont, Jura, Switzerland, 2800", + "address_snippet": "61 Queen Street, Maidenhead, Berkshire, England, SL6 1LT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1139,24 +1138,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/1iEM7Ct-_uCYZ1QmTFTfP1bA0Wo/appointments" + "self": "/officers/8mbFAFkH_QvC_WZCi_BcAQsIY2U/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "MONBOUAN ART & CARS SA" + "title": "JOHN ARTHUR RADLEY" }, { "address": { - "address_line_1": "Northwood Avenue", - "address_line_2": "Saltdean", + "address_line_1": "48 East Street", + "address_line_2": "Portchester", "country": "United Kingdom", - "locality": "Brighton", - "postal_code": "BN2 8RG", - "premises": "75" + "locality": "Fareham", + "postal_code": "PO16 9XS", + "premises": "Murrills House" }, - "address_snippet": "75 Northwood Avenue, Saltdean, Brighton, United Kingdom, BN2 8RG", + "address_snippet": "Murrills House, 48 East Street, Portchester, Fareham, United Kingdom, PO16 9XS", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1164,22 +1163,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/2GTq_oU7LXu9s-5Qa-lZ8hvh55E/appointments" + "self": "/officers/9MYsNDB5UjnT7HZiNh_4lZrC-eo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "TAMBAR ARTS LIMITED" + "title": "JOSEPH ARTHUR TRADING LTD" }, { "address": { - "address_line_1": "18 West Smithfield", - "locality": "London", - "postal_code": "EC1A 9HQ", - "premises": "Haberdashers' Hall" + "address_line_1": "B Rue Rabelais", + "country": "France", + "locality": "Montreuil", + "postal_code": "93100", + "premises": "11" }, - "address_snippet": "Haberdashers' Hall, 18 West Smithfield, London, EC1A 9HQ", + "address_snippet": "11 B Rue Rabelais, Montreuil, France, 93100", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1187,72 +1187,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/2Uit5cyBF0CF0i-8gXNhc-VhTas/appointments" + "self": "/officers/ACxq70q0to_i4haJqUNKK7FaIwg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "THE MASTER AND FOUR WARDENS OF THE FRATERNITY OF THE ART OR MYSTERY OF HABERDASHERS IN THE CITY OF LONDON" + "title": "ARTANA SARL" }, { "address": { - "address_line_1": "The Century Tower, 4th Floor, Suite No 401", - "country": "Pan", + "address_line_1": "Urbanizacion Obarrio", + "address_line_2": "Torre Swiss Bank, Piso 16", + "country": "Panama", "locality": "Panama", - "premises": "Avenida Ricardo J Alfaro" - }, - "address_snippet": "Avenida Ricardo J Alfaro, The Century Tower, 4th Floor, Suite No 401, Panama, Pan", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/3-g8LL8Bvjsu7mYZpH_8YnOdGLI/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTONA GROUP S.A." - }, - { - "address": { - "address_line_1": "Prospect Avenue", - "country": "United States", - "locality": "Shelter Island Heights", - "postal_code": "11965", - "premises": "33", - "region": "New York" - }, - "address_snippet": "33 Prospect Avenue, Shelter Island Heights, New York, United States, 11965", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/35_9PfWpfK2YbsO_yBV2fVnK2jo/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ART ADVISORY INTERNATIONAL LLC" - }, - { - "address": { - "address_line_1": "Oak Avenue", - "address_line_2": "Flushing", - "country": "United States", - "locality": "New York", - "postal_code": "11355", - "premises": "142-03" + "premises": "Calle 53" }, - "address_snippet": "142-03, Oak Avenue, Flushing, New York, United States, 11355", + "address_snippet": "Calle 53, Urbanizacion Obarrio, Torre Swiss Bank, Piso 16, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1260,23 +1211,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/4uICcfw2aLaHj8j8Et75w160On4/appointments" + "self": "/officers/ARgM63-6a0esFXQpFmcLTdnGzPc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "5AM ARTIST MANAGEMENT & EVENT PROMOTION LLC" + "title": "BABE ART INC" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Plaza 2000, 10th Floor", + "address_line_2": "50th Street", + "country": "Republic Of Panama", + "locality": "Panama" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1284,13 +1234,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/6fWaAU3Du1lmYqXgBIb6KvphOCA/appointments" + "self": "/officers/BQwLTxOypHlBId79_qllNtnSV-I/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTONA HOLDING S.A." }, { "address": { @@ -1471,7 +1421,273 @@ "postal_code": "B3 3QR", "premises": "Cornwall Buildings" }, - "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/DOyufTCY6HouR3-5y3GxKnixufU/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTA CONSULTING LTD" + }, + { + "address": { + "address_line_1": "153", + "address_line_2": "Great Ancoats Street", + "country": "England", + "locality": "Manchester", + "postal_code": "M4 6DT", + "premises": "176" + }, + "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/DUgkN0P-YYSdkyplc0jfenmMNNo/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTHOUSE 101 LTD" + }, + { + "address": { + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" + }, + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/DiPROXYZUziyU5H0VDIra2QEDbs/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTA CONSULTING LTD" + }, + { + "address": { + "address_line_1": "Plaza 2000, 10th Floor", + "address_line_2": "50th Street", + "country": "Republic Of Panama", + "locality": "Panama" + }, + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/EGe7PLzhtEpthPS04TcxaZEf9mo/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTONA HOLDING S.A." + }, + { + "address": { + "address_line_1": "Plough Way", + "country": "United Kingdom", + "locality": "Deptford", + "postal_code": "SE16 4AN", + "premises": "64" + }, + "address_snippet": "64 Plough Way, Deptford, United Kingdom, SE16 4AN", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/Ej4qc8-5bc_mXBKwdJGEOKcdwm8/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "GLITZ ARTISTE" + }, + { + "address": { + "address_line_1": "Old Bethnal Green Road", + "country": "England", + "locality": "London", + "postal_code": "E2 6AA", + "premises": "33" + }, + "address_snippet": "33 Old Bethnal Green Road, London, England, E2 6AA", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/sndqZEgx2T4-yXOuOxDtpHlZ5i8/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "DESIGN AND ARTISTS COPYRIGHT SOCIETY" + }, + { + "address": { + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" + }, + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/tRF5eLbAzrZL5czfO2GHoc_Npps/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTA CONSULTING LTD" + }, + { + "address": { + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" + }, + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/u--Cgum5VIH4u_1U_7AfiaeEKAI/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTA CONSULTING LTD" + }, + { + "address": { + "address_line_1": "Hilton Court", + "address_line_2": "Old Lane", + "country": "United Kingdom", + "locality": "Leeds", + "postal_code": "LS16 9LG", + "premises": "1", + "region": "West Yorkshire" + }, + "address_snippet": "1 Hilton Court, Old Lane, Leeds, West Yorkshire, United Kingdom, LS16 9LG", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/u-f4eUHMBFNoqv4Zn-jMOxGE79I/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTHINGTON SERVICES LTD" + }, + { + "address": { + "address_line_1": "92118", + "country": "United States", + "locality": "Coronado", + "premises": "1710 Avenida Del Mundo, La Playa Tower 1009" + }, + "address_snippet": "1710 Avenida Del Mundo, La Playa Tower 1009, 92118, Coronado, United States", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/uX2TS03g2R9Y2bb-_zzV4ZUh3Go/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTFO HOLDINGS LLC" + }, + { + "address": { + "country": "England", + "locality": "Waltham Cross", + "postal_code": "EN8 7AP", + "premises": "147a High Street" + }, + "address_snippet": "147a High Street, Waltham Cross, England, EN8 7AP", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/u_yGW9gGHF1jVVkWiBlghLmFjeM/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTEMIS NETWORK SERVICES LTD" + }, + { + "address": { + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" + }, + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1479,7 +1695,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/DOyufTCY6HouR3-5y3GxKnixufU/appointments" + "self": "/officers/ufy8xctzQnz3b9YRKVB2ahx3scM/appointments" }, "matches": { "snippet": [] @@ -1489,14 +1705,13 @@ }, { "address": { - "address_line_1": "153", - "address_line_2": "Great Ancoats Street", - "country": "England", - "locality": "Manchester", - "postal_code": "M4 6DT", - "premises": "176" + "address_line_1": "Grosvenor Road", + "country": "United Kingdom", + "locality": "Aldershot", + "postal_code": "GU11 3EA", + "premises": "66a" }, - "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", + "address_snippet": "66a, Grosvenor Road, Aldershot, United Kingdom, GU11 3EA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1504,24 +1719,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/DUgkN0P-YYSdkyplc0jfenmMNNo/appointments" + "self": "/officers/varGA97Myc3o4i12qbvt1ERpdzk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOUSE 101 LTD" + "title": "ARTHUR TUMIBAY COMPANIES LIMITED" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1529,7 +1743,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/DiPROXYZUziyU5H0VDIra2QEDbs/appointments" + "self": "/officers/vjuzcR6zOtO0-zMxSEsk86RWHSo/appointments" }, "matches": { "snippet": [] @@ -1539,12 +1753,13 @@ }, { "address": { - "address_line_1": "Plaza 2000, 10th Floor", - "address_line_2": "50th Street", - "country": "Republic Of Panama", - "locality": "Panama" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1552,23 +1767,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/EGe7PLzhtEpthPS04TcxaZEf9mo/appointments" + "self": "/officers/vy8su9sq1aI4or85RJRDJ4yvstQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Plough Way", - "country": "United Kingdom", - "locality": "Deptford", - "postal_code": "SE16 4AN", - "premises": "64" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "64 Plough Way, Deptford, United Kingdom, SE16 4AN", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1576,23 +1791,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Ej4qc8-5bc_mXBKwdJGEOKcdwm8/appointments" + "self": "/officers/wJEodSJgs9xwqWeU3VPHQpytuWI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "GLITZ ARTISTE" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ne 72nd St", - "country": "United States", - "locality": "Seattle", - "postal_code": "98115", - "premises": "2616" + "address_line_1": "20 Dawes Road", + "address_line_2": "London", + "country": "United Kingdom", + "locality": "London", + "postal_code": "SW6 7EN", + "premises": "Dawes Road Hub" }, - "address_snippet": "2616 Ne 72nd St, Seattle, United States, 98115", + "address_snippet": "Dawes Road Hub, 20 Dawes Road, London, London, United Kingdom, SW6 7EN", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1600,23 +1816,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/FOSoXARcAdfGzJYLjXMiCWjvfps/appointments" + "self": "/officers/w_Zaxt3Gj1Hdvo55hX_lvDOpWXc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISAN BRANDS INC" + "title": "CYNTHIA ARTRY" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Sound & Vision House", + "address_line_2": "Francis Rachel Str.", + "country": "Seychelles", + "locality": "Victoria", + "premises": "Suite 1, Second Floor,", + "region": "Mahe" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Suite 1, Second Floor,, Sound & Vision House, Francis Rachel Str., Victoria, Mahe, Seychelles", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1624,23 +1841,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/GGsFN-Oc27z3fH4_X9eI9XUz0-w/appointments" + "self": "/officers/x3tJYYjDK6vKotU4i4CRW8VnJYk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTIC STREAM CORPORATION" }, { "address": { - "address_line_1": "Old Bethnal Green Road", - "country": "England", - "locality": "London", - "postal_code": "E2 6AA", - "premises": "33" + "address_line_1": "Marina House", + "address_line_2": "Eden Island", + "country": "Seychelles", + "locality": "Mahe", + "premises": "Tenancy 10" }, - "address_snippet": "33 Old Bethnal Green Road, London, England, E2 6AA", + "address_snippet": "Tenancy 10, Marina House, Eden Island, Mahe, Seychelles", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1648,24 +1865,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/sndqZEgx2T4-yXOuOxDtpHlZ5i8/appointments" + "self": "/officers/yXqXGmRNrP6EML93zBAmDHmJh6g/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "DESIGN AND ARTISTS COPYRIGHT SOCIETY" + "title": "ARTNEAVEU S.A." }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1673,7 +1889,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/tRF5eLbAzrZL5czfO2GHoc_Npps/appointments" + "self": "/officers/yZSvRSLbS0SuPpc9IqFAFnGWRpE/appointments" }, "matches": { "snippet": [] @@ -1697,7 +1913,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/u--Cgum5VIH4u_1U_7AfiaeEKAI/appointments" + "self": "/officers/zXCUS48m3bq6UMmHKM-ZE0zT4Hk/appointments" }, "matches": { "snippet": [] @@ -1707,15 +1923,14 @@ }, { "address": { - "address_line_1": "Hilton Court", - "address_line_2": "Old Lane", + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", "country": "United Kingdom", - "locality": "Leeds", - "postal_code": "LS16 9LG", - "premises": "1", - "region": "West Yorkshire" + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "1 Hilton Court, Old Lane, Leeds, West Yorkshire, United Kingdom, LS16 9LG", + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1723,22 +1938,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/u-f4eUHMBFNoqv4Zn-jMOxGE79I/appointments" + "self": "/officers/zqUWXs8UBqb7tsDOoiws7WW6lHs/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHINGTON SERVICES LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "92118", - "country": "United States", - "locality": "Coronado", - "premises": "1710 Avenida Del Mundo, La Playa Tower 1009" + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "1710 Avenida Del Mundo, La Playa Tower 1009, 92118, Coronado, United States", + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1746,22 +1963,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/uX2TS03g2R9Y2bb-_zzV4ZUh3Go/appointments" + "self": "/officers/QVQEMhsVIdjp0kf9Qz2mAojOm2U/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTFO HOLDINGS LLC" + "title": "ARTA CONSULTING LTD" }, { "address": { - "country": "England", - "locality": "Waltham Cross", - "postal_code": "EN8 7AP", - "premises": "147a High Street" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "147a High Street, Waltham Cross, England, EN8 7AP", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1769,13 +1987,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/u_yGW9gGHF1jVVkWiBlghLmFjeM/appointments" + "self": "/officers/QffIHUThcE6vWKhM5IhrqK_syMw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS NETWORK SERVICES LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { @@ -1793,7 +2011,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ufy8xctzQnz3b9YRKVB2ahx3scM/appointments" + "self": "/officers/SrqR19BaVzjFMmiblyx7mETQhZ4/appointments" }, "matches": { "snippet": [] @@ -1803,13 +2021,12 @@ }, { "address": { - "address_line_1": "Grosvenor Road", - "country": "United Kingdom", - "locality": "Aldershot", - "postal_code": "GU11 3EA", - "premises": "66a" + "address_line_1": "Floor No 4, Suite No 401", + "country": "Panama", + "locality": "Panama City", + "premises": "Ricardo J. Alfaro Avenue, The Century Tower" }, - "address_snippet": "66a, Grosvenor Road, Aldershot, United Kingdom, GU11 3EA", + "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1817,23 +2034,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/varGA97Myc3o4i12qbvt1ERpdzk/appointments" + "self": "/officers/T_pz10vxu-iaM1mJIYaxtav-fT0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHUR TUMIBAY COMPANIES LIMITED" + "title": "ARTONA GROUP S.A." }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "700 Central Parkway", + "country": "Usa", + "locality": "Atlanta", + "premises": "700", + "region": "Ga" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "700 700 Central Parkway, Atlanta, Ga, Usa", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1841,23 +2058,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/vjuzcR6zOtO0-zMxSEsk86RWHSo/appointments" + "self": "/officers/UMpx313C-llFMsPINDdX0VcIsa0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ENTERTAINMENT ARTS RESEARCH INC." }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Park Avenue South", + "country": "United States", + "locality": "New York", + "postal_code": "10016", + "premises": "400, 36b" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "400, 36b, Park Avenue South, New York, United States, 10016", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1865,13 +2082,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/vy8su9sq1aI4or85RJRDJ4yvstQ/appointments" + "self": "/officers/V1MdnoJ7T7U9Zn4cxXZXPh8b550/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTEMIS DISTRIBUTION INTERNATIONAL INC" }, { "address": { @@ -1881,32 +2098,7 @@ "postal_code": "MH96960", "premises": "Trust Company Complex" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/wJEodSJgs9xwqWeU3VPHQpytuWI/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "20 Dawes Road", - "address_line_2": "London", - "country": "United Kingdom", - "locality": "London", - "postal_code": "SW6 7EN", - "premises": "Dawes Road Hub" - }, - "address_snippet": "Dawes Road Hub, 20 Dawes Road, London, London, United Kingdom, SW6 7EN", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1914,24 +2106,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/w_Zaxt3Gj1Hdvo55hX_lvDOpWXc/appointments" + "self": "/officers/V2RZzN9EVclyF41QqssRLAl3yY4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CYNTHIA ARTRY" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Sound & Vision House", - "address_line_2": "Francis Rachel Str.", - "country": "Seychelles", - "locality": "Victoria", - "premises": "Suite 1, Second Floor,", - "region": "Mahe" + "address_line_1": "45 Newhall Street, Suite 211", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "Suite 1, Second Floor,, Sound & Vision House, Francis Rachel Str., Victoria, Mahe, Seychelles", + "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1939,13 +2130,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/x3tJYYjDK6vKotU4i4CRW8VnJYk/appointments" + "self": "/officers/kfVFfxH4aukYfANYmeKJSqz-oEk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTIC STREAM CORPORATION" + "title": "ARTA CONSULTING LTD" }, { "address": { @@ -2165,174 +2356,6 @@ "snippet": "", "title": "ART HOUSE INTERNATIONAL (AHI INT) LTD" }, - { - "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" - }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/QVQEMhsVIdjp0kf9Qz2mAojOm2U/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" - }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/QffIHUThcE6vWKhM5IhrqK_syMw/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" - }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/SrqR19BaVzjFMmiblyx7mETQhZ4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Floor No 4, Suite No 401", - "country": "Panama", - "locality": "Panama City", - "premises": "Ricardo J. Alfaro Avenue, The Century Tower" - }, - "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/T_pz10vxu-iaM1mJIYaxtav-fT0/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTONA GROUP S.A." - }, - { - "address": { - "address_line_1": "700 Central Parkway", - "country": "Usa", - "locality": "Atlanta", - "premises": "700", - "region": "Ga" - }, - "address_snippet": "700 700 Central Parkway, Atlanta, Ga, Usa", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/UMpx313C-llFMsPINDdX0VcIsa0/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ENTERTAINMENT ARTS RESEARCH INC." - }, - { - "address": { - "address_line_1": "Park Avenue South", - "country": "United States", - "locality": "New York", - "postal_code": "10016", - "premises": "400, 36b" - }, - "address_snippet": "400, 36b, Park Avenue South, New York, United States, 10016", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/V1MdnoJ7T7U9Zn4cxXZXPh8b550/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTEMIS DISTRIBUTION INTERNATIONAL INC" - }, - { - "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" - }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/V2RZzN9EVclyF41QqssRLAl3yY4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, { "address": { "address_line_1": "Ajeltake Road, Ajeltake Island", @@ -2349,7 +2372,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/VV0OALKLc_aW3uVSPj1v4gOb9bo/appointments" + "self": "/officers/NlebtX1xVOMpEOEOLS44hwY4WoQ/appointments" }, "matches": { "snippet": [] @@ -2359,62 +2382,40 @@ }, { "address": { - "address_line_1": "Tavistock Place", - "locality": "Plymouth", - "postal_code": "PL4 8AT", - "region": "Devon" - }, - "address_snippet": "Tavistock Place, Plymouth, Devon, PL4 8AT", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/81HwCYqVpeGUDCYdyFfz29xdJ20/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "PLYMOUTH COLLEGE OF ART" - }, - { - "address": { - "address_line_1": "Trafalgar Court", - "address_line_2": "Admiral Park", - "country": "Guernsey", - "locality": "St Peter Port", - "postal_code": "GY1 3EL", - "premises": "2nd Floor, East Wing" + "address_line_1": "Pontefract Road", + "address_line_2": "Cudworth", + "care_of": "Stephen Sutton", + "country": "England", + "locality": "Barnsley", + "postal_code": "S72 8BE", + "premises": "176-178" }, - "address_snippet": "2nd Floor, East Wing, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, GY1 3EL", - "appointment_count": 1, - "description": "Total number of appointments 1", + "address_snippet": "Stephen Sutton, 176-178, Pontefract Road, Cudworth, Barnsley, England, S72 8BE", + "appointment_count": 2, + "description": "Total number of appointments 2", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/8k2UVW20D8fx7z2kBQoiuG9QY2E/appointments" + "self": "/officers/OM3JSGgGHFledDAK9s81-84lN9o/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS SECRETARY LIMITED" + "title": "DIVINE ART LIMITED" }, { "address": { - "address_line_1": "Queen Street", - "country": "England", - "locality": "Maidenhead", - "postal_code": "SL6 1LT", - "premises": "61", - "region": "Berkshire" + "address_line_1": "Park Lane", + "country": "United Kingdom", + "locality": "Hornchurch", + "postal_code": "RM11 1BH", + "premises": "Park Lane", + "region": "Essex" }, - "address_snippet": "61 Queen Street, Maidenhead, Berkshire, England, SL6 1LT", + "address_snippet": "Park Lane, Park Lane, Hornchurch, Essex, United Kingdom, RM11 1BH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2422,13 +2423,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/8mbFAFkH_QvC_WZCi_BcAQsIY2U/appointments" + "self": "/officers/OtkLyiygSEIz7bw3JqXVJJF4540/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "JOHN ARTHUR RADLEY" + "title": "HENLY ARTHUR LIMITED" } ], "items_per_page": 100, @@ -2446,16 +2447,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:16 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=20F5DDBC3B82A5B11E6ECCDE886D8834; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=EE22F09E7B6CC748988E0004E0770937; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "564", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "566", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_0df271dc.json b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_0df271dc.json index 0b8f4b8..0f92060 100644 --- a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_0df271dc.json +++ b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_0df271dc.json @@ -4,13 +4,14 @@ "items": [ { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "48 East Street", + "address_line_2": "Portchester", + "country": "United Kingdom", + "locality": "Fareham", + "postal_code": "PO16 9XS", + "premises": "Murrills House" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Murrills House, 48 East Street, Portchester, Fareham, United Kingdom, PO16 9XS", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -18,24 +19,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zXCUS48m3bq6UMmHKM-ZE0zT4Hk/appointments" + "self": "/officers/9MYsNDB5UjnT7HZiNh_4lZrC-eo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "JOSEPH ARTHUR TRADING LTD" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "10th Floor", + "address_line_2": "50th Street", + "country": "Republic Of Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -43,23 +43,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zqUWXs8UBqb7tsDOoiws7WW6lHs/appointments" + "self": "/officers/qw44qhC0Nesyb0mLf67_CXtiWrA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "country": "United Kingdom", + "locality": "Leicester", + "postal_code": "LE3 5GF", + "premises": "50 Woodgate", + "region": "Leicestershire" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "50 Woodgate, Leicester, Leicestershire, United Kingdom, LE3 5GF", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -67,50 +67,45 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/NlebtX1xVOMpEOEOLS44hwY4WoQ/appointments" + "self": "/officers/qwSWzA7iBnuyQCty9_IAy-V-8ys/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ART ASSETS LIMITED" }, { "address": { - "address_line_1": "Pontefract Road", - "address_line_2": "Cudworth", - "care_of": "Stephen Sutton", - "country": "England", - "locality": "Barnsley", - "postal_code": "S72 8BE", - "premises": "176-178" + "address_line_1": "New Brunswick Street", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "Stephen Sutton, 176-178, Pontefract Road, Cudworth, Barnsley, England, S72 8BE", - "appointment_count": 2, - "description": "Total number of appointments 2", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, WF1 5QW", + "appointment_count": 1, + "description": "Total number of appointments 1", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/OM3JSGgGHFledDAK9s81-84lN9o/appointments" + "self": "/officers/rxJRQUZnzPohyxfaHClFTDuM0C8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "DIVINE ART LIMITED" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Park Lane", - "country": "United Kingdom", - "locality": "Hornchurch", - "postal_code": "RM11 1BH", - "premises": "Park Lane", - "region": "Essex" + "address_line_1": "Plaza 2000, 10th Floor, 50th Street", + "country": "Republic Of Panama", + "locality": "Panama" }, - "address_snippet": "Park Lane, Park Lane, Hornchurch, Essex, United Kingdom, RM11 1BH", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -118,47 +113,47 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/OtkLyiygSEIz7bw3JqXVJJF4540/appointments" + "self": "/officers/sH3SkYn4hfvJARYiheU9jNkf45c/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "HENLY ARTHUR LIMITED" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "The Century Tower, Floor No 4, Suite No 401", - "country": "Panama", - "locality": "Panama City", - "premises": "Ricardo J. Alfaro Avenue" + "address_line_1": "25 Walbrook", + "country": "England", + "locality": "London", + "postal_code": "EC4N 8AW", + "premises": "The Walbrook Building" }, - "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", - "appointment_count": 1, - "description": "Total number of appointments 1", + "address_snippet": "The Walbrook Building, 25 Walbrook, London, England, EC4N 8AW", + "appointment_count": 11, + "description": "Total number of appointments 11", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/PBO3piouLKyhvcmVrFjyYnllgIg/appointments" + "self": "/officers/aPl6vnFflkylqSmvLpcplGn_3eU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA GROUP S.A." + "title": "ARTHUR J. GALLAGHER (UK) LIMITED" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "Ruette Braye", + "address_line_2": "St Peter Port", + "locality": "Guernsey", + "postal_code": "GY1 1EW", + "premises": "Elizabeth House" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Elizabeth House, Ruette Braye, St Peter Port, Guernsey, GY1 1EW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -166,23 +161,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/PZ8Kgs-51D1TG_JJEIH-gbrJyc8/appointments" + "self": "/officers/-71ursRVcWPLoUiZZTyQV1v-8lY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTERIAL EQUITY PARTNERS LIMITED" }, { "address": { - "address_line_1": "Ruette Braye", - "address_line_2": "St Peter Port", - "locality": "Guernsey", - "postal_code": "GY1 1EW", - "premises": "Elizabeth House" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Elizabeth House, Ruette Braye, St Peter Port, Guernsey, GY1 1EW", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -190,23 +185,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/-71ursRVcWPLoUiZZTyQV1v-8lY/appointments" + "self": "/officers/klOtDSMTNyrxPe05XgRVyts5sVw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTERIAL EQUITY PARTNERS LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Cremorne Road", - "country": "United Kingdom", + "address_line_1": "Lammas Park Road", + "country": "England", "locality": "London", - "postal_code": "SW10 0NB", - "premises": "35" + "postal_code": "W5 5JD", + "premises": "43" }, - "address_snippet": "35 Cremorne Road, London, United Kingdom, SW10 0NB", + "address_snippet": "43 Lammas Park Road, London, England, W5 5JD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -214,13 +209,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zrNEyoWQcx0hfoXQTAOBUL95DAQ/appointments" + "self": "/officers/X4uhxqngmxHY0gXFIhvXzP7ZIjI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "FINE ART INVESTMENT RESEARCH LIMITED" + "title": "WINE ARTIST LTD" }, { "address": { @@ -248,29 +243,6 @@ "snippet": "", "title": "EDGAR & ARTHUR LTD" }, - { - "address": { - "address_line_1": "50th Street", - "country": "Republic Of Panama", - "locality": "Panama", - "premises": "Plaza 2000 1oth Floor" - }, - "address_snippet": "Plaza 2000 1oth Floor, 50th Street, Panama, Republic Of Panama", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/cSAZFLyneoAdmPKcijrFAiaMglA/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTONA HOLDING S.A." - }, { "address": { "address_line_1": "Ajeltake Road, Ajeltake Island", @@ -287,7 +259,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/W1YKKVRd36KDRTBoMyWuVwxlDbU/appointments" + "self": "/officers/GHIu3BY0kXQlTGKtwJna8Vhn_Yk/appointments" }, "matches": { "snippet": [] @@ -297,14 +269,15 @@ }, { "address": { - "address_line_1": "Biz Hub Tees Valley", - "address_line_2": "Belasis Hall Technology Park", - "country": "England", - "locality": "Billingham", - "postal_code": "TS23 4EA", - "premises": "C/O Ocg Accountants Ltd" + "address_line_1": "Admiral Park", + "address_line_2": "St Peter Port", + "country": "Guernsey", + "locality": "Guernsey", + "po_box": "PO BOX 100", + "postal_code": "GY1 3EL", + "premises": "Trafalgar Court" }, - "address_snippet": "C/O Ocg Accountants Ltd, Biz Hub Tees Valley, Belasis Hall Technology Park, Billingham, England, TS23 4EA", + "address_snippet": "PO BOX 100, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -312,22 +285,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/WZkYCUH-kPjQIiyezGTIlO4hKP8/appointments" + "self": "/officers/GKpIDs9RNKJngnP62NrL9tp-Qio/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOUSING FAMILY HOLDINGS LTD" + "title": "ARTEMIS SECRETARIES LIMITED" }, { "address": { - "address_line_1": "Elizabeth And Shirley Street", - "country": "Bahamas", - "locality": "Nassau", - "premises": "Suite E-2 Union Court Building" + "address_line_1": "Brimington Road", + "country": "United Kingdom", + "locality": "Chesterfield", + "postal_code": "S41 7UG", + "premises": "Infinity Martial Arts", + "region": "Derbyshire" }, - "address_snippet": "Suite E-2 Union Court Building, Elizabeth And Shirley Street, Nassau, Bahamas", + "address_snippet": "Infinity Martial Arts, Brimington Road, Chesterfield, Derbyshire, United Kingdom, S41 7UG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -335,13 +310,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/WtauQJDxqqcX3TmgXEnbbOjBKYM/appointments" + "self": "/officers/Gkc4hFBaFVrSnYogWkwbBl6346Y/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNELL ASSOCIATES LTD" + "title": "INFINITY MARTIAL ARTS CHESTERFIELD LIMITED" }, { "address": { @@ -359,7 +334,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ZARrESkQbiw7GO8FJv9g37SKc6E/appointments" + "self": "/officers/H2Z4NnPtgWQzXiqNgJBG7g8lmm4/appointments" }, "matches": { "snippet": [] @@ -369,12 +344,13 @@ }, { "address": { - "address_line_1": "PO BOX 3175 3/F Geneva Place", - "address_line_2": "Waterfront Drive", - "locality": "Road Town Tortola", - "region": "British Virgin Islands" + "address_line_1": "New Road", + "country": "Belize", + "locality": "Belize City", + "postal_code": "NA", + "premises": "7" }, - "address_snippet": "PO BOX 3175 3/F Geneva Place, Waterfront Drive, Road Town Tortola, British Virgin Islands", + "address_snippet": "7 New Road, Belize City, Belize, NA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -382,23 +358,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ZsaT_-W1RoM_-QirOEdhKIoi6Hs/appointments" + "self": "/officers/HyaUAXMFK4QllxtAlwVSiuvqUTY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS VENTURES INTERNATIONAL LIMITED" + "title": "ARTEX LOGAN INC." }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "New Brunswick Street", + "country": "United Kingdom", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -406,23 +383,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ZzS2hzEKS2Si88v69X9JrW19n_g/appointments" + "self": "/officers/IaD5iGVuU3_J3fncw7zOk1H0jY4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Lower Brook Street", - "country": "United Kingdom", - "locality": "Oswestry", - "postal_code": "SY11 2HJ", - "region": "Shropshire" + "address_line_1": "Aspin Tower", + "address_line_2": "Sheikh Zayed Road", + "country": "United Arab Emirates", + "locality": "Dubai", + "premises": "1002-026" }, - "address_snippet": "Lower Brook Street, Oswestry, Shropshire, United Kingdom, SY11 2HJ", + "address_snippet": "1002-026, Aspin Tower, Sheikh Zayed Road, Dubai, United Arab Emirates", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -430,23 +407,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Zzos0sXlyVrhXACUKHKJNXP_yZc/appointments" + "self": "/officers/J4we7Ysj4aLvu6eycCONgKn-zUA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "J P ARTHUR & SONS LTD" + "title": "ZURANI FINE ARTS CONSULTANTS" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Bank Chambers", + "address_line_2": "25 Jermyn Street", + "country": "England", + "locality": "London", + "postal_code": "SW1Y 6HR", + "premises": "14" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "14 Bank Chambers, 25 Jermyn Street, London, England, SW1Y 6HR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -454,23 +432,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/aJS4VhLHPbjuW8BaKju40ZAKtMM/appointments" + "self": "/officers/JT8Hsh2saTMOmjZLB1LGyw5MC9Q/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTEMIS BUSINESS CONSULTANTS LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" + "address_line_1": "Granville Road", + "country": "United Kingdom", + "locality": "London", + "postal_code": "N12 0JG", + "premises": "49" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", + "address_snippet": "49 Granville Road, London, United Kingdom, N12 0JG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -478,23 +456,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/b8c-ywkTk_ZNf4H_UgxR-xH37nk/appointments" + "self": "/officers/JbxtRNzkj_zEivV1BC-RJp5O6kg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ABIDA ARTISTS UK LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Market Place", + "country": "England", + "locality": "Swaffham", + "postal_code": "PE37 7QH", + "premises": "Clenshaw Minns 22-24", + "region": "Norfolk" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Clenshaw Minns 22-24, Market Place, Swaffham, Norfolk, England, PE37 7QH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -502,23 +481,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/bUG8P9W4f4ZG2aTSSxi7xSeovxU/appointments" + "self": "/officers/K0pDkRi4JnhYw8CUTTiJF28pP4o/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "WESTACRE ARTS FOUNDATION LIMITED" }, { "address": { - "address_line_1": "Llanbedr Airfield", - "country": "Wales", - "locality": "Llanbedr", - "postal_code": "LL45 2PX", - "premises": "Building 278" + "address_line_1": "Fore Street Avenue", + "address_line_2": "C/O Praxis", + "country": "England", + "locality": "London", + "postal_code": "EC2Y 9DT", + "premises": "1" }, - "address_snippet": "Building 278, Llanbedr Airfield, Llanbedr, Wales, LL45 2PX", + "address_snippet": "1 Fore Street Avenue, C/O Praxis, London, England, EC2Y 9DT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -526,23 +506,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/OkarDd_hamCuVgMJOyWYdClAb44/appointments" + "self": "/officers/K1JfdWSl-hWTEbrHvBuJMgEkMf0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTRO PROPERTY SERVICES LTD" + "title": "ARTIRIX LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Strathearn Avenue", + "address_line_2": "Whitton", + "country": "United Kingdom", + "locality": "Twickenham", + "postal_code": "TW2 6JT", + "premises": "29", + "region": "Middlesex" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -550,23 +532,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Q135yovyN0uahaaBI4PBPE8TeM4/appointments" + "self": "/officers/LzqoVQZiCXOahEYkmsk5xnt8MuU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "EDGAR & ARTHUR LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island,", - "country": "Marshall Islands", - "locality": "Mh96960", - "premises": "Trust Company Complex,", - "region": "Majuro" + "address_line_1": "York House", + "address_line_2": "20 York Street", + "country": "United Kingdom", + "locality": "Manchester", + "postal_code": "M2 3BB", + "premises": "C/O Williamson & Croft", + "region": "Greater Manchester" }, - "address_snippet": "Trust Company Complex,, Ajeltake Road, Ajeltake Island,, Mh96960, Majuro, Marshall Islands", + "address_snippet": "C/O Williamson & Croft, York House, 20 York Street, Manchester, Greater Manchester, United Kingdom, M2 3BB", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -574,22 +558,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/QQPLnAHFzeK7K-Xxcalr00vA0Ac/appointments" + "self": "/officers/MCzUzIfogDWBkOgGQHKO-_nlkac/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHUR BUSINESS LTD" + "title": "ARTISAN PROPERTY DEVELOPMENTS LIMITED" }, { "address": { - "country": "England", - "locality": "London", - "postal_code": "W2 3BH", - "premises": "Flat 403, 18 Leinster Gardens" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Flat 403, 18 Leinster Gardens, London, England, W2 3BH", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -597,23 +582,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/R8SnRATl9Qgu9QSoDeqnawiKnLs/appointments" + "self": "/officers/MaIRkc2Pc90JbTGSHBFbCfbCkyg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CREATIVE CONSORTIUM ARTS HOLDINGS LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "1st Floor, 31 Victoria Street", - "country": "Bermuda", - "locality": "Hamilton", - "premises": "Victoria Place", - "region": "Hm 10" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Victoria Place, 1st Floor, 31 Victoria Street, Hamilton, Hm 10, Bermuda", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -621,24 +606,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/STAZRFro7Jo8TZI04JFd-CodB5E/appointments" + "self": "/officers/W1YKKVRd36KDRTBoMyWuVwxlDbU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "GLOBAL ARTIS LTD." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "New Brunswick Street", - "country": "United Kingdom", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "address_line_1": "Biz Hub Tees Valley", + "address_line_2": "Belasis Hall Technology Park", + "country": "England", + "locality": "Billingham", + "postal_code": "TS23 4EA", + "premises": "C/O Ocg Accountants Ltd" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", + "address_snippet": "C/O Ocg Accountants Ltd, Biz Hub Tees Valley, Belasis Hall Technology Park, Billingham, England, TS23 4EA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -646,23 +631,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/SgovMTuF-JkZsZsaMDv1-GLP2yA/appointments" + "self": "/officers/WZkYCUH-kPjQIiyezGTIlO4hKP8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "ARTHOUSING FAMILY HOLDINGS LTD" }, { "address": { - "address_line_1": "Boulevard De La Madeleine", - "country": "France", - "locality": "Paris", - "postal_code": "75001", - "premises": "9" + "address_line_1": "Elizabeth And Shirley Street", + "country": "Bahamas", + "locality": "Nassau", + "premises": "Suite E-2 Union Court Building" }, - "address_snippet": "9 Boulevard De La Madeleine, Paris, France, 75001", + "address_snippet": "Suite E-2 Union Court Building, Elizabeth And Shirley Street, Nassau, Bahamas", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -670,23 +654,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Shw5psPVa6aDNam32jL24S1_mwc/appointments" + "self": "/officers/WtauQJDxqqcX3TmgXEnbbOjBKYM/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNOVA" + "title": "ARTNELL ASSOCIATES LTD" }, { "address": { - "address_line_1": "Union Court Building", - "address_line_2": "Elizabeth And Shirley Streets", - "locality": "Nassau", - "premises": "Suite E-2", - "region": "Bahamas" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Suite E-2, Union Court Building, Elizabeth And Shirley Streets, Nassau, Bahamas", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -694,25 +678,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/T8O9iUeFwtxTparA6b-C2djdh6Q/appointments" + "self": "/officers/ZARrESkQbiw7GO8FJv9g37SKc6E/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNELL ASSOCIATES LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Oakhurst House", - "address_line_2": "77 Mount Ephraim", - "country": "England", - "locality": "Tunbridge Wells", - "postal_code": "TN4 8BS", - "premises": "Ground Floor", - "region": "Kent" + "address_line_1": "PO BOX 3175 3/F Geneva Place", + "address_line_2": "Waterfront Drive", + "locality": "Road Town Tortola", + "region": "British Virgin Islands" }, - "address_snippet": "Ground Floor, Oakhurst House, 77 Mount Ephraim, Tunbridge Wells, Kent, England, TN4 8BS", + "address_snippet": "PO BOX 3175 3/F Geneva Place, Waterfront Drive, Road Town Tortola, British Virgin Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -720,24 +701,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/TH0qqHj7pcaxhn_HrbjKs8x9iQY/appointments" + "self": "/officers/ZsaT_-W1RoM_-QirOEdhKIoi6Hs/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "BRYANT ARTISTS LTD" + "title": "ARTEMIS VENTURES INTERNATIONAL LIMITED" }, { "address": { - "address_line_1": "1 Station Hill", - "country": "United Kingdom", - "locality": "Reading", - "postal_code": "RG1 1NB", - "premises": "4th Floor Phoenix House", - "region": "Berkshire" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "4th Floor Phoenix House, 1 Station Hill, Reading, Berkshire, United Kingdom, RG1 1NB", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -745,24 +725,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/UdVdTo9u3JXG-6_SJ4H0NaKtR_I/appointments" + "self": "/officers/ZzS2hzEKS2Si88v69X9JrW19n_g/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISAN PARTNERS II LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Argyll Street", - "care_of": "Palladium House", + "address_line_1": "Lower Brook Street", "country": "United Kingdom", - "locality": "London", - "postal_code": "W1F 7LD", - "premises": "1-4" + "locality": "Oswestry", + "postal_code": "SY11 2HJ", + "region": "Shropshire" }, - "address_snippet": "Palladium House, 1-4, Argyll Street, London, United Kingdom, W1F 7LD", + "address_snippet": "Lower Brook Street, Oswestry, Shropshire, United Kingdom, SY11 2HJ", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -770,49 +749,47 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/UzdZ8h7ETaDsop_r3dNxGXO3oqw/appointments" + "self": "/officers/Zzos0sXlyVrhXACUKHKJNXP_yZc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "MORAN ART LIMITED" + "title": "J P ARTHUR & SONS LTD" }, { "address": { - "address_line_1": "South Norwood", - "country": "United Kingdom", - "locality": "London", - "postal_code": "SE25 4LB", - "premises": "6 Brocklesby Road" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "6 Brocklesby Road, South Norwood, London, United Kingdom, SE25 4LB", - "appointment_count": 2, - "description": "Total number of appointments 2", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "appointment_count": 1, + "description": "Total number of appointments 1", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/7bzpVzakUVGlztD1c9ZfJrX62fI/appointments" + "self": "/officers/aJS4VhLHPbjuW8BaKju40ZAKtMM/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTFUL DESIGN LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Strathearn Avenue", - "address_line_2": "Whitton", - "country": "United Kingdom", - "locality": "Twickenham", - "postal_code": "TW2 6JT", - "premises": "29", - "region": "Middlesex" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -820,13 +797,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/7zEfVXEwh5WeLT0FnwHmcip8euU/appointments" + "self": "/officers/b8c-ywkTk_ZNf4H_UgxR-xH37nk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "EDGAR & ARTHUR LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { @@ -844,7 +821,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/9IWWytbiv0Vv_o-8WqHI-x6DQqw/appointments" + "self": "/officers/bUG8P9W4f4ZG2aTSSxi7xSeovxU/appointments" }, "matches": { "snippet": [] @@ -854,13 +831,14 @@ }, { "address": { - "address_line_1": "New Brunswick Street", - "country": "United Kingdom", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House," + "address_line_1": "New York Mill", + "address_line_2": "Summerbridge", + "locality": "Harrogate", + "postal_code": "HG3 4LA", + "premises": "Unit 16", + "region": "North Yorkshire" }, - "address_snippet": "Waterfront House,, New Brunswick Street, Wakefield, United Kingdom, WF1 5QW", + "address_snippet": "Unit 16, New York Mill, Summerbridge, Harrogate, North Yorkshire, HG3 4LA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -868,22 +846,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/A2ye6jBvT1cxyMwoxNx7TEYcAN4/appointments" + "self": "/officers/cbqfyxB5BIJggu602YWO1PvQn0E/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "CLARITY ARTS LIMITED" }, { "address": { - "address_line_1": "10th Floor, 50th Street", - "country": "Panama", - "locality": "Panama", - "premises": "Plaza 2000" + "address_line_1": "Arundell Road", + "country": "England", + "locality": "Weston-Super-Mare", + "postal_code": "BS23 2QG", + "premises": "25b Arundell Road" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", + "address_snippet": "25b Arundell Road, Arundell Road, Weston-Super-Mare, England, BS23 2QG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -891,23 +870,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/AW5019Q2GR82RB-3CtbOWWlBwZU/appointments" + "self": "/officers/cwvQjq7r3C_vo5e7qCPNriHt-UU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "FRONT ROOM ARTS" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "153", + "address_line_2": "Great Ancoats Street", + "country": "England", + "locality": "Manchester", + "postal_code": "M4 6DT", + "premises": "176" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -915,23 +895,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Abi7kh_K2JnycP5SLAWqpL9XnGU/appointments" + "self": "/officers/d7I73iK0U2ygcRIkDXK5zawzNxk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTHOUSE 101 LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "country": "Switzerland", + "locality": "Del Mont", + "postal_code": "2800", + "premises": "30 Rue De La Malt Re", + "region": "Jura" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "30 Rue De La Malt Re, Del Mont, Jura, Switzerland, 2800", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -939,23 +919,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Cso5VFOq0a8xlL37_tdPjGuQYU4/appointments" + "self": "/officers/dN7wZvVzfVpkHB9e0zsL9-ziur0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "MONBOUAN ART & CARS SA" }, { "address": { - "address_line_1": "Wickhams Cay 1", - "country": "British Virgin Islands", - "locality": "Road Town", - "premises": "Vanterpool Plaza Bldg", - "region": "Tortola" + "address_line_1": "10 Salisbury Square", + "country": "England", + "locality": "London", + "postal_code": "EC4Y 8EH", + "premises": "St Bride's House" }, - "address_snippet": "Vanterpool Plaza Bldg, Wickhams Cay 1, Road Town, Tortola, British Virgin Islands", + "address_snippet": "St Bride's House, 10 Salisbury Square, London, England, EC4Y 8EH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -963,13 +943,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/DDUg-0bF9gxrV4Q265PiFNQ3N44/appointments" + "self": "/officers/dae8ZbDiSQ0laLJs1b6xOlgAz7s/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "VALDEN FINE ARTS LTD" + "title": "ORCHARD FINE ART STORAGE SERVICES LIMITED" }, { "address": { @@ -987,7 +967,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/klOtDSMTNyrxPe05XgRVyts5sVw/appointments" + "self": "/officers/gOctmCTTEgR7riXM7qIMGj-yGDE/appointments" }, "matches": { "snippet": [] @@ -997,13 +977,14 @@ }, { "address": { - "address_line_1": "Plaza Banco General", - "address_line_2": "14th Floor", - "country": "Republic Of Panama", - "locality": "Panama", - "premises": "50 And Aquilino De La Guardia Streets" + "address_line_1": "Foxhunter Drive", + "address_line_2": "Linford Wood", + "country": "United Kingdom", + "locality": "Milton Keynes", + "postal_code": "MK14 6GD", + "premises": "99 Milton Keynes Business Centre" }, - "address_snippet": "50 And Aquilino De La Guardia Streets, Plaza Banco General, 14th Floor, Panama, Republic Of Panama", + "address_snippet": "99 Milton Keynes Business Centre, Foxhunter Drive, Linford Wood, Milton Keynes, United Kingdom, MK14 6GD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1011,24 +992,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/kpRnO2kx7ekndaePNQ5Mr65NVDA/appointments" + "self": "/officers/h660zZh1tUCSsiM4MPcppo8O4-Y/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISTICAL PRIVATE FOUNDATION" + "title": "ARTH LIMITED" }, { "address": { - "address_line_1": "2nd Floor East Wing, Admiral Park", - "address_line_2": "St Peter Port", - "country": "Guernsey", - "locality": "Guernsey", - "postal_code": "GY1 3EL", - "premises": "Trafalgar Court" + "address_line_1": "Cree Avenue", + "address_line_2": "Bishopbriggs", + "locality": "Glasgow", + "postal_code": "G64 1XG", + "premises": "24" }, - "address_snippet": "Trafalgar Court, 2nd Floor East Wing, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", + "address_snippet": "24 Cree Avenue, Bishopbriggs, Glasgow, G64 1XG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1036,24 +1016,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ll7AOuaKTHT-8ZVbGY3Gw479cbg/appointments" + "self": "/officers/hV__HjvRTKgzF0erpG_akx4tqsY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS SECRETARIES LIMITED" + "title": "EVERYDAY MARTIAL ARTS C.I.C." }, { "address": { - "address_line_1": "153", - "address_line_2": "Great Ancoats Street", - "country": "England", - "locality": "Manchester", - "postal_code": "M4 6DT", - "premises": "176" + "address_line_1": "Ajeltake Road", + "address_line_2": "Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1061,23 +1041,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/m9DwcG3Oeoxz1LqlimosKQaUFaw/appointments" + "self": "/officers/hfpd1tWZD2t_eLdI70d-fkwJc-g/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOUSE 101 LTD" + "title": "ARTIONIS LTD" }, { "address": { - "address_line_1": "57 St James's Street", - "country": "England", - "locality": "London", - "postal_code": "SW1A 1LD", - "premises": "Cassini House" + "address_line_1": "Westwood Park", + "country": "United Kingdom", + "locality": "Wigan", + "postal_code": "WN3 4HE", + "premises": "Unity House, Suite 888" }, - "address_snippet": "Cassini House, 57 St James's Street, London, England, SW1A 1LD", + "address_snippet": "Unity House, Suite 888, Westwood Park, Wigan, United Kingdom, WN3 4HE", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1085,24 +1065,21 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/n3HbVbjk3KZDV0ZpjecSJuFPWAw/appointments" + "self": "/officers/hjNgfqpIX_pyuTybEtSZ9NYxrCA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS FUND MANAGERS LIMITED" + "title": "ARTEMIS INVESTMENT MANAGERS LTD" }, { "address": { - "address_line_1": "45-51 Newhall Street", - "address_line_2": "Office 330", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "country": "Slovakia", + "locality": "Slovakia", + "premises": "72 Strihovce" }, - "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "72 Strihovce, Slovakia, Slovakia", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1110,13 +1087,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/nqh8sl76eTjCdOycfszBVpEnhuE/appointments" + "self": "/officers/hsi0RqCDk9_KwzTBE80tJaNY4C4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "INSPIRATION OF ART DEVELOPMENT S.R.O." }, { "address": { @@ -1134,7 +1111,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/nzdUQmWr1THO-OswRdbjlggv5tQ/appointments" + "self": "/officers/i-XGNRQ6M0agazp2lhVQes6BI40/appointments" }, "matches": { "snippet": [] @@ -1144,11 +1121,13 @@ }, { "address": { - "address_line_1": "36 Dalmeny Street", - "locality": "Edinburgh", - "postal_code": "EH6 8RG" + "address_line_1": "Webb's Road", + "country": "England", + "locality": "London", + "postal_code": "SW11 6SE", + "premises": "60" }, - "address_snippet": "36 Dalmeny Street, Edinburgh, EH6 8RG", + "address_snippet": "60 Webb's Road, London, England, SW11 6SE", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1156,45 +1135,47 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/o77f1XwnMv5JfcrZyTgl-gHYNlc/appointments" + "self": "/officers/iMJ5GS2dwhu3ZsvWyYXkVPOBVmk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "OUT OF THE BLUE ARTS & EDUCATION TRUST" + "title": "ARTESIAN PROPERTY PARTNERSHIP" }, { "address": { - "address_line_1": "Bell Yard", - "country": "England", - "locality": "London", - "postal_code": "WC2A 2JR", - "premises": "7" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "7 Bell Yard, London, England, WC2A 2JR", - "appointment_count": 2, - "description": "Total number of appointments 2", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "appointment_count": 1, + "description": "Total number of appointments 1", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/rFGPfdOtSvaTVyufJndTlW027rA/appointments" + "self": "/officers/iOv_BjWbq3JsUunKZlNSwjICltI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTS & CULTURE FINANCE PARTNERS LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Plaza 2000, 10th Floor, 50th Street", - "country": "Republic Of Panama", - "locality": "Panama" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1202,13 +1183,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/rKUGOiqLWlCDn8AOvcktRzPLtVc/appointments" + "self": "/officers/kCSKk00C5O9OiJofbkAfy0UbvEA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { @@ -1226,7 +1207,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/rcMBNKiARMm5wfoawm_qnWDmio4/appointments" + "self": "/officers/sxdjNDdN0HTqxb8Po6GVHMSe6eM/appointments" }, "matches": { "snippet": [] @@ -1236,13 +1217,14 @@ }, { "address": { - "address_line_1": "Golders Green Road", - "country": "United Kingdom", - "locality": "London", - "postal_code": "NW11 9NN", - "premises": "Flat 6b" + "address_line_1": "New Brunswick Street", + "country": "England", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "Flat 6b, Golders Green Road, London, United Kingdom, NW11 9NN", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1250,24 +1232,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/rxM4QbRxBJwTC26gHsoYkqkAMsI/appointments" + "self": "/officers/txZUae6xjHy4I9XQ7fwy5yru2yI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHANEETI ADVISORS LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "New York Mill", - "address_line_2": "Summerbridge", - "locality": "Harrogate", - "postal_code": "HG3 4LA", - "premises": "Unit 16", - "region": "North Yorkshire" + "address_line_1": "Albert Hoy Street", + "country": "Belize", + "locality": "Belize City", + "premises": "2236" }, - "address_snippet": "Unit 16, New York Mill, Summerbridge, Harrogate, North Yorkshire, HG3 4LA", + "address_snippet": "2236 Albert Hoy Street, Belize City, Belize", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1275,23 +1255,51 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/cbqfyxB5BIJggu602YWO1PvQn0E/appointments" + "self": "/officers/vBgnRqpfc7T3WlEcdPLaPlnbLOc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CLARITY ARTS LIMITED" + "title": "ART BEST HOLDINGS LTD" }, { "address": { - "address_line_1": "Arundell Road", + "address_line_1": "Tong Street", + "address_line_2": "513 Tong St", + "care_of": "Mi5er Wormwood Scr00ge", "country": "England", - "locality": "Weston-Super-Mare", - "postal_code": "BS23 2QG", - "premises": "25b Arundell Road" + "locality": "Flat 5", + "po_box": "666", + "postal_code": "BD4 6NA", + "premises": "513", + "region": "Scr00geville" }, - "address_snippet": "25b Arundell Road, Arundell Road, Weston-Super-Mare, England, BS23 2QG", + "address_snippet": "Mi5er Wormwood Scr00ge, 666, 513 Tong Street, 513 Tong St, Flat 5, Scr00geville, England, BD4 6NA", + "appointment_count": 4, + "description": "Total number of appointments 4", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/vhWvgl07EP0X9me2CWhD4A71puY/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTFUL TWIST LTD." + }, + { + "address": { + "address_line_1": "The International House", + "country": "United Kingdom", + "locality": "Orpington", + "postal_code": "BR5 3RS", + "premises": "Office 10" + }, + "address_snippet": "Office 10, The International House, Orpington, United Kingdom, BR5 3RS", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1299,24 +1307,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/cwvQjq7r3C_vo5e7qCPNriHt-UU/appointments" + "self": "/officers/vveFkeVBt_-8cMzVBRyeDL_KYHw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "FRONT ROOM ARTS" + "title": "ARTHUR J LTD" }, { "address": { - "address_line_1": "153", - "address_line_2": "Great Ancoats Street", - "country": "England", - "locality": "Manchester", - "postal_code": "M4 6DT", - "premises": "176" + "address_line_1": "Akara Building 24 De Castro Street", + "address_line_2": "Wickams Cay 1 PO BOX 3136", + "country": "British Virgin Islands", + "locality": "Road Town", + "region": "Tortola" }, - "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", + "address_snippet": "Akara Building 24 De Castro Street, Wickams Cay 1 PO BOX 3136, Road Town, Tortola, British Virgin Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1324,23 +1331,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/d7I73iK0U2ygcRIkDXK5zawzNxk/appointments" + "self": "/officers/vyHAhNtpA3rmU9zX-8LiXc2BvMk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOUSE 101 LTD" + "title": "ARTAQ MANAGEMENT INC" }, { "address": { - "country": "Switzerland", - "locality": "Del Mont", - "postal_code": "2800", - "premises": "30 Rue De La Malt Re", - "region": "Jura" + "address_line_1": "Floor No 4, Suite No 401", + "country": "Panama", + "locality": "Panama City", + "premises": "Ricardo J. Alfaro Avenue, The Century Tower" }, - "address_snippet": "30 Rue De La Malt Re, Del Mont, Jura, Switzerland, 2800", + "address_snippet": "Ricardo J. Alfaro Avenue, The Century Tower, Floor No 4, Suite No 401, Panama City, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1348,23 +1354,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/dN7wZvVzfVpkHB9e0zsL9-ziur0/appointments" + "self": "/officers/xKJ62VJNFgZ_ZfbvzxAxbvwP56g/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "MONBOUAN ART & CARS SA" + "title": "ARTONA GROUP S.A." }, { "address": { - "address_line_1": "10 Salisbury Square", - "country": "England", + "address_line_1": "Cremorne Road", + "country": "United Kingdom", "locality": "London", - "postal_code": "EC4Y 8EH", - "premises": "St Bride's House" + "postal_code": "SW10 0NB", + "premises": "35" }, - "address_snippet": "St Bride's House, 10 Salisbury Square, London, England, EC4Y 8EH", + "address_snippet": "35 Cremorne Road, London, United Kingdom, SW10 0NB", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1372,13 +1378,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/dae8ZbDiSQ0laLJs1b6xOlgAz7s/appointments" + "self": "/officers/zrNEyoWQcx0hfoXQTAOBUL95DAQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ORCHARD FINE ART STORAGE SERVICES LIMITED" + "title": "FINE ART INVESTMENT RESEARCH LIMITED" }, { "address": { @@ -1396,7 +1402,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/gOctmCTTEgR7riXM7qIMGj-yGDE/appointments" + "self": "/officers/-oT4Aw4mGb0-x1MqImuzxg-vbxU/appointments" }, "matches": { "snippet": [] @@ -1406,14 +1412,14 @@ }, { "address": { - "address_line_1": "Foxhunter Drive", - "address_line_2": "Linford Wood", - "country": "United Kingdom", - "locality": "Milton Keynes", - "postal_code": "MK14 6GD", - "premises": "99 Milton Keynes Business Centre" + "address_line_1": "Centerville Road", + "address_line_2": "Suite 400, 19808", + "country": "Usa", + "locality": "Wilmington", + "premises": "2711", + "region": "Delaware" }, - "address_snippet": "99 Milton Keynes Business Centre, Foxhunter Drive, Linford Wood, Milton Keynes, United Kingdom, MK14 6GD", + "address_snippet": "2711 Centerville Road, Suite 400, 19808, Wilmington, Delaware, Usa", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1421,23 +1427,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/h660zZh1tUCSsiM4MPcppo8O4-Y/appointments" + "self": "/officers/-tMqEauxUwyrteObYdr4F5TqNwA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTH LIMITED" + "title": "ARTIS GROUP GP LLC" }, { "address": { - "address_line_1": "Cree Avenue", - "address_line_2": "Bishopbriggs", - "locality": "Glasgow", - "postal_code": "G64 1XG", - "premises": "24" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "24 Cree Avenue, Bishopbriggs, Glasgow, G64 1XG", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1445,24 +1451,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/hV__HjvRTKgzF0erpG_akx4tqsY/appointments" + "self": "/officers/0PIYRA6LSiAPqbo6EMwhmVQipEc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "EVERYDAY MARTIAL ARTS C.I.C." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ajeltake Road", - "address_line_2": "Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" + "address_line_1": "Queensferry Street", + "country": "United Kingdom", + "locality": "Edinburgh", + "postal_code": "EH2 4QW", + "premises": "18" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", + "address_snippet": "18 Queensferry Street, Edinburgh, United Kingdom, EH2 4QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1470,23 +1475,21 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/hfpd1tWZD2t_eLdI70d-fkwJc-g/appointments" + "self": "/officers/1ZNCIBxB3V0-mk7zhi5Ovx2Ne00/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTIONIS LTD" + "title": "UNIVERSAL ARTS (FESTIVAL) LTD" }, { "address": { - "address_line_1": "Westwood Park", - "country": "United Kingdom", - "locality": "Wigan", - "postal_code": "WN3 4HE", - "premises": "Unity House, Suite 888" + "address_line_1": "Plaza 2000, 10th Floor, 50th Street", + "country": "Republic Of Panama", + "locality": "Panama" }, - "address_snippet": "Unity House, Suite 888, Westwood Park, Wigan, United Kingdom, WN3 4HE", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1494,21 +1497,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/hjNgfqpIX_pyuTybEtSZ9NYxrCA/appointments" + "self": "/officers/217zA1LqqE6DHL9dJyo6BN1iFCY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS INVESTMENT MANAGERS LTD" + "title": "ARTONA HOLDING S.A." }, { "address": { - "country": "Slovakia", - "locality": "Slovakia", - "premises": "72 Strihovce" + "address_line_1": "Mount Street", + "country": "England", + "locality": "Manchester", + "postal_code": "M2 5FA", + "premises": "The Lexicon" }, - "address_snippet": "72 Strihovce, Slovakia, Slovakia", + "address_snippet": "The Lexicon, Mount Street, Manchester, England, M2 5FA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1516,13 +1521,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/hsi0RqCDk9_KwzTBE80tJaNY4C4/appointments" + "self": "/officers/23tEcE3SIEZXmEEhlvnsQRK27vk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "INSPIRATION OF ART DEVELOPMENT S.R.O." + "title": "ARTHUR CHAPMAN LIMITED" }, { "address": { @@ -1540,7 +1545,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/i-XGNRQ6M0agazp2lhVQes6BI40/appointments" + "self": "/officers/2A5IUGZQ8p9hjbe7JgSs3hnBdOw/appointments" }, "matches": { "snippet": [] @@ -1550,13 +1555,12 @@ }, { "address": { - "address_line_1": "Webb's Road", - "country": "England", - "locality": "London", - "postal_code": "SW11 6SE", - "premises": "60" + "address_line_1": "23 Calcutta Crescent Apapa", + "country": "Nigeria", + "locality": "Lagos", + "postal_code": "1111" }, - "address_snippet": "60 Webb's Road, London, England, SW11 6SE", + "address_snippet": "23 Calcutta Crescent Apapa, Lagos, Nigeria, 1111", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1564,23 +1568,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/iMJ5GS2dwhu3ZsvWyYXkVPOBVmk/appointments" + "self": "/officers/2lX52CrgredxX9smyaU2uIKMivE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTESIAN PROPERTY PARTNERSHIP" + "title": "ARTENAS AVIATION SERVICES LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Odessa Road", + "address_line_2": "London", + "country": "England", + "locality": "Forest Gate", + "postal_code": "E7 9BH", + "premises": "26a", + "region": "New Ham" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "26a, Odessa Road, London, Forest Gate, New Ham, England, E7 9BH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1588,23 +1594,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/iOv_BjWbq3JsUunKZlNSwjICltI/appointments" + "self": "/officers/3Kj0AtFMhwnmOgtu5mgMYq7-JN8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "PALOMA, RICHES, O'BRIEN ART & APPAREL LTD." }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Preston Parade", + "address_line_2": "Seasalter", + "country": "England", + "locality": "Whitstable", + "postal_code": "CT5 4AA", + "premises": "3", + "region": "Kent" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "3 Preston Parade, Seasalter, Whitstable, Kent, England, CT5 4AA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1612,23 +1620,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/kCSKk00C5O9OiJofbkAfy0UbvEA/appointments" + "self": "/officers/3UA2Hi65YTWTHmk3saGXPYBZcTo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "THE ENGLISH ARTISAN LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Old Gloucester Street", + "country": "United Kingdom", + "locality": "London", + "postal_code": "WC1N 3AX", + "premises": "27", + "region": "London" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "27 Old Gloucester Street, London, London, United Kingdom, WC1N 3AX", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1636,25 +1645,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/GHIu3BY0kXQlTGKtwJna8Vhn_Yk/appointments" + "self": "/officers/3jZ4l2hLP96C1yfFrYqIMwyL0_U/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "OPENMIND ART LTD" }, { "address": { - "address_line_1": "Admiral Park", - "address_line_2": "St Peter Port", - "country": "Guernsey", - "locality": "Guernsey", - "po_box": "PO BOX 100", - "postal_code": "GY1 3EL", - "premises": "Trafalgar Court" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "PO BOX 100, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1662,24 +1669,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/GKpIDs9RNKJngnP62NrL9tp-Qio/appointments" + "self": "/officers/3okXfzaHNcC0YFJMovGQBFvSWWE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS SECRETARIES LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Brimington Road", - "country": "United Kingdom", - "locality": "Chesterfield", - "postal_code": "S41 7UG", - "premises": "Infinity Martial Arts", - "region": "Derbyshire" + "address_line_1": "2nd Floor East Wing", + "address_line_2": "Admiral Park", + "country": "Channel Islands", + "locality": "St Peter Port", + "postal_code": "GY1 3EL", + "premises": "Trafalgar Court", + "region": "Guernsey" }, - "address_snippet": "Infinity Martial Arts, Brimington Road, Chesterfield, Derbyshire, United Kingdom, S41 7UG", + "address_snippet": "Trafalgar Court, 2nd Floor East Wing, Admiral Park, St Peter Port, Guernsey, Channel Islands, GY1 3EL", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1687,23 +1695,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/Gkc4hFBaFVrSnYogWkwbBl6346Y/appointments" + "self": "/officers/4kgNC-qYuHaORMPTUwGygOQwtdU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "INFINITY MARTIAL ARTS CHESTERFIELD LIMITED" + "title": "ARTEMIS CORPORATE SERVICES LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "London Road", + "address_line_2": "West Kingsdown", + "locality": "Sevenoaks", + "postal_code": "TN15 6AR", + "premises": "Kings Lodge", + "region": "Kent" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Kings Lodge, London Road, West Kingsdown, Sevenoaks, Kent, TN15 6AR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1711,23 +1720,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/H2Z4NnPtgWQzXiqNgJBG7g8lmm4/appointments" + "self": "/officers/5aKG7NyJtWxSJsNzGKPsyzufNeE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "MANOS ART LIMITED" }, { "address": { - "address_line_1": "New Road", - "country": "Belize", - "locality": "Belize City", - "postal_code": "NA", - "premises": "7" + "address_line_1": "1 -3 Brixton Road", + "country": "United Kingdom", + "locality": "London", + "postal_code": "SW9 6DE", + "premises": "Kennington Park 3.43 Canterbury Court" }, - "address_snippet": "7 New Road, Belize City, Belize, NA", + "address_snippet": "Kennington Park 3.43 Canterbury Court, 1 -3 Brixton Road, London, United Kingdom, SW9 6DE", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1735,24 +1744,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/HyaUAXMFK4QllxtAlwVSiuvqUTY/appointments" + "self": "/officers/6Bfho9F0VdCGIUUpwLoKcAXG99A/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEX LOGAN INC." + "title": "ARTECH 2000 LTD" }, { "address": { - "address_line_1": "New Brunswick Street", + "address_line_1": "Lytton Road", "country": "United Kingdom", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "locality": "Barnet", + "postal_code": "EN5 5BY", + "premises": "42" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", + "address_snippet": "42 Lytton Road, Barnet, United Kingdom, EN5 5BY", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1760,23 +1768,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/IaD5iGVuU3_J3fncw7zOk1H0jY4/appointments" + "self": "/officers/6fZEIZpD3v4Snd9qxp780Dz5b_o/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "KARMA ARTISTS LTD" }, { "address": { - "address_line_1": "Aspin Tower", - "address_line_2": "Sheikh Zayed Road", - "country": "United Arab Emirates", - "locality": "Dubai", - "premises": "1002-026" + "address_line_1": "50th Street", + "country": "Republic Of Panama", + "locality": "Panama", + "premises": "Plaza 2000 1oth Floor" }, - "address_snippet": "1002-026, Aspin Tower, Sheikh Zayed Road, Dubai, United Arab Emirates", + "address_snippet": "Plaza 2000 1oth Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1784,24 +1791,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/J4we7Ysj4aLvu6eycCONgKn-zUA/appointments" + "self": "/officers/cSAZFLyneoAdmPKcijrFAiaMglA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ZURANI FINE ARTS CONSULTANTS" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "Bank Chambers", - "address_line_2": "25 Jermyn Street", - "country": "England", - "locality": "London", - "postal_code": "SW1Y 6HR", - "premises": "14" + "address_line_1": "Plaza Banco General", + "address_line_2": "14th Floor", + "country": "Republic Of Panama", + "locality": "Panama", + "premises": "50 And Aquilino De La Guardia Streets" }, - "address_snippet": "14 Bank Chambers, 25 Jermyn Street, London, England, SW1Y 6HR", + "address_snippet": "50 And Aquilino De La Guardia Streets, Plaza Banco General, 14th Floor, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1809,23 +1815,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/JT8Hsh2saTMOmjZLB1LGyw5MC9Q/appointments" + "self": "/officers/kpRnO2kx7ekndaePNQ5Mr65NVDA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS BUSINESS CONSULTANTS LIMITED" + "title": "ARTISTICAL PRIVATE FOUNDATION" }, { "address": { - "address_line_1": "Granville Road", - "country": "United Kingdom", - "locality": "London", - "postal_code": "N12 0JG", - "premises": "49" + "address_line_1": "2nd Floor East Wing, Admiral Park", + "address_line_2": "St Peter Port", + "country": "Guernsey", + "locality": "Guernsey", + "postal_code": "GY1 3EL", + "premises": "Trafalgar Court" }, - "address_snippet": "49 Granville Road, London, United Kingdom, N12 0JG", + "address_snippet": "Trafalgar Court, 2nd Floor East Wing, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1833,24 +1840,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/JbxtRNzkj_zEivV1BC-RJp5O6kg/appointments" + "self": "/officers/ll7AOuaKTHT-8ZVbGY3Gw479cbg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ABIDA ARTISTS UK LTD" + "title": "ARTEMIS SECRETARIES LIMITED" }, { "address": { - "address_line_1": "Market Place", + "address_line_1": "153", + "address_line_2": "Great Ancoats Street", "country": "England", - "locality": "Swaffham", - "postal_code": "PE37 7QH", - "premises": "Clenshaw Minns 22-24", - "region": "Norfolk" + "locality": "Manchester", + "postal_code": "M4 6DT", + "premises": "176" }, - "address_snippet": "Clenshaw Minns 22-24, Market Place, Swaffham, Norfolk, England, PE37 7QH", + "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1858,24 +1865,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/K0pDkRi4JnhYw8CUTTiJF28pP4o/appointments" + "self": "/officers/m9DwcG3Oeoxz1LqlimosKQaUFaw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "WESTACRE ARTS FOUNDATION LIMITED" + "title": "ARTHOUSE 101 LTD" }, { "address": { - "address_line_1": "Fore Street Avenue", - "address_line_2": "C/O Praxis", + "address_line_1": "57 St James's Street", "country": "England", "locality": "London", - "postal_code": "EC2Y 9DT", - "premises": "1" + "postal_code": "SW1A 1LD", + "premises": "Cassini House" }, - "address_snippet": "1 Fore Street Avenue, C/O Praxis, London, England, EC2Y 9DT", + "address_snippet": "Cassini House, 57 St James's Street, London, England, SW1A 1LD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1883,25 +1889,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/K1JfdWSl-hWTEbrHvBuJMgEkMf0/appointments" + "self": "/officers/n3HbVbjk3KZDV0ZpjecSJuFPWAw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTIRIX LTD" + "title": "ARTEMIS FUND MANAGERS LIMITED" }, { "address": { - "address_line_1": "Strathearn Avenue", - "address_line_2": "Whitton", + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", "country": "United Kingdom", - "locality": "Twickenham", - "postal_code": "TW2 6JT", - "premises": "29", - "region": "Middlesex" + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1909,25 +1914,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/LzqoVQZiCXOahEYkmsk5xnt8MuU/appointments" + "self": "/officers/nqh8sl76eTjCdOycfszBVpEnhuE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "EDGAR & ARTHUR LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "York House", - "address_line_2": "20 York Street", - "country": "United Kingdom", - "locality": "Manchester", - "postal_code": "M2 3BB", - "premises": "C/O Williamson & Croft", - "region": "Greater Manchester" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "C/O Williamson & Croft, York House, 20 York Street, Manchester, Greater Manchester, United Kingdom, M2 3BB", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1935,23 +1938,21 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/MCzUzIfogDWBkOgGQHKO-_nlkac/appointments" + "self": "/officers/nzdUQmWr1THO-OswRdbjlggv5tQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTISAN PROPERTY DEVELOPMENTS LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "36 Dalmeny Street", + "locality": "Edinburgh", + "postal_code": "EH6 8RG" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "36 Dalmeny Street, Edinburgh, EH6 8RG", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1959,23 +1960,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/MaIRkc2Pc90JbTGSHBFbCfbCkyg/appointments" + "self": "/officers/o77f1XwnMv5JfcrZyTgl-gHYNlc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "OUT OF THE BLUE ARTS & EDUCATION TRUST" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", + "address_line_1": "Ajeltake Road, Ajeltake Island,", "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "locality": "Mh96960", + "premises": "Trust Company Complex,", + "region": "Majuro" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Trust Company Complex,, Ajeltake Road, Ajeltake Island,, Mh96960, Majuro, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1983,24 +1984,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/-oT4Aw4mGb0-x1MqImuzxg-vbxU/appointments" + "self": "/officers/QQPLnAHFzeK7K-Xxcalr00vA0Ac/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTHUR BUSINESS LTD" }, { "address": { - "address_line_1": "Centerville Road", - "address_line_2": "Suite 400, 19808", - "country": "Usa", - "locality": "Wilmington", - "premises": "2711", - "region": "Delaware" + "country": "England", + "locality": "London", + "postal_code": "W2 3BH", + "premises": "Flat 403, 18 Leinster Gardens" }, - "address_snippet": "2711 Centerville Road, Suite 400, 19808, Wilmington, Delaware, Usa", + "address_snippet": "Flat 403, 18 Leinster Gardens, London, England, W2 3BH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2008,23 +2007,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/-tMqEauxUwyrteObYdr4F5TqNwA/appointments" + "self": "/officers/R8SnRATl9Qgu9QSoDeqnawiKnLs/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTIS GROUP GP LLC" + "title": "CREATIVE CONSORTIUM ARTS HOLDINGS LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "1st Floor, 31 Victoria Street", + "country": "Bermuda", + "locality": "Hamilton", + "premises": "Victoria Place", + "region": "Hm 10" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Victoria Place, 1st Floor, 31 Victoria Street, Hamilton, Hm 10, Bermuda", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2032,23 +2031,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/0PIYRA6LSiAPqbo6EMwhmVQipEc/appointments" + "self": "/officers/STAZRFro7Jo8TZI04JFd-CodB5E/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "GLOBAL ARTIS LTD." }, { "address": { - "address_line_1": "Queensferry Street", + "address_line_1": "New Brunswick Street", "country": "United Kingdom", - "locality": "Edinburgh", - "postal_code": "EH2 4QW", - "premises": "18" + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "18 Queensferry Street, Edinburgh, United Kingdom, EH2 4QW", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2056,21 +2056,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/1ZNCIBxB3V0-mk7zhi5Ovx2Ne00/appointments" + "self": "/officers/SgovMTuF-JkZsZsaMDv1-GLP2yA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "UNIVERSAL ARTS (FESTIVAL) LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Plaza 2000, 10th Floor, 50th Street", - "country": "Republic Of Panama", - "locality": "Panama" + "address_line_1": "Boulevard De La Madeleine", + "country": "France", + "locality": "Paris", + "postal_code": "75001", + "premises": "9" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "address_snippet": "9 Boulevard De La Madeleine, Paris, France, 75001", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2078,23 +2080,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/217zA1LqqE6DHL9dJyo6BN1iFCY/appointments" + "self": "/officers/Shw5psPVa6aDNam32jL24S1_mwc/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTNOVA" }, { "address": { - "address_line_1": "Mount Street", - "country": "England", - "locality": "Manchester", - "postal_code": "M2 5FA", - "premises": "The Lexicon" + "address_line_1": "Union Court Building", + "address_line_2": "Elizabeth And Shirley Streets", + "locality": "Nassau", + "premises": "Suite E-2", + "region": "Bahamas" }, - "address_snippet": "The Lexicon, Mount Street, Manchester, England, M2 5FA", + "address_snippet": "Suite E-2, Union Court Building, Elizabeth And Shirley Streets, Nassau, Bahamas", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2102,23 +2104,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/23tEcE3SIEZXmEEhlvnsQRK27vk/appointments" + "self": "/officers/T8O9iUeFwtxTparA6b-C2djdh6Q/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHUR CHAPMAN LIMITED" + "title": "ARTNELL ASSOCIATES LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Oakhurst House", + "address_line_2": "77 Mount Ephraim", + "country": "England", + "locality": "Tunbridge Wells", + "postal_code": "TN4 8BS", + "premises": "Ground Floor", + "region": "Kent" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Ground Floor, Oakhurst House, 77 Mount Ephraim, Tunbridge Wells, Kent, England, TN4 8BS", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2126,22 +2130,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/2A5IUGZQ8p9hjbe7JgSs3hnBdOw/appointments" + "self": "/officers/TH0qqHj7pcaxhn_HrbjKs8x9iQY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "BRYANT ARTISTS LTD" }, { "address": { - "address_line_1": "23 Calcutta Crescent Apapa", - "country": "Nigeria", - "locality": "Lagos", - "postal_code": "1111" + "address_line_1": "1 Station Hill", + "country": "United Kingdom", + "locality": "Reading", + "postal_code": "RG1 1NB", + "premises": "4th Floor Phoenix House", + "region": "Berkshire" }, - "address_snippet": "23 Calcutta Crescent Apapa, Lagos, Nigeria, 1111", + "address_snippet": "4th Floor Phoenix House, 1 Station Hill, Reading, Berkshire, United Kingdom, RG1 1NB", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2149,25 +2155,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/2lX52CrgredxX9smyaU2uIKMivE/appointments" + "self": "/officers/UdVdTo9u3JXG-6_SJ4H0NaKtR_I/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTENAS AVIATION SERVICES LTD" + "title": "ARTISAN PARTNERS II LIMITED" }, { "address": { - "address_line_1": "Odessa Road", - "address_line_2": "London", - "country": "England", - "locality": "Forest Gate", - "postal_code": "E7 9BH", - "premises": "26a", - "region": "New Ham" + "address_line_1": "Argyll Street", + "care_of": "Palladium House", + "country": "United Kingdom", + "locality": "London", + "postal_code": "W1F 7LD", + "premises": "1-4" }, - "address_snippet": "26a, Odessa Road, London, Forest Gate, New Ham, England, E7 9BH", + "address_snippet": "Palladium House, 1-4, Argyll Street, London, United Kingdom, W1F 7LD", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2175,50 +2180,49 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/3Kj0AtFMhwnmOgtu5mgMYq7-JN8/appointments" + "self": "/officers/UzdZ8h7ETaDsop_r3dNxGXO3oqw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "PALOMA, RICHES, O'BRIEN ART & APPAREL LTD." + "title": "MORAN ART LIMITED" }, { "address": { - "address_line_1": "Preston Parade", - "address_line_2": "Seasalter", - "country": "England", - "locality": "Whitstable", - "postal_code": "CT5 4AA", - "premises": "3", - "region": "Kent" + "address_line_1": "South Norwood", + "country": "United Kingdom", + "locality": "London", + "postal_code": "SE25 4LB", + "premises": "6 Brocklesby Road" }, - "address_snippet": "3 Preston Parade, Seasalter, Whitstable, Kent, England, CT5 4AA", - "appointment_count": 1, - "description": "Total number of appointments 1", + "address_snippet": "6 Brocklesby Road, South Norwood, London, United Kingdom, SE25 4LB", + "appointment_count": 2, + "description": "Total number of appointments 2", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/3UA2Hi65YTWTHmk3saGXPYBZcTo/appointments" + "self": "/officers/7bzpVzakUVGlztD1c9ZfJrX62fI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "THE ENGLISH ARTISAN LTD" + "title": "ARTFUL DESIGN LIMITED" }, { "address": { - "address_line_1": "Old Gloucester Street", + "address_line_1": "Strathearn Avenue", + "address_line_2": "Whitton", "country": "United Kingdom", - "locality": "London", - "postal_code": "WC1N 3AX", - "premises": "27", - "region": "London" + "locality": "Twickenham", + "postal_code": "TW2 6JT", + "premises": "29", + "region": "Middlesex" }, - "address_snippet": "27 Old Gloucester Street, London, London, United Kingdom, WC1N 3AX", + "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2226,13 +2230,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/3jZ4l2hLP96C1yfFrYqIMwyL0_U/appointments" + "self": "/officers/7zEfVXEwh5WeLT0FnwHmcip8euU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "OPENMIND ART LTD" + "title": "EDGAR & ARTHUR LTD" }, { "address": { @@ -2250,7 +2254,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/3okXfzaHNcC0YFJMovGQBFvSWWE/appointments" + "self": "/officers/9IWWytbiv0Vv_o-8WqHI-x6DQqw/appointments" }, "matches": { "snippet": [] @@ -2260,15 +2264,13 @@ }, { "address": { - "address_line_1": "2nd Floor East Wing", - "address_line_2": "Admiral Park", - "country": "Channel Islands", - "locality": "St Peter Port", - "postal_code": "GY1 3EL", - "premises": "Trafalgar Court", - "region": "Guernsey" + "address_line_1": "New Brunswick Street", + "country": "United Kingdom", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House," }, - "address_snippet": "Trafalgar Court, 2nd Floor East Wing, Admiral Park, St Peter Port, Guernsey, Channel Islands, GY1 3EL", + "address_snippet": "Waterfront House,, New Brunswick Street, Wakefield, United Kingdom, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2276,24 +2278,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/4kgNC-qYuHaORMPTUwGygOQwtdU/appointments" + "self": "/officers/A2ye6jBvT1cxyMwoxNx7TEYcAN4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS CORPORATE SERVICES LIMITED" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "London Road", - "address_line_2": "West Kingsdown", - "locality": "Sevenoaks", - "postal_code": "TN15 6AR", - "premises": "Kings Lodge", - "region": "Kent" + "address_line_1": "10th Floor, 50th Street", + "country": "Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "Kings Lodge, London Road, West Kingsdown, Sevenoaks, Kent, TN15 6AR", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2301,23 +2301,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/5aKG7NyJtWxSJsNzGKPsyzufNeE/appointments" + "self": "/officers/AW5019Q2GR82RB-3CtbOWWlBwZU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "MANOS ART LIMITED" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "1 -3 Brixton Road", - "country": "United Kingdom", - "locality": "London", - "postal_code": "SW9 6DE", - "premises": "Kennington Park 3.43 Canterbury Court" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Kennington Park 3.43 Canterbury Court, 1 -3 Brixton Road, London, United Kingdom, SW9 6DE", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2325,23 +2325,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/6Bfho9F0VdCGIUUpwLoKcAXG99A/appointments" + "self": "/officers/Abi7kh_K2JnycP5SLAWqpL9XnGU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTECH 2000 LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Lytton Road", - "country": "United Kingdom", - "locality": "Barnet", - "postal_code": "EN5 5BY", - "premises": "42" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "42 Lytton Road, Barnet, United Kingdom, EN5 5BY", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2349,23 +2349,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/6fZEIZpD3v4Snd9qxp780Dz5b_o/appointments" + "self": "/officers/Cso5VFOq0a8xlL37_tdPjGuQYU4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "KARMA ARTISTS LTD" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Wickhams Cay 1", + "country": "British Virgin Islands", + "locality": "Road Town", + "premises": "Vanterpool Plaza Bldg", + "region": "Tortola" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Vanterpool Plaza Bldg, Wickhams Cay 1, Road Town, Tortola, British Virgin Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2373,24 +2373,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/sxdjNDdN0HTqxb8Po6GVHMSe6eM/appointments" + "self": "/officers/DDUg-0bF9gxrV4Q265PiFNQ3N44/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "VALDEN FINE ARTS LTD" }, { "address": { - "address_line_1": "New Brunswick Street", - "country": "England", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "address_line_1": "Llanbedr Airfield", + "country": "Wales", + "locality": "Llanbedr", + "postal_code": "LL45 2PX", + "premises": "Building 278" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", + "address_snippet": "Building 278, Llanbedr Airfield, Llanbedr, Wales, LL45 2PX", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2398,23 +2397,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/txZUae6xjHy4I9XQ7fwy5yru2yI/appointments" + "self": "/officers/OkarDd_hamCuVgMJOyWYdClAb44/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "ARTRO PROPERTY SERVICES LTD" }, { "address": { - "address_line_1": "Deacons Court", - "country": "United Kingdom", - "locality": "Northampton", - "postal_code": "NN3 5JX", - "premises": "2" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "2 Deacons Court, Northampton, United Kingdom, NN3 5JX", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2422,13 +2421,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/uN3gl3Y5RvR-xW4vx9e80DAM3t4/appointments" + "self": "/officers/Q135yovyN0uahaaBI4PBPE8TeM4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ART INVEST TOUR LTD" + "title": "ARTA CONSULTING LTD" } ], "items_per_page": 100, @@ -2446,16 +2445,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:16 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=185BB5C868A3A7ABA92B601958005436; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=3F9A139F8D1D0110E549F5DF398E825A; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "563", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "565", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_36e11bb5.json b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_36e11bb5.json index bba42d9..ecaec59 100644 --- a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_36e11bb5.json +++ b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_company/search_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_36e11bb5.json @@ -2,54 +2,6 @@ "content": { "json": { "items": [ - { - "address": { - "address_line_1": "Llanbedr Airfield", - "country": "Wales", - "locality": "Llanbedr", - "postal_code": "LL45 2PX", - "premises": "Building 278" - }, - "address_snippet": "Building 278, Llanbedr Airfield, Llanbedr, Wales, LL45 2PX", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/OkarDd_hamCuVgMJOyWYdClAb44/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTRO PROPERTY SERVICES LTD" - }, - { - "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" - }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/Q135yovyN0uahaaBI4PBPE8TeM4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, { "address": { "address_line_1": "Bell Yard", @@ -170,62 +122,13 @@ }, { "address": { - "address_line_1": "45 Newhall Street", - "address_line_2": "Suite 211", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" - }, - "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/Nk4_sg_6hS4F6zzA20Wqs4aaF4c/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Union Court Building", - "address_line_2": "Elizabeth And Shirley Streets", - "country": "Bahrain", - "locality": "Nassau", - "premises": "Suite E-2" - }, - "address_snippet": "Suite E-2, Union Court Building, Elizabeth And Shirley Streets, Nassau, Bahrain", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/OUPg2TQHhRuckL85kMP34fSG5xA/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTNELL ASSOCIATES LTD." - }, - { - "address": { - "address_line_1": "Levenshulme Trading Estate, Printworks Lane", - "country": "England", - "locality": "Manchester", - "postal_code": "M19 3JP", - "premises": "Calico House" + "address_line_1": "New Road", + "locality": "Lancaster", + "postal_code": "LA1 1EZ", + "premises": "C/O Clb Coopers Fleet House", + "region": "Lancashire" }, - "address_snippet": "Calico House, Levenshulme Trading Estate, Printworks Lane, Manchester, England, M19 3JP", + "address_snippet": "C/O Clb Coopers Fleet House, New Road, Lancaster, Lancashire, LA1 1EZ", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -233,13 +136,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/PHD9mSXdJwSPid9kyKVMT2gdbN0/appointments" + "self": "/officers/kXQahhZ1nnel6M3Kde4z0Z6sSjQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "HOULDSWORTH BUSINESS & ARTS CENTRE NW LTD" + "title": "ARTDALE LIMITED" }, { "address": { @@ -257,7 +160,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/PnGiyrdfajhVGT2HOld8yHg96hU/appointments" + "self": "/officers/cbQiDPvlsWln-lCoBVMQsQccT14/appointments" }, "matches": { "snippet": [] @@ -265,55 +168,6 @@ "snippet": "", "title": "ARTA CONSULTING LTD" }, - { - "address": { - "address_line_1": "New Brunswick Street", - "country": "England", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" - }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/PsotoIsiw33otqfAVFQXbgU_h0M/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" - }, - { - "address": { - "address_line_1": "Hastskovagen", - "address_line_2": "282 95 Hassleholm", - "country": "Sweden", - "locality": "Sk?Ne Lan", - "premises": "39" - }, - "address_snippet": "39 Hastskovagen, 282 95 Hassleholm, Sk?Ne Lan, Sweden", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/cUjOGcX1rA-uOG2BRLW47e9hGo4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTEG AB" - }, { "address": { "address_line_1": "Old Steine", @@ -924,15 +778,14 @@ }, { "address": { - "address_line_1": "Admiral Park", - "country": "Guernsey", - "locality": "St Peter Port", - "po_box": "100", - "postal_code": "GY1 3EL", - "premises": "Trafalgar Court", - "region": "Guernsey" + "address_line_1": "45 Newhall Street", + "address_line_2": "Suite 211", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "100, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", + "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -940,25 +793,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/7NsBooRh9FDUiNenzBVkcxVkAUY/appointments" + "self": "/officers/Nk4_sg_6hS4F6zzA20Wqs4aaF4c/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS SECRETARIES LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "5-7 Park Street", - "address_line_2": "The Katfm Building", - "country": "United Kingdom", - "locality": "Ashford", - "postal_code": "TN24 8LR", - "premises": "Ashford Arts Centre", - "region": "Kent" + "address_line_1": "Union Court Building", + "address_line_2": "Elizabeth And Shirley Streets", + "country": "Bahrain", + "locality": "Nassau", + "premises": "Suite E-2" }, - "address_snippet": "Ashford Arts Centre, 5-7 Park Street, The Katfm Building, Ashford, Kent, United Kingdom, TN24 8LR", + "address_snippet": "Suite E-2, Union Court Building, Elizabeth And Shirley Streets, Nassau, Bahrain", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -966,23 +817,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/7WTnclVC0odc-AP35Rk1MrT8kI0/appointments" + "self": "/officers/OUPg2TQHhRuckL85kMP34fSG5xA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "KATFM COMMUNITY ARTS AND EDUCATION" + "title": "ARTNELL ASSOCIATES LTD." }, { "address": { - "address_line_1": "New Brunswick Street", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "address_line_1": "Levenshulme Trading Estate, Printworks Lane", + "country": "England", + "locality": "Manchester", + "postal_code": "M19 3JP", + "premises": "Calico House" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, WF1 5QW", + "address_snippet": "Calico House, Levenshulme Trading Estate, Printworks Lane, Manchester, England, M19 3JP", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -990,24 +841,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/8KzDd2vTCjF1H5f5IrGJv6PQdo4/appointments" + "self": "/officers/PHD9mSXdJwSPid9kyKVMT2gdbN0/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "HOULDSWORTH BUSINESS & ARTS CENTRE NW LTD" }, { "address": { - "address_line_1": "Cresthill Farm", - "address_line_2": "Lower Froyle", - "country": " ", - "locality": "Alton", - "postal_code": "GU34 4LP", - "region": " " + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Cresthill Farm, Lower Froyle, Alton, GU34 4LP", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1015,23 +865,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/8edHAbEWICpjFkWfPnjZl_kJFmQ/appointments" + "self": "/officers/PnGiyrdfajhVGT2HOld8yHg96hU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTLAND LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Chapel Street", + "address_line_1": "New Brunswick Street", "country": "England", - "locality": "Liverpool", - "postal_code": "L3 9AG", - "premises": "20" + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "20 Chapel Street, Liverpool, England, L3 9AG", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1039,24 +890,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/9htgAV0BV2UR_VIaZNESN6UW3wY/appointments" + "self": "/officers/PsotoIsiw33otqfAVFQXbgU_h0M/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTIGAS VENTURES COMPANY LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "45 Newhall Street", - "address_line_2": "Suite 211", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1064,7 +914,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/9zhGibPjkxRxYTwHjrtytrSTSrA/appointments" + "self": "/officers/QfVRcLwwrooVeqZX4lIe2sWc89Q/appointments" }, "matches": { "snippet": [] @@ -1074,62 +924,14 @@ }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "address_line_2": "Suite 211", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" - }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Suite 211, Majuro, Marshall Islands, MH96960", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/AMzPRc0wulDiAbwYdKGtyqp0WhI/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Seven Sisters Road", - "country": "United Kingdom", - "locality": "London", - "postal_code": "N7 7QP", - "premises": "105" - }, - "address_snippet": "105 Seven Sisters Road, London, United Kingdom, N7 7QP", - "appointment_count": 1, - "description": "Total number of appointments 1", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/AhQVGte8YwxkfMI5fwAHrj4R5h4/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTEMIS FIFTEEN LTD" - }, - { - "address": { - "address_line_1": "Hanover Walk", - "address_line_2": "Leeds", - "locality": "West Yorkshire Ls3 1ab", - "postal_code": "LS3 1AB", - "premises": "C7 Josephs Well" + "address_line_1": "Heol Dewi Sant", + "address_line_2": "Bettws", + "country": "Wales", + "locality": "Bridgend", + "postal_code": "CF32 8SU", + "premises": "Sardis Media Centre" }, - "address_snippet": "C7 Josephs Well, Hanover Walk, Leeds, West Yorkshire Ls3 1ab, LS3 1AB", + "address_snippet": "Sardis Media Centre, Heol Dewi Sant, Bettws, Bridgend, Wales, CF32 8SU", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1137,23 +939,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/BcBBIaP_DexwXOhnt54oo9PyR9k/appointments" + "self": "/officers/SeunthYBman9t8WCrrJcfeXXIT8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "CHARTFORD ARTHINGTON LIMITED" + "title": "VALLEY & VALE COMMUNITY ARTS LIMITED" }, { "address": { - "address_line_1": "Purcell Estate", - "country": "British Virgin Islands", - "locality": "Road Town", - "premises": "Marcy Building, 2nd Floor", - "region": "Tortola" + "address_line_1": "45 Newhall Street, Suite 211", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "Marcy Building, 2nd Floor, Purcell Estate, Road Town, Tortola, British Virgin Islands", + "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1161,13 +963,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/CMqoX9HTRALHY3xSMZG8oKK5kdg/appointments" + "self": "/officers/TC9TCE0acpUnFhWmNrPtRtvxayU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNEG LTD." + "title": "ARTA CONSULTING LTD" }, { "address": { @@ -1175,9 +977,10 @@ "country": "United Kingdom", "locality": "Wakefield", "postal_code": "WF1 5QW", - "premises": "Waterfront House" + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, United Kingdom, WF1 5QW", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1185,7 +988,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/CgAOY_gt3N-YKv2UKJYb8aPpfVk/appointments" + "self": "/officers/TnifPJGTic1_omDZxxbZLM9d0VI/appointments" }, "matches": { "snippet": [] @@ -1195,13 +998,12 @@ }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Mapp Street", + "country": "Belize", + "locality": "Belize City", + "premises": "1" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "1 Mapp Street, Belize City, Belize", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1209,60 +1011,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/0wel2bxTO1w-e9X4keZ_GvUK9GQ/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ARTA CONSULTING LTD" - }, - { - "address": { - "address_line_1": "Express Networks 3", - "address_line_2": "6 Oldham Road", - "locality": "Manchester", - "postal_code": "M4 5DE" - }, - "address_snippet": "Express Networks 3, 6 Oldham Road, Manchester, M4 5DE", - "appointment_count": 2, - "description": "Total number of appointments 2", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/3VmKh6j11kZSmVLzj7ztTCF5_Uo/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "BASE ARTISAN LLP" - }, - { - "address": { - "address_line_1": "27 Lee Chung Street", - "country": "Hong Kong", - "locality": "Chai Wan", - "premises": "Unit 20, 5/F Tai King Ind. Bldg.", - "region": "Hong Kong" - }, - "address_snippet": "Unit 20, 5/F Tai King Ind. Bldg., 27 Lee Chung Street, Chai Wan, Hong Kong, Hong Kong", - "appointment_count": 2, - "description": "Total number of appointments 2", - "description_identifiers": [ - "appointment-count" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/5VjdFMFMyuWLxIM323Kj7TnJK-E/appointments" + "self": "/officers/UpmNjFvS0_k8MBkaITQtyivovs4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHUR & COMPANY (HK) LIMITED" + "title": "ART HOUSE INTERNATIONAL (AHI INT) LTD" }, { "address": { @@ -1280,7 +1035,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/5Z2gcKIDBweRyzB03c2UwEyJNuc/appointments" + "self": "/officers/UyWqzbaQaoFueaeh1YFP07v0aIY/appointments" }, "matches": { "snippet": [] @@ -1290,13 +1045,13 @@ }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "Hastskovagen", + "address_line_2": "282 95 Hassleholm", + "country": "Sweden", + "locality": "Sk?Ne Lan", + "premises": "39" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "39 Hastskovagen, 282 95 Hassleholm, Sk?Ne Lan, Sweden", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1304,13 +1059,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/cbQiDPvlsWln-lCoBVMQsQccT14/appointments" + "self": "/officers/cUjOGcX1rA-uOG2BRLW47e9hGo4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTEG AB" }, { "address": { @@ -1530,14 +1285,13 @@ }, { "address": { - "address_line_1": "New Brunswick Street", + "address_line_1": "65 Mabgate", "country": "England", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "locality": "Leeds", + "postal_code": "LS9 7DR", + "premises": "Hope House" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", + "address_snippet": "Hope House, 65 Mabgate, Leeds, England, LS9 7DR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1545,23 +1299,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/st2-xovTkvlPj6zGsoDWAJskcPI/appointments" + "self": "/officers/lCovVS9qotTQFM6NXNoJiYGW3iA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "MUSIC AND ARTS PRODUCTION LEEDS" }, { "address": { - "address_line_1": "Cabot Square", + "address_line_1": "153", + "address_line_2": "Great Ancoats Street", "country": "England", - "locality": "Canary Wharf", - "postal_code": "E14 9UE", - "premises": "25" + "locality": "Manchester", + "postal_code": "M4 6DT", + "premises": "176" }, - "address_snippet": "25 Cabot Square, Canary Wharf, England, E14 9UE", + "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1569,22 +1324,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/t7xU007cQl3ZLW9g8I1nTKnhiws/appointments" + "self": "/officers/nTW5ukoDYnulCwnFPTLFJU0cgIY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "JOHAN N\u00c9LSON FINE ART GALLERY LIMITED" + "title": "ARTHOUSE 101 LTD" }, { "address": { - "address_line_1": "Plaza 2000 10th Floor", - "address_line_2": "50th Street", - "country": "Panama", - "locality": "Panama" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "Plaza 2000 10th Floor, 50th Street, Panama, Panama", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1592,24 +1348,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/vQ_zDDbrY9iOken8kFXy41nR8vU/appointments" + "self": "/officers/ovG5_2iNioyHmWucBfcnrv95h0k/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Acland Street", + "address_line_1": "209/211 Ashley Road", + "address_line_2": "Hale", "country": "England", - "locality": "Gainsborough", - "postal_code": "DN21 2LN", - "premises": "4 Acland Street", - "region": "Lincolnshire" + "locality": "Altrincham", + "postal_code": "WA15 9SQ", + "premises": "Ollerburrow House" }, - "address_snippet": "4 Acland Street, Acland Street, Gainsborough, Lincolnshire, England, DN21 2LN", + "address_snippet": "Ollerburrow House, 209/211 Ashley Road, Hale, Altrincham, England, WA15 9SQ", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1617,22 +1373,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/vRj3vn_6MkhjQP812DCoONUcTwk/appointments" + "self": "/officers/puwacTnfZ5eG8rF_h6iSNWs9gSY/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "PERFORMING ARTS CLUB ST JOHNS CIC" + "title": "ARTHOG HOLDINGS LIMITED" }, { "address": { - "address_line_1": "10th Floor, 50th Street", - "country": "Republic Of Panama", - "locality": "Panama", - "premises": "Plaza 2000" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1640,23 +1397,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zLejRPHWExud-QKpDtm66Ms3Pgw/appointments" + "self": "/officers/qC7CzXhgfsTbvrw11K34xE7cm_k/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTONA HOLDING S.A." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "Ground Floor", - "address_line_2": "Blake Building, Corner Eyre & Hutson Streets", - "country": "Belize", - "locality": "Belize City", - "premises": "Suite 102" + "address_line_1": "1944 Kz", + "country": "Netherlands", + "locality": "Beverwijk", + "premises": "Gladiolenlaan 78" }, - "address_snippet": "Suite 102, Ground Floor, Blake Building, Corner Eyre & Hutson Streets, Belize City, Belize", + "address_snippet": "Gladiolenlaan 78, 1944 Kz, Beverwijk, Netherlands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1664,23 +1420,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zb8-7sl1o3YferLXuTYf06nHXE4/appointments" + "self": "/officers/qChK6rCvlDB4aHLZYK4gOdUrRmU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTLINK ENTERTAINMENT LTD." + "title": "ART 5 CONSULTANCY B.V" }, { "address": { - "address_line_1": "Union Court Building,", - "address_line_2": "Elizabeth And Shirley Street", - "locality": "Nassau", - "premises": "Suite E-2", - "region": "Bahamas" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "premises": "Trust Company Complex", + "region": "Mh96960" }, - "address_snippet": "Suite E-2, Union Court Building,, Elizabeth And Shirley Street, Nassau, Bahamas", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1688,23 +1444,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/zpN3jHQVeopNw6_PApqYOgaPRXo/appointments" + "self": "/officers/qolHsuzFJNuPFfLZrilY1h9Uqcw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTNELL ASSOCIATES LTD." + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "New Road", - "locality": "Lancaster", - "postal_code": "LA1 1EZ", - "premises": "C/O Clb Coopers Fleet House", - "region": "Lancashire" + "address_line_1": "Le Marchant Street", + "address_line_2": "St Peter Port", + "country": "Guernsey", + "locality": "Guernsey", + "postal_code": "GY1 4JH", + "premises": "Heritage Hall" }, - "address_snippet": "C/O Clb Coopers Fleet House, New Road, Lancaster, Lancashire, LA1 1EZ", + "address_snippet": "Heritage Hall, Le Marchant Street, St Peter Port, Guernsey, Guernsey, GY1 4JH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1712,23 +1469,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/kXQahhZ1nnel6M3Kde4z0Z6sSjQ/appointments" + "self": "/officers/rTsvr86vJdXgiHbxHnCsQBQgboE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTDALE LIMITED" + "title": "ARTEX RISK SOLUTIONS (GUERNSEY) LIMITED" }, { "address": { - "address_line_1": "65 Mabgate", - "country": "England", - "locality": "Leeds", - "postal_code": "LS9 7DR", - "premises": "Hope House" + "address_line_1": "Strathearn Avenue", + "address_line_2": "Whitton", + "country": "United Kingdom", + "locality": "Twickenham", + "postal_code": "TW2 6JT", + "premises": "29", + "region": "Middlesex" }, - "address_snippet": "Hope House, 65 Mabgate, Leeds, England, LS9 7DR", + "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1736,24 +1495,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/lCovVS9qotTQFM6NXNoJiYGW3iA/appointments" + "self": "/officers/sKMVUQ6hJTmRBpN1_9rhz9VbT9Y/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "MUSIC AND ARTS PRODUCTION LEEDS" + "title": "EDGAR & ARTHUR LTD" }, { "address": { - "address_line_1": "153", - "address_line_2": "Great Ancoats Street", + "address_line_1": "New Brunswick Street", "country": "England", - "locality": "Manchester", - "postal_code": "M4 6DT", - "premises": "176" + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "176 153, Great Ancoats Street, Manchester, England, M4 6DT", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1761,23 +1520,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/nTW5ukoDYnulCwnFPTLFJU0cgIY/appointments" + "self": "/officers/st2-xovTkvlPj6zGsoDWAJskcPI/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOUSE 101 LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" + "address_line_1": "Cabot Square", + "country": "England", + "locality": "Canary Wharf", + "postal_code": "E14 9UE", + "premises": "25" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", + "address_snippet": "25 Cabot Square, Canary Wharf, England, E14 9UE", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1785,22 +1544,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/ovG5_2iNioyHmWucBfcnrv95h0k/appointments" + "self": "/officers/t7xU007cQl3ZLW9g8I1nTKnhiws/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "JOHAN N\u00c9LSON FINE ART GALLERY LIMITED" }, { "address": { - "country": "Greece", - "locality": "Ios Island", - "postal_code": "84001", - "premises": "Ios Island" + "address_line_1": "Plaza 2000 10th Floor", + "address_line_2": "50th Street", + "country": "Panama", + "locality": "Panama" }, - "address_snippet": "Ios Island, Ios Island, Greece, 84001", + "address_snippet": "Plaza 2000 10th Floor, 50th Street, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1808,24 +1567,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/pfNu_HihzpjwKa-K3j0XzoHzBRM/appointments" + "self": "/officers/vQ_zDDbrY9iOken8kFXy41nR8vU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "DENAXAS S.A. ARTEMIS DENAXAS" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "209/211 Ashley Road", - "address_line_2": "Hale", + "address_line_1": "Acland Street", "country": "England", - "locality": "Altrincham", - "postal_code": "WA15 9SQ", - "premises": "Ollerburrow House" + "locality": "Gainsborough", + "postal_code": "DN21 2LN", + "premises": "4 Acland Street", + "region": "Lincolnshire" }, - "address_snippet": "Ollerburrow House, 209/211 Ashley Road, Hale, Altrincham, England, WA15 9SQ", + "address_snippet": "4 Acland Street, Acland Street, Gainsborough, Lincolnshire, England, DN21 2LN", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1833,13 +1592,84 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/puwacTnfZ5eG8rF_h6iSNWs9gSY/appointments" + "self": "/officers/vRj3vn_6MkhjQP812DCoONUcTwk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTHOG HOLDINGS LIMITED" + "title": "PERFORMING ARTS CLUB ST JOHNS CIC" + }, + { + "address": { + "address_line_1": "10th Floor, 50th Street", + "country": "Republic Of Panama", + "locality": "Panama", + "premises": "Plaza 2000" + }, + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/zLejRPHWExud-QKpDtm66Ms3Pgw/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTONA HOLDING S.A." + }, + { + "address": { + "address_line_1": "Ground Floor", + "address_line_2": "Blake Building, Corner Eyre & Hutson Streets", + "country": "Belize", + "locality": "Belize City", + "premises": "Suite 102" + }, + "address_snippet": "Suite 102, Ground Floor, Blake Building, Corner Eyre & Hutson Streets, Belize City, Belize", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/zb8-7sl1o3YferLXuTYf06nHXE4/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTLINK ENTERTAINMENT LTD." + }, + { + "address": { + "address_line_1": "Union Court Building,", + "address_line_2": "Elizabeth And Shirley Street", + "locality": "Nassau", + "premises": "Suite E-2", + "region": "Bahamas" + }, + "address_snippet": "Suite E-2, Union Court Building,, Elizabeth And Shirley Street, Nassau, Bahamas", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/zpN3jHQVeopNw6_PApqYOgaPRXo/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTNELL ASSOCIATES LTD." }, { "address": { @@ -1857,7 +1687,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/qC7CzXhgfsTbvrw11K34xE7cm_k/appointments" + "self": "/officers/0wel2bxTO1w-e9X4keZ_GvUK9GQ/appointments" }, "matches": { "snippet": [] @@ -1867,12 +1697,186 @@ }, { "address": { - "address_line_1": "1944 Kz", - "country": "Netherlands", - "locality": "Beverwijk", - "premises": "Gladiolenlaan 78" + "address_line_1": "Express Networks 3", + "address_line_2": "6 Oldham Road", + "locality": "Manchester", + "postal_code": "M4 5DE" + }, + "address_snippet": "Express Networks 3, 6 Oldham Road, Manchester, M4 5DE", + "appointment_count": 2, + "description": "Total number of appointments 2", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/3VmKh6j11kZSmVLzj7ztTCF5_Uo/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "BASE ARTISAN LLP" + }, + { + "address": { + "address_line_1": "27 Lee Chung Street", + "country": "Hong Kong", + "locality": "Chai Wan", + "premises": "Unit 20, 5/F Tai King Ind. Bldg.", + "region": "Hong Kong" + }, + "address_snippet": "Unit 20, 5/F Tai King Ind. Bldg., 27 Lee Chung Street, Chai Wan, Hong Kong, Hong Kong", + "appointment_count": 2, + "description": "Total number of appointments 2", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/5VjdFMFMyuWLxIM323Kj7TnJK-E/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTHUR & COMPANY (HK) LIMITED" + }, + { + "address": { + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" + }, + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/5Z2gcKIDBweRyzB03c2UwEyJNuc/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTA CONSULTING LTD" + }, + { + "address": { + "address_line_1": "Admiral Park", + "country": "Guernsey", + "locality": "St Peter Port", + "po_box": "100", + "postal_code": "GY1 3EL", + "premises": "Trafalgar Court", + "region": "Guernsey" + }, + "address_snippet": "100, Trafalgar Court, Admiral Park, St Peter Port, Guernsey, Guernsey, GY1 3EL", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/7NsBooRh9FDUiNenzBVkcxVkAUY/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTEMIS SECRETARIES LIMITED" + }, + { + "address": { + "address_line_1": "5-7 Park Street", + "address_line_2": "The Katfm Building", + "country": "United Kingdom", + "locality": "Ashford", + "postal_code": "TN24 8LR", + "premises": "Ashford Arts Centre", + "region": "Kent" + }, + "address_snippet": "Ashford Arts Centre, 5-7 Park Street, The Katfm Building, Ashford, Kent, United Kingdom, TN24 8LR", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/7WTnclVC0odc-AP35Rk1MrT8kI0/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "KATFM COMMUNITY ARTS AND EDUCATION" + }, + { + "address": { + "address_line_1": "New Brunswick Street", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" + }, + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, WF1 5QW", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/8KzDd2vTCjF1H5f5IrGJv6PQdo4/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "A.R.T. BUSINESS CONSULTING LIMITED" + }, + { + "address": { + "address_line_1": "Cresthill Farm", + "address_line_2": "Lower Froyle", + "country": " ", + "locality": "Alton", + "postal_code": "GU34 4LP", + "region": " " + }, + "address_snippet": "Cresthill Farm, Lower Froyle, Alton, GU34 4LP", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/8edHAbEWICpjFkWfPnjZl_kJFmQ/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ARTLAND LIMITED" + }, + { + "address": { + "address_line_1": "45 Newhall Street", + "address_line_2": "Suite 211", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "Gladiolenlaan 78, 1944 Kz, Beverwijk, Netherlands", + "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1880,23 +1884,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/qChK6rCvlDB4aHLZYK4gOdUrRmU/appointments" + "self": "/officers/9zhGibPjkxRxYTwHjrtytrSTSrA/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ART 5 CONSULTANCY B.V" + "title": "ARTA CONSULTING LTD" }, { "address": { "address_line_1": "Ajeltake Road, Ajeltake Island", + "address_line_2": "Suite 211", "country": "Marshall Islands", "locality": "Majuro", "postal_code": "MH96960", "premises": "Trust Company Complex" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Suite 211, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1904,7 +1909,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/QfVRcLwwrooVeqZX4lIe2sWc89Q/appointments" + "self": "/officers/AMzPRc0wulDiAbwYdKGtyqp0WhI/appointments" }, "matches": { "snippet": [] @@ -1914,14 +1919,13 @@ }, { "address": { - "address_line_1": "Heol Dewi Sant", - "address_line_2": "Bettws", - "country": "Wales", - "locality": "Bridgend", - "postal_code": "CF32 8SU", - "premises": "Sardis Media Centre" + "address_line_1": "Seven Sisters Road", + "country": "United Kingdom", + "locality": "London", + "postal_code": "N7 7QP", + "premises": "105" }, - "address_snippet": "Sardis Media Centre, Heol Dewi Sant, Bettws, Bridgend, Wales, CF32 8SU", + "address_snippet": "105 Seven Sisters Road, London, United Kingdom, N7 7QP", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1929,23 +1933,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/SeunthYBman9t8WCrrJcfeXXIT8/appointments" + "self": "/officers/AhQVGte8YwxkfMI5fwAHrj4R5h4/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "VALLEY & VALE COMMUNITY ARTS LIMITED" + "title": "ARTEMIS FIFTEEN LTD" }, { "address": { - "address_line_1": "45 Newhall Street, Suite 211", - "country": "United Kingdom", - "locality": "Birmingham", - "postal_code": "B3 3QR", - "premises": "Cornwall Buildings" + "address_line_1": "Hanover Walk", + "address_line_2": "Leeds", + "locality": "West Yorkshire Ls3 1ab", + "postal_code": "LS3 1AB", + "premises": "C7 Josephs Well" }, - "address_snippet": "Cornwall Buildings, 45 Newhall Street, Suite 211, Birmingham, United Kingdom, B3 3QR", + "address_snippet": "C7 Josephs Well, Hanover Walk, Leeds, West Yorkshire Ls3 1ab, LS3 1AB", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1953,24 +1957,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/TC9TCE0acpUnFhWmNrPtRtvxayU/appointments" + "self": "/officers/BcBBIaP_DexwXOhnt54oo9PyR9k/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "CHARTFORD ARTHINGTON LIMITED" }, { "address": { - "address_line_1": "New Brunswick Street", - "country": "United Kingdom", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "address_line_1": "Purcell Estate", + "country": "British Virgin Islands", + "locality": "Road Town", + "premises": "Marcy Building, 2nd Floor", + "region": "Tortola" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, United Kingdom, WF1 5QW", + "address_snippet": "Marcy Building, 2nd Floor, Purcell Estate, Road Town, Tortola, British Virgin Islands", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -1978,22 +1981,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/TnifPJGTic1_omDZxxbZLM9d0VI/appointments" + "self": "/officers/CMqoX9HTRALHY3xSMZG8oKK5kdg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "ARTNEG LTD." }, { "address": { - "address_line_1": "Mapp Street", - "country": "Belize", - "locality": "Belize City", - "premises": "1" + "address_line_1": "New Brunswick Street", + "country": "United Kingdom", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House" }, - "address_snippet": "1 Mapp Street, Belize City, Belize", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, United Kingdom, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2001,13 +2005,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/UpmNjFvS0_k8MBkaITQtyivovs4/appointments" + "self": "/officers/CgAOY_gt3N-YKv2UKJYb8aPpfVk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ART HOUSE INTERNATIONAL (AHI INT) LTD" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { @@ -2025,7 +2029,7 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/UyWqzbaQaoFueaeh1YFP07v0aIY/appointments" + "self": "/officers/cJrAjUDXWav18Lb9gyJSSIwnXSM/appointments" }, "matches": { "snippet": [] @@ -2035,13 +2039,13 @@ }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "premises": "Trust Company Complex", - "region": "Mh96960" + "address_line_1": "Kings Avenue", + "country": "United Kingdom", + "locality": "London", + "postal_code": "N21 3NA", + "premises": "1" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Mh96960, Marshall Islands", + "address_snippet": "1 Kings Avenue, London, United Kingdom, N21 3NA", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2049,24 +2053,25 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/qolHsuzFJNuPFfLZrilY1h9Uqcw/appointments" + "self": "/officers/NIHEZyDyvmJm3kQoZ1gt4Y9ehEE/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTURO MANAGEMENT LTD" }, { "address": { - "address_line_1": "Le Marchant Street", - "address_line_2": "St Peter Port", - "country": "Guernsey", - "locality": "Guernsey", - "postal_code": "GY1 4JH", - "premises": "Heritage Hall" + "address_line_1": "Montgomery Street", + "address_line_2": "Suite 200", + "country": "United States", + "locality": "San Francisco", + "postal_code": "94104", + "premises": "220", + "region": "California" }, - "address_snippet": "Heritage Hall, Le Marchant Street, St Peter Port, Guernsey, Guernsey, GY1 4JH", + "address_snippet": "220 Montgomery Street, Suite 200, San Francisco, California, United States, 94104", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2074,25 +2079,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/rTsvr86vJdXgiHbxHnCsQBQgboE/appointments" + "self": "/officers/76Hri_bsAqqjKClcNLK7lQ93_Fk/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEX RISK SOLUTIONS (GUERNSEY) LIMITED" + "title": "M. ARTHUR GENSLER JR. & ASSOCIATES, INC" }, { "address": { - "address_line_1": "Strathearn Avenue", - "address_line_2": "Whitton", - "country": "United Kingdom", - "locality": "Twickenham", - "postal_code": "TW2 6JT", - "premises": "29", - "region": "Middlesex" + "address_line_1": "Regent Street", + "country": "England", + "locality": "London", + "postal_code": "W1B 3HH", + "premises": "207" }, - "address_snippet": "29 Strathearn Avenue, Whitton, Twickenham, Middlesex, United Kingdom, TW2 6JT", + "address_snippet": "207 Regent Street, London, England, W1B 3HH", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2100,23 +2103,22 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/sKMVUQ6hJTmRBpN1_9rhz9VbT9Y/appointments" + "self": "/officers/bcJVw6H-f3oTMSNUNDAMxo4CNr8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "EDGAR & ARTHUR LTD" + "title": "SINO-UK ART ORGANISATION FOR SUSTAINABLE DEVELOPMENT CIC" }, { "address": { - "address_line_1": "Kings Avenue", - "country": "United Kingdom", - "locality": "London", - "postal_code": "N21 3NA", - "premises": "1" + "address_line_1": "10th Floor, 50th Street", + "country": "Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "1 Kings Avenue, London, United Kingdom, N21 3NA", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2124,48 +2126,46 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/NIHEZyDyvmJm3kQoZ1gt4Y9ehEE/appointments" + "self": "/officers/G0gGmyH-6AXLa7mP5K-0btm2tNg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTURO MANAGEMENT LTD" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "Ground Floor", - "address_line_2": "Blake Building", - "country": "Belize", - "locality": "Corner Eyre & Hutson Streets", - "premises": "Suite 102", - "region": "Belize City" + "address_line_1": "New Cavendish Street", + "country": "United Kingdom", + "locality": "London", + "postal_code": "W1G 8AU", + "premises": "72" }, - "address_snippet": "Suite 102, Ground Floor, Blake Building, Corner Eyre & Hutson Streets, Belize City, Belize", - "appointment_count": 1, - "description": "Total number of appointments 1", + "address_snippet": "72 New Cavendish Street, London, United Kingdom, W1G 8AU", + "appointment_count": 6, + "description": "Total number of appointments 6", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/qDa3Q5ukoV8B-_AxvdsNXF3TRnM/appointments" + "self": "/officers/GCwVPDMd_WeJLxc9rWE4HMkyb2A/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTAMAX TRADING LTD" + "title": "UNITED ARTISTS CORPORATION LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "10th Floor, 50th Street", + "country": "Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2173,23 +2173,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/VWaar_N8DwoSd9L5Fqxnsw4HX60/appointments" + "self": "/officers/GfTQYsDKQOb7-af-dD979cG93gg/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "Regent Street", - "country": "England", - "locality": "London", - "postal_code": "W1B 3HH", - "premises": "207" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "207 Regent Street, London, England, W1B 3HH", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2197,23 +2197,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/bcJVw6H-f3oTMSNUNDAMxo4CNr8/appointments" + "self": "/officers/H8W89VHXboQs7qfvTuW3gvih_QU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "SINO-UK ART ORGANISATION FOR SUSTAINABLE DEVELOPMENT CIC" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "28 Commercial Street", + "address_line_1": "303-306 High Holborn", "country": "United Kingdom", "locality": "London", - "postal_code": "E1 6AB", - "premises": "Toynbee Studios" + "postal_code": "WC1V 7JZ", + "premises": "2nd Floor, Northumberland House" }, - "address_snippet": "Toynbee Studios, 28 Commercial Street, London, United Kingdom, E1 6AB", + "address_snippet": "2nd Floor, Northumberland House, 303-306 High Holborn, London, United Kingdom, WC1V 7JZ", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2221,23 +2221,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/qO3NiwJJQffofJg-XEIgrk0GZGI/appointments" + "self": "/officers/HKCZvlOz2OMt0rIgmOAyv_b7iaU/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTSADMIN" + "title": "CLOSER ARTISTS LIMITED" }, { "address": { - "address_line_1": "Ajeltake Road, Ajeltake Island", - "country": "Marshall Islands", - "locality": "Majuro", - "postal_code": "MH96960", - "premises": "Trust Company Complex" + "address_line_1": "The Century Tower, Floor No 4", + "country": "Panama", + "locality": "Suite No 401", + "postal_code": "PANAMA CITY", + "premises": "Ricardo J. Alfaro Avenue," }, - "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", + "address_snippet": "Ricardo J. Alfaro Avenue,, The Century Tower, Floor No 4, Suite No 401, Panama, PANAMA CITY", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2245,25 +2245,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/yHjP47f9aJc7w1RqzsG8QtQzmCg/appointments" + "self": "/officers/HaXutkDkjAECPd4VjQdoQ0ojIoQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTA CONSULTING LTD" + "title": "ARTONA GROUP S.A." }, { "address": { - "address_line_1": "Montgomery Street", - "address_line_2": "Suite 200", - "country": "United States", - "locality": "San Francisco", - "postal_code": "94104", - "premises": "220", - "region": "California" + "address_line_1": "New Brunswick Street", + "country": "England", + "locality": "Wakefield", + "postal_code": "WF1 5QW", + "premises": "Waterfront House", + "region": "West Yorkshire" }, - "address_snippet": "220 Montgomery Street, Suite 200, San Francisco, California, United States, 94104", + "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2271,23 +2270,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/76Hri_bsAqqjKClcNLK7lQ93_Fk/appointments" + "self": "/officers/IBZOCrlizS4o-p6UKL3fPbMso40/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "M. ARTHUR GENSLER JR. & ASSOCIATES, INC" + "title": "A.R.T. BUSINESS CONSULTING LIMITED" }, { "address": { - "address_line_1": "Pasea Estate", - "country": "British Virgin Islands", - "locality": "Road Town", - "premises": "Morgan & Morgan Building", - "region": "Tortola" + "address_line_1": "Ajeltake Road, Ajeltake Island", + "country": "Marshall Islands", + "locality": "Majuro", + "postal_code": "MH96960", + "premises": "Trust Company Complex" }, - "address_snippet": "Morgan & Morgan Building, Pasea Estate, Road Town, Tortola, British Virgin Islands", + "address_snippet": "Trust Company Complex, Ajeltake Road, Ajeltake Island, Majuro, Marshall Islands, MH96960", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2295,24 +2294,21 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/pNIHx0hKSsG034rLrGlFAo3w8Co/appointments" + "self": "/officers/IngsEKkaeqLlOVSnXrAJhHWpjd8/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "JAMM - ART LIMITED" + "title": "ARTA CONSULTING LTD" }, { "address": { - "address_line_1": "New Brunswick Street", - "country": "England", - "locality": "Wakefield", - "postal_code": "WF1 5QW", - "premises": "Waterfront House", - "region": "West Yorkshire" + "country": "Belize", + "locality": "Belize City", + "premises": "1 Mapp Street" }, - "address_snippet": "Waterfront House, New Brunswick Street, Wakefield, West Yorkshire, England, WF1 5QW", + "address_snippet": "1 Mapp Street, Belize City, Belize", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2320,25 +2316,23 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/pfz3zb6cvP55XMJAY1Lr3jRmcio/appointments" + "self": "/officers/J9jUaVwUseUpXaTQaXrnLxeGets/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "A.R.T. BUSINESS CONSULTING LIMITED" + "title": "ARTONI MANAGEMENT LTD" }, { "address": { - "address_line_1": "Great North Road", - "address_line_2": "Wyboston", - "country": "England", - "locality": "Bedford", - "postal_code": "MK44 3BY", - "premises": "The Knowledge Centre, Wyboston Lakes", - "region": "Bedfordshire" + "address_line_1": "10th Floor", + "address_line_2": "50th Street", + "country": "Republic Of Panama", + "locality": "Panama", + "premises": "Plaza 2000" }, - "address_snippet": "The Knowledge Centre, Wyboston Lakes, Great North Road, Wyboston, Bedford, Bedfordshire, England, MK44 3BY", + "address_snippet": "Plaza 2000, 10th Floor, 50th Street, Panama, Republic Of Panama", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2346,25 +2340,24 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/pvBMEeJBZgJQ19XVJxAsH6JwDkM/appointments" + "self": "/officers/JUO3YfTq2uCC8HNGzxKER2j7Tyw/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ASHTON ARTISTS LIMITED" + "title": "ARTONA HOLDING S.A." }, { "address": { - "address_line_1": "London Road", - "address_line_2": "Blewbury", - "country": "England", - "locality": "Didcot", - "postal_code": "OX11 9PD", - "premises": "Little Triton", - "region": "Oxfordshire" + "address_line_1": "South Street", + "country": "United Kingdom", + "locality": "Bridport", + "postal_code": "DT6 3NR", + "premises": "Bridport Arts Centre", + "region": "Dorset" }, - "address_snippet": "Little Triton, London Road, Blewbury, Didcot, Oxfordshire, England, OX11 9PD", + "address_snippet": "Bridport Arts Centre, South Street, Bridport, Dorset, United Kingdom, DT6 3NR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2372,47 +2365,48 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/pzyDTIXw1Xt0JdqBIv2VDchgoKI/appointments" + "self": "/officers/JWPLh3wuxZ4VmNjwWkSydYxCZFo/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTELNA LIMITED" + "title": "BRIDPORT ARTS CENTRE LTD" }, { "address": { - "address_line_1": "Sydney Vane House", - "address_line_2": "Admiral Park St Peter Port", - "locality": "Guernsey", - "postal_code": "GY1 2HU", - "region": "Channelislands" + "address_line_1": "Bond Street", + "locality": "Wakefield", + "postal_code": "WF1 2QP", + "premises": "34", + "region": "West Yorkshire" }, - "address_snippet": "Sydney Vane House, Admiral Park St Peter Port, Guernsey, Channelislands, GY1 2HU", - "appointment_count": 3, - "description": "Total number of appointments 3", + "address_snippet": "34 Bond Street, Wakefield, West Yorkshire, WF1 2QP", + "appointment_count": 1, + "description": "Total number of appointments 1", "description_identifiers": [ "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/AiMV-hIf2OosTXLAKebPLoDlaxk/appointments" + "self": "/officers/KISBK3cAq3hQ_gvmq3YJCEukj8A/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS CORPORATE SERVICES LIMITED" + "title": "MIXED MARTIAL ARTS (MMA) LIMITED" }, { "address": { - "address_line_1": "Long Path Road", - "address_line_2": "Suite 2a", - "country": "British West Indies", - "locality": "Anguilla", - "premises": "212" + "address_line_1": "45-51 Newhall Street", + "address_line_2": "Office 330", + "country": "United Kingdom", + "locality": "Birmingham", + "postal_code": "B3 3QR", + "premises": "Cornwall Buildings" }, - "address_snippet": "212 Long Path Road, Suite 2a, Anguilla, British West Indies", + "address_snippet": "Cornwall Buildings, 45-51 Newhall Street, Office 330, Birmingham, United Kingdom, B3 3QR", "appointment_count": 1, "description": "Total number of appointments 1", "description_identifiers": [ @@ -2420,13 +2414,13 @@ ], "kind": "searchresults#officer", "links": { - "self": "/officers/C9831pfXl2mNVWzCaQDcevWagOg/appointments" + "self": "/officers/KmQJXJWeHUrZlIVAOYR2kBhACTQ/appointments" }, "matches": { "snippet": [] }, "snippet": "", - "title": "ARTEMIS CORPORATION" + "title": "ARTA CONSULTING LTD" } ], "items_per_page": 100, @@ -2444,16 +2438,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:06:16 GMT", + "date": "Thu, 18 Jun 2026 12:00:25 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4222B5E1A8CC8CBC0BEE687BEFEB5662; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=B4A13202C3DB292F42F24C46069975CE; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "562", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "564", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_director/search_get_data_0_items_per_page_200_q_Orlovs_start_index_0_3b2666a9.json b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_director/search_get_data_0_items_per_page_200_q_Orlovs_start_index_0_3b2666a9.json index 8f61c90..254098c 100644 --- a/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_director/search_get_data_0_items_per_page_200_q_Orlovs_start_index_0_3b2666a9.json +++ b/tests/resources/tests/test_search_api.py/TestGenericSearch/test_search_director/search_get_data_0_items_per_page_200_q_Orlovs_start_index_0_3b2666a9.json @@ -1430,13 +1430,18 @@ }, "address_snippet": "128 City Road, London, United Kingdom, EC1V 2NX", "appointment_count": 1, - "description": "Total number of appointments 1", + "date_of_birth": { + "month": 3, + "year": 1980 + }, + "description": "Total number of appointments 1 - Born March 1980", "description_identifiers": [ - "appointment-count" + "appointment-count", + "born-on" ], "kind": "searchresults#officer", "links": { - "self": "/officers/_PMexMkJMw9LfhIK2acCfiPijn4/appointments" + "self": "/officers/Kf3tDk4id4I8kRXJNdyxJ_uVbv4/appointments" }, "title": "Stoyan ORLOVSKI" }, @@ -1450,18 +1455,13 @@ }, "address_snippet": "128 City Road, London, United Kingdom, EC1V 2NX", "appointment_count": 1, - "date_of_birth": { - "month": 3, - "year": 1980 - }, - "description": "Total number of appointments 1 - Born March 1980", + "description": "Total number of appointments 1", "description_identifiers": [ - "appointment-count", - "born-on" + "appointment-count" ], "kind": "searchresults#officer", "links": { - "self": "/officers/Kf3tDk4id4I8kRXJNdyxJ_uVbv4/appointments" + "self": "/officers/_PMexMkJMw9LfhIK2acCfiPijn4/appointments" }, "title": "Stoyan ORLOVSKI" }, @@ -1865,6 +1865,31 @@ }, "title": "Andrii ORLOVSKYI" }, + { + "address": { + "address_line_1": "Skelton Road", + "country": "England", + "locality": "London", + "postal_code": "E7 9NJ", + "premises": "32a" + }, + "address_snippet": "32a, Skelton Road, London, England, E7 9NJ", + "appointment_count": 1, + "date_of_birth": { + "month": 4, + "year": 2002 + }, + "description": "Total number of appointments 1 - Born April 2002", + "description_identifiers": [ + "appointment-count", + "born-on" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/fKOg3S0ft1l3NYYJ1EKP58CwOPs/appointments" + }, + "title": "Andrii ORLOVSKYI" + }, { "address": { "address_line_1": "West Park Close", @@ -2177,7 +2202,7 @@ "kind": "search#all", "page_number": 1, "start_index": 0, - "total_results": 81 + "total_results": 82 } }, "headers": { @@ -2188,16 +2213,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:41 GMT", + "date": "Thu, 18 Jun 2026 12:00:26 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=A3DB2B3845A5B29269658B1100D188D9; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=F0C6D10A802FD0C0C4EB2A71C685EC77; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "561", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "563", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_alphabetical_companies_search/alphabetical-search_companies_get_data_0_q_Barclays_size_100_7492c371.json b/tests/resources/tests/test_search_api.py/test_alphabetical_companies_search/alphabetical-search_companies_get_data_0_q_Barclays_size_100_7492c371.json index 94b142e..8f8d28c 100644 --- a/tests/resources/tests/test_search_api.py/test_alphabetical_companies_search/alphabetical-search_companies_get_data_0_q_Barclays_size_100_7492c371.json +++ b/tests/resources/tests/test_search_api.py/test_alphabetical_companies_search/alphabetical-search_companies_get_data_0_q_Barclays_size_100_7492c371.json @@ -1125,16 +1125,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:43 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=F6A19FE87EB7D41E594C8B33FAB8DD41; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=24F94FD735FA0F89E63F479D04168568; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "555", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "557", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_02317c7a.json b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_02317c7a.json index d5a8e83..ec839ad 100644 --- a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_02317c7a.json +++ b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_0_02317c7a.json @@ -390,13 +390,14 @@ }, { "address": { - "address_line_1": "Cuthbert Road", + "address_line_1": "Bradmore Court", + "address_line_2": "2 Enstone Road", "country": "England", - "locality": "London", - "postal_code": "N18 2QL", - "premises": "6" + "locality": "Enfield", + "postal_code": "EN3 7WJ", + "premises": "20" }, - "address_snippet": "6 Cuthbert Road, London, England, N18 2QL", + "address_snippet": "20 Bradmore Court, 2 Enstone Road, Enfield, England, EN3 7WJ", "company_number": "14290999", "company_status": "active", "company_type": "ltd", @@ -442,6 +443,33 @@ "snippet": "", "title": "A&F FLORAL ART AND DESIGN LTD" }, + { + "address": { + "address_line_1": "23 Highbury New Park", + "country": "England", + "locality": "London", + "postal_code": "N5 2EN", + "premises": "Flat D-G" + }, + "address_snippet": "Flat D-G, 23 Highbury New Park, London, England, N5 2EN", + "company_number": "17218021", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2026-05-13", + "description": "17218021 - Incorporated on 13 May 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17218021" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "A&G FINE ART LTD" + }, { "address": { "address_line_1": "Floor National House", @@ -858,36 +886,6 @@ "snippet": "", "title": "AARON&PARTNERS ART DEALERS COOPERATION LP" }, - { - "address": { - "address_line_1": "Regency House", - "address_line_2": "Harold Wood", - "country": "United Kingdom", - "locality": "Romford", - "postal_code": "RM3 0BP", - "premises": "Suite 2", - "region": "Essex" - }, - "address_snippet": "Suite 2 Regency House, Harold Wood, Romford, Essex, United Kingdom, RM3 0BP", - "company_number": "14370867", - "company_status": "dissolved", - "company_type": "ltd", - "date_of_cessation": "2025-04-29", - "date_of_creation": "2022-09-22", - "description": "14370867 - Dissolved on 29 April 2025", - "description_identifier": [ - "dissolved-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/14370867" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AARON ARTHUR WASTE CO. LIMITED" - }, { "address": { "address_line_1": "Block E", @@ -1038,12 +1036,13 @@ }, "address_snippet": "29 Mara Drive, Oakmere, Northwich, England, CW8 2FT", "company_number": "15617808", - "company_status": "active", + "company_status": "dissolved", "company_type": "ltd", + "date_of_cessation": "2026-05-19", "date_of_creation": "2024-04-05", - "description": "15617808 - Incorporated on 5 April 2024", + "description": "15617808 - Dissolved on 19 May 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -1669,13 +1668,13 @@ }, { "address": { - "address_line_1": "Little Houghton", + "address_line_1": "Hollowell", "country": "England", "locality": "Northampton", - "postal_code": "NN7 1AB", - "premises": "Little Houghton House Bedford Road" + "postal_code": "NN6 8RN", + "premises": "Keepers Lodge Guilsborough Hill" }, - "address_snippet": "Little Houghton House Bedford Road, Little Houghton, Northampton, England, NN7 1AB", + "address_snippet": "Keepers Lodge Guilsborough Hill, Hollowell, Northampton, England, NN6 8RN", "company_number": "10988599", "company_status": "active", "company_type": "ltd", @@ -1940,7 +1939,7 @@ "postal_code": "HA1 1BQ", "premises": "Metroline House, 4th Floor, 118-122 College Road, 118-122 College Road," }, - "address_snippet": "Metroline House, 4th Floor, 118-122 College Road, 118-122 College Road, Harrow, England, HA1 1BQ", + "address_snippet": "Metroline House, 4th Floor, 118-122 College Road, 118-122 College Road,, Harrow, England, HA1 1BQ", "company_number": "12716005", "company_status": "active", "company_type": "private-limited-guarant-nsc-limited-exemption", @@ -2262,15 +2261,13 @@ }, { "address": { - "address_line_1": "Windsor Gate", - "address_line_2": "Boyatt Wood", + "address_line_1": "Mountview Rd 12 Mount View Road", "country": "England", - "locality": "Eastleigh", - "postal_code": "SO50 4PU", - "premises": "30", - "region": "Hampshire" + "locality": "Winchester", + "postal_code": "SO22 4JJ", + "premises": "12" }, - "address_snippet": "30 Windsor Gate, Boyatt Wood, Eastleigh, Hampshire, England, SO50 4PU", + "address_snippet": "12 Mountview Rd 12 Mount View Road, Winchester, England, SO22 4JJ", "company_number": "08102792", "company_status": "active", "company_type": "ltd", @@ -2289,6 +2286,34 @@ "snippet": "", "title": "THE ACADEMY OF ARTS LIMITED" }, + { + "address": { + "address_line_1": "High Street", + "address_line_2": "Hampton Hill", + "country": "England", + "locality": "Hampton", + "postal_code": "TW12 1NB", + "premises": "29" + }, + "address_snippet": "29 High Street, Hampton Hill, Hampton, England, TW12 1NB", + "company_number": "17182776", + "company_status": "active", + "company_type": "private-limited-guarant-nsc", + "date_of_creation": "2026-04-27", + "description": "17182776 - Incorporated on 27 April 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17182776" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ACADEMY OF ARTS AND ANTIQUES STEWARDSHIP LTD" + }, { "address": { "address_line_1": "Screenworks 22 Highbury Grove", @@ -2410,12 +2435,13 @@ }, "address_snippet": "16 A&B South Cliff Tower, Bolsover Road, Eastbourne, England, BN20 7JW", "company_number": "11929207", - "company_status": "active", + "company_status": "dissolved", "company_type": "ltd", + "date_of_cessation": "2026-06-16", "date_of_creation": "2019-04-05", - "description": "11929207 - Incorporated on 5 April 2019", + "description": "11929207 - Dissolved on 16 June 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -2701,34 +2727,6 @@ }, "snippet": "", "title": "THE ACADEMY PERFORMING ARTS SCHOOL ESSEX LIMITED" - }, - { - "address": { - "address_line_1": "Lodge Avenue", - "country": "England", - "locality": "Romford", - "postal_code": "RM2 5AL", - "premises": "20" - }, - "address_snippet": "20 Lodge Avenue, Romford, England, RM2 5AL", - "company_number": "12046368", - "company_status": "dissolved", - "company_type": "ltd", - "date_of_cessation": "2025-11-18", - "date_of_creation": "2019-06-12", - "description": "12046368 - Dissolved on 18 November 2025", - "description_identifier": [ - "dissolved-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/12046368" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ACA PERFORMING ARTS LTD" } ], "items_per_page": 100, @@ -2746,16 +2744,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:43 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=880E6AD474CE588CBE25ADC25D100919; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=3556D9152EA9719DFBEE8F0B406ED6B8; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "554", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "556", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_7c6d9a11.json b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_7c6d9a11.json index d34aab0..ca4bfd5 100644 --- a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_7c6d9a11.json +++ b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_100_7c6d9a11.json @@ -2,6 +2,34 @@ "content": { "json": { "items": [ + { + "address": { + "address_line_1": "Lodge Avenue", + "country": "England", + "locality": "Romford", + "postal_code": "RM2 5AL", + "premises": "20" + }, + "address_snippet": "20 Lodge Avenue, Romford, England, RM2 5AL", + "company_number": "12046368", + "company_status": "dissolved", + "company_type": "ltd", + "date_of_cessation": "2025-11-18", + "date_of_creation": "2019-06-12", + "description": "12046368 - Dissolved on 18 November 2025", + "description_identifier": [ + "dissolved-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/12046368" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ACA PERFORMING ARTS LTD" + }, { "address": { "address_line_1": "Ritz Parade", @@ -335,6 +363,33 @@ "snippet": "", "title": "ACCESS ARTS TW CIC" }, + { + "address": { + "address_line_1": "City Road", + "country": "United Kingdom", + "locality": "London", + "postal_code": "EC1V 2NX", + "premises": "128" + }, + "address_snippet": "128 City Road, London, United Kingdom, EC1V 2NX", + "company_number": "17234167", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2026-05-21", + "description": "17234167 - Incorporated on 21 May 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17234167" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ACCESS COMMUNITY ARTS LTD" + }, { "address": {}, "company_number": "CS005109", @@ -515,7 +570,7 @@ "matches": { "snippet": [] }, - "snippet": "ACCOMMODATION ARTISTRY", + "snippet": "ACCOMMODATION ARTISTRY LTD", "title": "ACCOMADATION ARTIST LTD" }, { @@ -611,7 +666,7 @@ "postal_code": "CO16 0BG", "premises": "Offices 5&" }, - "address_snippet": "Offices 5& 6 Mauds Court, Long Lane, Tendring, Clacton-On-Sea, England, CO16 0BG", + "address_snippet": "Offices 5&6 Mauds Court, Long Lane, Tendring, Clacton-On-Sea, England, CO16 0BG", "company_number": "14307780", "company_status": "active", "company_type": "ltd", @@ -657,6 +712,33 @@ "snippet": "", "title": "ACCURATE GLASS ART LTD" }, + { + "address": { + "address_line_1": "Ashley Drive", + "country": "England", + "locality": "Borehamwood", + "postal_code": "WD6 2JD", + "premises": "30" + }, + "address_snippet": "30 Ashley Drive, Borehamwood, England, WD6 2JD", + "company_number": "14494613", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2022-11-20", + "description": "14494613 - Incorporated on 20 November 2022", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/14494613" + }, + "matches": { + "snippet": [] + }, + "snippet": "FADEDKARMA ART GALLARY LTD", + "title": "ACCURATEPAY SOLUTIONS LTD" + }, { "address": { "address_line_1": "Wormholt Road", @@ -766,6 +848,33 @@ "snippet": "GECKO ARTS LIMITED", "title": "ACE AUTO WORKS LTD" }, + { + "address": { + "address_line_1": "Innings Way", + "country": "England", + "locality": "Rochdale", + "postal_code": "OL11 3DE", + "premises": "15" + }, + "address_snippet": "15 Innings Way, Rochdale, England, OL11 3DE", + "company_number": "15931845", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2024-09-03", + "description": "15931845 - Incorporated on 3 September 2024", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/15931845" + }, + "matches": { + "snippet": [] + }, + "snippet": "ARTY & HAIR PRODUCT LTD", + "title": "ACE CUTZ & HAIR STYLE LTD" + }, { "address": { "address_line_1": "Conisborough Crescent", @@ -849,6 +958,33 @@ "snippet": "", "title": "ACES HIGH FINE ART LIMITED" }, + { + "address": { + "address_line_1": "71-75 Uxbridge Road, Ealing", + "country": "England", + "locality": "London", + "postal_code": "W5 5SL", + "premises": "Spaces Ealing Aurora C/O Specialised Travel Ltd" + }, + "address_snippet": "Spaces Ealing Aurora C/O Specialised Travel Ltd, 71-75 Uxbridge Road, Ealing, London, England, W5 5SL", + "company_number": "03054214", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "1995-05-09", + "description": "03054214 - Incorporated on 9 May 1995", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/03054214" + }, + "matches": { + "snippet": [] + }, + "snippet": "TRAVEL FOR THE ARTS LIMITED", + "title": "ACFEA TOUR CONSULTANTS LIMITED" + }, { "address": { "address_line_1": "Frogmore Road", @@ -1111,6 +1247,34 @@ "snippet": "", "title": "ACQUISITION FINE ART" }, + { + "address": { + "address_line_1": "Freeland Park", + "address_line_2": "Wareham Road", + "country": "United Kingdom", + "locality": "Poole", + "postal_code": "BH16 6FH", + "premises": "Unit 13," + }, + "address_snippet": "Unit 13, Freeland Park, Wareham Road, Poole, United Kingdom, BH16 6FH", + "company_number": "17254813", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2026-06-01", + "description": "17254813 - Incorporated on 1 June 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17254813" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ACRE & ARTISAN LTD" + }, { "address": { "address_line_1": "The Pantiles", @@ -1567,12 +1731,13 @@ }, "address_snippet": "34 Larch Grove 34 Larch Grove, Prenton, Merseyside, England, CH43 7FF", "company_number": "16350645", - "company_status": "active", + "company_status": "dissolved", "company_type": "private-limited-guarant-nsc", + "date_of_cessation": "2026-05-19", "date_of_creation": "2025-03-28", - "description": "16350645 - Incorporated on 28 March 2025", + "description": "16350645 - Dissolved on 19 May 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -1944,35 +2109,6 @@ "snippet": "", "title": "ACUTE ART LIMITED" }, - { - "address": { - "address_line_1": "Pixies Holt Flats", - "address_line_2": "Buller Street", - "country": "England", - "locality": "Looe", - "postal_code": "PL13 1AR", - "premises": "1" - }, - "address_snippet": "1 Pixies Holt Flats, Buller Street, Looe, England, PL13 1AR", - "company_number": "14737322", - "company_status": "dissolved", - "company_type": "ltd", - "date_of_cessation": "2025-04-29", - "date_of_creation": "2023-03-17", - "description": "14737322 - Dissolved on 29 April 2025", - "description_identifier": [ - "dissolved-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/14737322" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADA - ART LTD" - }, { "address": { "address_line_1": "Latimer Road", @@ -2067,12 +2203,13 @@ }, "address_snippet": "66 Grove Hall Court, Hall Road, London, United Kingdom, NW8 9NY", "company_number": "16967935", - "company_status": "active", + "company_status": "dissolved", "company_type": "ltd", + "date_of_cessation": "2026-06-16", "date_of_creation": "2026-01-16", - "description": "16967935 - Incorporated on 16 January 2026", + "description": "16967935 - Dissolved on 16 June 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -2459,13 +2596,13 @@ }, { "address": { - "address_line_1": "Bruce House Kemble Street", - "country": "England", - "locality": "London", - "postal_code": "WC2B 4AW", - "premises": "Flat 281," + "address_line_1": "Chester Street", + "country": "United Kingdom", + "locality": "Wallasey", + "postal_code": "CH44 5SJ", + "premises": "1" }, - "address_snippet": "Flat 281, Bruce House Kemble Street, London, England, WC2B 4AW", + "address_snippet": "1 Chester Street, Wallasey, United Kingdom, CH44 5SJ", "company_number": "15056073", "company_status": "active", "company_type": "private-limited-guarant-nsc", @@ -2593,147 +2730,6 @@ }, "snippet": "", "title": "ADDIS FINE ART LIMITED" - }, - { - "address": { - "address_line_1": "Comet House", - "address_line_2": "Calleva Park", - "country": "England", - "locality": "Aldermaston", - "postal_code": "RG7 8JA", - "premises": "4", - "region": "Berkshire" - }, - "address_snippet": "4 Comet House, Calleva Park, Aldermaston, Berkshire, England, RG7 8JA", - "company_number": "11246293", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2018-03-09", - "description": "11246293 - Incorporated on 9 March 2018", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/11246293" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADDITAE ARTES LIMITED" - }, - { - "address": { - "address_line_1": "Hillersdon Avenue", - "country": "England", - "locality": "Edgware", - "postal_code": "HA8 7FG", - "premises": "3", - "region": "Middlesex" - }, - "address_snippet": "3 Hillersdon Avenue, Edgware, Middlesex, England, HA8 7FG", - "company_number": "09282558", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2014-10-27", - "description": "09282558 - Incorporated on 27 October 2014", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/09282558" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADDO ARTS LIMITED" - }, - { - "address": { - "address_line_1": "Waterside", - "country": "United Kingdom", - "locality": "Ely", - "postal_code": "CB7 4AU", - "premises": "Babylon Gallery Babylon Bridge", - "region": "Cambridgeshire" - }, - "address_snippet": "Babylon Gallery Babylon Bridge, Waterside, Ely, Cambridgeshire, United Kingdom, CB7 4AU", - "company_number": "02999055", - "company_status": "active", - "company_type": "private-limited-guarant-nsc-limited-exemption", - "date_of_creation": "1994-12-07", - "description": "02999055 - Incorporated on 7 December 1994", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/02999055" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADEC (ARTS DEVELOPMENT IN EAST CAMBRIDGESHIRE)" - }, - { - "address": { - "address_line_1": "Floor Suite", - "address_line_2": "207 Regent Street", - "country": "England", - "locality": "London", - "postal_code": "W1B 3HH", - "premises": "3rd" - }, - "address_snippet": "3rd Floor Suite, 207 Regent Street, London, England, W1B 3HH", - "company_number": "06604862", - "company_status": "active", - "company_type": "private-limited-guarant-nsc-limited-exemption", - "date_of_creation": "2008-05-28", - "description": "06604862 - Incorporated on 28 May 2008", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/06604862" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADEJOJU ADEYEMI FOUNDATION - CARING ART" - }, - { - "address": { - "address_line_1": "Felpham", - "country": "England", - "locality": "Bognor Regis", - "postal_code": "PO22 7LQ", - "premises": "Gloucester Lodge 2 West Close", - "region": "West Sussex" - }, - "address_snippet": "Gloucester Lodge 2 West Close Felpham, Bognor Regis, West Sussex, England, PO22 7LQ", - "company_number": "09509556", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2015-03-25", - "description": "09509556 - Incorporated on 25 March 2015", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/09509556" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADELE PETERS PERFORMING ARTS SCHOOL LIMITED" } ], "items_per_page": 100, @@ -2751,16 +2747,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:43 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4EE9B2B7464E4E1B1FDE2426FC7AF31D; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=2137BC85CC3B2A5C0DCFFD9BFF5B7874; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "553", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "555", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_fe007d92.json b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_fe007d92.json index fac7056..30b9f9c 100644 --- a/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_fe007d92.json +++ b/tests/resources/tests/test_search_api.py/test_search_companies/search_companies_get_data_0_items_per_page_200_q_R5E_ART_LIMITED_start_index_200_fe007d92.json @@ -2,6 +2,147 @@ "content": { "json": { "items": [ + { + "address": { + "address_line_1": "Comet House", + "address_line_2": "Calleva Park", + "country": "England", + "locality": "Aldermaston", + "postal_code": "RG7 8JA", + "premises": "4", + "region": "Berkshire" + }, + "address_snippet": "4 Comet House, Calleva Park, Aldermaston, Berkshire, England, RG7 8JA", + "company_number": "11246293", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2018-03-09", + "description": "11246293 - Incorporated on 9 March 2018", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/11246293" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADDITAE ARTES LIMITED" + }, + { + "address": { + "address_line_1": "Hillersdon Avenue", + "country": "England", + "locality": "Edgware", + "postal_code": "HA8 7FG", + "premises": "3", + "region": "Middlesex" + }, + "address_snippet": "3 Hillersdon Avenue, Edgware, Middlesex, England, HA8 7FG", + "company_number": "09282558", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2014-10-27", + "description": "09282558 - Incorporated on 27 October 2014", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/09282558" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADDO ARTS LIMITED" + }, + { + "address": { + "address_line_1": "Waterside", + "country": "United Kingdom", + "locality": "Ely", + "postal_code": "CB7 4AU", + "premises": "Babylon Gallery Babylon Bridge", + "region": "Cambridgeshire" + }, + "address_snippet": "Babylon Gallery Babylon Bridge, Waterside, Ely, Cambridgeshire, United Kingdom, CB7 4AU", + "company_number": "02999055", + "company_status": "active", + "company_type": "private-limited-guarant-nsc-limited-exemption", + "date_of_creation": "1994-12-07", + "description": "02999055 - Incorporated on 7 December 1994", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/02999055" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADEC (ARTS DEVELOPMENT IN EAST CAMBRIDGESHIRE)" + }, + { + "address": { + "address_line_1": "Floor Suite", + "address_line_2": "207 Regent Street", + "country": "England", + "locality": "London", + "postal_code": "W1B 3HH", + "premises": "3rd" + }, + "address_snippet": "3rd Floor Suite, 207 Regent Street, London, England, W1B 3HH", + "company_number": "06604862", + "company_status": "active", + "company_type": "private-limited-guarant-nsc-limited-exemption", + "date_of_creation": "2008-05-28", + "description": "06604862 - Incorporated on 28 May 2008", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/06604862" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADEJOJU ADEYEMI FOUNDATION - CARING ART" + }, + { + "address": { + "address_line_1": "Felpham", + "country": "England", + "locality": "Bognor Regis", + "postal_code": "PO22 7LQ", + "premises": "Gloucester Lodge 2 West Close", + "region": "West Sussex" + }, + "address_snippet": "Gloucester Lodge 2 West Close, Felpham, Bognor Regis, West Sussex, England, PO22 7LQ", + "company_number": "09509556", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2015-03-25", + "description": "09509556 - Incorporated on 25 March 2015", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/09509556" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADELE PETERS PERFORMING ARTS SCHOOL LIMITED" + }, { "address": { "address_line_1": "High Street", @@ -87,35 +228,6 @@ "snippet": "", "title": "AD ENTERTAINMENT ARTS LIMITED" }, - { - "address": { - "address_line_1": "Freeland Park Wareham Road", - "address_line_2": "Lytchett Matravers", - "country": "England", - "locality": "Poole", - "postal_code": "BH16 6FA", - "premises": "Unit 13" - }, - "address_snippet": "Unit 13 Freeland Park Wareham Road, Lytchett Matravers, Poole, England, BH16 6FA", - "company_number": "15285943", - "company_status": "dissolved", - "company_type": "ltd", - "date_of_cessation": "2025-04-22", - "date_of_creation": "2023-11-15", - "description": "15285943 - Dissolved on 22 April 2025", - "description_identifier": [ - "dissolved-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/15285943" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "ADH ART LTD" - }, { "address": { "address_line_1": "Flat 2 Blockd3, Wentworth College", @@ -998,6 +1110,33 @@ "snippet": "", "title": "ADVENTURE ARTISANS LTD" }, + { + "address": { + "address_line_1": "2 Chichester Street", + "country": "England", + "locality": "Rochdale", + "postal_code": "OL16 2AX", + "premises": "Ccm Chichester House" + }, + "address_snippet": "Ccm Chichester House, 2 Chichester Street, Rochdale, England, OL16 2AX", + "company_number": "17240609", + "company_status": "active", + "company_type": "private-limited-guarant-nsc", + "date_of_creation": "2026-05-25", + "description": "17240609 - Incorporated on 25 May 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17240609" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADVENTURE ARTS CIC" + }, { "address": { "address_line_1": "Kingsdale Avenue", @@ -1025,6 +1164,34 @@ "snippet": "", "title": "ADVENTURE INTO ART CIC" }, + { + "address": { + "address_line_1": "Great Portland Street", + "address_line_2": "Fifth Floor", + "country": "England", + "locality": "London", + "postal_code": "W1W 5PF", + "premises": "167-169" + }, + "address_snippet": "167-169 Great Portland Street, Fifth Floor, London, England, W1W 5PF", + "company_number": "17246192", + "company_status": "active", + "company_type": "ltd", + "date_of_creation": "2026-05-27", + "description": "17246192 - Incorporated on 27 May 2026", + "description_identifier": [ + "incorporated-on" + ], + "kind": "searchresults#company", + "links": { + "self": "/company/17246192" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "ADVENTURES IN ART & WONDER LTD" + }, { "address": { "address_line_1": "Freeland Park Wareham Road", @@ -1231,12 +1398,13 @@ }, "address_snippet": "Bidwell Accountancy Suite 103 Milton Keynes Business Centre, Linford Wood East, Milton Keynes, Buckinghamshire, England, MK14 6GD", "company_number": "15949574", - "company_status": "active", + "company_status": "dissolved", "company_type": "ltd", + "date_of_cessation": "2026-05-26", "date_of_creation": "2024-09-11", - "description": "15949574 - Incorporated on 11 September 2024", + "description": "15949574 - Dissolved on 26 May 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -2100,12 +2268,13 @@ }, "address_snippet": "Studio High Mcgowanston Farm, Turnberry, Girvan, Ayrshire, KA26 9JT", "company_number": "SC301330", - "company_status": "active", + "company_status": "dissolved", "company_type": "ltd", + "date_of_cessation": "2026-05-05", "date_of_creation": "2006-04-25", - "description": "SC301330 - Incorporated on 25 April 2006", + "description": "SC301330 - Dissolved on 5 May 2026", "description_identifier": [ - "incorporated-on" + "dissolved-on" ], "kind": "searchresults#company", "links": { @@ -2584,163 +2753,6 @@ }, "snippet": "", "title": "AFRICAN CULTURAL DEVELOPMENT (ACD ARTS)" - }, - { - "address": { - "address_line_1": "27- 43 Eastern Road", - "country": "England", - "locality": "Romford", - "postal_code": "RM1 3NH", - "premises": "St. James House", - "region": "Essex" - }, - "address_snippet": "St. James House, 27- 43 Eastern Road, Romford, Essex, England, RM1 3NH", - "company_number": "09113128", - "company_status": "active", - "company_type": "private-limited-guarant-nsc-limited-exemption", - "date_of_creation": "2014-07-02", - "description": "09113128 - Incorporated on 2 July 2014", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/09113128" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICAN CULTURE ARTS AND SPORT NETWORK" - }, - { - "address": { - "address_line_1": "A Enterprise House", - "address_line_2": "44-46 Terrace Road", - "country": "England", - "locality": "Walton-On-Thames", - "postal_code": "KT12 2SD", - "premises": "36", - "region": "Surrey" - }, - "address_snippet": "36 A Enterprise House, 44-46 Terrace Road, Walton-On-Thames, Surrey, England, KT12 2SD", - "company_number": "15553840", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2024-03-11", - "description": "15553840 - Incorporated on 11 March 2024", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/15553840" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICAN FINE ARTS LIMITED" - }, - { - "address": {}, - "company_number": "RS004898", - "company_type": "registered-society-non-jurisdictional", - "description": "RS004898", - "description_identifier": [ - null - ], - "kind": "searchresults#company", - "links": { - "self": "/company/RS004898" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICANFUTURIST ARTS COOPERATIVE SOCIETY LIMITED" - }, - { - "address": { - "address_line_1": "Nightingale Court", - "address_line_2": "Sillitoe Drive", - "country": "England", - "locality": "Wigan", - "postal_code": "WN6 7AB", - "premises": "Flat 12" - }, - "address_snippet": "Flat 12 Nightingale Court, Sillitoe Drive, Wigan, England, WN6 7AB", - "company_number": "15451929", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2024-01-30", - "description": "15451929 - Incorporated on 30 January 2024", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/15451929" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICAN TRIBAL ART LTD" - }, - { - "address": { - "address_line_1": "Queen Isabels Avenue", - "country": "England", - "locality": "Coventry", - "postal_code": "CV3 5GE", - "premises": "61" - }, - "address_snippet": "61 Queen Isabels Avenue, Coventry, England, CV3 5GE", - "company_number": "12059533", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2019-06-19", - "description": "12059533 - Incorporated on 19 June 2019", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/12059533" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICA PERFORMING ARTS THEATRE LIMITED" - }, - { - "address": { - "address_line_1": "Purves Road", - "country": "United Kingdom", - "locality": "London", - "postal_code": "NW10 5TG", - "premises": "152" - }, - "address_snippet": "152 Purves Road, London, United Kingdom, NW10 5TG", - "company_number": "11850405", - "company_status": "active", - "company_type": "ltd", - "date_of_creation": "2019-02-27", - "description": "11850405 - Incorporated on 27 February 2019", - "description_identifier": [ - "incorporated-on" - ], - "kind": "searchresults#company", - "links": { - "self": "/company/11850405" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "AFRICA'S BEST ARTISANS LIMITED" } ], "items_per_page": 100, @@ -2758,16 +2770,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:07:43 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=8766228CC1D33679882F56434C8C048C; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=07BA42257167CE76C91D082C2A503E09; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "552", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "554", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_disqualified_officers/search_disqualified-officers_get_data_0_items_per_page_200_q_bob_start_index_0_8747c50e.json b/tests/resources/tests/test_search_api.py/test_search_disqualified_officers/search_disqualified-officers_get_data_0_items_per_page_200_q_bob_start_index_0_8747c50e.json index d6dbb01..f184473 100644 --- a/tests/resources/tests/test_search_api.py/test_search_disqualified_officers/search_disqualified-officers_get_data_0_items_per_page_200_q_bob_start_index_0_8747c50e.json +++ b/tests/resources/tests/test_search_api.py/test_search_disqualified_officers/search_disqualified-officers_get_data_0_items_per_page_200_q_bob_start_index_0_8747c50e.json @@ -39,16 +39,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:08:02 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=0F64C72B982F720DD2DBCC538C10BEDB; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=6F57363E8E67808EF51A0489B389F23C; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "550", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "552", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[alphabetical-BOAZ OFRI FILM PRODUCTIONS LTD.]/dissolved-search_companies_get_data_0_q_bob_search_type_alphabetical_size_10_7d6a56cd.json b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[alphabetical-BOAZ OFRI FILM PRODUCTIONS LTD.]/dissolved-search_companies_get_data_0_q_bob_search_type_alphabetical_size_10_7d6a56cd.json index 2149856..8db5e51 100644 --- a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[alphabetical-BOAZ OFRI FILM PRODUCTIONS LTD.]/dissolved-search_companies_get_data_0_q_bob_search_type_alphabetical_size_10_7d6a56cd.json +++ b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[alphabetical-BOAZ OFRI FILM PRODUCTIONS LTD.]/dissolved-search_companies_get_data_0_q_bob_search_type_alphabetical_size_10_7d6a56cd.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "038366fff483f2c2e305171b7315c79a8cf87a56", + "etag": "8c865333d6e985986e9102b6e8faf632d923c63a", "items": [ { "company_name": "BOAZ JACHIN LIMITED", @@ -136,16 +136,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:08:03 GMT", + "date": "Thu, 18 Jun 2026 12:00:28 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=1B2B77CE35E0C918DB7B783F7AAE8FE4; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=EBB05500E390C1357D67BE80A05B1EC8; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "549", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "551", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[best-match-BOB INTERNATIONAL LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_best-match_size_10_ef34428a.json b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[best-match-BOB INTERNATIONAL LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_best-match_size_10_ef34428a.json index 7c01d48..0961490 100644 --- a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[best-match-BOB INTERNATIONAL LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_best-match_size_10_ef34428a.json +++ b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[best-match-BOB INTERNATIONAL LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_best-match_size_10_ef34428a.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "5db85658fd375deb650b7b9fb8d211ea72b15463", + "etag": "c715f49686163fd5a57a4f763f65af000ef5846e", "hits": 456, "items": [ { @@ -139,16 +139,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:08:03 GMT", + "date": "Thu, 18 Jun 2026 12:00:28 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=0E13518617F4BCD3DFC1B507871F8CD8; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=99E4CF76644E84E78427697ECC985B27; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "548", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "550", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[previous-name-dissolved-G. & H. MOTOR ENGINEERS LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_previous-name-dissol_size_10_3f156bc2.json b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[previous-name-dissolved-G. & H. MOTOR ENGINEERS LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_previous-name-dissol_size_10_3f156bc2.json index 53c61f0..94d89ec 100644 --- a/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[previous-name-dissolved-G. & H. MOTOR ENGINEERS LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_previous-name-dissol_size_10_3f156bc2.json +++ b/tests/resources/tests/test_search_api.py/test_search_dissolved_companies[previous-name-dissolved-G. & H. MOTOR ENGINEERS LIMITED]/dissolved-search_companies_get_data_0_q_bob_search_type_previous-name-dissol_size_10_3f156bc2.json @@ -1,7 +1,7 @@ { "content": { "json": { - "etag": "fd9ca0b9a13506e54e5778aeb4414f7b7f1a5012", + "etag": "430a054e6480cdba9a2331b3557de09f4b6f2626", "hits": 49, "items": [ { @@ -74,13 +74,7 @@ "effective_from": "1973-02-02", "name": "BOB GURNEY LIMITED" } - ], - "registered_office_address": { - "address_line_1": "38 Bromborough Village Road", - "address_line_2": "Wirral", - "locality": "Merseyside", - "postal_code": "CH62 7ET" - } + ] }, { "company_name": "BYKER BOTTLE GLASS COMPANY LTD", @@ -330,16 +324,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:08:03 GMT", + "date": "Thu, 18 Jun 2026 12:00:28 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=1EBAA42F04360FB758BB77838F2577BA; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=B086460A7700D6E8AEB0ED6803E56238; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "547", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "549", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, diff --git a/tests/resources/tests/test_search_api.py/test_search_officers/search_officers_get_data_0_items_per_page_200_q_Ilja_Orlovs_start_index_0_f9033425.json b/tests/resources/tests/test_search_api.py/test_search_officers/search_officers_get_data_0_items_per_page_200_q_Ilja_Orlovs_start_index_0_f9033425.json index ffac1db..178ad8f 100644 --- a/tests/resources/tests/test_search_api.py/test_search_officers/search_officers_get_data_0_items_per_page_200_q_Ilja_Orlovs_start_index_0_f9033425.json +++ b/tests/resources/tests/test_search_api.py/test_search_officers/search_officers_get_data_0_items_per_page_200_q_Ilja_Orlovs_start_index_0_f9033425.json @@ -1110,12 +1110,12 @@ "premises": "3" }, "address_snippet": "3 Harte Road, Hounslow, England, TW3 4LD", - "appointment_count": 1, + "appointment_count": 2, "date_of_birth": { "month": 5, "year": 1973 }, - "description": "Total number of appointments 1 - Born May 1973", + "description": "Total number of appointments 2 - Born May 1973", "description_identifiers": [ "appointment-count", "born-on" @@ -1595,35 +1595,6 @@ "snippet": "", "title": "Ilja BUZIN" }, - { - "address": { - "address_line_1": "Green Lanes", - "country": "England", - "locality": "London", - "postal_code": "N13 4BS", - "premises": "483" - }, - "address_snippet": "483 Green Lanes, London, England, N13 4BS", - "appointment_count": 1, - "date_of_birth": { - "month": 12, - "year": 2000 - }, - "description": "Total number of appointments 1 - Born December 2000", - "description_identifiers": [ - "appointment-count", - "born-on" - ], - "kind": "searchresults#officer", - "links": { - "self": "/officers/_XDRuCrjdd52b6vHkk3_LlHBEOE/appointments" - }, - "matches": { - "snippet": [] - }, - "snippet": "", - "title": "Ilja CAIKOVSKIS" - }, { "address": { "address_line_1": "8 Gainsborough Road", @@ -1937,13 +1908,13 @@ }, { "address": { - "address_line_1": "15 Belmont Road", - "country": "England", + "address_line_1": "7 Dampier Close", + "country": "United Kingdom", "locality": "London", - "postal_code": "N15 3LS", - "premises": "Flat B" + "postal_code": "E16 2RW", + "premises": "E16 2rw" }, - "address_snippet": "Flat B, 15 Belmont Road, London, England, N15 3LS", + "address_snippet": "E16 2rw, 7 Dampier Close, London, United Kingdom, E16 2RW", "appointment_count": 1, "date_of_birth": { "month": 9, @@ -2407,13 +2378,14 @@ { "address": { "address_line_1": "Central Boulevard", - "address_line_2": "Blythe Valley Park, Shirley", + "address_line_2": "Shirley", "country": "England", "locality": "Solihull", "postal_code": "B90 8BG", - "premises": "One" + "premises": "2nd Floor, The Hub, Blythe Valley Park", + "region": "West Midlands" }, - "address_snippet": "One, Central Boulevard, Blythe Valley Park, Shirley, Solihull, England, B90 8BG", + "address_snippet": "2nd Floor, The Hub, Blythe Valley Park, Central Boulevard, Shirley, Solihull, West Midlands, England, B90 8BG", "appointment_count": 1, "date_of_birth": { "month": 6, @@ -2467,13 +2439,14 @@ { "address": { "address_line_1": "Central Boulevard", - "address_line_2": "Blythe Valley Park, Shirley", + "address_line_2": "Shirley", "country": "England", "locality": "Solihull", "postal_code": "B90 8BG", - "premises": "One" + "premises": "2nd Floor, The Hub, Blythe Valley Park", + "region": "West Midlands" }, - "address_snippet": "One, Central Boulevard, Blythe Valley Park, Shirley, Solihull, England, B90 8BG", + "address_snippet": "2nd Floor, The Hub, Blythe Valley Park, Central Boulevard, Shirley, Solihull, West Midlands, England, B90 8BG", "appointment_count": 4, "date_of_birth": { "month": 5, @@ -2501,9 +2474,10 @@ "country": "England", "locality": "Solihull", "postal_code": "B90 8BG", - "premises": "One" + "premises": "2nd Floor, The Hub, Blythe Valley Park", + "region": "West Midlands" }, - "address_snippet": "One, Central Boulevard, Shirley, Solihull, England, B90 8BG", + "address_snippet": "2nd Floor, The Hub, Blythe Valley Park, Central Boulevard, Shirley, Solihull, West Midlands, England, B90 8BG", "appointment_count": 2, "date_of_birth": { "month": 5, @@ -2703,14 +2677,13 @@ }, { "address": { - "address_line_1": "London Road", - "address_line_2": "Old Basing", + "address_line_1": "Chamberlayne Road", "country": "England", - "locality": "Basingstoke", - "postal_code": "RG24 7JL", - "premises": "17 Freya House" + "locality": "Eastleigh", + "postal_code": "SO50 5JG", + "premises": "133a Chamberlayne Road" }, - "address_snippet": "17 Freya House, London Road, Old Basing, Basingstoke, England, RG24 7JL", + "address_snippet": "133a Chamberlayne Road, Chamberlayne Road, Eastleigh, England, SO50 5JG", "appointment_count": 1, "date_of_birth": { "month": 12, @@ -2907,13 +2880,37 @@ }, "snippet": "", "title": "Ilja HONY" + }, + { + "address": { + "address_line_1": "Prazakova 22", + "country": "Czech Republic", + "locality": "61900 Brno", + "postal_code": "FOREIGN", + "region": "Czech Republic" + }, + "address_snippet": "Prazakova 22, 61900 Brno, Czech Republic, Czech Republic, FOREIGN", + "appointment_count": 1, + "description": "Total number of appointments 1", + "description_identifiers": [ + "appointment-count" + ], + "kind": "searchresults#officer", + "links": { + "self": "/officers/IgM1Tm2oupU2QVYRXrbPc6PHrPo/appointments" + }, + "matches": { + "snippet": [] + }, + "snippet": "", + "title": "Ilja HUSTY" } ], "items_per_page": 100, "kind": "search#officers", "page_number": 1, "start_index": 0, - "total_results": 323 + "total_results": 325 } }, "headers": { @@ -2924,16 +2921,16 @@ "cache-control": "no-cache, no-store, max-age=0, must-revalidate", "connection": "keep-alive", "content-type": "application/json", - "date": "Tue, 21 Apr 2026 11:08:02 GMT", + "date": "Thu, 18 Jun 2026 12:00:27 GMT", "expires": "0", "pragma": "no-cache", - "set-cookie": "JSESSIONID=4A40765C77C8DD16EE433A6E0F2476D7; Path=/; HttpOnly", + "set-cookie": "JSESSIONID=EE278FE933F4834BAFCE014F5A3B0577; Path=/; HttpOnly", "transfer-encoding": "chunked", "x-content-type-options": "nosniff", "x-frame-options": "DENY", "x-ratelimit-limit": "600", - "x-ratelimit-remain": "551", - "x-ratelimit-reset": "1776769792", + "x-ratelimit-remain": "553", + "x-ratelimit-reset": "1781784313", "x-ratelimit-window": "5m", "x-xss-protection": "0" }, From 1ffd9589b45dff9a6f7aad1c11e61ce18fd65d27 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 13:08:27 +0100 Subject: [PATCH 10/12] dedup --- src/ch_api/_paginate.py | 32 ++++++++++---------------- src/ch_api/types/pagination/types.py | 22 +++++++----------- tests/unit/test_api_branch_coverage.py | 7 ++---- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index da2b16f..101cf77 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -4,38 +4,30 @@ """ import contextvars -import dataclasses import functools import inspect import typing import pydantic -_PaginatedFn = typing.TypeVar("_PaginatedFn", bound=typing.Callable[..., typing.Awaitable[typing.Any]]) - - -@dataclasses.dataclass(frozen=True, slots=True) -class _ResumeState: - """Endpoint name and call arguments of the active ``@paginated`` call. +from .types.pagination.types import _PageState - The fetch helpers read it to build the ``next_page`` token; an empty - ``endpoint`` means no paginated call is active. - """ - - endpoint: str = "" - params: typing.Dict[str, typing.Any] = dataclasses.field(default_factory=dict) +_PaginatedFn = typing.TypeVar("_PaginatedFn", bound=typing.Callable[..., typing.Awaitable[typing.Any]]) -#: Task-local channel from :func:`paginated` to the fetch helpers. Task-local so -#: concurrent paginated calls on one client don't clash. -_resume_ctx: contextvars.ContextVar[typing.Optional[_ResumeState]] = contextvars.ContextVar( +#: Task-local channel from :func:`paginated` to the fetch helpers, carrying the +#: active call's endpoint name and arguments (its position fields are unset here; +#: the helpers add those when stamping the ``next_page`` token). Task-local so +#: concurrent paginated calls on one client don't clash. An empty ``endpoint`` +#: means no paginated call is active. +_resume_ctx: contextvars.ContextVar[typing.Optional[_PageState]] = contextvars.ContextVar( "ch_api_resume_ctx", default=None ) -def current_resume_state() -> _ResumeState: - """The active ``@paginated`` call's :class:`_ResumeState`, or an empty one.""" - return _resume_ctx.get() or _ResumeState() +def current_resume_state() -> _PageState: + """The active ``@paginated`` call's :class:`_PageState`, or an empty one.""" + return _resume_ctx.get() or _PageState() def paginated(*, exclude: typing.Collection[str] = ("self",)) -> typing.Callable[[_PaginatedFn], _PaginatedFn]: @@ -63,7 +55,7 @@ async def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: bound = sig.bind(*args, **kwargs) bound.apply_defaults() params = {name: value for name, value in bound.arguments.items() if name not in excluded} - ctx_token = _resume_ctx.set(_ResumeState(endpoint=endpoint, params=params)) + ctx_token = _resume_ctx.set(_PageState(endpoint=endpoint, params=params)) try: result = await validated(*args, **kwargs) finally: diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index 23f5843..1883ab8 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -34,6 +34,10 @@ class _PageState(pydantic.BaseModel, frozen=True): re-dispatch (params are JSON-safe values). * ``start_index`` — next offset for offset-based endpoints. * ``search_below`` — cursor for cursor-based endpoints (alphabetical / dissolved). + + The ``@paginated`` decorator also publishes a position-less instance (just + ``endpoint`` / ``params``) as the active call's resume context; the fetch + helpers read it and fill in the position when stamping the next token. """ start_index: int = 0 @@ -222,31 +226,21 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): _client: typing.Optional[_NextPageFetcher] = pydantic.PrivateAttr(default=None) """Client used by :meth:`get_next`. Set when the list is produced; ``None`` on - deserialized instances (rebind with :meth:`with_client`).""" - - def with_client(self, client: _NextPageFetcher) -> "MultipageList[_ItemT]": - """Bind a client so :meth:`get_next` works (e.g. on a deserialized list); returns ``self``. - - The token is self-contained, so any ``Client`` will do — it only supplies - the HTTP session. - """ - self._client = client - return self + deserialized instances (resume those via ``Client.fetch_next_page(token)``).""" async def get_next(self) -> "MultipageList[_ItemT]": """Fetch the next batch via :meth:`Client.fetch_next_page` and this list's token. Raises: NoMorePagesError: If ``pagination.has_next`` is ``False``. - RuntimeError: If no client is bound (e.g. a deserialized list); call - ``client.fetch_next_page(token)`` or :meth:`with_client` first. + RuntimeError: If no client is bound (e.g. a deserialized list); resume + with ``client.fetch_next_page(pagination.next_page)`` instead. """ if not self.pagination.has_next: raise exc.NoMorePagesError("This is the last page; no more results to fetch.") if self._client is None or self.pagination.next_page is None: raise RuntimeError( "MultipageList has no client bound — it was likely constructed manually " - "or deserialized. Call `client.fetch_next_page(pagination.next_page)` " - "directly, or bind a client with `.with_client(client)` first." + "or deserialized. Resume with `client.fetch_next_page(pagination.next_page)`." ) return await self._client.fetch_next_page(self.pagination.next_page) diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index 84f444a..a03faae 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -218,8 +218,8 @@ async def fake(url, result_type): assert any("start_index=2" in u for u in urls) @pytest.mark.asyncio - async def test_with_client_rebinds_get_next(self): - """A page with no client can be re-bound so get_next works again.""" + async def test_get_next_without_client_raises(self): + """A page that lost its client binding (e.g. deserialized) raises on get_next.""" client = _make_client() async def fake(url, result_type): @@ -234,9 +234,6 @@ async def fake(url, result_type): page._client = None with pytest.raises(RuntimeError, match="no client bound"): await page.get_next() - page.with_client(client) - page2 = await page.get_next() - assert len(page2.data) == 2 class TestMultipageListGetNext: From 2c627eeb4130f3c02f940b695f2d532f77586024 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 13:32:49 +0100 Subject: [PATCH 11/12] tuples --- README.md | 4 +- docs/sources/api-overview.rst | 4 +- docs/sources/usage.rst | 13 ++- src/ch_api/__init__.py | 11 +- src/ch_api/_paginate.py | 2 - src/ch_api/api.py | 22 ++-- src/ch_api/exc.py | 10 +- src/ch_api/types/base.py | 5 + src/ch_api/types/pagination/__init__.py | 6 +- src/ch_api/types/pagination/types.py | 61 ++-------- src/ch_api/types/public_data/charges.py | 8 +- .../types/public_data/company_officers.py | 2 +- .../types/public_data/company_profile.py | 6 +- .../types/public_data/company_registers.py | 14 +-- .../types/public_data/disqualifications.py | 14 +-- src/ch_api/types/public_data/exemptions.py | 10 +- .../types/public_data/filing_history.py | 8 +- src/ch_api/types/public_data/insolvency.py | 12 +- .../types/public_data/officer_appointments.py | 4 +- .../types/public_data/officer_changes.py | 4 +- src/ch_api/types/public_data/psc.py | 26 ++--- src/ch_api/types/public_data/search.py | 18 +-- .../types/public_data/search_companies.py | 14 +-- .../types/public_data/uk_establishments.py | 2 +- src/ch_api/types/test_data_generator.py | 10 +- tests/unit/test_api_branch_coverage.py | 105 ++++++------------ 26 files changed, 167 insertions(+), 228 deletions(-) diff --git a/README.md b/README.md index b571fd6..2287a00 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ Example of getting company information: ## Pagination -List endpoints return a `MultipageList[T]` with `.data` (list) and `.pagination` +List endpoints return a `MultipageList[T]` with `.data` (tuple) and `.pagination` metadata. Pass `result_count` to collect more items per call, and advance with -`.get_next()`: +`client.fetch_next_page(page.pagination.next_page)`: >>> async def search_example(client): ... results = await client.search_companies("tech") diff --git a/docs/sources/api-overview.rst b/docs/sources/api-overview.rst index 17fedba..f4925e1 100644 --- a/docs/sources/api-overview.rst +++ b/docs/sources/api-overview.rst @@ -66,9 +66,9 @@ Key Endpoints Pagination ========== -List endpoints return a ``MultipageList[T]`` with ``data`` (list) and ``pagination`` +List endpoints return a ``MultipageList[T]`` with ``data`` (tuple) and ``pagination`` metadata. Pass ``result_count`` to collect at least that many items in one call, -advance with ``get_next`` (or ``Client.fetch_next_page`` for stateless resume), and +advance with ``Client.fetch_next_page`` (the basis for stateless resume), and use ``page_size`` to control the underlying per-request size: >>> async def pagination_example(client): diff --git a/docs/sources/usage.rst b/docs/sources/usage.rst index a822cbc..ee683b3 100644 --- a/docs/sources/usage.rst +++ b/docs/sources/usage.rst @@ -192,7 +192,7 @@ Search for disqualified officers: Working with Pagination ======================= -Many endpoints return a :class:`ch_api.types.pagination.types.MultipageList`: a value object with ``data`` (this call's items), ``pagination`` (cursor metadata), and a ``get_next`` handle. Pass ``result_count`` to collect at least that many items in one call (the client issues multiple ``page_size`` requests as needed). +Many endpoints return a :class:`ch_api.types.pagination.types.MultipageList`: an immutable value object with ``data`` (this call's items, a tuple) and ``pagination`` (cursor metadata). Pass ``result_count`` to collect at least that many items in one call (the client issues multiple ``page_size`` requests as needed). Fetching a page:: @@ -206,7 +206,7 @@ Fetching multiple pages ----------------------- Pass ``result_count`` to collect more items in one call, then walk the rest of -the result set with ``get_next``: +the result set with ``client.fetch_next_page``: .. code:: python @@ -217,7 +217,8 @@ the result set with ``get_next``: print(company.title) if not page.pagination.has_next: break - page = await page.get_next() # fetches the next batch of result_count items + # fetches the next batch of result_count items + page = await client.fetch_next_page(page.pagination.next_page) # Or resume statelessly with the opaque cursor token (see "Restarting from a # token" below) — endpoints themselves take no next_page argument @@ -566,8 +567,8 @@ Pagination Issues If pagination isn't working as expected: -- Use a regular ``for`` loop over ``result.data`` (it's a plain list) +- Use a regular ``for`` loop over ``result.data`` (it's a tuple) - Pass ``result_count`` to collect more items per call; ``page_size`` controls the underlying per-request size -- Call ``await result.get_next()`` (or ``await client.fetch_next_page(result.pagination.next_page)`` - for stateless resume) to fetch the next batch +- Call ``await client.fetch_next_page(result.pagination.next_page)`` to fetch the + next batch diff --git a/src/ch_api/__init__.py b/src/ch_api/__init__.py index 0e3e4a2..a26824c 100644 --- a/src/ch_api/__init__.py +++ b/src/ch_api/__init__.py @@ -142,19 +142,20 @@ Pagination ---------- -Search and list endpoints return a ``MultipageList[T]`` with a ``data`` list and +Search and list endpoints return a ``MultipageList[T]`` with a ``data`` tuple and ``pagination`` metadata. Pass ``result_count`` to collect at least that many items in one call (issuing multiple underlying requests if needed), advance with -``get_next``, and use ``page_size`` to control the underlying per-request size:: +``client.fetch_next_page``, and use ``page_size`` to control the underlying +per-request size:: >>> @run_async_func ... async def pagination_example(client): ... page = await client.search_companies("Apple") - ... # page.data is a plain list — iterate with a regular for loop + ... # page.data is a tuple — iterate with a regular for loop ... assert len(page.data) >= 1 - ... # Fetch the next page with the bound get_next handle + ... # Fetch the next page via the client and this page's token ... if page.pagination.has_next: - ... page2 = await page.get_next() + ... page2 = await client.fetch_next_page(page.pagination.next_page) ... ``pagination.next_page`` is a **self-contained** cursor: it embeds the endpoint and diff --git a/src/ch_api/_paginate.py b/src/ch_api/_paginate.py index 101cf77..1e31a0c 100644 --- a/src/ch_api/_paginate.py +++ b/src/ch_api/_paginate.py @@ -37,7 +37,6 @@ def paginated(*, exclude: typing.Collection[str] = ("self",)) -> typing.Callable method's annotations). * Publishes the endpoint name and arguments on :data:`_resume_ctx` so the fetch helpers can build a replayable ``next_page`` token. - * Binds the client to the result so :meth:`MultipageList.get_next` works. Args: exclude: Argument names left out of the resume token's ``params`` @@ -60,7 +59,6 @@ async def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: result = await validated(*args, **kwargs) finally: _resume_ctx.reset(ctx_token) - result._client = args[0] # bind the Client (self) for get_next() return result #: Marks the method as a resumable paginated endpoint. ``Client.fetch_next_page`` diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 68cc558..76a3ac6 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -94,14 +94,14 @@ class Client: ... officers = await client.get_officer_list("09370755") ... for officer in officers.data: ... print(f"Officer: {officer.name}") - ... # Search for companies; walk every page with get_next + ... # Search for companies; walk every page via fetch_next_page ... results = await client.search_companies("Apple") ... while True: ... for result in results.data: ... print(f"Found: {result.title} ({result.company_number})") ... if not results.pagination.has_next: ... break - ... results = await results.get_next() + ... results = await client.fetch_next_page(results.pagination.next_page) ... ... # doctest: +SKIP @@ -409,7 +409,7 @@ async def _fetch_paginated( next_page_out = self._encode_next_page(next_state) return types.pagination.types.MultipageList( - data=items, + data=tuple(items), pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -459,7 +459,7 @@ async def _fetch_paginated_cursor( next_page_out = self._encode_next_page(next_state) return types.pagination.types.MultipageList( - data=items, + data=tuple(items), pagination=types.pagination.types.PaginationInfo( has_next=has_next, next_page=next_page_out, @@ -548,7 +548,7 @@ async def _fetch(search_below: typing.Optional[str]) -> tuple[list, typing.Optio return await self._fetch_paginated_cursor(_fetch, result_count) async def fetch_next_page( - self, next_page: types.pagination.types.NextPageToken + self, next_page: typing.Optional[types.pagination.types.NextPageToken] ) -> types.pagination.types.MultipageList: """Resume a paginated request from a ``next_page`` token. @@ -562,20 +562,22 @@ async def fetch_next_page( next_page: A ``pagination.next_page`` token from a prior result. Returns: - The next ``MultipageList``, bound for further iteration. + The next ``MultipageList``. Raises: + NoMorePagesError: If ``next_page`` is ``None`` (the prior page was the + last one — check ``pagination.has_next`` before calling). ValueError: If the token does not name a resumable endpoint (e.g. malformed or tampered). Example:: page = await client.search_companies("Apple", page_size=20) - token = page.pagination.next_page # hand this to the caller - - # ... later, in a new request with only `token` in hand ... - page2 = await client.fetch_next_page(token) + while page.pagination.has_next: + page = await client.fetch_next_page(page.pagination.next_page) """ + if next_page is None: + raise exc.NoMorePagesError("This is the last page; no more results to fetch.") state = self._decode_next_page(next_page) # Only ``@paginated`` methods carry ``_ch_paginated``; gating on it stops a # tampered token from re-dispatching to an arbitrary client method, and the diff --git a/src/ch_api/exc.py b/src/ch_api/exc.py index d98b872..e73ec75 100644 --- a/src/ch_api/exc.py +++ b/src/ch_api/exc.py @@ -87,11 +87,11 @@ class UnexpectedApiResponseError(CompaniesHouseApiError): class NoMorePagesError(CompaniesHouseApiError): - """Raised when :meth:`MultipageList.get_next` is called on the last page. + """Raised when :meth:`Client.fetch_next_page` is called with a terminal token. - Indicates the caller asked for a page beyond the end of the result set. - Check ``MultipageList.pagination.has_next`` before calling ``get_next`` - to avoid this exception. + Indicates the caller asked for a page beyond the end of the result set (the + prior page's ``pagination.next_page`` was ``None``). Check + ``MultipageList.pagination.has_next`` before calling to avoid this exception. Example ------- @@ -99,7 +99,7 @@ class NoMorePagesError(CompaniesHouseApiError): page = await client.search_companies("Apple") while page.pagination.has_next: - page = await page.get_next() + page = await client.fetch_next_page(page.pagination.next_page) See Also -------- diff --git a/src/ch_api/types/base.py b/src/ch_api/types/base.py index a2b2d9e..5e59dc1 100644 --- a/src/ch_api/types/base.py +++ b/src/ch_api/types/base.py @@ -18,10 +18,15 @@ class BaseModel(pydantic.BaseModel): Automatically normalizes field names to lowercase for consistency. Inherits from this class for all API response models. + + Instances are immutable (``frozen``) and therefore hashable: collection + fields are declared as tuples rather than lists so a whole response — and a + ``MultipageList`` of them — can be used in sets or as dict keys. """ model_config = pydantic.ConfigDict( extra=settings.model_validate_extra, + frozen=True, ) @classmethod diff --git a/src/ch_api/types/pagination/__init__.py b/src/ch_api/types/pagination/__init__.py index 350e977..12981e4 100644 --- a/src/ch_api/types/pagination/__init__.py +++ b/src/ch_api/types/pagination/__init__.py @@ -2,11 +2,11 @@ All paginated endpoints on ``Client`` accept ``page_size`` and ``result_count`` and return a ``MultipageList[T]``. ``result_count`` sets the minimum number of -items to collect in one call; ``get_next`` fetches the next batch:: +items to collect in one call; ``client.fetch_next_page`` fetches the next batch:: page = await client.search_companies("Apple", result_count=50) while page.pagination.has_next: - page = await page.get_next() + page = await client.fetch_next_page(page.pagination.next_page) ``pagination.next_page`` is a self-contained cursor (it embeds the endpoint and its arguments), so a fresh process can resume statelessly with only the token — @@ -17,7 +17,7 @@ Key Classes ----------- -- :class:`types.MultipageList` - Result-batch container with a ``get_next`` handle +- :class:`types.MultipageList` - Immutable result-batch container - :class:`types.PaginationInfo` - Pagination metadata - :class:`types.NextPageToken` - Self-contained, restartable opaque cursor - :class:`types.PageTokenSerializer` - Optional token encryption protocol diff --git a/src/ch_api/types/pagination/types.py b/src/ch_api/types/pagination/types.py index 1883ab8..315b287 100644 --- a/src/ch_api/types/pagination/types.py +++ b/src/ch_api/types/pagination/types.py @@ -4,7 +4,7 @@ NextPageToken: Opaque string cursor passed between calls to page through results. PageTokenSerializer: Protocol for encrypting/decrypting pagination tokens. PaginationInfo: Pagination metadata returned alongside each page of results. - MultipageList: Generic value object holding one batch of results plus a get_next handle. + MultipageList: Immutable generic value object holding one batch of results. Internal types (not part of the public API): _PageState: Self-contained, restartable pagination cursor (the encoded NextPageToken). @@ -14,8 +14,6 @@ import pydantic -from ... import exc - _ItemT = typing.TypeVar("_ItemT", bound=pydantic.BaseModel) @@ -74,8 +72,7 @@ def first(cls) -> "_PageState": """An opaque string cursor for retrieving the next page of results. Returned in ``PaginationInfo.next_page`` when more results exist. Pass it to -``Client.fetch_next_page`` to fetch the next batch (or call -``MultipageList.get_next``, which does this for you). +``Client.fetch_next_page`` to fetch the next batch. The internal format is an implementation detail and may change. Always treat this value as opaque. @@ -136,16 +133,15 @@ def deserialize(self, token: str) -> str: class PaginationInfo(pydantic.BaseModel): """Pagination state for a result set returned by the CH API. - Returned alongside every page of results from the async client. Use - ``MultipageList.get_next`` (or pass ``next_page`` to - ``Client.fetch_next_page``) to retrieve the next page of items. + Returned alongside every page of results from the async client. Pass + ``next_page`` to ``Client.fetch_next_page`` to retrieve the next page of items. Example:: page = await client.search_companies("Apple") while page.pagination.has_next: - page = await page.get_next() + page = await client.fetch_next_page(page.pagination.next_page) """ model_config = pydantic.ConfigDict(frozen=True) @@ -163,18 +159,6 @@ class PaginationInfo(pydantic.BaseModel): ) -# --------------------------------------------------------------------------- -# Internal: client handle used by MultipageList.get_next -# --------------------------------------------------------------------------- - - -@typing.runtime_checkable -class _NextPageFetcher(typing.Protocol): - """Minimal client interface :meth:`MultipageList.get_next` needs (``Client`` satisfies it).""" - - async def fetch_next_page(self, next_page: str) -> typing.Any: ... - - # --------------------------------------------------------------------------- # Public: result page model # --------------------------------------------------------------------------- @@ -184,14 +168,14 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): """A batch of typed results from a paginated CH API endpoint. Holds the items from one client call — at least ``result_count``, or all - remaining if fewer — plus pagination metadata. A plain value object: advance - with :meth:`get_next` (or pass ``pagination.next_page`` to - :meth:`Client.fetch_next_page`); it does not fetch lazily or merge batches. + remaining if fewer — plus pagination metadata. An immutable value object: it + does not fetch lazily or merge batches. Advance by passing + ``pagination.next_page`` to :meth:`Client.fetch_next_page`. Type Parameters: _ItemT: The type of items in ``data``. - Walking the whole result set with ``get_next``:: + Walking the whole result set:: page = await client.search_companies("Apple") while True: @@ -199,7 +183,7 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): ... # process this batch's items if not page.pagination.has_next: break - page = await page.get_next() + page = await client.fetch_next_page(page.pagination.next_page) Fetching a larger batch in one call:: @@ -217,30 +201,9 @@ class MultipageList(pydantic.BaseModel, typing.Generic[_ItemT]): page2 = await client.fetch_next_page(token) """ - model_config = pydantic.ConfigDict(frozen=True, arbitrary_types_allowed=True) + model_config = pydantic.ConfigDict(frozen=True) - data: typing.List[_ItemT] = pydantic.Field(description="The result items for this page.") + data: typing.Tuple[_ItemT, ...] = pydantic.Field(description="The result items for this page.") pagination: PaginationInfo = pydantic.Field( description="Pagination state, including whether more results exist and how to fetch them." ) - - _client: typing.Optional[_NextPageFetcher] = pydantic.PrivateAttr(default=None) - """Client used by :meth:`get_next`. Set when the list is produced; ``None`` on - deserialized instances (resume those via ``Client.fetch_next_page(token)``).""" - - async def get_next(self) -> "MultipageList[_ItemT]": - """Fetch the next batch via :meth:`Client.fetch_next_page` and this list's token. - - Raises: - NoMorePagesError: If ``pagination.has_next`` is ``False``. - RuntimeError: If no client is bound (e.g. a deserialized list); resume - with ``client.fetch_next_page(pagination.next_page)`` instead. - """ - if not self.pagination.has_next: - raise exc.NoMorePagesError("This is the last page; no more results to fetch.") - if self._client is None or self.pagination.next_page is None: - raise RuntimeError( - "MultipageList has no client bound — it was likely constructed manually " - "or deserialized. Resume with `client.fetch_next_page(pagination.next_page)`." - ) - return await self._client.fetch_next_page(self.pagination.next_page) diff --git a/src/ch_api/types/public_data/charges.py b/src/ch_api/types/public_data/charges.py index ad37ea5..fd35050 100644 --- a/src/ch_api/types/public_data/charges.py +++ b/src/ch_api/types/public_data/charges.py @@ -495,7 +495,7 @@ class ChargeDetails(base.BaseModel): ] persons_entitled: typing.Annotated[ - list[PersonsEntitled] | None, + tuple[PersonsEntitled, ...] | None, pydantic.Field( description="People that are entitled to the charge", default=None, @@ -503,7 +503,7 @@ class ChargeDetails(base.BaseModel): ] transactions: typing.Annotated[ - list[Transactions] | None, + tuple[Transactions, ...] | None, pydantic.Field( description="Transactions that have been filed for the charge.", default=None, @@ -511,7 +511,7 @@ class ChargeDetails(base.BaseModel): ] insolvency_cases: typing.Annotated[ - list[InsolvencyCases] | None, + tuple[InsolvencyCases, ...] | None, pydantic.Field( description="Transactions that have been filed for the charge.", default=None, @@ -538,7 +538,7 @@ class ChargeList(base.BaseModel): ] items: typing.Annotated[ - list[ChargeDetails], + tuple[ChargeDetails, ...], pydantic.Field( description="List of charges", ), diff --git a/src/ch_api/types/public_data/company_officers.py b/src/ch_api/types/public_data/company_officers.py index d03d0d6..0d1f836 100644 --- a/src/ch_api/types/public_data/company_officers.py +++ b/src/ch_api/types/public_data/company_officers.py @@ -523,7 +523,7 @@ class OfficerSummary(base.BaseModel): ] former_names: typing.Annotated[ - list[FormerNames] | None, + tuple[FormerNames, ...] | None, pydantic.Field( description="Former names for the officer.", default=None, diff --git a/src/ch_api/types/public_data/company_profile.py b/src/ch_api/types/public_data/company_profile.py index 93932e0..0419778 100644 --- a/src/ch_api/types/public_data/company_profile.py +++ b/src/ch_api/types/public_data/company_profile.py @@ -1038,7 +1038,7 @@ class CompanyProfile(base.BaseModel): ] sic_codes: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="SIC codes for this company.", default=None, @@ -1046,7 +1046,7 @@ class CompanyProfile(base.BaseModel): ] previous_company_names: typing.Annotated[ - list[PreviousCompanyNames] | None, + tuple[PreviousCompanyNames, ...] | None, pydantic.Field( description="The previous names of this company.", default=None, @@ -1054,7 +1054,7 @@ class CompanyProfile(base.BaseModel): ] corporate_annotation: typing.Annotated[ - list[CorporateAnnotation] | None, + tuple[CorporateAnnotation, ...] | None, pydantic.Field( description=( "A corporate level message published by Companies House about a company, " diff --git a/src/ch_api/types/public_data/company_registers.py b/src/ch_api/types/public_data/company_registers.py index e84d4f8..85912dd 100644 --- a/src/ch_api/types/public_data/company_registers.py +++ b/src/ch_api/types/public_data/company_registers.py @@ -202,7 +202,7 @@ class RegisterListDirectors(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered directors.", ), @@ -229,7 +229,7 @@ class RegisterListSecretaries(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered secretaries.", ), @@ -256,7 +256,7 @@ class RegisterListPersonsWithSignificantControl(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered persons with significant control.", ), @@ -283,7 +283,7 @@ class RegisterListUsualResidentialAddress(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered addresses.", ), @@ -310,7 +310,7 @@ class RegisterListLLPUsualResidentialAddress(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered LLP addresses.", ), @@ -337,7 +337,7 @@ class RegisterListMembers(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered members.", ), @@ -364,7 +364,7 @@ class RegisterListLLPMembers(base.BaseModel): ] items: typing.Annotated[ - list[RegisteredItems], + tuple[RegisteredItems, ...], pydantic.Field( description="List of registered LLP members.", ), diff --git a/src/ch_api/types/public_data/disqualifications.py b/src/ch_api/types/public_data/disqualifications.py index a818847..b74c291 100644 --- a/src/ch_api/types/public_data/disqualifications.py +++ b/src/ch_api/types/public_data/disqualifications.py @@ -223,7 +223,7 @@ class Disqualification(base.BaseModel): ] company_names: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="The companies in which the misconduct took place.", default=None, @@ -280,7 +280,7 @@ class Disqualification(base.BaseModel): ] last_variation: typing.Annotated[ - list[LastVariation] | None, + tuple[LastVariation, ...] | None, pydantic.Field( description="The latest variation made to the disqualification.", default=None, @@ -299,7 +299,7 @@ class PermissionToAct(base.BaseModel): """Permission to act that has been granted for the disqualified officer.""" company_names: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="The companies for which the disqualified officer has permission to act.", default=None, @@ -409,14 +409,14 @@ class NaturalDisqualification(base.BaseModel): ] disqualifications: typing.Annotated[ - list[Disqualification], + tuple[Disqualification, ...], pydantic.Field( description="The officer's disqualifications.", ), ] permissions_to_act: typing.Annotated[ - list[PermissionToAct] | None, + tuple[PermissionToAct, ...] | None, pydantic.Field( description="Permissions to act that have been granted for the disqualified officer.", default=None, @@ -480,14 +480,14 @@ class CorporateDisqualification(base.BaseModel): ] disqualifications: typing.Annotated[ - list[Disqualification], + tuple[Disqualification, ...], pydantic.Field( description="The officer's disqualifications.", ), ] permissions_to_act: typing.Annotated[ - list[PermissionToAct] | None, + tuple[PermissionToAct, ...] | None, pydantic.Field( description="Permissions that the disqualified officer has to act outside of their disqualification.", default=None, diff --git a/src/ch_api/types/public_data/exemptions.py b/src/ch_api/types/public_data/exemptions.py index 701194b..32aead4 100644 --- a/src/ch_api/types/public_data/exemptions.py +++ b/src/ch_api/types/public_data/exemptions.py @@ -93,7 +93,7 @@ class PscExemptAsTradingOnRegulatedMarketItem(base.BaseModel): ] items: typing.Annotated[ - list[ExemptionItem], + tuple[ExemptionItem, ...], pydantic.Field( description="List of dates", ), @@ -111,7 +111,7 @@ class PscExemptAsSharesAdmittedOnMarketItem(base.BaseModel): ] items: typing.Annotated[ - list[ExemptionItem], + tuple[ExemptionItem, ...], pydantic.Field( description="List of dates", ), @@ -129,7 +129,7 @@ class PscExemptAsTradingOnUkRegulatedMarketItem(base.BaseModel): ] items: typing.Annotated[ - list[ExemptionItem], + tuple[ExemptionItem, ...], pydantic.Field( description="List of dates", ), @@ -147,7 +147,7 @@ class PscExemptAsTradingOnEuRegulatedMarketItem(base.BaseModel): ] items: typing.Annotated[ - list[ExemptionItem], + tuple[ExemptionItem, ...], pydantic.Field( description="List of dates", ), @@ -165,7 +165,7 @@ class DisclosureTransparencyRulesChapterFiveAppliesItem(base.BaseModel): ] items: typing.Annotated[ - list[ExemptionItem], + tuple[ExemptionItem, ...], pydantic.Field( description="List of exemption periods.", ), diff --git a/src/ch_api/types/public_data/filing_history.py b/src/ch_api/types/public_data/filing_history.py index 4452fd9..6101dc4 100644 --- a/src/ch_api/types/public_data/filing_history.py +++ b/src/ch_api/types/public_data/filing_history.py @@ -298,7 +298,7 @@ class FilingHistoryItem(base.BaseModel): ] annotations: typing.Annotated[ - list[Annotation] | None, + tuple[Annotation, ...] | None, pydantic.Field( description="Annotations for the filing", default=None, @@ -306,7 +306,7 @@ class FilingHistoryItem(base.BaseModel): ] associated_filings: typing.Annotated[ - list[AssociatedFiling] | None, + tuple[AssociatedFiling, ...] | None, pydantic.Field( description="Any filings associated with the current item", default=None, @@ -346,7 +346,7 @@ class FilingHistoryItem(base.BaseModel): ] resolutions: typing.Annotated[ - list[Resolution] | None, + tuple[Resolution, ...] | None, pydantic.Field( description="Resolutions for the filing", default=None, @@ -378,7 +378,7 @@ class FilingHistoryList(base.BaseModel): ] items: typing.Annotated[ - list[FilingHistoryItem], + tuple[FilingHistoryItem, ...], pydantic.Field( description="The filing history items.", ), diff --git a/src/ch_api/types/public_data/insolvency.py b/src/ch_api/types/public_data/insolvency.py index b81a4a6..32cca1e 100644 --- a/src/ch_api/types/public_data/insolvency.py +++ b/src/ch_api/types/public_data/insolvency.py @@ -245,21 +245,21 @@ class Case(base.BaseModel): ] dates: typing.Annotated[ - list[CaseDates], + tuple[CaseDates, ...], pydantic.Field( description="The dates specific to the case.", ), ] practitioners: typing.Annotated[ - list[Practitioners], + tuple[Practitioners, ...], pydantic.Field( description="The practitioners for the case.", ), ] notes: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="The dates specific to the case.", default=None, @@ -295,14 +295,14 @@ class CompanyInsolvency(base.BaseModel): ] cases: typing.Annotated[ - list[Case], + tuple[Case, ...], pydantic.Field( description="List of insolvency cases.", ), ] status: typing.Annotated[ - list[ + tuple[ typing.Annotated[ str, field_types.RelaxedLiteral( @@ -317,7 +317,7 @@ class CompanyInsolvency(base.BaseModel): "voluntary-arrangement-receivership", ), ] - ] + , ...] | None, pydantic.Field( description="Company insolvency status details", diff --git a/src/ch_api/types/public_data/officer_appointments.py b/src/ch_api/types/public_data/officer_appointments.py index dbb12a9..d5802c4 100644 --- a/src/ch_api/types/public_data/officer_appointments.py +++ b/src/ch_api/types/public_data/officer_appointments.py @@ -436,7 +436,7 @@ class OfficerAppointmentSummary(base.BaseModel): ] former_names: typing.Annotated[ - list[FormerNames] | None, + tuple[FormerNames, ...] | None, pydantic.Field( description="Former names for the officer, if there are any.", default=None, @@ -544,7 +544,7 @@ class AppointmentList(base.BaseModel): ] items: typing.Annotated[ - list[OfficerAppointmentSummary], + tuple[OfficerAppointmentSummary, ...], pydantic.Field( description="The list of officer appointments.", ), diff --git a/src/ch_api/types/public_data/officer_changes.py b/src/ch_api/types/public_data/officer_changes.py index ce7c1c1..f2b1815 100644 --- a/src/ch_api/types/public_data/officer_changes.py +++ b/src/ch_api/types/public_data/officer_changes.py @@ -226,7 +226,7 @@ class IdentityVerificationDetails(base.BaseModel): """Information relating to the identity verification of the officer.""" anti_money_laundering_supervisory_bodies: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description=( "The Anti-Money Laundering supervisory bodies that the authorised " @@ -445,7 +445,7 @@ class OfficerChange(base.BaseModel): ] former_names: typing.Annotated[ - list[FormerNames] | None, + tuple[FormerNames, ...] | None, pydantic.Field( description="Former names for the officer.", default=None, diff --git a/src/ch_api/types/public_data/psc.py b/src/ch_api/types/public_data/psc.py index e481feb..4730dc5 100644 --- a/src/ch_api/types/public_data/psc.py +++ b/src/ch_api/types/public_data/psc.py @@ -575,7 +575,7 @@ class IdentityVerificationDetails(base.BaseModel): """Identity verification details for a PSC.""" anti_money_laundering_supervisory_bodies: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description=( "The Anti-Money Laundering supervisory bodies that the " @@ -787,7 +787,7 @@ class ListSummary(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str], + tuple[str, ...], pydantic.Field(description=("Indicates the nature of control the person with significant control holds.")), ] @@ -826,7 +826,7 @@ class PSCList(base.BaseModel): ] items: typing.Annotated[ - list[ListSummary], + tuple[ListSummary, ...], pydantic.Field(description="The list of persons with significant control."), ] @@ -875,7 +875,7 @@ class IndividualList(base.BaseModel): ] items: typing.Annotated[ - list[ListSummary], + tuple[ListSummary, ...], pydantic.Field(description="The list of individual persons with significant control."), ] @@ -928,7 +928,7 @@ class CorporateEntityList(base.BaseModel): ] items: typing.Annotated[ - list[ListSummary], + tuple[ListSummary, ...], pydantic.Field(description="The list of corporate entity persons with significant control."), ] @@ -979,7 +979,7 @@ class LegalPersonList(base.BaseModel): ] items: typing.Annotated[ - list[ListSummary], + tuple[ListSummary, ...], pydantic.Field(description="The list of legal persons with significant control."), ] @@ -1018,7 +1018,7 @@ class StatementList(base.BaseModel): ] items: typing.Annotated[ - list["Statement"], + tuple["Statement", ...], pydantic.Field(description="The list of persons with significant control statements."), ] @@ -1272,7 +1272,7 @@ class Individual(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str], + tuple[str, ...], pydantic.Field(description=("Indicates the nature of control the person with significant control holds.")), ] @@ -1364,7 +1364,7 @@ class IndividualBeneficialOwner(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description=("Indicates the nature of control the beneficial owner holds."), default=None, @@ -1439,7 +1439,7 @@ class CorporateEntity(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str], + tuple[str, ...], pydantic.Field(description=("Indicates the nature of control the person with significant control holds.")), ] @@ -1512,7 +1512,7 @@ class CorporateEntityBeneficialOwner(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description=("Indicates the nature of control the beneficial owner holds."), default=None, @@ -1587,7 +1587,7 @@ class LegalPerson(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str], + tuple[str, ...], pydantic.Field(description="Indicates the nature of control the person with significant control holds."), ] @@ -1660,7 +1660,7 @@ class LegalPersonBeneficialOwner(base.BaseModel): ] natures_of_control: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="Indicates the nature of control the beneficial owner holds.", default=None, diff --git a/src/ch_api/types/public_data/search.py b/src/ch_api/types/public_data/search.py index 1765bfe..b532dcc 100644 --- a/src/ch_api/types/public_data/search.py +++ b/src/ch_api/types/public_data/search.py @@ -53,7 +53,7 @@ auth = api_settings.AuthSettings(api_key="your-key") client = Client(credentials=auth) - # Simple search (one page; call results.get_next() for more) + # Simple search (one page; fetch_next_page(results.pagination.next_page) for more) results = await client.search_companies("Apple") for company in results.data: print(f"{company.title} ({company.company_number})") @@ -72,7 +72,7 @@ ----- Search results are returned as a :class:`ch_api.types.pagination.types.MultipageList`. Pass ``result_count`` to collect at least that many items in one call; advance with -``get_next`` (or ``Client.fetch_next_page`` for stateless resume). +``Client.fetch_next_page`` (the basis for stateless resume). See Also -------- @@ -100,7 +100,7 @@ class MatchesModel(base.BaseModel): """Character offsets defining substrings that matched the search terms.""" title: typing.Annotated[ - list[int] | None, + tuple[int, ...] | None, pydantic.Field( description=( "An array of character offset into the `title` string. These always occur in pairs " @@ -112,7 +112,7 @@ class MatchesModel(base.BaseModel): ] snippet: typing.Annotated[ - list[int] | None, + tuple[int, ...] | None, pydantic.Field( description=( "An array of character offset into the `snippet` string. These always occur in pairs " @@ -124,7 +124,7 @@ class MatchesModel(base.BaseModel): ] address_snippet: typing.Annotated[ - list[int] | None, + tuple[int, ...] | None, pydantic.Field( description=( "An array of character offset into the `address_snippet` string. These always occur " @@ -486,7 +486,7 @@ class CompanySearchItem(base.BaseModel): ] description_identifier: typing.Annotated[ - list[ + tuple[ typing.Annotated[ str, field_types.RelaxedLiteral( @@ -509,7 +509,7 @@ class CompanySearchItem(base.BaseModel): "registered-externally", ), ] - ] + , ...] | None, pydantic.Field( description=( @@ -621,7 +621,7 @@ class OfficerSearchItem(base.BaseModel): ] description_identifiers: typing.Annotated[ - list[typing.Annotated[str, field_types.RelaxedLiteral("appointment-count", "born-on")]] | None, + tuple[typing.Annotated[str, field_types.RelaxedLiteral("appointment-count", "born-on")], ...] | None, pydantic.Field( description=( "An array of enumeration types that make up the search description. " @@ -700,7 +700,7 @@ class DisqualifiedOfficerSearchItem(base.BaseModel): ] description_identifiers: typing.Annotated[ - list[typing.Annotated[str, field_types.RelaxedLiteral("born-on")]] | None, + tuple[typing.Annotated[str, field_types.RelaxedLiteral("born-on")], ...] | None, pydantic.Field( description=( "An array of enumeration types that make up the search description. " diff --git a/src/ch_api/types/public_data/search_companies.py b/src/ch_api/types/public_data/search_companies.py index 2385164..89d1f68 100644 --- a/src/ch_api/types/public_data/search_companies.py +++ b/src/ch_api/types/public_data/search_companies.py @@ -74,8 +74,8 @@ Pagination ----- All searches are paginated. The client's search methods return a MultipageList; -pass ``result_count`` to collect more items per call and advance with ``get_next`` -(or ``Client.fetch_next_page`` for stateless resume). +pass ``result_count`` to collect more items per call and advance with +``Client.fetch_next_page`` (the basis for stateless resume). Example Usage ----- @@ -290,7 +290,7 @@ class DissolvedCompany(base.BaseModel): ] previous_company_names: typing.Annotated[ - list[PreviousCompanyName] | None, + tuple[PreviousCompanyName, ...] | None, pydantic.Field( default=None, ), @@ -538,7 +538,7 @@ class AdvancedCompany(base.BaseModel): ] sic_codes: typing.Annotated[ - list[str] | None, + tuple[str, ...] | None, pydantic.Field( description="SIC codes for this company", default=None, @@ -550,7 +550,7 @@ class AlphabeticalCompanySearchResult(base.BaseModel, typing.Generic[T]): """List of companies from alphabetical search.""" items: typing.Annotated[ - list[T] | None, + tuple[T, ...] | None, pydantic.Field( default=None, ), @@ -613,7 +613,7 @@ class GenericSearchResult(base.BaseModel, typing.Generic[T]): ] items: typing.Annotated[ - list[T] | None, + tuple[T, ...] | None, pydantic.Field( description="The results of the completed search.", default=None, @@ -640,7 +640,7 @@ class AdvancedSearchResult(base.BaseModel, typing.Generic[T]): ] items: typing.Annotated[ - list[T] | None, + tuple[T, ...] | None, pydantic.Field( description="The results of the completed search.", default=None, diff --git a/src/ch_api/types/public_data/uk_establishments.py b/src/ch_api/types/public_data/uk_establishments.py index f1d51e7..787a9a8 100644 --- a/src/ch_api/types/public_data/uk_establishments.py +++ b/src/ch_api/types/public_data/uk_establishments.py @@ -156,7 +156,7 @@ class CompanyUKEstablishments(base.BaseModel): ] items: typing.Annotated[ - typing.Sequence[CompanyEstablishmentDetails], + tuple[CompanyEstablishmentDetails, ...], pydantic.Field( description="List of UK Establishment companies.", ), diff --git a/src/ch_api/types/test_data_generator.py b/src/ch_api/types/test_data_generator.py index 1c5a443..d1fc7ec 100644 --- a/src/ch_api/types/test_data_generator.py +++ b/src/ch_api/types/test_data_generator.py @@ -303,7 +303,7 @@ class FilingHistoryItem(base.BaseModel): pydantic.Field(description="The sub category of the filing history"), ] resolutions: typing.Annotated[ - list[FilingHistoryResolution] | None, + tuple[FilingHistoryResolution, ...] | None, pydantic.Field( description=( "The resolutions associated with the filing history. Maximum of 20 resolutions can be specified" @@ -460,7 +460,7 @@ class CreateTestCompanyRequest(base.BaseModel): ] filing_history: typing.Annotated[ - list[FilingHistoryItem] | None, + tuple[FilingHistoryItem, ...] | None, pydantic.Field( description=( "The filing history of the test company to generate. " @@ -480,7 +480,7 @@ class CreateTestCompanyRequest(base.BaseModel): ] registers: typing.Annotated[ - list[RegisterItem] | None, + tuple[RegisterItem, ...] | None, pydantic.Field( description=("The registers for the test company to generate. Maximum of 20 registers can be specified"), max_length=20, @@ -512,7 +512,7 @@ class CreateTestCompanyRequest(base.BaseModel): ] officer_roles: typing.Annotated[ - list[ + tuple[ typing.Literal[ "cic-manager", "corporate-director", @@ -545,7 +545,7 @@ class CreateTestCompanyRequest(base.BaseModel): "receiver-and-manager", "secretary", ] - ] + , ...] | None, pydantic.Field( description=( diff --git a/tests/unit/test_api_branch_coverage.py b/tests/unit/test_api_branch_coverage.py index a03faae..d3c3d9c 100644 --- a/tests/unit/test_api_branch_coverage.py +++ b/tests/unit/test_api_branch_coverage.py @@ -217,55 +217,24 @@ async def fake(url, result_type): await client.fetch_next_page(page.pagination.next_page) assert any("start_index=2" in u for u in urls) - @pytest.mark.asyncio - async def test_get_next_without_client_raises(self): - """A page that lost its client binding (e.g. deserialized) raises on get_next.""" - client = _make_client() - async def fake(url, result_type): - result = MagicMock() - result.items = [search_types.CompanySearchItem.model_construct() for _ in range(2)] - result.total_results = 4 - return result - - client._get_resource = fake - page = await client.search_companies("Apple", page_size=2) - # simulate a reconstructed page that lost its client binding - page._client = None - with pytest.raises(RuntimeError, match="no client bound"): - await page.get_next() - - -class TestMultipageListGetNext: - """MultipageList.get_next error paths on manually-constructed instances.""" +class TestFetchNextPageTerminal: + """fetch_next_page on a terminal token.""" @pytest.mark.asyncio - async def test_get_next_on_last_page_raises_no_more_pages(self): - """has_next is False → NoMorePagesError.""" - page = pagination_types.MultipageList( - data=[], - pagination=pagination_types.PaginationInfo(has_next=False), - ) + async def test_fetch_next_page_none_raises_no_more_pages(self): + """A None token (the last page's next_page) → NoMorePagesError.""" + client = _make_client() with pytest.raises(exc.NoMorePagesError): - await page.get_next() - - @pytest.mark.asyncio - async def test_get_next_without_client_raises_runtime_error(self): - """has_next True but no bound client (e.g. deserialized) → RuntimeError.""" - page = pagination_types.MultipageList( - data=[], - pagination=pagination_types.PaginationInfo(has_next=True, next_page="tok"), - ) - with pytest.raises(RuntimeError, match="no client bound"): - await page.get_next() + await client.fetch_next_page(None) class TestOffsetPagination: - """Offset accumulation to result_count + get_next advances the batch via fetch_next_page.""" + """Offset accumulation to result_count + fetch_next_page advances the batch.""" @pytest.mark.asyncio - async def test_get_next_offset(self): - """get_next fetches the next batch from the next offset (page_size 2, total 4).""" + async def test_fetch_next_page_offset(self): + """fetch_next_page fetches the next batch from the next offset (page_size 2, total 4).""" client = _make_client() urls = [] @@ -283,7 +252,7 @@ async def fake(url, result_type): assert any("start_index=0" in u for u in urls) urls.clear() - page2 = await page.get_next() + page2 = await client.fetch_next_page(page.pagination.next_page) assert len(page2.data) == 2 assert not page2.pagination.has_next assert any("start_index=2" in u for u in urls) @@ -346,7 +315,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.alphabetical_companies_search("test") - assert page.data == [] + assert page.data == () assert not page.pagination.has_next @pytest.mark.asyncio @@ -359,11 +328,11 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.alphabetical_companies_search("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio - async def test_get_next_via_alphabetical_search_uses_search_below(self): - """alphabetical_companies_search: get_next carries the search_below cursor.""" + async def test_fetch_next_page_via_alphabetical_search_uses_search_below(self): + """alphabetical_companies_search: fetch_next_page carries the search_below cursor.""" client = _make_client() call_count = 0 item = _alpha_company("KEY_ALPHA:00000001") @@ -383,8 +352,8 @@ async def fake_get_resource(url, result_type): assert page.pagination.has_next assert call_count == 1 - page2 = await page.get_next() - assert page2.data == [] + page2 = await client.fetch_next_page(page.pagination.next_page) + assert page2.data == () assert call_count == 2 assert any("search_below=KEY_ALPHA" in u for u in urls_seen) @@ -423,7 +392,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_dissolved_companies("test") - assert page.data == [] + assert page.data == () class TestOfficerListBranches: @@ -454,7 +423,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_officer_list("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_result_returns_empty(self): @@ -466,7 +435,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_officer_list("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_http_error_propagates(self): @@ -493,7 +462,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_search_none_returns_empty(self): @@ -504,7 +473,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_search_non_416_propagates(self): @@ -631,7 +600,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.advanced_company_search(company_name_includes="test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -643,7 +612,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.advanced_company_search(company_name_includes="test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -669,7 +638,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_companies("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -680,7 +649,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_companies("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -706,7 +675,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_officers("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -717,7 +686,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_officers("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -743,7 +712,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_disqualified_officers("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -754,7 +723,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.search_disqualified_officers("test") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -780,7 +749,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_filing_history("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -791,7 +760,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_filing_history("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -832,7 +801,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -844,7 +813,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_officer_appointments("_y4370DCOaJgIqvAlmHtJ7HdiqU") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -870,7 +839,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_psc_list("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -881,7 +850,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_psc_list("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): @@ -907,7 +876,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_psc_statements("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_none_returns_empty(self): @@ -918,7 +887,7 @@ async def fake_get_resource(url, result_type): client._get_resource = fake_get_resource page = await client.get_company_psc_statements("12345678") - assert page.data == [] + assert page.data == () @pytest.mark.asyncio async def test_non_416_propagates(self): From 151572500442a3ecd77aa898a6d8c9b1bfd98918 Mon Sep 17 00:00:00 2001 From: Ilja Orlovs Date: Thu, 18 Jun 2026 13:33:04 +0100 Subject: [PATCH 12/12] fmt --- src/ch_api/api.py | 4 ++-- src/ch_api/types/public_data/insolvency.py | 5 +++-- src/ch_api/types/public_data/search.py | 5 +++-- src/ch_api/types/test_data_generator.py | 5 +++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/ch_api/api.py b/src/ch_api/api.py index 76a3ac6..a41ee82 100644 --- a/src/ch_api/api.py +++ b/src/ch_api/api.py @@ -34,8 +34,8 @@ #: Task-local channel for the position to resume from, set by #: :meth:`Client.fetch_next_page` and read by the fetch helpers. ``None`` for a #: fresh call (start from the beginning). -_resume_from_ctx: contextvars.ContextVar[typing.Optional[types.pagination.types._PageState]] = ( - contextvars.ContextVar("ch_api_resume_from", default=None) +_resume_from_ctx: contextvars.ContextVar[typing.Optional[types.pagination.types._PageState]] = contextvars.ContextVar( + "ch_api_resume_from", default=None ) LimiterContextT = typing.Callable[[], typing.AsyncContextManager[None]] diff --git a/src/ch_api/types/public_data/insolvency.py b/src/ch_api/types/public_data/insolvency.py index 32cca1e..2d50f6c 100644 --- a/src/ch_api/types/public_data/insolvency.py +++ b/src/ch_api/types/public_data/insolvency.py @@ -316,8 +316,9 @@ class CompanyInsolvency(base.BaseModel): "voluntary-arrangement", "voluntary-arrangement-receivership", ), - ] - , ...] + ], + ..., + ] | None, pydantic.Field( description="Company insolvency status details", diff --git a/src/ch_api/types/public_data/search.py b/src/ch_api/types/public_data/search.py index b532dcc..9023a7c 100644 --- a/src/ch_api/types/public_data/search.py +++ b/src/ch_api/types/public_data/search.py @@ -508,8 +508,9 @@ class CompanySearchItem(base.BaseModel): "removed", "registered-externally", ), - ] - , ...] + ], + ..., + ] | None, pydantic.Field( description=( diff --git a/src/ch_api/types/test_data_generator.py b/src/ch_api/types/test_data_generator.py index d1fc7ec..02b9a99 100644 --- a/src/ch_api/types/test_data_generator.py +++ b/src/ch_api/types/test_data_generator.py @@ -544,8 +544,9 @@ class CreateTestCompanyRequest(base.BaseModel): "person-authorised-to-represent-and-accept", "receiver-and-manager", "secretary", - ] - , ...] + ], + ..., + ] | None, pydantic.Field( description=(