diff --git a/.fern/metadata.json b/.fern/metadata.json index 2239b83..0ff0268 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,7 +1,7 @@ { - "cliVersion": "5.45.3", + "cliVersion": "5.50.6", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "5.14.12", + "generatorVersion": "5.14.21", "generatorConfig": { "package_name": "newscatcher_catchall", "client": { @@ -9,10 +9,10 @@ "filename": "client.py" } }, - "originGitCommit": "56ea60da11c1b8d90274a137f5fbc3bb89b9fac4", + "originGitCommit": "f9a7ba07dda24e9d9936bb543686bc542355479e", "originGitCommitIsDirty": true, "invokedBy": "ci", "requestedVersion": "AUTO", "ciProvider": "unknown", - "sdkVersion": "3.0.1" + "sdkVersion": "0.0.0.dev0" } \ No newline at end of file diff --git a/.fern/replay.lock b/.fern/replay.lock index cae5c71..f04ebfb 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -6,5 +6,11 @@ generations: timestamp: 2026-06-10T12:28:53.089Z cli_version: unknown generator_versions: {} -current_generation: 045deff8d2bc498634f39b6baaea0778b69bfecd + - commit_sha: e6179f9ae1194808b56ce70363413efa8fb470f4 + tree_hash: 7c9cb6f2e645434ac16a824fbb05bb2b02af1074 + timestamp: 2026-06-24T15:56:21.929Z + cli_version: unknown + generator_versions: + fernapi/fern-python-sdk: 5.14.21 +current_generation: e6179f9ae1194808b56ce70363413efa8fb470f4 patches: [] diff --git a/README.md b/README.md index 78d5917..e2f2fc4 100644 --- a/README.md +++ b/README.md @@ -218,3 +218,4 @@ On the other hand, contributions to the README are always very welcome! + diff --git a/poetry.lock b/poetry.lock index c7aad2a..bc07681 100644 --- a/poetry.lock +++ b/poetry.lock @@ -188,14 +188,14 @@ files = [ [[package]] name = "anyio" -version = "4.13.0" +version = "4.14.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708"}, - {file = "anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc"}, + {file = "anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9"}, + {file = "anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89"}, ] [package.dependencies] @@ -247,14 +247,14 @@ files = [ [[package]] name = "certifi" -version = "2026.5.20" +version = "2026.6.17" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"}, - {file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"}, + {file = "certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db"}, + {file = "certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432"}, ] [[package]] @@ -1106,14 +1106,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, - {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, + {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, + {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, ] [package.dependencies] @@ -1466,4 +1466,4 @@ aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "8a2af44ec07cc26b89a775d8383a2fbf2203875bc97f422dec79ecd475a30518" +content-hash = "ae72458cc9170491280c32063e114504d6d14e4e72794aefcc730f6d72478732" diff --git a/pyproject.toml b/pyproject.toml index 1dfc9fd..c6579e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "newscatcher-catchall-sdk" -version = "3.0.1" +version = "0.0.0.dev0" description = "" readme = "README.md" authors = [] @@ -38,7 +38,7 @@ Repository = 'https://github.com/Newscatcher/newscatcher-catchall-python' [tool.poetry.dependencies] python = "^3.10" -aiohttp = { version = ">=3.14.0,<4", optional = true, python = ">=3.10"} +aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} pydantic = ">= 1.9.2" @@ -58,6 +58,7 @@ ruff = "==0.11.5" [tool.pytest.ini_options] testpaths = [ "tests" ] asyncio_mode = "auto" +norecursedirs = [ "src" ] markers = [ "aiohttp: tests that require httpx_aiohttp to be installed", ] diff --git a/reference.md b/reference.md index 53aba90..cd104dc 100644 --- a/reference.md +++ b/reference.md @@ -651,6 +651,79 @@ client.jobs.get_job_results( + + + + +
client.jobs.get_job_results_csv(...) -> str +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns a completed job's result records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from newscatcher_catchall import CatchAllApi +from newscatcher_catchall.environment import CatchAllApiEnvironment + +client = CatchAllApi( + api_key="", + environment=CatchAllApiEnvironment.DEFAULT, +) + +client.jobs.get_job_results_csv( + job_id="job_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**job_id:** `str` — Unique job identifier returned from [`POST /catchAll/submit`](https://www.newscatcherapi.com/docs/web-search-api/api-reference/jobs/create-job). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -1134,6 +1207,79 @@ client.monitors.pull_monitor_results( + + + + +
client.monitors.pull_monitor_results_csv(...) -> str +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Returns the most recent run's records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from newscatcher_catchall import CatchAllApi +from newscatcher_catchall.environment import CatchAllApiEnvironment + +client = CatchAllApi( + api_key="", + environment=CatchAllApiEnvironment.DEFAULT, +) + +client.monitors.pull_monitor_results_csv( + monitor_id="monitor_id", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**monitor_id:** `str` — Monitor identifier. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -3312,6 +3458,14 @@ client.entities.update_entity(
+**external_entity_id:** `typing.Optional[str]` — Updated external identifier for this entity. + +
+
+ +
+
+ **additional_attributes:** `typing.Optional[AdditionalAttributes]`
diff --git a/src/newscatcher_catchall/__init__.py b/src/newscatcher_catchall/__init__.py index 3a2cea4..b217417 100644 --- a/src/newscatcher_catchall/__init__.py +++ b/src/newscatcher_catchall/__init__.py @@ -21,6 +21,7 @@ CompanyEnrichmentOutput, CompanyEnrichmentOutputMetadata, CompanyEnrichmentOutputMetadataDomainUrlConfidence, + ConnectedDataset, ConnectedEntity, Context, ContinueResponseDto, @@ -173,6 +174,7 @@ "CompanyEnrichmentOutput": ".types", "CompanyEnrichmentOutputMetadata": ".types", "CompanyEnrichmentOutputMetadataDomainUrlConfidence": ".types", + "ConnectedDataset": ".types", "ConnectedEntity": ".types", "Context": ".types", "ContinueResponseDto": ".types", @@ -353,6 +355,7 @@ def __dir__(): "CompanyEnrichmentOutput", "CompanyEnrichmentOutputMetadata", "CompanyEnrichmentOutputMetadataDomainUrlConfidence", + "ConnectedDataset", "ConnectedEntity", "Context", "ContinueResponseDto", diff --git a/src/newscatcher_catchall/core/client_wrapper.py b/src/newscatcher_catchall/core/client_wrapper.py index 24f6fe8..4841652 100644 --- a/src/newscatcher_catchall/core/client_wrapper.py +++ b/src/newscatcher_catchall/core/client_wrapper.py @@ -29,12 +29,12 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "newscatcher-catchall-sdk/3.0.1", + "User-Agent": "newscatcher-catchall-sdk/0.0.0.dev0", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "newscatcher-catchall-sdk", - "X-Fern-SDK-Version": "3.0.1", + "X-Fern-SDK-Version": "0.0.0.dev0", **(self.get_custom_headers() or {}), } headers["x-api-key"] = self.api_key diff --git a/src/newscatcher_catchall/core/http_sse/_api.py b/src/newscatcher_catchall/core/http_sse/_api.py index b375399..fd13730 100644 --- a/src/newscatcher_catchall/core/http_sse/_api.py +++ b/src/newscatcher_catchall/core/http_sse/_api.py @@ -10,6 +10,8 @@ from ._exceptions import SSEError from ._models import ServerSentEvent +MAX_LINE_SIZE: int = 1_048_576 # 1 MiB + class EventSource: def __init__(self, response: httpx.Response) -> None: @@ -75,10 +77,20 @@ def iter_sse(self) -> Iterator[ServerSentEvent]: if sse is not None: yield sse + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + # Flush any remaining bytes from the incremental decoder buf += text_decoder.decode(b"", final=True) buf = buf.replace("\r\n", "\n").replace("\r", "\n") + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + while "\n" in buf: line, buf = buf.split("\n", 1) sse = decoder.decode(line) @@ -107,10 +119,20 @@ async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: if sse is not None: yield sse + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + # Flush any remaining bytes from the incremental decoder buf += text_decoder.decode(b"", final=True) buf = buf.replace("\r\n", "\n").replace("\r", "\n") + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + while "\n" in buf: line, buf = buf.split("\n", 1) sse = decoder.decode(line) diff --git a/src/newscatcher_catchall/entities/client.py b/src/newscatcher_catchall/entities/client.py index 22cfea7..7d1f12d 100644 --- a/src/newscatcher_catchall/entities/client.py +++ b/src/newscatcher_catchall/entities/client.py @@ -106,6 +106,7 @@ def create_entity( name: str, entity_type: typing.Optional[EntityType] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> CreateEntityResponse: @@ -127,6 +128,9 @@ def create_entity( description : typing.Optional[str] Free-text description of the entity used for disambiguation when similar names exist. + external_entity_id : typing.Optional[str] + Optional external identifier for this entity. Free-form string, not enforced as unique. Use it to store your own CRM, data warehouse, or internal database ID so you can join CatchAll results back to your systems. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -165,6 +169,7 @@ def create_entity( name=name, entity_type=entity_type, description=description, + external_entity_id=external_entity_id, additional_attributes=additional_attributes, request_options=request_options, ) @@ -303,6 +308,7 @@ def update_entity( *, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> EntityResponse: @@ -320,6 +326,9 @@ def update_entity( description : typing.Optional[str] Updated description. + external_entity_id : typing.Optional[str] + Updated external identifier for this entity. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -355,6 +364,7 @@ def update_entity( entity_id, name=name, description=description, + external_entity_id=external_entity_id, additional_attributes=additional_attributes, request_options=request_options, ) @@ -455,6 +465,7 @@ async def create_entity( name: str, entity_type: typing.Optional[EntityType] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> CreateEntityResponse: @@ -476,6 +487,9 @@ async def create_entity( description : typing.Optional[str] Free-text description of the entity used for disambiguation when similar names exist. + external_entity_id : typing.Optional[str] + Optional external identifier for this entity. Free-form string, not enforced as unique. Use it to store your own CRM, data warehouse, or internal database ID so you can join CatchAll results back to your systems. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -522,6 +536,7 @@ async def main() -> None: name=name, entity_type=entity_type, description=description, + external_entity_id=external_entity_id, additional_attributes=additional_attributes, request_options=request_options, ) @@ -686,6 +701,7 @@ async def update_entity( *, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> EntityResponse: @@ -703,6 +719,9 @@ async def update_entity( description : typing.Optional[str] Updated description. + external_entity_id : typing.Optional[str] + Updated external identifier for this entity. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -746,6 +765,7 @@ async def main() -> None: entity_id, name=name, description=description, + external_entity_id=external_entity_id, additional_attributes=additional_attributes, request_options=request_options, ) diff --git a/src/newscatcher_catchall/entities/raw_client.py b/src/newscatcher_catchall/entities/raw_client.py index 64759f3..2607f89 100644 --- a/src/newscatcher_catchall/entities/raw_client.py +++ b/src/newscatcher_catchall/entities/raw_client.py @@ -127,6 +127,7 @@ def create_entity( name: str, entity_type: typing.Optional[EntityType] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[CreateEntityResponse]: @@ -148,6 +149,9 @@ def create_entity( description : typing.Optional[str] Free-text description of the entity used for disambiguation when similar names exist. + external_entity_id : typing.Optional[str] + Optional external identifier for this entity. Free-form string, not enforced as unique. Use it to store your own CRM, data warehouse, or internal database ID so you can join CatchAll results back to your systems. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -165,6 +169,7 @@ def create_entity( "name": name, "entity_type": entity_type, "description": description, + "external_entity_id": external_entity_id, "additional_attributes": convert_and_respect_annotation_metadata( object_=additional_attributes, annotation=AdditionalAttributes, direction="write" ), @@ -419,6 +424,7 @@ def update_entity( *, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[EntityResponse]: @@ -436,6 +442,9 @@ def update_entity( description : typing.Optional[str] Updated description. + external_entity_id : typing.Optional[str] + Updated external identifier for this entity. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -452,6 +461,7 @@ def update_entity( json={ "name": name, "description": description, + "external_entity_id": external_entity_id, "additional_attributes": convert_and_respect_annotation_metadata( object_=additional_attributes, annotation=AdditionalAttributes, direction="write" ), @@ -611,6 +621,7 @@ async def create_entity( name: str, entity_type: typing.Optional[EntityType] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[CreateEntityResponse]: @@ -632,6 +643,9 @@ async def create_entity( description : typing.Optional[str] Free-text description of the entity used for disambiguation when similar names exist. + external_entity_id : typing.Optional[str] + Optional external identifier for this entity. Free-form string, not enforced as unique. Use it to store your own CRM, data warehouse, or internal database ID so you can join CatchAll results back to your systems. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -649,6 +663,7 @@ async def create_entity( "name": name, "entity_type": entity_type, "description": description, + "external_entity_id": external_entity_id, "additional_attributes": convert_and_respect_annotation_metadata( object_=additional_attributes, annotation=AdditionalAttributes, direction="write" ), @@ -903,6 +918,7 @@ async def update_entity( *, name: typing.Optional[str] = OMIT, description: typing.Optional[str] = OMIT, + external_entity_id: typing.Optional[str] = OMIT, additional_attributes: typing.Optional[AdditionalAttributes] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[EntityResponse]: @@ -920,6 +936,9 @@ async def update_entity( description : typing.Optional[str] Updated description. + external_entity_id : typing.Optional[str] + Updated external identifier for this entity. + additional_attributes : typing.Optional[AdditionalAttributes] request_options : typing.Optional[RequestOptions] @@ -936,6 +955,7 @@ async def update_entity( json={ "name": name, "description": description, + "external_entity_id": external_entity_id, "additional_attributes": convert_and_respect_annotation_metadata( object_=additional_attributes, annotation=AdditionalAttributes, direction="write" ), diff --git a/src/newscatcher_catchall/jobs/client.py b/src/newscatcher_catchall/jobs/client.py index dbede84..e277aa9 100644 --- a/src/newscatcher_catchall/jobs/client.py +++ b/src/newscatcher_catchall/jobs/client.py @@ -394,6 +394,37 @@ def get_job_results( ) return _response.data + def get_job_results_csv(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Returns a completed job's result records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + job_id : str + Unique job identifier returned from [`POST /catchAll/submit`](https://www.newscatcherapi.com/docs/web-search-api/api-reference/jobs/create-job). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + CSV export of job results. + + Examples + -------- + from newscatcher_catchall import CatchAllApi + + client = CatchAllApi( + api_key="YOUR_API_KEY", + ) + client.jobs.get_job_results_csv( + job_id="job_id", + ) + """ + _response = self._raw_client.get_job_results_csv(job_id, request_options=request_options) + return _response.data + def continue_job( self, *, @@ -886,6 +917,45 @@ async def main() -> None: ) return _response.data + async def get_job_results_csv(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Returns a completed job's result records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + job_id : str + Unique job identifier returned from [`POST /catchAll/submit`](https://www.newscatcherapi.com/docs/web-search-api/api-reference/jobs/create-job). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + CSV export of job results. + + Examples + -------- + import asyncio + + from newscatcher_catchall import AsyncCatchAllApi + + client = AsyncCatchAllApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.jobs.get_job_results_csv( + job_id="job_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_job_results_csv(job_id, request_options=request_options) + return _response.data + async def continue_job( self, *, diff --git a/src/newscatcher_catchall/jobs/raw_client.py b/src/newscatcher_catchall/jobs/raw_client.py index c1de127..86a8d58 100644 --- a/src/newscatcher_catchall/jobs/raw_client.py +++ b/src/newscatcher_catchall/jobs/raw_client.py @@ -598,6 +598,64 @@ def get_job_results( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def get_job_results_csv( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[str]: + """ + Returns a completed job's result records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + job_id : str + Unique job identifier returned from [`POST /catchAll/submit`](https://www.newscatcherapi.com/docs/web-search-api/api-reference/jobs/create-job). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[str] + CSV export of job results. + """ + _response = self._client_wrapper.httpx_client.request( + f"catchAll/pull/{encode_path_param(job_id)}/csv", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=_response.text) # type: ignore + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def continue_job( self, *, @@ -1315,6 +1373,64 @@ async def get_job_results( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def get_job_results_csv( + self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[str]: + """ + Returns a completed job's result records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + job_id : str + Unique job identifier returned from [`POST /catchAll/submit`](https://www.newscatcherapi.com/docs/web-search-api/api-reference/jobs/create-job). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[str] + CSV export of job results. + """ + _response = await self._client_wrapper.httpx_client.request( + f"catchAll/pull/{encode_path_param(job_id)}/csv", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=_response.text) # type: ignore + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def continue_job( self, *, diff --git a/src/newscatcher_catchall/monitors/client.py b/src/newscatcher_catchall/monitors/client.py index 37e5bdc..7d3fe89 100644 --- a/src/newscatcher_catchall/monitors/client.py +++ b/src/newscatcher_catchall/monitors/client.py @@ -210,6 +210,39 @@ def pull_monitor_results( _response = self._raw_client.pull_monitor_results(monitor_id, request_options=request_options) return _response.data + def pull_monitor_results_csv( + self, monitor_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> str: + """ + Returns the most recent run's records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + monitor_id : str + Monitor identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + CSV export of the latest monitor run. + + Examples + -------- + from newscatcher_catchall import CatchAllApi + + client = CatchAllApi( + api_key="YOUR_API_KEY", + ) + client.monitors.pull_monitor_results_csv( + monitor_id="monitor_id", + ) + """ + _response = self._raw_client.pull_monitor_results_csv(monitor_id, request_options=request_options) + return _response.data + def list_monitor_jobs( self, monitor_id: str, @@ -662,6 +695,47 @@ async def main() -> None: _response = await self._raw_client.pull_monitor_results(monitor_id, request_options=request_options) return _response.data + async def pull_monitor_results_csv( + self, monitor_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> str: + """ + Returns the most recent run's records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + monitor_id : str + Monitor identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + CSV export of the latest monitor run. + + Examples + -------- + import asyncio + + from newscatcher_catchall import AsyncCatchAllApi + + client = AsyncCatchAllApi( + api_key="YOUR_API_KEY", + ) + + + async def main() -> None: + await client.monitors.pull_monitor_results_csv( + monitor_id="monitor_id", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.pull_monitor_results_csv(monitor_id, request_options=request_options) + return _response.data + async def list_monitor_jobs( self, monitor_id: str, diff --git a/src/newscatcher_catchall/monitors/raw_client.py b/src/newscatcher_catchall/monitors/raw_client.py index 16f87f1..dc48387 100644 --- a/src/newscatcher_catchall/monitors/raw_client.py +++ b/src/newscatcher_catchall/monitors/raw_client.py @@ -295,6 +295,64 @@ def pull_monitor_results( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def pull_monitor_results_csv( + self, monitor_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[str]: + """ + Returns the most recent run's records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + monitor_id : str + Monitor identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[str] + CSV export of the latest monitor run. + """ + _response = self._client_wrapper.httpx_client.request( + f"catchAll/monitors/pull/{encode_path_param(monitor_id)}/csv", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return HttpResponse(response=_response, data=_response.text) # type: ignore + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + def list_monitor_jobs( self, monitor_id: str, @@ -1035,6 +1093,64 @@ async def pull_monitor_results( ) raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def pull_monitor_results_csv( + self, monitor_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[str]: + """ + Returns the most recent run's records as a CSV download. One row per record, with enrichment fields as columns, citations as a JSON column, and connected entities split into `event_associated_entities` and `mention_entities` JSON columns. + + Parameters + ---------- + monitor_id : str + Monitor identifier. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[str] + CSV export of the latest monitor run. + """ + _response = await self._client_wrapper.httpx_client.request( + f"catchAll/monitors/pull/{encode_path_param(monitor_id)}/csv", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return AsyncHttpResponse(response=_response, data=_response.text) # type: ignore + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + async def list_monitor_jobs( self, monitor_id: str, diff --git a/src/newscatcher_catchall/types/__init__.py b/src/newscatcher_catchall/types/__init__.py index 018e931..76e0f9f 100644 --- a/src/newscatcher_catchall/types/__init__.py +++ b/src/newscatcher_catchall/types/__init__.py @@ -22,6 +22,7 @@ from .company_enrichment_output_metadata_domain_url_confidence import ( CompanyEnrichmentOutputMetadataDomainUrlConfidence, ) + from .connected_dataset import ConnectedDataset from .connected_entity import ConnectedEntity from .context import Context from .continue_response_dto import ContinueResponseDto @@ -144,6 +145,7 @@ "CompanyEnrichmentOutput": ".company_enrichment_output", "CompanyEnrichmentOutputMetadata": ".company_enrichment_output_metadata", "CompanyEnrichmentOutputMetadataDomainUrlConfidence": ".company_enrichment_output_metadata_domain_url_confidence", + "ConnectedDataset": ".connected_dataset", "ConnectedEntity": ".connected_entity", "Context": ".context", "ContinueResponseDto": ".continue_response_dto", @@ -290,6 +292,7 @@ def __dir__(): "CompanyEnrichmentOutput", "CompanyEnrichmentOutputMetadata", "CompanyEnrichmentOutputMetadataDomainUrlConfidence", + "ConnectedDataset", "ConnectedEntity", "Context", "ContinueResponseDto", diff --git a/src/newscatcher_catchall/types/connected_dataset.py b/src/newscatcher_catchall/types/connected_dataset.py new file mode 100644 index 0000000..82f0ba5 --- /dev/null +++ b/src/newscatcher_catchall/types/connected_dataset.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ConnectedDataset(UniversalBaseModel): + """ + A dataset used to narrow the retrieval scope of a job or monitor, returned with its identifier and name. + """ + + id: str = pydantic.Field() + """ + Dataset identifier. + """ + + name: str = pydantic.Field() + """ + Dataset name as stored. Check `is_deleted` to determine whether the dataset is still active. + """ + + is_deleted: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the dataset has been deleted. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/newscatcher_catchall/types/connected_entity.py b/src/newscatcher_catchall/types/connected_entity.py index 82adc82..f5f68f4 100644 --- a/src/newscatcher_catchall/types/connected_entity.py +++ b/src/newscatcher_catchall/types/connected_entity.py @@ -51,9 +51,14 @@ class ConnectedEntity(UniversalBaseModel): The entity type. """ + external_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + External identifier for this entity. Null when not set. + """ + company: typing.Optional[CompanyAttributes] = pydantic.Field(default=None) """ - The stored attributes for this entity. Present only when attributes exist in the database. + The stored attributes for this entity. Present only when attributes exist in the database. The field name matches the value of `type` — for example, `"company"` type entities have a `company` field. """ diff --git a/src/newscatcher_catchall/types/create_entity_request.py b/src/newscatcher_catchall/types/create_entity_request.py index 8ef4123..3539791 100644 --- a/src/newscatcher_catchall/types/create_entity_request.py +++ b/src/newscatcher_catchall/types/create_entity_request.py @@ -25,6 +25,11 @@ class CreateEntityRequest(UniversalBaseModel): Free-text description of the entity used for disambiguation when similar names exist. """ + external_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + Optional external identifier for this entity. Free-form string, not enforced as unique. Use it to store your own CRM, data warehouse, or internal database ID so you can join CatchAll results back to your systems. + """ + additional_attributes: typing.Optional[AdditionalAttributes] = None if IS_PYDANTIC_V2: diff --git a/src/newscatcher_catchall/types/entity_response.py b/src/newscatcher_catchall/types/entity_response.py index 3b100d6..ebde3eb 100644 --- a/src/newscatcher_catchall/types/entity_response.py +++ b/src/newscatcher_catchall/types/entity_response.py @@ -32,6 +32,11 @@ class EntityResponse(UniversalBaseModel): Free-text description. """ + external_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + External identifier for this entity. Null when not set. + """ + additional_attributes: typing.Optional[AdditionalAttributes] = None status: EntityStatus created_by_user_id: typing.Optional[str] = pydantic.Field(default=None) diff --git a/src/newscatcher_catchall/types/entity_summary.py b/src/newscatcher_catchall/types/entity_summary.py index 50fdc5b..465b763 100644 --- a/src/newscatcher_catchall/types/entity_summary.py +++ b/src/newscatcher_catchall/types/entity_summary.py @@ -34,6 +34,11 @@ class EntitySummary(UniversalBaseModel): Free-text description. """ + external_entity_id: typing.Optional[str] = pydantic.Field(default=None) + """ + External identifier for this entity. Null when not set. + """ + attributes: typing.Optional[CompanyAttributes] = None if IS_PYDANTIC_V2: diff --git a/src/newscatcher_catchall/types/pull_job_response_dto.py b/src/newscatcher_catchall/types/pull_job_response_dto.py index 187087c..b9da2e5 100644 --- a/src/newscatcher_catchall/types/pull_job_response_dto.py +++ b/src/newscatcher_catchall/types/pull_job_response_dto.py @@ -4,6 +4,7 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .connected_dataset import ConnectedDataset from .pull_job_response_dto_date_range import PullJobResponseDtoDateRange from .pull_job_response_dto_mode import PullJobResponseDtoMode from .record import Record @@ -94,6 +95,16 @@ class PullJobResponseDto(UniversalBaseModel): Processing mode used for this job. """ + connected_datasets: typing.Optional[typing.List[ConnectedDataset]] = pydantic.Field(default=None) + """ + Datasets used to narrow retrieval scope, each with `id` and `name`. + """ + + is_all_news_query: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the query was submitted as an all-news (watchlist-generic) query. + """ + sharing_info: typing.Optional[SharingInfo] = pydantic.Field(default=None) """ Present when the job was shared with the authenticated user by another organization member. `null` when the user owns the job. diff --git a/src/newscatcher_catchall/types/pull_monitor_response_dto.py b/src/newscatcher_catchall/types/pull_monitor_response_dto.py index ce08a6b..12d58a7 100644 --- a/src/newscatcher_catchall/types/pull_monitor_response_dto.py +++ b/src/newscatcher_catchall/types/pull_monitor_response_dto.py @@ -4,6 +4,7 @@ import pydantic from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .connected_dataset import ConnectedDataset from .monitor_record import MonitorRecord from .pull_monitor_response_dto_run_info import PullMonitorResponseDtoRunInfo from .reference_job import ReferenceJob @@ -53,6 +54,16 @@ class PullMonitorResponseDto(UniversalBaseModel): Record limit applied to this monitor's jobs. """ + connected_datasets: typing.Optional[typing.List[ConnectedDataset]] = pydantic.Field(default=None) + """ + Datasets used to narrow retrieval scope, each with `id` and `name`. + """ + + is_all_news_query: typing.Optional[bool] = pydantic.Field(default=None) + """ + True when the reference job was submitted as an all-news (watchlist-generic) query. + """ + if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 else: