chainalysis 0.0.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 chainalysis might be problematic. Click here for more details.

@@ -0,0 +1,22 @@
1
+ from chainalysis.sql.sql import Sql
2
+ from chainalysis.utils.utils import Utils
3
+
4
+
5
+ class DataSolutionsClient:
6
+ """
7
+ This class provides SDK functions for users to query
8
+ Data Solutions databases.
9
+
10
+ The Analytical class queries the Data Solutions Databricks table.
11
+ The Transactional class queries the Data Solutions Postgres table.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ api_key: str,
17
+ ):
18
+ self.api_key = api_key
19
+
20
+ # Define SDK function classes
21
+ self.sql = Sql(api_key)
22
+ self.utils = Utils()
@@ -0,0 +1,3 @@
1
+ from chainalysis import *
2
+ from sql import *
3
+ from utils import *
@@ -0,0 +1,12 @@
1
+ BASE_URL = {
2
+ "base_url": "https://api.transpose.io",
3
+ }
4
+
5
+ ANALYTICAL_ENDPOINTS = {
6
+ "async_query_execution": "sql/analytical/async",
7
+ "async_query_status": "sql/analytical/status",
8
+ }
9
+
10
+ TRANSACTIONAL_ENDPOINTS = {
11
+ "query_execution": "sql/transactional",
12
+ }
@@ -0,0 +1 @@
1
+ from chainalysis.sql import *
@@ -0,0 +1,234 @@
1
+ from time import sleep
2
+
3
+ import pandas as pd
4
+
5
+ from chainalysis.constants import ANALYTICAL_ENDPOINTS, BASE_URL
6
+ from chainalysis.util_functions.exceptions import (
7
+ BadRequest,
8
+ DataSolutionsAPIException,
9
+ DataSolutionsSDKException,
10
+ UnhandledException,
11
+ )
12
+ from chainalysis.util_functions.requests import issue_request
13
+
14
+
15
+ class Analytical:
16
+ """
17
+ This class provides methods to execute SQL queries on Data Solutions
18
+ DataBricks tables. It supports fetching results as JSON or a
19
+ pandas DataFrame, and provides query execution statistics.
20
+ """
21
+
22
+ def __init__(self, api_key: str):
23
+ """
24
+ Initialize the Analytical class with the provided API key.
25
+
26
+ :param api_key: The API key for accessing the analytical service.
27
+ :type api_key: str
28
+ """
29
+
30
+ self.api_key = api_key
31
+ self._status_code = 0
32
+ self.results = {}
33
+ self._stats = {}
34
+ self.json_response = {}
35
+ self.dataframe_data = None
36
+ self._status = "error"
37
+ self.next = ""
38
+ self._total_pages = 0
39
+ self.query_id = None
40
+ self.exception = UnhandledException()
41
+
42
+ def __call__(
43
+ self,
44
+ query: str,
45
+ parameters: dict[str] = {},
46
+ polling_interval_sec: int = 5,
47
+ ):
48
+ """
49
+ Execute a SQL query asynchronously using the provided parameters
50
+ and polling interval.
51
+
52
+ :param query: The SQL query to be executed.
53
+ :type query: str
54
+ :param parameters: A dictionary of parameters to be used in the query.
55
+ :type parameters: dict[str], optional
56
+ :param polling_interval_sec: The interval in seconds between status checks.
57
+ :type polling_interval_sec: int, optional
58
+ :return: An instance of the Analytical class with query results.
59
+ :rtype: Analytical
60
+ """
61
+
62
+ query_execution_url = (
63
+ f"{BASE_URL['base_url']}/{ANALYTICAL_ENDPOINTS['async_query_execution']}"
64
+ )
65
+
66
+ body = {
67
+ "sql": query,
68
+ "parameters": parameters,
69
+ }
70
+
71
+ async_response = issue_request(
72
+ api_key=self.api_key,
73
+ url=query_execution_url,
74
+ body=body,
75
+ method="POST",
76
+ )
77
+
78
+ self.query_id = async_response.get("query_id")
79
+ if not self.query_id:
80
+ raise DataSolutionsAPIException(
81
+ "Unexpected response. Query ID was not returned."
82
+ )
83
+ async_query_status_url = f"{BASE_URL['base_url']}/{ANALYTICAL_ENDPOINTS['async_query_status']}?query_id={self.query_id}"
84
+
85
+ try:
86
+ while True:
87
+ self.json_response = issue_request(
88
+ api_key=self.api_key,
89
+ url=async_query_status_url,
90
+ method="GET",
91
+ )
92
+
93
+ self._status = self.json_response["status"]
94
+
95
+ if self._status == "running" or self._status == "pending":
96
+ sleep(polling_interval_sec)
97
+ elif self._status == "error":
98
+ self.error_message = self.json_response["message"]
99
+ self.error_details = self.json_response.get("details")
100
+ break
101
+ elif self._status == "success":
102
+ self._status_code = 200
103
+ self._stats = self.json_response["stats"]
104
+ self.results = self.json_response["results"]
105
+ self.next_url = self.json_response["next"]
106
+ self._total_pages = self._stats["total_pages"]
107
+ break
108
+ except DataSolutionsSDKException as e:
109
+ self.exception = e.get_exception()
110
+ self._status_code = e.status_code
111
+ except Exception as e:
112
+ self.exception = UnhandledException(
113
+ details=e,
114
+ )
115
+ return self
116
+
117
+ def next_page(self):
118
+ """
119
+ Fetch the next page of DataBricks query results.
120
+
121
+ :return: An instance of the Analytical class with the next page of results.
122
+ :rtype: Analytical
123
+ """
124
+
125
+ if self.next_url:
126
+
127
+ self.json_response = issue_request(
128
+ api_key=self.api_key,
129
+ url=self.next_url,
130
+ method="GET",
131
+ )
132
+ self._status = self.json_response["status"]
133
+
134
+ if self._status == "error":
135
+ self.error_message = self.json_response["message"]
136
+ self.error_details = self.json_response.get("details")
137
+ elif self._status == "success":
138
+ self._status = self._status
139
+ self._stats = self.json_response["stats"]
140
+ self.results = self.json_response["results"]
141
+ self.next_url = self.json_response["next"]
142
+ else:
143
+ raise BadRequest(
144
+ "No next page available. Use the method has_next() to check if there is a next page that can be retrieved."
145
+ )
146
+ return self
147
+
148
+ def json(self):
149
+ """
150
+ Return results as a JSON.
151
+
152
+ :raises Exception: Raises an exception if the query resulted in an error.
153
+ :return: Results of the SQL query as a JSON.
154
+ :rtype: dict
155
+ """
156
+
157
+ if self._status != "error":
158
+ return self.results
159
+ else:
160
+ raise self.exception
161
+
162
+ def df(self):
163
+ """
164
+ Convert query results into a pandas DataFrame.
165
+
166
+ :raises Exception: Raises an exception if the query resulted in an error.
167
+ :return: DataFrame containing the results of the SQL query.
168
+ :rtype: pd.DataFrame
169
+ """
170
+
171
+ if self._status != "error":
172
+ if self.dataframe_data:
173
+ return self.dataframe_data
174
+ self.dataframe_data = pd.DataFrame(self.results)
175
+ return self.dataframe_data
176
+ else:
177
+ raise self.exception
178
+
179
+ def stats(self):
180
+ """
181
+ Get the statistics of the executed query.
182
+
183
+ :return: Statistics of the query execution.
184
+ :rtype: dict
185
+ """
186
+
187
+ if self._status != "error":
188
+ return self._stats
189
+ else:
190
+ raise self.exception
191
+
192
+ def status_code(self):
193
+ """
194
+ Get the HTTP status code of the response.
195
+
196
+ :return: HTTP status code.
197
+ :rtype: int
198
+ """
199
+
200
+ return self._status_code
201
+
202
+ def was_successful(self):
203
+ """
204
+ Determine if the query executed successfully.
205
+
206
+ :return: True if the query was successful, False otherwise.
207
+ :rtype: bool
208
+ """
209
+
210
+ if self._status != "error":
211
+ return True
212
+ return False
213
+
214
+ def total_pages(self) -> int:
215
+ """
216
+ Return total number of pages.
217
+
218
+ :return: Number of pages.
219
+ :rtype: int
220
+ """
221
+
222
+ return self._total_pages
223
+
224
+ def has_next(self) -> bool:
225
+ """
226
+ Return if the next page exists.
227
+
228
+ :return: Whether next page exists.
229
+ :rtype: bool
230
+ """
231
+
232
+ if self.next_url:
233
+ return True
234
+ return False
chainalysis/sql/sql.py ADDED
@@ -0,0 +1,8 @@
1
+ from chainalysis.sql.analytical import Analytical
2
+ from chainalysis.sql.transactional import Transactional
3
+
4
+
5
+ class Sql:
6
+ def __init__(self, api_key: str):
7
+ self.transactional = Transactional(api_key)
8
+ self.analytical = Analytical(api_key)
@@ -0,0 +1,145 @@
1
+ import pandas as pd
2
+
3
+ from chainalysis.constants import BASE_URL, TRANSACTIONAL_ENDPOINTS
4
+ from chainalysis.util_functions.exceptions import (
5
+ DataSolutionsSDKException,
6
+ UnhandledException,
7
+ )
8
+ from chainalysis.util_functions.requests import issue_request
9
+
10
+
11
+ class Transactional:
12
+ """
13
+ This class provides methods to handle transactional queries.
14
+ It supports fetching results as JSON or pandas DataFrame,
15
+ and provides query execution statistics.
16
+ """
17
+
18
+ def __init__(self, api_key: str):
19
+ """
20
+ Initialize the Transactional object with an API key.
21
+
22
+ :param api_key: API key for authenticating requests.
23
+ :type api_key: str
24
+ """
25
+ self.api_key = api_key
26
+ self._status_code = 0
27
+ self.results = {}
28
+ self._stats = {}
29
+ self.json_response = {}
30
+ self.status = "error"
31
+ self.dataframe_data = None
32
+
33
+ def __call__(
34
+ self, query: str, parameters: dict[str, str] = {}, options: dict[str, str] = {}
35
+ ) -> "Transactional":
36
+ """
37
+ Execute a SQL query using the provided parameters and options.
38
+
39
+ :param query: SQL query template with placeholders for parameters.
40
+ :type query: str
41
+ :param parameters: Parameters to format the SQL query.
42
+ :type parameters: dict[str, str]
43
+ :param options: Additional options for query execution.
44
+ :type options: dict[str, str]
45
+ :return: Returns self for chaining or method chaining.
46
+ :rtype: Transactional
47
+ """
48
+
49
+ query_execution_url = (
50
+ f"{BASE_URL['base_url']}/{TRANSACTIONAL_ENDPOINTS['query_execution']}"
51
+ )
52
+
53
+ body = {
54
+ "sql": query,
55
+ "parameters": parameters,
56
+ "options": options,
57
+ }
58
+
59
+ try:
60
+ self.json_response = issue_request(
61
+ api_key=self.api_key,
62
+ url=query_execution_url,
63
+ body=body,
64
+ method="POST",
65
+ )
66
+ self.status = self.json_response["status"]
67
+ if self.status == "error":
68
+ self.error_message = self.json_response["message"]
69
+ self.error_details = self.json_response.get("details")
70
+ self.exception = DataSolutionsSDKException(self.error_message)
71
+ else:
72
+ self._stats = self.json_response["stats"]
73
+ self.results = self.json_response["results"]
74
+ self._status_code = 200
75
+
76
+ except DataSolutionsSDKException as e:
77
+ self.exception = e.get_exception()
78
+ self._status_code = e.status_code
79
+ except Exception as e:
80
+ self.exception = UnhandledException(
81
+ details=e,
82
+ )
83
+ return self
84
+
85
+ def json(self) -> dict:
86
+ """
87
+ Return the JSON data of the results.
88
+
89
+ :raises Exception: Raises an exception if the query resulted in an error.
90
+ :return: JSON results of the SQL query.
91
+ :rtype: dict
92
+ """
93
+
94
+ if self.status != "error":
95
+ return self.results
96
+ else:
97
+ raise self.exception
98
+
99
+ def df(self) -> pd.DataFrame:
100
+ """
101
+ Convert query results into a pandas DataFrame.
102
+
103
+ :raises Exception: Raises an exception if the query resulted in an error.
104
+ :return: DataFrame containing the results of the SQL query.
105
+ :rtype: pd.DataFrame
106
+ """
107
+ if self.status != "error":
108
+ if self.dataframe_data:
109
+ return self.dataframe_data
110
+ self.dataframe_data = pd.DataFrame(self.results)
111
+ return self.dataframe_data
112
+ elif self.exception:
113
+ raise self.exception
114
+
115
+ def stats(self) -> dict:
116
+ """
117
+ Get the statistics of the executed query.
118
+
119
+ :return: Statistics of the query execution.
120
+ :rtype: dict
121
+ """
122
+ if self.status != "error":
123
+ return self._stats
124
+ else:
125
+ raise self.exception
126
+
127
+ def was_successful(self) -> bool:
128
+ """
129
+ Determine if the query executed successfully.
130
+
131
+ :return: True if the query was successful, False otherwise.
132
+ :rtype: bool
133
+ """
134
+ if self.status != "error":
135
+ return True
136
+ return False
137
+
138
+ def status_code(self) -> int:
139
+ """
140
+ Return the status code of the query.
141
+
142
+ :return: The status code of the query.
143
+ :rtype: int
144
+ """
145
+ return self._status_code
@@ -0,0 +1,18 @@
1
+ from typing import Any, List, Type
2
+
3
+ from chainalysis.util_functions.exceptions import BadRequest
4
+
5
+
6
+ def get_list_type(my_list: List[Any]) -> Type:
7
+ """Return the type of list elements if all are the same type, otherwise raise an error."""
8
+ if not my_list:
9
+ raise ValueError("The list is empty and has no element type.")
10
+
11
+ element_types = {type(item) for item in my_list}
12
+
13
+ if len(element_types) > 1:
14
+ raise BadRequest(
15
+ f"The list contains multiple types: {element_types}. Enter a list with only numbers, strings, or bools."
16
+ )
17
+
18
+ return element_types.pop()
@@ -0,0 +1,87 @@
1
+ class DataSolutionsSDKException(Exception):
2
+ """Base class for SDK exceptions."""
3
+
4
+ def __init__(
5
+ self,
6
+ message=None,
7
+ status_code=0,
8
+ ):
9
+ super().__init__(message)
10
+ self.status_code = status_code
11
+
12
+ def get_exception(self):
13
+ return self
14
+
15
+
16
+ class BadRequest(DataSolutionsSDKException):
17
+ """Exception for Bad Request."""
18
+
19
+ def __init__(self, message="Bad Request"):
20
+ super().__init__(
21
+ message,
22
+ status_code=400,
23
+ )
24
+
25
+
26
+ class Unauthorized(DataSolutionsSDKException):
27
+ """Exception for 401 Unauthorized."""
28
+
29
+ def __init__(self, message="Unauthorized"):
30
+ super().__init__(
31
+ message,
32
+ status_code=401,
33
+ )
34
+
35
+
36
+ class Forbidden(DataSolutionsSDKException):
37
+ """Exception for 403 Forbidden."""
38
+
39
+ def __init__(
40
+ self,
41
+ message="Forbidden. Contact Data Solutions if you believe you should have access to this endpoint/data.",
42
+ ):
43
+ super().__init__(
44
+ message,
45
+ status_code=403,
46
+ )
47
+
48
+
49
+ class NotFound(DataSolutionsSDKException):
50
+ """Exception for 404 Not Found."""
51
+
52
+ def __init__(self, message="Not Found. Is your query correct?"):
53
+ super().__init__(
54
+ message,
55
+ status_code=404,
56
+ )
57
+
58
+
59
+ class InternalServerException(DataSolutionsSDKException):
60
+ """Exception for 500 Internal Server Error."""
61
+
62
+ def __init__(self, message="Internal Server Error"):
63
+ super().__init__(
64
+ message,
65
+ status_code=500,
66
+ )
67
+
68
+
69
+ class DataSolutionsAPIException(DataSolutionsSDKException):
70
+ """Exception for the API returning an unexpected response."""
71
+
72
+ def __init__(self, message="Unexpected response from the API"):
73
+ super().__init__(
74
+ message,
75
+ status_code=0,
76
+ )
77
+
78
+
79
+ class UnhandledException(DataSolutionsSDKException):
80
+ """Unhandled exception."""
81
+
82
+ def __init__(
83
+ self,
84
+ message="An unhandled exception occured. Please contact the Data Solutions Team.",
85
+ details="",
86
+ ):
87
+ super().__init__((message, details))
@@ -0,0 +1,87 @@
1
+ from requests import JSONDecodeError, request
2
+ from tenacity import retry, retry_if_exception, stop_after_attempt, wait_exponential
3
+
4
+ from chainalysis.util_functions.exceptions import (
5
+ BadRequest,
6
+ DataSolutionsAPIException,
7
+ )
8
+
9
+
10
+ def get_headers(api_key: str) -> dict:
11
+ """
12
+ Generate headers for an HTTP request.
13
+
14
+ This function creates a dictionary of headers required for an HTTP request,
15
+ including the API key for authorization and JSON content-type headers which
16
+ indicate what sort of information will be sent with the request.
17
+
18
+ :param api_key: The API key used for authenticating the request.
19
+ :return: A dictionary containing the headers for the HTTP request.
20
+ """
21
+
22
+ return {
23
+ "X-API-KEY": api_key,
24
+ "Content-Type": "application/json",
25
+ "Accept": "application/json",
26
+ }
27
+
28
+
29
+ def retry_if_not_bad_request(exception):
30
+ if isinstance(exception, BadRequest):
31
+ return False # Do not retry if it's a BadRequestException
32
+ return True # Retry for other exceptions
33
+
34
+
35
+ @retry(
36
+ wait=wait_exponential(multiplier=1, min=4, max=10),
37
+ retry=retry_if_exception(retry_if_not_bad_request),
38
+ stop=stop_after_attempt(10),
39
+ )
40
+ def issue_request(
41
+ url: str,
42
+ api_key: str,
43
+ params: dict = {},
44
+ body: dict = {},
45
+ method: str = "GET",
46
+ ) -> dict:
47
+ """
48
+ Helper method to issue a request to the Data Solutions API.
49
+ This method will automatically retry the request, and handle
50
+ basic error checking.
51
+ """
52
+
53
+ # Encode params and body into a single variable data
54
+ data = {**(params or {}), **(body or {})}
55
+
56
+ response = request(
57
+ method,
58
+ url,
59
+ headers=get_headers(api_key),
60
+ json=data,
61
+ )
62
+
63
+ if response.status_code >= 300:
64
+ try: # Try to decode the json response of the error
65
+ json_output = response.json()
66
+ error_message = json_output.get("message")
67
+
68
+ except JSONDecodeError:
69
+ raise DataSolutionsAPIException(
70
+ message="Unexpected response from the API - response was not JSON",
71
+ )
72
+ if response.status_code == 400:
73
+ raise BadRequest(
74
+ message=error_message,
75
+ )
76
+ raise DataSolutionsAPIException(
77
+ message=error_message,
78
+ )
79
+
80
+ try: # Try to decode the json response of the successful request
81
+ response_json = response.json()
82
+ except JSONDecodeError:
83
+ raise DataSolutionsAPIException(
84
+ message="Unexpected response from the API - response was not JSON",
85
+ )
86
+
87
+ return response_json
@@ -0,0 +1 @@
1
+ from chainalysis.utils import *
@@ -0,0 +1,79 @@
1
+ import datetime
2
+ import decimal
3
+
4
+ from chainalysis.util_functions.check_list_type import get_list_type
5
+ from chainalysis.util_functions.exceptions import BadRequest
6
+
7
+
8
+ class Stringify:
9
+ """
10
+ This class contains functions to transform python objects
11
+ into formatted string params so they can be used to query
12
+ Data Solutions' databases without needing to be transformed
13
+ manually by the user.
14
+ """
15
+
16
+ def lists(self, _list: list) -> str:
17
+ """
18
+ Convert a list to a formatted string param.
19
+
20
+ :param list: The list to be converted.
21
+ :type list: list
22
+
23
+ :raises Exception: Raises an exception if a list type is not submitted.
24
+ :raises Exception: If lists do not contain the right types.
25
+ :return: The converted list.
26
+ :rtype: str
27
+ """
28
+
29
+ if not isinstance(_list, list):
30
+ raise BadRequest(
31
+ "Incorrect type. Supply a list.",
32
+ )
33
+
34
+ type = get_list_type(_list)
35
+
36
+ if type == int or type == bool or type == float or type == decimal:
37
+ return f"({', '.join(map(str, _list))})"
38
+
39
+ if type == str:
40
+ result = "("
41
+
42
+ for i, element in enumerate(_list):
43
+ if i > 0:
44
+ result += ", "
45
+ result += f"'{element}'"
46
+
47
+ result += ")"
48
+ return result
49
+
50
+ def columns(self, columns: list) -> str:
51
+ """
52
+ Convert a column select list to a formatted string param.
53
+
54
+ :param list: The column select list object to be converted.
55
+ :type list: list
56
+
57
+ :return: The converted column select list.
58
+ :rtype: str
59
+ """
60
+ type = get_list_type(columns)
61
+ if type != str:
62
+ raise BadRequest("Columns must be a list")
63
+ return ", ".join(columns)
64
+
65
+ def datetimes(self, _datetime: datetime.datetime) -> str:
66
+ """
67
+ Convert a datetime object to a formatted string param.
68
+
69
+ :param list: The datetime object to be converted.
70
+ :type list: list
71
+
72
+ :return: The converted datetime object.
73
+ :rtype: str
74
+ """
75
+
76
+ if not isinstance(_datetime, datetime.datetime):
77
+ raise BadRequest("Incorrect type. Supply a datetime.datetime object.")
78
+
79
+ return f"'{_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')}'"
@@ -0,0 +1,11 @@
1
+ from chainalysis.utils.stringify import Stringify
2
+
3
+
4
+ class Utils:
5
+ """
6
+ This class contains util/helper functions for users
7
+ of the SDK.
8
+ """
9
+
10
+ def __init__(self):
11
+ self.stringify = Stringify()
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.3
2
+ Name: chainalysis
3
+ Version: 0.0.1
4
+ Summary: SDK functions for users to query Data Solutions databases.
5
+ Project-URL: Homepage, https://github.com/TransposeData/chainalysis-data-solutions-python-sdk
6
+ Author-email: "Rah Tarar (musa-tarar)" <musa.nishat@chainalysis.com>
7
+ License-File: LICENCE
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+
14
+ ![Transpose Banner](https://files.readme.io/c019281-Main_Docs_Banners_v1.png)
15
+
16
+ # Welcome to the Transpose Python SDK
17
+ A modern python wrapper for the Transpose API Suite.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install DataSolutions
23
+ ```
@@ -0,0 +1,17 @@
1
+ chainalysis/DataSolutions.py,sha256=qeqKENCyCoJrerfcit7j6yELuY-ePk3gHzT-X6ckDR4,545
2
+ chainalysis/__init__.py,sha256=NnWPLQkHHK52JYDzj5MISZNd0LXVJm0vvop1v-oZGSY,64
3
+ chainalysis/constants.py,sha256=LDSDcwxDaKx0bJC-nXL_3piwMdTynMz2mVOZdVeHgDU,266
4
+ chainalysis/sql/__init__.py,sha256=27BfFf5rFz3kg4ColUnLTOpy0VMEpALJhcZcHh8zu6g,30
5
+ chainalysis/sql/analytical.py,sha256=H8RNXQkl1JMyGsVrDBfp8y9BADWC1XDUECIZL20igmg,7045
6
+ chainalysis/sql/sql.py,sha256=6wpc1_rKqSpF-ULDyVzJ_8h28hb3ncIXps4Pd86ryPQ,255
7
+ chainalysis/sql/transactional.py,sha256=fh-3rgoJAMcSHYj6c2wTvAk5SOgBbaBpHVCxmG_dYYI,4472
8
+ chainalysis/util_functions/check_list_type.py,sha256=_lZmdnEkG0uFQYqPUkZFP8cj1qVXEKaBN-MtzaRw06o,603
9
+ chainalysis/util_functions/exceptions.py,sha256=FQSrVGJu1amQAj1SFYb1p5ZUesRNGUli6JyxdT5VX4U,2120
10
+ chainalysis/util_functions/requests.py,sha256=qDpxtkYbPI0vx5jJBtmik1i7O7J76k1qVe7ExgyoxIc,2589
11
+ chainalysis/utils/__init__.py,sha256=OI-IE3BHkwsChB89cE0wB1TQRL_m4_43Fl09CIdPyaI,32
12
+ chainalysis/utils/stringify.py,sha256=srG1QPj4p3tXvt-yWK7fbOigT0zMrPlFmIhQNFSxgXA,2325
13
+ chainalysis/utils/utils.py,sha256=iVWQ8C-9xOmb83uz8xL9cOHHdigY75qf2ej2nkqKHwE,215
14
+ chainalysis-0.0.1.dist-info/METADATA,sha256=yPKOb1cMRUedkGIT3QHbJ6JUiXGHldRpMG6iPYAW0gc,744
15
+ chainalysis-0.0.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
16
+ chainalysis-0.0.1.dist-info/licenses/LICENCE,sha256=7EI8xVBu6h_7_JlVw-yPhhOZlpY9hP8wal7kHtqKT_E,1074
17
+ chainalysis-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.