pyapiary 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. pyapiary/__init__.py +31 -0
  2. pyapiary/api_connectors/__init__.py +0 -0
  3. pyapiary/api_connectors/broker.py +398 -0
  4. pyapiary/api_connectors/flashpoint.py +195 -0
  5. pyapiary/api_connectors/generic.py +105 -0
  6. pyapiary/api_connectors/ipqs.py +68 -0
  7. pyapiary/api_connectors/spycloud.py +207 -0
  8. pyapiary/api_connectors/twilio.py +114 -0
  9. pyapiary/api_connectors/urlscan.py +148 -0
  10. pyapiary/dbms_connectors/__init__.py +0 -0
  11. pyapiary/dbms_connectors/elasticsearch.py +143 -0
  12. pyapiary/dbms_connectors/mongo.py +390 -0
  13. pyapiary/dbms_connectors/mongo_async.py +323 -0
  14. pyapiary/dbms_connectors/odbc.py +110 -0
  15. pyapiary/dbms_connectors/splunk.py +131 -0
  16. pyapiary/helpers.py +102 -0
  17. pyapiary/tests/__init__.py +0 -0
  18. pyapiary/tests/conftest.py +47 -0
  19. pyapiary/tests/test_broker/test_integration_broker.py +14 -0
  20. pyapiary/tests/test_broker/test_unit_asyncbroker.py +17 -0
  21. pyapiary/tests/test_broker/test_unit_broker.py +67 -0
  22. pyapiary/tests/test_elasticsearch/test_unit_elasticsearch.py +67 -0
  23. pyapiary/tests/test_flashpoint/cassettes/test_flashpoint_search_fraud_vcr.yaml +66 -0
  24. pyapiary/tests/test_flashpoint/test_integration_flashpoint.py +11 -0
  25. pyapiary/tests/test_flashpoint/test_unit_async_flashpoint.py +49 -0
  26. pyapiary/tests/test_flashpoint/test_unit_flashpoint.py +45 -0
  27. pyapiary/tests/test_generic/cassettes/test_generic_get_github_api.yaml +87 -0
  28. pyapiary/tests/test_generic/test_integration_generic_connector.py +12 -0
  29. pyapiary/tests/test_generic/test_unit_async_generic_connector.py +54 -0
  30. pyapiary/tests/test_generic/test_unit_generic_connector.py +34 -0
  31. pyapiary/tests/test_ipqs/__init__.py +0 -0
  32. pyapiary/tests/test_ipqs/cassettes/test_ipqs_malicious_url_vcr.yaml +64 -0
  33. pyapiary/tests/test_ipqs/test_integration_ipqs.py +13 -0
  34. pyapiary/tests/test_ipqs/test_unit_async_ipqs.py +53 -0
  35. pyapiary/tests/test_ipqs/test_unit_ipqs.py +45 -0
  36. pyapiary/tests/test_mongodb/test_unit_async_mongo.py +109 -0
  37. pyapiary/tests/test_mongodb/test_unit_mongo.py +219 -0
  38. pyapiary/tests/test_odbc/test_unit_odbc.py +82 -0
  39. pyapiary/tests/test_splunk/test_unit_splunk.py +56 -0
  40. pyapiary/tests/test_spycloud/cassettes/test_spycloud_ato_search_vcr.yaml +1870 -0
  41. pyapiary/tests/test_spycloud/test_integration_spycloud.py +12 -0
  42. pyapiary/tests/test_spycloud/test_unit_async_spycloud.py +44 -0
  43. pyapiary/tests/test_spycloud/test_unit_spycloud.py +46 -0
  44. pyapiary/tests/test_twilio/cassettes/test_lookup_phone_vcr.yaml +68 -0
  45. pyapiary/tests/test_twilio/test_integration_twilio.py +14 -0
  46. pyapiary/tests/test_twilio/test_unit_async_twilio.py +34 -0
  47. pyapiary/tests/test_twilio/test_unit_twilio.py +45 -0
  48. pyapiary/tests/test_urlscan/cassettes/test_urlscan_results_vcr.yaml +279 -0
  49. pyapiary/tests/test_urlscan/test_integration_urlscan.py +12 -0
  50. pyapiary/tests/test_urlscan/test_unit_async_urlscan.py +49 -0
  51. pyapiary/tests/test_urlscan/test_unit_urlscan.py +39 -0
  52. pyapiary-2.0.0.dist-info/METADATA +435 -0
  53. pyapiary-2.0.0.dist-info/RECORD +54 -0
  54. pyapiary-2.0.0.dist-info/WHEEL +4 -0
pyapiary/__init__.py ADDED
@@ -0,0 +1,31 @@
1
+ # API connectors
2
+ from pyapiary.api_connectors import (
3
+ urlscan,
4
+ spycloud,
5
+ twilio,
6
+ flashpoint,
7
+ ipqs,
8
+ generic,
9
+ )
10
+
11
+ # DBMS connectors
12
+ from pyapiary.dbms_connectors import (
13
+ elasticsearch,
14
+ mongo,
15
+ odbc,
16
+ splunk
17
+ )
18
+
19
+ # Export the modules and re-exports
20
+ __all__ = [
21
+ "elasticsearch",
22
+ "flashpoint",
23
+ "generic",
24
+ "ipqs",
25
+ "mongo",
26
+ "odbc",
27
+ "splunk",
28
+ "spycloud",
29
+ "twilio",
30
+ "urlscan",
31
+ ]
File without changes
@@ -0,0 +1,398 @@
1
+ import httpx
2
+ from httpx import Auth
3
+ from typing import Optional, Dict, Any, Union, Iterable, Callable, ParamSpec, TypeVar, Type
4
+ from tenacity import retry, stop_after_attempt, wait_exponential, RetryError, retry_if_exception, AsyncRetrying
5
+ from pyapiary.helpers import setup_logger, combine_env_configs
6
+ from functools import wraps
7
+ import inspect
8
+ import os
9
+ from types import TracebackType
10
+
11
+
12
+ P = ParamSpec("P")
13
+ R = TypeVar("R")
14
+
15
+ def log_method_call(func: Callable[P, R]) -> Callable[P, R]:
16
+ @wraps(func)
17
+ def wrapper(self, *args, **kwargs):
18
+ caller = func.__name__
19
+ sig = inspect.signature(func)
20
+ bound = sig.bind(self, *args, **kwargs)
21
+ bound.apply_defaults()
22
+ query_value = bound.arguments.get("query")
23
+ self._log(f"{caller} called with query: {query_value}")
24
+ return func(self, *args, **kwargs)
25
+ return wrapper
26
+
27
+
28
+ def bubble_broker_init_signature(*, exclude: Iterable[str] = ("base_url",)):
29
+ """
30
+ Class decorator that augments a connector subclass' __init__ signature with
31
+ parameters from Broker.__init__ for better IDE/tab-completion hints.
32
+
33
+ Usage:
34
+ from pyapiary.api_connectors.broker import Broker, bubble_broker_init_signature
35
+
36
+ @bubble_broker_init_signature()
37
+ class MyConnector(Broker):
38
+ def __init__(self, api_key: str | None = None, **kwargs):
39
+ super().__init__(base_url="https://example.com", **kwargs)
40
+ ...
41
+
42
+ Notes:
43
+ - This affects *introspection only* (via __signature__). Runtime behavior is unchanged.
44
+ - Subclass-specific parameters remain first (e.g., api_key), followed by Broker params.
45
+ - `base_url` is excluded by default since subclasses set it themselves.
46
+ - The subclass' **kwargs (if present) is preserved at the end so httpx.Client kwargs
47
+ can still be passed through.
48
+ """
49
+ def _decorate(cls):
50
+ sub_init = cls.__init__
51
+ broker_init = Broker.__init__
52
+
53
+ sub_sig = inspect.signature(sub_init)
54
+ broker_sig = inspect.signature(broker_init)
55
+
56
+ new_params = []
57
+ saw_var_kw = None
58
+
59
+ # Keep subclass params first; remember its **kwargs if present
60
+ for p in sub_sig.parameters.values():
61
+ if p.kind is inspect.Parameter.VAR_KEYWORD:
62
+ saw_var_kw = p
63
+ else:
64
+ new_params.append(p)
65
+
66
+ present = {p.name for p in new_params}
67
+
68
+ # Append Broker params (skip self, excluded, already-present, and **kwargs)
69
+ for name, p in list(broker_sig.parameters.items())[1:]:
70
+ if name in exclude or name in present:
71
+ continue
72
+ if p.kind is inspect.Parameter.VAR_KEYWORD:
73
+ continue
74
+ new_params.append(p)
75
+
76
+ # Re-append subclass **kwargs (or add a generic one to keep flexibility)
77
+ if saw_var_kw is not None:
78
+ new_params.append(saw_var_kw)
79
+ else:
80
+ new_params.append(
81
+ inspect.Parameter(
82
+ "client_kwargs",
83
+ kind=inspect.Parameter.VAR_KEYWORD,
84
+ )
85
+ )
86
+
87
+ cls.__init__.__signature__ = inspect.Signature(parameters=new_params)
88
+ return cls
89
+
90
+ return _decorate
91
+
92
+
93
+ class SharedConnectorBase:
94
+ """
95
+ Shared base class for Broker and AsyncBroker.
96
+ Houses reusable logic (constructor, logging, proxy config, retry predicate).
97
+ """
98
+ def __init__(
99
+ self,
100
+ base_url: str,
101
+ headers: Optional[Dict[str, str]] = None,
102
+ enable_logging: bool = False,
103
+ enable_backoff: bool = False,
104
+ timeout: int = 10,
105
+ load_env_vars: bool = False,
106
+ trust_env: bool = True,
107
+ proxy: Optional[str] = None,
108
+ mounts: Optional[Dict[str, httpx.HTTPTransport]] = None,
109
+ **client_kwargs,
110
+ ):
111
+ self.base_url = base_url.rstrip('/')
112
+ self.logger = setup_logger(self.__class__.__name__) if enable_logging else None
113
+ self.enable_backoff = enable_backoff
114
+ self.timeout = timeout
115
+ self.headers = headers or {}
116
+ self.trust_env = trust_env
117
+ self.proxy = proxy
118
+ self.mounts = mounts
119
+ self.env_config = combine_env_configs() if load_env_vars else {}
120
+ self._client_kwargs = dict(client_kwargs) if client_kwargs else {}
121
+
122
+ def _log(self, message: str):
123
+ if self.logger:
124
+ self.logger.info(message)
125
+
126
+ def _collect_proxy_config(self) -> tuple[Optional[str], Optional[Dict[str, httpx.HTTPTransport]]]:
127
+ source_env: Optional[Dict[str, str]] = None
128
+ if isinstance(self.env_config, dict) and len(self.env_config) > 0:
129
+ source_env = {k: v for k, v in self.env_config.items() if isinstance(k, str) and isinstance(v, str)}
130
+ elif self.trust_env:
131
+ source_env = dict(os.environ)
132
+ else:
133
+ return None, None
134
+
135
+ def _get(key: str) -> Optional[str]:
136
+ return source_env.get(key) or source_env.get(key.lower())
137
+
138
+ all_proxy = _get("ALL_PROXY")
139
+ http_proxy = _get("HTTP_PROXY")
140
+ https_proxy = _get("HTTPS_PROXY")
141
+
142
+ if http_proxy and https_proxy and http_proxy != https_proxy:
143
+ return None, {
144
+ "http://": httpx.HTTPTransport(proxy=http_proxy),
145
+ "https://": httpx.HTTPTransport(proxy=https_proxy),
146
+ }
147
+ single = all_proxy or https_proxy or http_proxy
148
+ if single:
149
+ return single, None
150
+ return None, None
151
+
152
+ @staticmethod
153
+ def _default_retry_exc(exc: BaseException) -> bool:
154
+ if isinstance(exc, httpx.HTTPStatusError):
155
+ r = exc.response
156
+ if r is not None:
157
+ return r.status_code == 429 or 500 <= r.status_code < 600
158
+ return isinstance(exc, (
159
+ httpx.ConnectError,
160
+ httpx.ReadTimeout,
161
+ httpx.WriteError,
162
+ httpx.RemoteProtocolError,
163
+ httpx.PoolTimeout,
164
+ ))
165
+
166
+
167
+ class Broker(SharedConnectorBase):
168
+ """
169
+ A base HTTP client that provides structured request handling, logging, retries, and optional environment config loading.
170
+ Designed to be inherited by specific API connector classes.
171
+ """
172
+ def __init__(
173
+ self,
174
+ base_url: str,
175
+ headers: Optional[Dict[str, str]] = None,
176
+ enable_logging: bool = False,
177
+ enable_backoff: bool = False,
178
+ timeout: int = 10,
179
+ load_env_vars: bool = False,
180
+ trust_env: bool = True,
181
+ proxy: Optional[str] = None,
182
+ mounts: Optional[Dict[str, httpx.HTTPTransport]] = None,
183
+ **client_kwargs,
184
+ ):
185
+ super().__init__(
186
+ base_url=base_url,
187
+ headers=headers,
188
+ enable_logging=enable_logging,
189
+ enable_backoff=enable_backoff,
190
+ timeout=timeout,
191
+ load_env_vars=load_env_vars,
192
+ trust_env=trust_env,
193
+ proxy=proxy,
194
+ mounts=mounts,
195
+ **client_kwargs,
196
+ )
197
+
198
+ client_options = dict(self._client_kwargs)
199
+ client_options.pop("timeout", None)
200
+
201
+ client_args = {
202
+ "timeout": self.timeout,
203
+ "trust_env": self.trust_env,
204
+ **client_options,
205
+ }
206
+
207
+ if self.mounts:
208
+ client_args["mounts"] = self.mounts
209
+ elif self.proxy:
210
+ client_args["proxy"] = self.proxy
211
+ else:
212
+ env_proxy, env_mounts = self._collect_proxy_config()
213
+ if env_mounts:
214
+ self.mounts = env_mounts
215
+ client_args["mounts"] = self.mounts
216
+ elif env_proxy:
217
+ self.proxy = env_proxy
218
+ client_args["proxy"] = self.proxy
219
+ elif not self.trust_env:
220
+ client_args["trust_env"] = False
221
+
222
+ self.session = httpx.Client(**client_args)
223
+
224
+ def __enter__(self) -> "Broker":
225
+ return self
226
+
227
+ def __exit__(
228
+ self,
229
+ exc_type: Optional[Type[BaseException]],
230
+ exc: Optional[BaseException],
231
+ tb: Optional[TracebackType],
232
+ ) -> None:
233
+ self.session.close()
234
+
235
+ def _make_request(
236
+ self,
237
+ method: str,
238
+ endpoint: str,
239
+ params: Optional[Dict[str, Any]] = None,
240
+ json: Optional[Dict[str, Any]] = None,
241
+ auth: Optional[Union[tuple, Auth]] = None,
242
+ headers: Optional[Dict[str, str]] = None,
243
+ retry_kwargs: Optional[Dict[str, Any]] = None,
244
+ **request_kwargs,
245
+ ) -> httpx.Response:
246
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
247
+
248
+ def do_request() -> httpx.Response:
249
+ resp = self.session.request(
250
+ method=method,
251
+ url=url,
252
+ headers=headers or self.headers,
253
+ params=params,
254
+ json=json,
255
+ auth=auth,
256
+ **request_kwargs,
257
+ )
258
+ resp.raise_for_status()
259
+ return resp
260
+
261
+ call = do_request
262
+ if self.enable_backoff:
263
+ rk = dict(retry_kwargs or {})
264
+ if "retry" not in rk:
265
+ rk["retry"] = retry_if_exception(self._default_retry_exc)
266
+ if "stop" not in rk:
267
+ rk["stop"] = stop_after_attempt(3)
268
+ if "wait" not in rk:
269
+ rk["wait"] = wait_exponential(multiplier=1, min=2, max=10)
270
+ call = retry(reraise=True, **rk)(do_request)
271
+
272
+ try:
273
+ return call()
274
+ except RetryError as re:
275
+ last = re.last_attempt.exception()
276
+ self._log(f"Retry failed: {last}")
277
+ raise
278
+ except httpx.HTTPStatusError as he:
279
+ self._log(f"HTTP error: {he}")
280
+ raise
281
+
282
+ def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
283
+ return self._make_request("GET", endpoint, params=params, **kwargs)
284
+
285
+ def post(self, endpoint: str, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
286
+ return self._make_request("POST", endpoint, json=json, **kwargs)
287
+
288
+
289
+ class AsyncBroker(SharedConnectorBase):
290
+ """
291
+ Async HTTP client connector. Provides async _make_request, get, and post.
292
+ """
293
+ def __init__(
294
+ self,
295
+ base_url: str,
296
+ headers: Optional[Dict[str, str]] = None,
297
+ enable_logging: bool = False,
298
+ enable_backoff: bool = False,
299
+ timeout: int = 10,
300
+ load_env_vars: bool = False,
301
+ trust_env: bool = True,
302
+ proxy: Optional[str] = None,
303
+ mounts: Optional[Dict[str, httpx.HTTPTransport]] = None,
304
+ **client_kwargs,
305
+ ):
306
+ super().__init__(
307
+ base_url=base_url,
308
+ headers=headers,
309
+ enable_logging=enable_logging,
310
+ enable_backoff=enable_backoff,
311
+ timeout=timeout,
312
+ load_env_vars=load_env_vars,
313
+ trust_env=trust_env,
314
+ proxy=proxy,
315
+ mounts=mounts,
316
+ **client_kwargs,
317
+ )
318
+
319
+ if self.mounts:
320
+ raise ValueError("The 'mounts' parameter is not supported in AsyncBroker but "
321
+ "you can still use 'proxy' or 'trust_env' if 'HTTP_PROXY' or "
322
+ "'HTTPS_PROXY' are in your system environment variables ")
323
+
324
+ resolved_proxy = self.proxy or self._collect_proxy_config()[0]
325
+
326
+ self.session = httpx.AsyncClient(
327
+ timeout=self.timeout,
328
+ proxy=resolved_proxy,
329
+ trust_env=self.trust_env,
330
+ **self._client_kwargs,
331
+ )
332
+
333
+ async def __aenter__(self) -> "AsyncBroker":
334
+ return self
335
+
336
+ async def __aexit__(
337
+ self,
338
+ exc_type: Optional[Type[BaseException]],
339
+ exc: Optional[BaseException],
340
+ tb: Optional[TracebackType],
341
+ ) -> None:
342
+ await self.session.aclose()
343
+
344
+ async def _make_request(
345
+ self,
346
+ method: str,
347
+ endpoint: str,
348
+ params: Optional[Dict[str, Any]] = None,
349
+ json: Optional[Dict[str, Any]] = None,
350
+ auth: Optional[Union[tuple, Auth]] = None,
351
+ headers: Optional[Dict[str, str]] = None,
352
+ retry_kwargs: Optional[Dict[str, Any]] = None,
353
+ **request_kwargs,
354
+ ) -> httpx.Response:
355
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
356
+
357
+ async def do_request() -> httpx.Response:
358
+ resp = await self.session.request(
359
+ method=method,
360
+ url=url,
361
+ headers=headers or self.headers,
362
+ params=params,
363
+ json=json,
364
+ auth=auth,
365
+ **request_kwargs,
366
+ )
367
+ resp.raise_for_status()
368
+ return resp
369
+
370
+ call = do_request
371
+ if self.enable_backoff:
372
+
373
+ rk = dict(retry_kwargs or {})
374
+ retry_pred = rk.get("retry", retry_if_exception(self._default_retry_exc))
375
+ stop_cond = rk.get("stop", stop_after_attempt(3))
376
+ wait_cond = rk.get("wait", wait_exponential(multiplier=1, min=2, max=10))
377
+
378
+ async def retry_wrapper():
379
+ async for attempt in AsyncRetrying(reraise=True, retry=retry_pred, stop=stop_cond, wait=wait_cond):
380
+ with attempt:
381
+ return await do_request()
382
+ call = retry_wrapper
383
+
384
+ try:
385
+ return await call()
386
+ except RetryError as re:
387
+ last = re.last_attempt.exception()
388
+ self._log(f"Retry failed: {last}")
389
+ raise
390
+ except httpx.HTTPStatusError as he:
391
+ self._log(f"HTTP error: {he}")
392
+ raise
393
+
394
+ async def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
395
+ return await self._make_request("GET", endpoint, params=params, **kwargs)
396
+
397
+ async def post(self, endpoint: str, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
398
+ return await self._make_request("POST", endpoint, json=json, **kwargs)
@@ -0,0 +1,195 @@
1
+ import httpx
2
+ from typing import Optional
3
+ from pyapiary.api_connectors.broker import Broker, AsyncBroker, bubble_broker_init_signature, log_method_call
4
+
5
+ @bubble_broker_init_signature()
6
+ class FlashpointConnector(Broker):
7
+ """
8
+ FlashpointConnector provides access to various Flashpoint API search and retrieval endpoints
9
+ using a consistent Broker-based interface.
10
+
11
+ Attributes:
12
+ api_key (str): Flashpoint API token used for bearer authentication.
13
+ """
14
+ def __init__(self, api_key: Optional[str] = None, **kwargs):
15
+ super().__init__(base_url="https://api.flashpoint.io", **kwargs)
16
+ self.api_key = api_key or self.env_config.get("FLASHPOINT_API_KEY")
17
+ if not self.api_key:
18
+ raise ValueError("FLASHPOINT_API_KEY is required")
19
+ self.headers.update({
20
+ "accept": "application/json",
21
+ "content-type": "application/json",
22
+ "Authorization": f"Bearer {self.api_key}",
23
+ })
24
+
25
+ @log_method_call
26
+ def search_communities(self, query: str, **kwargs) -> httpx.Response:
27
+ """
28
+ Search Flashpoint communities data.
29
+
30
+ Args:
31
+ query (str): The search string used in the API query.
32
+ **kwargs: Additional query logic per the Flashpoint API documentation.
33
+ """
34
+ return self.post("/sources/v2/communities", json={"query": query, **kwargs})
35
+
36
+ @log_method_call
37
+ def search_fraud(self, query: str, **kwargs) -> httpx.Response:
38
+ """
39
+ Search Flashpoint fraud datasets.
40
+
41
+ Args:
42
+ query (str): The search string used in the API query.
43
+ **kwargs: Additional query logic per the Flashpoint API documentation.
44
+ """
45
+ return self.post("/sources/v2/fraud", json={"query": query, **kwargs})
46
+
47
+ @log_method_call
48
+ def search_marketplaces(self, query: str, **kwargs) -> httpx.Response:
49
+ """
50
+ Search Flashpoint marketplace datasets.
51
+
52
+ Args:
53
+ query (str): The search string used in the API query.
54
+ **kwargs: Additional query logic per the Flashpoint API documentation.
55
+ """
56
+ return self.post("/sources/v2/markets", json={"query": query, **kwargs})
57
+
58
+ @log_method_call
59
+ def search_media(self, query: str, **kwargs) -> httpx.Response:
60
+ """
61
+ Search OCR-processed media from Flashpoint.
62
+
63
+ Args:
64
+ query (str): The search string used in the API query.
65
+ **kwargs: Additional query logic per the Flashpoint API documentation.
66
+ """
67
+ return self.post("/sources/v2/media", json={"query": query, **kwargs})
68
+
69
+ @log_method_call
70
+ def get_media_object(self, query: str, **kwargs) -> httpx.Response:
71
+ """
72
+ Retrieve metadata for a specific media object.
73
+
74
+ Args:
75
+ query (str): The media_id of the object to retrieve.
76
+ **kwargs: Additional request options.
77
+ """
78
+ return self.get(f"/sources/v2/media/{query}")
79
+
80
+ @log_method_call
81
+ def get_media_image(self, query: str, **kwargs) -> httpx.Response:
82
+ """
83
+ Download image asset by storage_uri.
84
+
85
+ Args:
86
+ query (str): The storage_uri (asset_id) of the image to download.
87
+ **kwargs: Additional request options.
88
+ """
89
+ safe_headers = {"Authorization": f"Bearer {self.api_key}"}
90
+ return self.get("/sources/v1/media/", headers=safe_headers, params={"asset_id": query})
91
+
92
+ @log_method_call
93
+ def search_checks(self, query: str, **kwargs) -> httpx.Response:
94
+ """
95
+ Search Flashpoint fraud check datasets.
96
+
97
+ Args:
98
+ query (str): The search string used in the API query.
99
+ **kwargs: Additional query logic per the Flashpoint API documentation.
100
+ """
101
+ return self.post("/sources/v2/fraud/checks", json={"query": query, **kwargs})
102
+
103
+
104
+ # Async version of FlashpointConnector
105
+ @bubble_broker_init_signature()
106
+ class AsyncFlashpointConnector(AsyncBroker):
107
+ """
108
+ AsyncFlashpointConnector provides async access to Flashpoint API endpoints.
109
+ """
110
+ def __init__(self, api_key: Optional[str] = None, **kwargs):
111
+ super().__init__(base_url="https://api.flashpoint.io", **kwargs)
112
+ self.api_key = api_key or self.env_config.get("FLASHPOINT_API_KEY")
113
+ if not self.api_key:
114
+ raise ValueError("FLASHPOINT_API_KEY is required")
115
+ self.headers.update({
116
+ "accept": "application/json",
117
+ "content-type": "application/json",
118
+ "Authorization": f"Bearer {self.api_key}",
119
+ })
120
+
121
+ @log_method_call
122
+ async def search_communities(self, query: str, **kwargs) -> httpx.Response:
123
+ """
124
+ Search Flashpoint communities data.
125
+
126
+ Args:
127
+ query (str): The search string used in the API query.
128
+ **kwargs: Additional query logic per the Flashpoint API documentation.
129
+ """
130
+ return await self.post("/sources/v2/communities", json={"query": query, **kwargs})
131
+
132
+ @log_method_call
133
+ async def search_fraud(self, query: str, **kwargs) -> httpx.Response:
134
+ """
135
+ Search Flashpoint fraud datasets.
136
+
137
+ Args:
138
+ query (str): The search string used in the API query.
139
+ **kwargs: Additional query logic per the Flashpoint API documentation.
140
+ """
141
+ return await self.post("/sources/v2/fraud", json={"query": query, **kwargs})
142
+
143
+ @log_method_call
144
+ async def search_marketplaces(self, query: str, **kwargs) -> httpx.Response:
145
+ """
146
+ Search Flashpoint marketplace datasets.
147
+
148
+ Args:
149
+ query (str): The search string used in the API query.
150
+ **kwargs: Additional query logic per the Flashpoint API documentation.
151
+ """
152
+ return await self.post("/sources/v2/markets", json={"query": query, **kwargs})
153
+
154
+ @log_method_call
155
+ async def search_media(self, query: str, **kwargs) -> httpx.Response:
156
+ """
157
+ Search OCR-processed media from Flashpoint.
158
+
159
+ Args:
160
+ query (str): The search string used in the API query.
161
+ **kwargs: Additional query logic per the Flashpoint API documentation.
162
+ """
163
+ return await self.post("/sources/v2/media", json={"query": query, **kwargs})
164
+
165
+ @log_method_call
166
+ async def get_media_object(self, query: str) -> httpx.Response:
167
+ """
168
+ Retrieve metadata for a specific media object.
169
+
170
+ Args:
171
+ query (str): The media_id of the object to retrieve.
172
+ """
173
+ return await self.get(f"/sources/v2/media/{query}")
174
+
175
+ @log_method_call
176
+ async def get_media_image(self, query: str) -> httpx.Response:
177
+ """
178
+ Download image asset by storage_uri.
179
+
180
+ Args:
181
+ query (str): The storage_uri (asset_id) of the image to download.
182
+ """
183
+ safe_headers = {"Authorization": f"Bearer {self.api_key}"}
184
+ return await self.get("/sources/v1/media/", headers=safe_headers, params={"asset_id": query})
185
+
186
+ @log_method_call
187
+ async def search_checks(self, query: str, **kwargs) -> httpx.Response:
188
+ """
189
+ Search Flashpoint fraud check datasets asynchronously.
190
+
191
+ Args:
192
+ query (str): The search string used in the API query.
193
+ **kwargs: Additional query logic per the Flashpoint API documentation.
194
+ """
195
+ return await self.post("/sources/v2/fraud/checks", json={"query": query, **kwargs})