opportify-sdk 0.1.1__py3-none-any.whl → 0.3.1__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.

Potentially problematic release.


This version of opportify-sdk might be problematic. Click here for more details.

Files changed (113) hide show
  1. openapi_client/__init__.py +186 -0
  2. openapi_client/api/email_insights_api.py +1491 -0
  3. openapi_client/api/ip_insights_api.py +1494 -0
  4. {lib/v1/openapi_client → openapi_client}/api_client.py +14 -7
  5. {lib/v1/openapi_client → openapi_client}/configuration.py +16 -5
  6. {lib/v1/openapi_client → openapi_client}/exceptions.py +18 -1
  7. openapi_client/models/__init__.py +84 -0
  8. {lib/v1/openapi_client → openapi_client}/models/abuse_contact.py +1 -1
  9. openapi_client/models/address_signals.py +99 -0
  10. {lib/v1/openapi_client → openapi_client}/models/admin_contact.py +1 -1
  11. openapi_client/models/analyze_email200_response.py +127 -0
  12. lib/v1/openapi_client/models/analyze_email400_response_error.py → openapi_client/models/analyze_email400_response.py +9 -9
  13. openapi_client/models/analyze_email403_response.py +207 -0
  14. openapi_client/models/analyze_email500_response.py +89 -0
  15. openapi_client/models/analyze_email_request.py +94 -0
  16. {lib/v1/openapi_client → openapi_client}/models/analyze_ip200_response.py +4 -4
  17. lib/v1/openapi_client/models/analyze_ip400_response_error.py → openapi_client/models/analyze_ip400_response.py +20 -20
  18. {lib/v1/openapi_client → openapi_client}/models/analyze_ip_request.py +1 -1
  19. {lib/v1/openapi_client → openapi_client}/models/asn.py +1 -1
  20. openapi_client/models/batch_analyze_emails202_response.py +93 -0
  21. openapi_client/models/batch_analyze_emails400_response.py +137 -0
  22. openapi_client/models/batch_analyze_emails401_response.py +89 -0
  23. openapi_client/models/batch_analyze_emails402_response.py +137 -0
  24. openapi_client/models/batch_analyze_emails403_response.py +193 -0
  25. openapi_client/models/batch_analyze_emails413_response.py +89 -0
  26. openapi_client/models/batch_analyze_emails429_response.py +89 -0
  27. openapi_client/models/batch_analyze_emails_request.py +93 -0
  28. openapi_client/models/batch_analyze_ips202_response.py +93 -0
  29. openapi_client/models/batch_analyze_ips400_response.py +137 -0
  30. openapi_client/models/batch_analyze_ips_request.py +91 -0
  31. {lib/v1/openapi_client → openapi_client}/models/block_listed.py +3 -4
  32. openapi_client/models/create_email_batch_export400_response.py +89 -0
  33. openapi_client/models/create_email_batch_export403_response.py +89 -0
  34. openapi_client/models/create_email_batch_export404_response.py +89 -0
  35. openapi_client/models/create_email_batch_export409_response.py +137 -0
  36. openapi_client/models/email_dns.py +97 -0
  37. openapi_client/models/email_domain.py +113 -0
  38. openapi_client/models/export_created_response.py +91 -0
  39. openapi_client/models/export_filter.py +95 -0
  40. openapi_client/models/export_request.py +92 -0
  41. openapi_client/models/export_status_response.py +119 -0
  42. openapi_client/models/exportnotfound.py +89 -0
  43. openapi_client/models/forbidden.py +89 -0
  44. {lib/v1/openapi_client → openapi_client}/models/geo.py +1 -1
  45. openapi_client/models/get_email_batch_export_status400_response.py +89 -0
  46. openapi_client/models/get_email_batch_export_status404_response.py +137 -0
  47. openapi_client/models/get_email_batch_status200_response.py +101 -0
  48. openapi_client/models/get_email_batch_status200_response_download_urls.py +93 -0
  49. openapi_client/models/get_email_batch_status404_response.py +89 -0
  50. openapi_client/models/get_ip_batch_status200_response.py +101 -0
  51. openapi_client/models/internalerror.py +89 -0
  52. openapi_client/models/internalerror1.py +89 -0
  53. openapi_client/models/invaliddata.py +89 -0
  54. openapi_client/models/invaliddata1.py +89 -0
  55. openapi_client/models/invalidemail.py +89 -0
  56. openapi_client/models/invalidplan.py +89 -0
  57. openapi_client/models/invalidplan1.py +89 -0
  58. openapi_client/models/invalidtoken.py +89 -0
  59. openapi_client/models/ipvalidationfailed.py +89 -0
  60. openapi_client/models/jobnotfound.py +89 -0
  61. openapi_client/models/jobnotready.py +89 -0
  62. openapi_client/models/malformedrequest.py +89 -0
  63. openapi_client/models/malformedrequest1.py +89 -0
  64. openapi_client/models/malformedrequest2.py +89 -0
  65. openapi_client/models/malformedrequest3.py +89 -0
  66. openapi_client/models/manifestnotavailable.py +89 -0
  67. openapi_client/models/notfound.py +89 -0
  68. {lib/v1/openapi_client → openapi_client}/models/organization.py +1 -1
  69. openapi_client/models/quotaexceeded.py +89 -0
  70. openapi_client/models/risk_report_email.py +92 -0
  71. openapi_client/models/risk_report_ip.py +89 -0
  72. {lib/v1/openapi_client → openapi_client}/models/tech_contact.py +1 -1
  73. openapi_client/models/toomanyrequests.py +89 -0
  74. {lib/v1/openapi_client → openapi_client}/models/trusted_provider.py +1 -1
  75. {lib/v1/openapi_client → openapi_client}/models/whois.py +2 -2
  76. {lib/v1/openapi_client → openapi_client}/rest.py +2 -1
  77. opportify_sdk/email_insights.py +427 -0
  78. opportify_sdk/ip_insights.py +410 -0
  79. opportify_sdk-0.3.1.dist-info/METADATA +300 -0
  80. opportify_sdk-0.3.1.dist-info/RECORD +86 -0
  81. {opportify_sdk-0.1.1.dist-info → opportify_sdk-0.3.1.dist-info}/WHEEL +1 -1
  82. opportify_sdk-0.3.1.dist-info/top_level.txt +2 -0
  83. lib/__init__.py +0 -0
  84. lib/v1/__init__.py +0 -0
  85. lib/v1/openapi_client/__init__.py +0 -63
  86. lib/v1/openapi_client/api/email_insights_api.py +0 -317
  87. lib/v1/openapi_client/api/ip_insights_api.py +0 -322
  88. lib/v1/openapi_client/models/__init__.py +0 -45
  89. lib/v1/openapi_client/models/analyze_email200_response.py +0 -113
  90. lib/v1/openapi_client/models/analyze_email400_response.py +0 -91
  91. lib/v1/openapi_client/models/analyze_email500_response.py +0 -91
  92. lib/v1/openapi_client/models/analyze_email500_response_error.py +0 -89
  93. lib/v1/openapi_client/models/analyze_email_request.py +0 -92
  94. lib/v1/openapi_client/models/analyze_ip400_response.py +0 -91
  95. lib/v1/openapi_client/models/analyze_ip404_response.py +0 -91
  96. lib/v1/openapi_client/models/analyze_ip500_response.py +0 -91
  97. lib/v1/openapi_client/models/email_dns.py +0 -87
  98. lib/v1/openapi_client/models/internalerror.py +0 -89
  99. lib/v1/openapi_client/models/invalidemail.py +0 -89
  100. lib/v1/openapi_client/models/ipvalidationfailed.py +0 -89
  101. lib/v1/openapi_client/models/malformedrequest.py +0 -89
  102. lib/v1/openapi_client/models/malformedrequest1.py +0 -89
  103. lib/v1/openapi_client/models/notfound.py +0 -89
  104. lib/v1/openapi_client/models/risk_report.py +0 -89
  105. opportify_sdk-0.1.1.dist-info/METADATA +0 -108
  106. opportify_sdk-0.1.1.dist-info/RECORD +0 -49
  107. opportify_sdk-0.1.1.dist-info/top_level.txt +0 -2
  108. src/email_insights.py +0 -97
  109. src/ip_insights.py +0 -93
  110. {lib/v1/openapi_client → openapi_client}/api/__init__.py +0 -0
  111. {lib/v1/openapi_client → openapi_client}/api_response.py +0 -0
  112. {lib/v1/openapi_client → openapi_client}/py.typed +0 -0
  113. {src → opportify_sdk}/__init__.py +0 -0
@@ -0,0 +1,410 @@
1
+ # src/ip_insights.py
2
+ import os
3
+ from typing import Optional, Dict, Any, List, Union
4
+ import openapi_client
5
+ from openapi_client.configuration import Configuration as ApiConfiguration
6
+ from openapi_client.api_client import ApiClient
7
+ from openapi_client.api.ip_insights_api import IPInsightsApi
8
+ from openapi_client.models.analyze_ip_request import AnalyzeIpRequest
9
+ from openapi_client.models.batch_analyze_ips_request import BatchAnalyzeIpsRequest
10
+ from openapi_client.models.export_request import ExportRequest
11
+ from openapi_client.exceptions import ApiException
12
+
13
+
14
+ class IpInsights:
15
+ def __init__(self, api_key: str, api_instance: Optional[IPInsightsApi] = None):
16
+ """
17
+ Initialize the IpInsights class with the provided API key.
18
+
19
+ :param api_key: The API key for authentication.
20
+ :param api_instance: Optional API instance for testing purposes.
21
+ """
22
+ self.config = ApiConfiguration()
23
+ self.config.api_key = {"opportifyToken": api_key}
24
+ self.host = "https://api.opportify.ai"
25
+ self.prefix = "insights"
26
+ self.version = "v1"
27
+ self.debug_mode = False
28
+ self.final_url = ""
29
+ self.config_changed = False
30
+
31
+ self._update_final_url()
32
+
33
+ if api_instance:
34
+ self.api_instance = api_instance
35
+ else:
36
+ self._refresh_api_instance(first_run=True)
37
+
38
+ def _refresh_api_instance(self, first_run: bool = False) -> None:
39
+ """
40
+ Ensures API instance is updated only if config has changed.
41
+
42
+ :param first_run: Whether this is the first initialization.
43
+ """
44
+ if not self.config_changed and not first_run:
45
+ return
46
+
47
+ self._update_final_url()
48
+ self.config.host = self.final_url
49
+ api_client = ApiClient(configuration=self.config)
50
+ api_client.configuration.debug = self.debug_mode
51
+ self.api_instance = IPInsightsApi(api_client)
52
+ self.config_changed = False
53
+
54
+ def _update_final_url(self) -> None:
55
+ """
56
+ Updates the final URL used for API requests.
57
+ """
58
+ base = self.host.rstrip('/')
59
+ segments = []
60
+
61
+ prefix = self.prefix.strip('/')
62
+ if prefix:
63
+ segments.append(prefix)
64
+
65
+ version = self.version.strip('/')
66
+ if version:
67
+ segments.append(version)
68
+
69
+ self.final_url = base + ('/' + '/'.join(segments) if segments else '')
70
+
71
+ def analyze(self, params: Dict[str, Any]) -> Dict[str, Any]:
72
+ """
73
+ Analyze the IP address based on the provided parameters.
74
+
75
+ :param params: Dictionary containing parameters for IP analysis.
76
+ :return: The analysis result as a dictionary.
77
+ :raises Exception: If an API exception occurs.
78
+ """
79
+ # Ensure latest config before API call
80
+ self._refresh_api_instance()
81
+
82
+ params = self._normalize_request(params)
83
+ analyze_ip_request = AnalyzeIpRequest(**params)
84
+
85
+ try:
86
+ result = self.api_instance.analyze_ip(analyze_ip_request)
87
+ return result.to_dict()
88
+ except ApiException as e:
89
+ raise Exception(f"API exception: {e.reason}")
90
+
91
+ def batch_analyze(self, params: Dict[str, Any], content_type: Optional[str] = None) -> Dict[str, Any]:
92
+ """
93
+ Submit a batch of IPs for analysis.
94
+
95
+ :param params: Dictionary containing parameters for batch IP analysis.
96
+ :param content_type: Optional content type (defaults to application/json).
97
+ Supported: 'application/json', 'multipart/form-data', 'text/plain'
98
+ :return: The batch job information as a dictionary (job_id, status, etc.).
99
+ :raises Exception: If an API exception occurs.
100
+ """
101
+ # Ensure latest config before API call
102
+ self._refresh_api_instance()
103
+
104
+ # Default to application/json if not specified
105
+ content_type = content_type or 'application/json'
106
+
107
+ try:
108
+ if content_type == 'application/json':
109
+ params = self._normalize_batch_request(params)
110
+ batch_analyze_ips_request = BatchAnalyzeIpsRequest(**params)
111
+ result = self.api_instance.batch_analyze_ips(
112
+ batch_analyze_ips_request,
113
+ _content_type=content_type
114
+ )
115
+ elif content_type == 'multipart/form-data':
116
+ if 'file' not in params or not os.path.exists(params['file']):
117
+ raise ValueError('File parameter is required and must be a valid file path')
118
+
119
+ with open(params['file'], 'rb') as file_handle:
120
+ # Create multipart data
121
+ files = {'file': (os.path.basename(params['file']), file_handle)}
122
+ data = {}
123
+
124
+ # Add optional parameters
125
+ enable_ai = self._resolve_boolean(params, ['enable_ai', 'enableAi'])
126
+ if enable_ai is not None:
127
+ data['enable_ai'] = 'true' if enable_ai else 'false'
128
+
129
+ # Add name parameter if provided
130
+ if 'name' in params:
131
+ data['name'] = str(params['name'])
132
+
133
+ # Prepare the request body as multipart
134
+ multipart_data = self._prepare_multipart_data(files, data)
135
+ result = self.api_instance.batch_analyze_ips(
136
+ multipart_data,
137
+ _content_type=content_type
138
+ )
139
+ elif content_type == 'text/plain':
140
+ if 'text' not in params:
141
+ raise ValueError('Text parameter is required for text/plain content type')
142
+
143
+ result = self.api_instance.batch_analyze_ips(
144
+ params['text'],
145
+ _content_type=content_type
146
+ )
147
+ else:
148
+ raise ValueError(f'Unsupported content type: {content_type}')
149
+
150
+ return result.to_dict()
151
+ except ApiException as e:
152
+ raise Exception(f"API exception: {e.reason}")
153
+
154
+ def batch_analyze_file(self, file_path: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
155
+ """
156
+ Submit a batch of IPs for analysis using a file.
157
+
158
+ :param file_path: Path to the file containing IPs (CSV or text).
159
+ :param options: Additional options like enableAi, name.
160
+ :return: The batch job information as a dictionary.
161
+ :raises Exception: If an API exception occurs.
162
+ """
163
+ options = options or {}
164
+ params = {'file': file_path, **options}
165
+ return self.batch_analyze(params, 'multipart/form-data')
166
+
167
+ def get_batch_status(self, job_id: str) -> Dict[str, Any]:
168
+ """
169
+ Get the status of a batch IP analysis job.
170
+
171
+ :param job_id: The unique identifier of the batch job.
172
+ :return: The batch job status as a dictionary.
173
+ :raises Exception: If an API exception occurs.
174
+ """
175
+ # Ensure latest config before API call
176
+ self._refresh_api_instance()
177
+
178
+ try:
179
+ result = self.api_instance.get_ip_batch_status(job_id)
180
+ return result.to_dict()
181
+ except ApiException as e:
182
+ raise Exception(f"API exception: {e.reason}")
183
+
184
+ def create_batch_export(self, job_id: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
185
+ """
186
+ Request a custom export for a completed IP batch job.
187
+
188
+ :param job_id: The unique identifier of the batch job.
189
+ :param payload: Optional export configuration (export_type, filters, columns).
190
+ :return: The export creation response as a dictionary.
191
+ :raises Exception: If an API exception occurs.
192
+ """
193
+ self._refresh_api_instance()
194
+
195
+ job_id = job_id.strip()
196
+ if not job_id:
197
+ raise ValueError('Job ID cannot be empty when creating an export.')
198
+
199
+ payload = payload or {}
200
+ normalized_payload = self._normalize_export_request(payload)
201
+ export_request = ExportRequest(**normalized_payload) if normalized_payload else None
202
+
203
+ try:
204
+ result = self.api_instance.create_ip_batch_export(job_id, export_request)
205
+ return result.to_dict()
206
+ except ApiException as e:
207
+ raise Exception(f"API exception: {e.reason}")
208
+
209
+ def get_batch_export_status(self, job_id: str, export_id: str) -> Dict[str, Any]:
210
+ """
211
+ Retrieve the status of a previously requested IP batch export.
212
+
213
+ :param job_id: The unique identifier of the batch job.
214
+ :param export_id: The unique identifier of the export.
215
+ :return: The export status as a dictionary.
216
+ :raises Exception: If an API exception occurs.
217
+ """
218
+ self._refresh_api_instance()
219
+
220
+ job_id = job_id.strip()
221
+ export_id = export_id.strip()
222
+
223
+ if not job_id or not export_id:
224
+ raise ValueError('Job ID and export ID are required to fetch export status.')
225
+
226
+ try:
227
+ result = self.api_instance.get_ip_batch_export_status(job_id, export_id)
228
+ return result.to_dict()
229
+ except ApiException as e:
230
+ raise Exception(f"API exception: {e.reason}")
231
+
232
+ def set_host(self, host: str) -> "IpInsights":
233
+ """
234
+ Set the host.
235
+
236
+ :param host: The host URL.
237
+ :return: The current instance for chaining.
238
+ """
239
+ if self.host != host:
240
+ self.host = host
241
+ self.config_changed = True
242
+ return self
243
+
244
+ def set_version(self, version: str) -> "IpInsights":
245
+ """
246
+ Set the version.
247
+
248
+ :param version: The API version.
249
+ :return: The current instance for chaining.
250
+ """
251
+ if self.version != version:
252
+ self.version = version
253
+ self.config_changed = True
254
+ return self
255
+
256
+ def set_prefix(self, prefix: str) -> "IpInsights":
257
+ """
258
+ Set the prefix.
259
+
260
+ :param prefix: The URL prefix.
261
+ :return: The current instance for chaining.
262
+ """
263
+ prefix = prefix.strip('/')
264
+ if self.prefix != prefix:
265
+ self.prefix = prefix
266
+ self.config_changed = True
267
+ return self
268
+
269
+ def set_debug_mode(self, debug_mode: bool) -> "IpInsights":
270
+ """
271
+ Set the debug mode.
272
+
273
+ :param debug_mode: Enable or disable debug mode.
274
+ :return: The current instance for chaining.
275
+ """
276
+ if self.debug_mode != debug_mode:
277
+ self.debug_mode = debug_mode
278
+ self.config_changed = True
279
+ return self
280
+
281
+ def _normalize_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
282
+ """
283
+ Normalize the request parameters.
284
+
285
+ :param params: The raw parameters.
286
+ :return: Normalized parameters.
287
+ """
288
+ if 'ip' not in params:
289
+ raise ValueError('The ip parameter is required for analysis.')
290
+
291
+ normalized = {}
292
+ normalized["ip"] = str(params["ip"])
293
+
294
+ # Only include optional parameters if explicitly provided by user
295
+ enable_ai = self._resolve_boolean(params, ['enable_ai', 'enableAi'])
296
+ if enable_ai is not None:
297
+ normalized["enable_ai"] = enable_ai
298
+
299
+ return normalized
300
+
301
+ def _normalize_batch_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
302
+ """
303
+ Normalize the batch request parameters.
304
+
305
+ :param params: The raw parameters.
306
+ :return: Normalized parameters.
307
+ """
308
+ if 'ips' not in params:
309
+ raise ValueError("'ips' parameter is required for batch analysis")
310
+
311
+ ips = params['ips']
312
+ if not isinstance(ips, list):
313
+ raise ValueError("'ips' parameter must be a list")
314
+
315
+ normalized = {}
316
+ normalized["ips"] = [str(ip) for ip in ips]
317
+
318
+ enable_ai = self._resolve_boolean(params, ['enable_ai', 'enableAi'])
319
+ if enable_ai is not None:
320
+ normalized['enable_ai'] = enable_ai
321
+
322
+ # Add name parameter if provided
323
+ if 'name' in params:
324
+ normalized['name'] = str(params['name'])
325
+
326
+ return normalized
327
+
328
+ def _normalize_export_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
329
+ """
330
+ Normalize export payload for batch exports.
331
+
332
+ :param params: The raw parameters.
333
+ :return: Normalized parameters.
334
+ """
335
+ normalized = {}
336
+
337
+ if self._has_any_key(params, ['export_type', 'exportType']):
338
+ value = params.get('export_type') or params.get('exportType')
339
+ normalized['export_type'] = str(value).lower()
340
+
341
+ if 'filters' in params and params['filters'] is not None:
342
+ if not isinstance(params['filters'], dict):
343
+ raise ValueError('Filters must be provided as a dictionary.')
344
+ normalized['filters'] = params['filters']
345
+
346
+ if 'columns' in params and params['columns'] is not None:
347
+ if not isinstance(params['columns'], list):
348
+ raise ValueError('Columns must be provided as a list.')
349
+ normalized['columns'] = [str(column) for column in params['columns']]
350
+
351
+ return normalized
352
+
353
+ def _has_any_key(self, params: Dict[str, Any], keys: List[str]) -> bool:
354
+ """
355
+ Check if params has any of the specified keys.
356
+
357
+ :param params: The parameters dictionary.
358
+ :param keys: List of keys to check.
359
+ :return: True if any key exists.
360
+ """
361
+ return any(key in params for key in keys)
362
+
363
+ def _resolve_boolean(self, params: Dict[str, Any], keys: List[str], default: Optional[bool] = None) -> Optional[bool]:
364
+ """
365
+ Resolve boolean value from params using multiple possible keys.
366
+
367
+ :param params: The parameters dictionary.
368
+ :param keys: List of possible keys.
369
+ :param default: Default value if none found.
370
+ :return: Boolean value or default.
371
+ """
372
+ for key in keys:
373
+ if key in params:
374
+ return self._to_boolean(params[key], key)
375
+ return default
376
+
377
+ def _to_boolean(self, value: Any, parameter_name: str) -> bool:
378
+ """
379
+ Convert a value to boolean.
380
+
381
+ :param value: The value to convert.
382
+ :param parameter_name: Name for error messages.
383
+ :return: Boolean value.
384
+ """
385
+ if isinstance(value, bool):
386
+ return value
387
+
388
+ if value in (1, 0, '1', '0'):
389
+ return bool(int(value))
390
+
391
+ if isinstance(value, str):
392
+ value_lower = value.lower()
393
+ if value_lower in ('true', 'yes', '1'):
394
+ return True
395
+ if value_lower in ('false', 'no', '0'):
396
+ return False
397
+
398
+ raise ValueError(f'Invalid boolean value provided for {parameter_name}')
399
+
400
+ def _prepare_multipart_data(self, files: Dict[str, Any], data: Dict[str, Any]) -> Any:
401
+ """
402
+ Prepare multipart form data for file upload.
403
+
404
+ :param files: Files dictionary.
405
+ :param data: Additional form data.
406
+ :return: Prepared multipart data.
407
+ """
408
+ # For Python SDK, we'll pass the file path directly and let the OpenAPI client handle it
409
+ # This is a placeholder that may need adjustment based on how the openapi_client handles multipart
410
+ return {**files, **data}