datafiniti-sdk 0.1.0__tar.gz
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.
- datafiniti_sdk-0.1.0/PKG-INFO +153 -0
- datafiniti_sdk-0.1.0/README.md +135 -0
- datafiniti_sdk-0.1.0/datafiniti/__init__.py +13 -0
- datafiniti_sdk-0.1.0/datafiniti/_base.py +109 -0
- datafiniti_sdk-0.1.0/datafiniti/business/__init__.py +4 -0
- datafiniti_sdk-0.1.0/datafiniti/business/queries.py +50 -0
- datafiniti_sdk-0.1.0/datafiniti/business/sdk.py +17 -0
- datafiniti_sdk-0.1.0/datafiniti/errors.py +7 -0
- datafiniti_sdk-0.1.0/datafiniti/people/__init__.py +4 -0
- datafiniti_sdk-0.1.0/datafiniti/people/queries.py +38 -0
- datafiniti_sdk-0.1.0/datafiniti/people/sdk.py +17 -0
- datafiniti_sdk-0.1.0/datafiniti/product/__init__.py +4 -0
- datafiniti_sdk-0.1.0/datafiniti/product/queries.py +39 -0
- datafiniti_sdk-0.1.0/datafiniti/product/sdk.py +15 -0
- datafiniti_sdk-0.1.0/datafiniti/property/__init__.py +4 -0
- datafiniti_sdk-0.1.0/datafiniti/property/queries.py +64 -0
- datafiniti_sdk-0.1.0/datafiniti/property/sdk.py +29 -0
- datafiniti_sdk-0.1.0/datafiniti_sdk.egg-info/PKG-INFO +153 -0
- datafiniti_sdk-0.1.0/datafiniti_sdk.egg-info/SOURCES.txt +22 -0
- datafiniti_sdk-0.1.0/datafiniti_sdk.egg-info/dependency_links.txt +1 -0
- datafiniti_sdk-0.1.0/datafiniti_sdk.egg-info/requires.txt +4 -0
- datafiniti_sdk-0.1.0/datafiniti_sdk.egg-info/top_level.txt +1 -0
- datafiniti_sdk-0.1.0/pyproject.toml +32 -0
- datafiniti_sdk-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datafiniti-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Datafiniti Data APIs (Business, People, Property, Product)
|
|
5
|
+
Author-email: Leonard Trahan <leonard@datafiniti.co>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: datafiniti,api,sdk,business,property,people,product
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Datafiniti SDK
|
|
20
|
+
|
|
21
|
+
Python SDK for the [Datafiniti](https://datafiniti.co) Data APIs — Business, People, Property, and Product data.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install datafiniti-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Authentication
|
|
30
|
+
|
|
31
|
+
All SDK classes require a Datafiniti API key. You can pass it directly or set it as an environment variable:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export DATAFINITI_API_KEY="your_api_key_here"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from datafiniti import DatafinitiBusinessSDK, DatafinitiPeopleSDK, PropertyDataSDK, DatafinitiProductSDK
|
|
41
|
+
|
|
42
|
+
# Initialize from environment variable
|
|
43
|
+
sdk = PropertyDataSDK.from_env()
|
|
44
|
+
|
|
45
|
+
# Or pass the key directly
|
|
46
|
+
sdk = PropertyDataSDK(api_key="your_api_key_here")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Business Data
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from datafiniti import DatafinitiBusinessSDK
|
|
53
|
+
|
|
54
|
+
sdk = DatafinitiBusinessSDK.from_env()
|
|
55
|
+
|
|
56
|
+
# Search by name
|
|
57
|
+
results = sdk.search_by_name("Starbucks", city="Seattle", num_records=5)
|
|
58
|
+
|
|
59
|
+
# Count matching records
|
|
60
|
+
count = sdk.count('name:"Starbucks" AND country:US')
|
|
61
|
+
|
|
62
|
+
# Build a custom query
|
|
63
|
+
query = sdk.query().country("US").build()
|
|
64
|
+
results = sdk.search(query, num_records=10)
|
|
65
|
+
|
|
66
|
+
# Paginate through large result sets
|
|
67
|
+
for record in sdk.paginate('name:"Starbucks"', page_size=100, max_records=500):
|
|
68
|
+
print(record)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## People Data
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from datafiniti import DatafinitiPeopleSDK
|
|
75
|
+
|
|
76
|
+
sdk = DatafinitiPeopleSDK.from_env()
|
|
77
|
+
|
|
78
|
+
# Search by name
|
|
79
|
+
results = sdk.search_by_name("John Smith", city="Austin", num_records=10)
|
|
80
|
+
|
|
81
|
+
# Count matching records
|
|
82
|
+
count = sdk.count('name:"John Smith" AND country:US')
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Property Data
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datafiniti import PropertyDataSDK
|
|
89
|
+
|
|
90
|
+
sdk = PropertyDataSDK.from_env()
|
|
91
|
+
|
|
92
|
+
# Search by address
|
|
93
|
+
results = sdk.search_by_address("123 Main St", postal_code="78701", num_records=1)
|
|
94
|
+
|
|
95
|
+
# Find properties for sale
|
|
96
|
+
results = sdk.for_sale(
|
|
97
|
+
postal_codes=["78701", "78702"],
|
|
98
|
+
property_types=["Single Family Residential"],
|
|
99
|
+
num_records=20
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Count matching records
|
|
103
|
+
count = sdk.count('country:US AND mostRecentStatus:"For Sale"')
|
|
104
|
+
|
|
105
|
+
# Paginate through results
|
|
106
|
+
for record in sdk.paginate('country:US AND mostRecentStatus:"For Sale"', page_size=100):
|
|
107
|
+
print(record["address"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Product Data
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from datafiniti import DatafinitiProductSDK
|
|
114
|
+
|
|
115
|
+
sdk = DatafinitiProductSDK.from_env()
|
|
116
|
+
|
|
117
|
+
# Search by product name
|
|
118
|
+
results = sdk.search_by_name("iPhone 15", brand="Apple", num_records=10)
|
|
119
|
+
|
|
120
|
+
# Count matching records
|
|
121
|
+
count = sdk.count('brand:"Apple"')
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Common Methods
|
|
125
|
+
|
|
126
|
+
All four SDK classes inherit these methods:
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `search(query, num_records)` | Search and return up to `num_records` results |
|
|
131
|
+
| `count(query)` | Return the total number of matching records |
|
|
132
|
+
| `paginate(query, page_size, max_records)` | Generator that yields records page by page |
|
|
133
|
+
| `query()` | Returns a query builder for the data type |
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from datafiniti import DatafinitiAPIError
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
results = sdk.search("country:US", num_records=10)
|
|
142
|
+
except DatafinitiAPIError as e:
|
|
143
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.9+
|
|
149
|
+
- `requests >= 2.31.0`
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Datafiniti SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for the [Datafiniti](https://datafiniti.co) Data APIs — Business, People, Property, and Product data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install datafiniti-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Authentication
|
|
12
|
+
|
|
13
|
+
All SDK classes require a Datafiniti API key. You can pass it directly or set it as an environment variable:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
export DATAFINITI_API_KEY="your_api_key_here"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from datafiniti import DatafinitiBusinessSDK, DatafinitiPeopleSDK, PropertyDataSDK, DatafinitiProductSDK
|
|
23
|
+
|
|
24
|
+
# Initialize from environment variable
|
|
25
|
+
sdk = PropertyDataSDK.from_env()
|
|
26
|
+
|
|
27
|
+
# Or pass the key directly
|
|
28
|
+
sdk = PropertyDataSDK(api_key="your_api_key_here")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Business Data
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from datafiniti import DatafinitiBusinessSDK
|
|
35
|
+
|
|
36
|
+
sdk = DatafinitiBusinessSDK.from_env()
|
|
37
|
+
|
|
38
|
+
# Search by name
|
|
39
|
+
results = sdk.search_by_name("Starbucks", city="Seattle", num_records=5)
|
|
40
|
+
|
|
41
|
+
# Count matching records
|
|
42
|
+
count = sdk.count('name:"Starbucks" AND country:US')
|
|
43
|
+
|
|
44
|
+
# Build a custom query
|
|
45
|
+
query = sdk.query().country("US").build()
|
|
46
|
+
results = sdk.search(query, num_records=10)
|
|
47
|
+
|
|
48
|
+
# Paginate through large result sets
|
|
49
|
+
for record in sdk.paginate('name:"Starbucks"', page_size=100, max_records=500):
|
|
50
|
+
print(record)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## People Data
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from datafiniti import DatafinitiPeopleSDK
|
|
57
|
+
|
|
58
|
+
sdk = DatafinitiPeopleSDK.from_env()
|
|
59
|
+
|
|
60
|
+
# Search by name
|
|
61
|
+
results = sdk.search_by_name("John Smith", city="Austin", num_records=10)
|
|
62
|
+
|
|
63
|
+
# Count matching records
|
|
64
|
+
count = sdk.count('name:"John Smith" AND country:US')
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Property Data
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from datafiniti import PropertyDataSDK
|
|
71
|
+
|
|
72
|
+
sdk = PropertyDataSDK.from_env()
|
|
73
|
+
|
|
74
|
+
# Search by address
|
|
75
|
+
results = sdk.search_by_address("123 Main St", postal_code="78701", num_records=1)
|
|
76
|
+
|
|
77
|
+
# Find properties for sale
|
|
78
|
+
results = sdk.for_sale(
|
|
79
|
+
postal_codes=["78701", "78702"],
|
|
80
|
+
property_types=["Single Family Residential"],
|
|
81
|
+
num_records=20
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Count matching records
|
|
85
|
+
count = sdk.count('country:US AND mostRecentStatus:"For Sale"')
|
|
86
|
+
|
|
87
|
+
# Paginate through results
|
|
88
|
+
for record in sdk.paginate('country:US AND mostRecentStatus:"For Sale"', page_size=100):
|
|
89
|
+
print(record["address"])
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Product Data
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from datafiniti import DatafinitiProductSDK
|
|
96
|
+
|
|
97
|
+
sdk = DatafinitiProductSDK.from_env()
|
|
98
|
+
|
|
99
|
+
# Search by product name
|
|
100
|
+
results = sdk.search_by_name("iPhone 15", brand="Apple", num_records=10)
|
|
101
|
+
|
|
102
|
+
# Count matching records
|
|
103
|
+
count = sdk.count('brand:"Apple"')
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Common Methods
|
|
107
|
+
|
|
108
|
+
All four SDK classes inherit these methods:
|
|
109
|
+
|
|
110
|
+
| Method | Description |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `search(query, num_records)` | Search and return up to `num_records` results |
|
|
113
|
+
| `count(query)` | Return the total number of matching records |
|
|
114
|
+
| `paginate(query, page_size, max_records)` | Generator that yields records page by page |
|
|
115
|
+
| `query()` | Returns a query builder for the data type |
|
|
116
|
+
|
|
117
|
+
## Error Handling
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from datafiniti import DatafinitiAPIError
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
results = sdk.search("country:US", num_records=10)
|
|
124
|
+
except DatafinitiAPIError as e:
|
|
125
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Requirements
|
|
129
|
+
|
|
130
|
+
- Python 3.9+
|
|
131
|
+
- `requests >= 2.31.0`
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .business.sdk import DatafinitiBusinessSDK
|
|
2
|
+
from .people.sdk import DatafinitiPeopleSDK
|
|
3
|
+
from .property.sdk import PropertyDataSDK
|
|
4
|
+
from .product.sdk import DatafinitiProductSDK
|
|
5
|
+
from .errors import DatafinitiAPIError
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"DatafinitiBusinessSDK",
|
|
9
|
+
"DatafinitiPeopleSDK",
|
|
10
|
+
"PropertyDataSDK",
|
|
11
|
+
"DatafinitiProductSDK",
|
|
12
|
+
"DatafinitiAPIError",
|
|
13
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from .errors import DatafinitiAPIError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseDatafinitiSDK:
|
|
9
|
+
BASE_URL = ""
|
|
10
|
+
|
|
11
|
+
def __init__(self, api_key=None, timeout=60, max_retries=3):
|
|
12
|
+
self.api_key = api_key or os.getenv("DATAFINITI_API_KEY")
|
|
13
|
+
self.timeout = timeout
|
|
14
|
+
self.max_retries = max_retries
|
|
15
|
+
|
|
16
|
+
if not self.api_key:
|
|
17
|
+
raise ValueError(
|
|
18
|
+
"Missing Datafiniti API key. Pass api_key or set DATAFINITI_API_KEY."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_env(cls):
|
|
23
|
+
return cls(api_key=os.getenv("DATAFINITI_API_KEY"))
|
|
24
|
+
|
|
25
|
+
def _headers(self):
|
|
26
|
+
return {
|
|
27
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def _post(self, endpoint, payload, params=None):
|
|
32
|
+
url = f"{self.BASE_URL}/{endpoint.lstrip('/')}"
|
|
33
|
+
last_error = None
|
|
34
|
+
|
|
35
|
+
for attempt in range(1, self.max_retries + 1):
|
|
36
|
+
try:
|
|
37
|
+
response = requests.post(
|
|
38
|
+
url,
|
|
39
|
+
json=payload,
|
|
40
|
+
params=params,
|
|
41
|
+
headers=self._headers(),
|
|
42
|
+
timeout=self.timeout,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if response.status_code in (408, 429, 500, 502, 503, 504):
|
|
46
|
+
last_error = response
|
|
47
|
+
time.sleep(2 ** (attempt - 1))
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
if not response.ok:
|
|
51
|
+
raise DatafinitiAPIError(
|
|
52
|
+
message=f"Datafiniti API error: {response.status_code}",
|
|
53
|
+
status_code=response.status_code,
|
|
54
|
+
response_body=response.text,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return response.json()
|
|
58
|
+
|
|
59
|
+
except requests.RequestException as exc:
|
|
60
|
+
last_error = exc
|
|
61
|
+
time.sleep(2 ** (attempt - 1))
|
|
62
|
+
|
|
63
|
+
raise DatafinitiAPIError(
|
|
64
|
+
message="Datafiniti API request failed after retries.",
|
|
65
|
+
response_body=str(last_error),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def search(self, query, num_records=10, format="JSON", view=None, download=False):
|
|
69
|
+
payload = {
|
|
70
|
+
"query": query,
|
|
71
|
+
"num_records": num_records,
|
|
72
|
+
"format": format,
|
|
73
|
+
}
|
|
74
|
+
if view:
|
|
75
|
+
payload["view"] = view
|
|
76
|
+
if download:
|
|
77
|
+
payload["download"] = True
|
|
78
|
+
return self._post("search", payload)
|
|
79
|
+
|
|
80
|
+
def count(self, query):
|
|
81
|
+
response = self.search(query=query, num_records=0)
|
|
82
|
+
return response.get("num_found", 0)
|
|
83
|
+
|
|
84
|
+
def paginate(self, query, page_size=100, max_records=None, format="JSON", view=None):
|
|
85
|
+
page = 1
|
|
86
|
+
total_yielded = 0
|
|
87
|
+
|
|
88
|
+
while True:
|
|
89
|
+
payload = {"query": query, "format": format}
|
|
90
|
+
if view:
|
|
91
|
+
payload["view"] = view
|
|
92
|
+
|
|
93
|
+
response = self._post(
|
|
94
|
+
endpoint="paginate",
|
|
95
|
+
payload=payload,
|
|
96
|
+
params={"page": page, "limit": page_size},
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
records = response.get("records", [])
|
|
100
|
+
if not records:
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
for record in records:
|
|
104
|
+
yield record
|
|
105
|
+
total_yielded += 1
|
|
106
|
+
if max_records and total_yielded >= max_records:
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
page += 1
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
class BusinessQuery:
|
|
2
|
+
def __init__(self):
|
|
3
|
+
self.parts = []
|
|
4
|
+
|
|
5
|
+
def raw(self, query):
|
|
6
|
+
self.parts.append(f"({query})")
|
|
7
|
+
return self
|
|
8
|
+
|
|
9
|
+
def country(self, country):
|
|
10
|
+
self.parts.append(f"country:{country}")
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def province(self, province):
|
|
14
|
+
self.parts.append(f'province:"{province}"')
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def city(self, city):
|
|
18
|
+
self.parts.append(f'city:"{city}"')
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def postal_code(self, postal_code):
|
|
22
|
+
self.parts.append(f'postalCode:"{postal_code}"')
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def postal_codes(self, postal_codes):
|
|
26
|
+
values = " OR ".join([f'"{code}"' for code in postal_codes])
|
|
27
|
+
self.parts.append(f"postalCode:({values})")
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def name(self, name):
|
|
31
|
+
self.parts.append(f'name:"{name}"')
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def categories(self, categories):
|
|
35
|
+
if isinstance(categories, str):
|
|
36
|
+
categories = [categories]
|
|
37
|
+
values = " OR ".join([f'"{cat}"' for cat in categories])
|
|
38
|
+
self.parts.append(f"categories:({values})")
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def updated_after(self, date_string):
|
|
42
|
+
self.parts.append(f"dateUpdated:[{date_string} TO *]")
|
|
43
|
+
return self
|
|
44
|
+
|
|
45
|
+
def has_field(self, field):
|
|
46
|
+
self.parts.append(f"{field}:*")
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
def build(self):
|
|
50
|
+
return " AND ".join(self.parts)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .._base import BaseDatafinitiSDK
|
|
2
|
+
from .queries import BusinessQuery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DatafinitiBusinessSDK(BaseDatafinitiSDK):
|
|
6
|
+
BASE_URL = "https://api.datafiniti.co/v4/businesses"
|
|
7
|
+
|
|
8
|
+
def query(self):
|
|
9
|
+
return BusinessQuery()
|
|
10
|
+
|
|
11
|
+
def search_by_name(self, name, city=None, country="US", num_records=10):
|
|
12
|
+
parts = [f'name:"{name}"']
|
|
13
|
+
if city:
|
|
14
|
+
parts.append(f'city:"{city}"')
|
|
15
|
+
if country:
|
|
16
|
+
parts.append(f"country:{country}")
|
|
17
|
+
return self.search(" AND ".join(parts), num_records=num_records)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class DatafinitiAPIError(Exception):
|
|
2
|
+
"""Raised when the Datafiniti API returns an error response."""
|
|
3
|
+
|
|
4
|
+
def __init__(self, message, status_code=None, response_body=None):
|
|
5
|
+
super().__init__(message)
|
|
6
|
+
self.status_code = status_code
|
|
7
|
+
self.response_body = response_body
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
class PeopleQuery:
|
|
2
|
+
def __init__(self):
|
|
3
|
+
self.parts = []
|
|
4
|
+
|
|
5
|
+
def raw(self, query):
|
|
6
|
+
self.parts.append(f"({query})")
|
|
7
|
+
return self
|
|
8
|
+
|
|
9
|
+
def country(self, country):
|
|
10
|
+
self.parts.append(f"country:{country}")
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def province(self, province):
|
|
14
|
+
self.parts.append(f'province:"{province}"')
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def city(self, city):
|
|
18
|
+
self.parts.append(f'city:"{city}"')
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def postal_code(self, postal_code):
|
|
22
|
+
self.parts.append(f'postalCode:"{postal_code}"')
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def name(self, name):
|
|
26
|
+
self.parts.append(f'name:"{name}"')
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def updated_after(self, date_string):
|
|
30
|
+
self.parts.append(f"dateUpdated:[{date_string} TO *]")
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def has_field(self, field):
|
|
34
|
+
self.parts.append(f"{field}:*")
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
def build(self):
|
|
38
|
+
return " AND ".join(self.parts)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .._base import BaseDatafinitiSDK
|
|
2
|
+
from .queries import PeopleQuery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DatafinitiPeopleSDK(BaseDatafinitiSDK):
|
|
6
|
+
BASE_URL = "https://api.datafiniti.co/v4/people"
|
|
7
|
+
|
|
8
|
+
def query(self):
|
|
9
|
+
return PeopleQuery()
|
|
10
|
+
|
|
11
|
+
def search_by_name(self, name, city=None, country="US", num_records=10):
|
|
12
|
+
parts = [f'name:"{name}"']
|
|
13
|
+
if city:
|
|
14
|
+
parts.append(f'city:"{city}"')
|
|
15
|
+
if country:
|
|
16
|
+
parts.append(f"country:{country}")
|
|
17
|
+
return self.search(" AND ".join(parts), num_records=num_records)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class ProductQuery:
|
|
2
|
+
def __init__(self):
|
|
3
|
+
self.parts = []
|
|
4
|
+
|
|
5
|
+
def raw(self, query):
|
|
6
|
+
self.parts.append(f"({query})")
|
|
7
|
+
return self
|
|
8
|
+
|
|
9
|
+
def name(self, name):
|
|
10
|
+
self.parts.append(f'name:"{name}"')
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def brand(self, brand):
|
|
14
|
+
self.parts.append(f'brand:"{brand}"')
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def categories(self, categories):
|
|
18
|
+
if isinstance(categories, str):
|
|
19
|
+
categories = [categories]
|
|
20
|
+
values = " OR ".join([f'"{cat}"' for cat in categories])
|
|
21
|
+
self.parts.append(f"categories:({values})")
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def price_range(self, min_price=None, max_price=None):
|
|
25
|
+
min_val = min_price if min_price is not None else "*"
|
|
26
|
+
max_val = max_price if max_price is not None else "*"
|
|
27
|
+
self.parts.append(f"prices.amountMin:[{min_val} TO {max_val}]")
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def updated_after(self, date_string):
|
|
31
|
+
self.parts.append(f"dateUpdated:[{date_string} TO *]")
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def has_field(self, field):
|
|
35
|
+
self.parts.append(f"{field}:*")
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
def build(self):
|
|
39
|
+
return " AND ".join(self.parts)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .._base import BaseDatafinitiSDK
|
|
2
|
+
from .queries import ProductQuery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DatafinitiProductSDK(BaseDatafinitiSDK):
|
|
6
|
+
BASE_URL = "https://api.datafiniti.co/v4/products"
|
|
7
|
+
|
|
8
|
+
def query(self):
|
|
9
|
+
return ProductQuery()
|
|
10
|
+
|
|
11
|
+
def search_by_name(self, name, brand=None, num_records=10):
|
|
12
|
+
parts = [f'name:"{name}"']
|
|
13
|
+
if brand:
|
|
14
|
+
parts.append(f'brand:"{brand}"')
|
|
15
|
+
return self.search(" AND ".join(parts), num_records=num_records)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
class PropertyQuery:
|
|
2
|
+
def __init__(self):
|
|
3
|
+
self.parts = []
|
|
4
|
+
|
|
5
|
+
def raw(self, query):
|
|
6
|
+
self.parts.append(f"({query})")
|
|
7
|
+
return self
|
|
8
|
+
|
|
9
|
+
def country(self, country):
|
|
10
|
+
self.parts.append(f"country:{country}")
|
|
11
|
+
return self
|
|
12
|
+
|
|
13
|
+
def province(self, province):
|
|
14
|
+
self.parts.append(f'province:"{province}"')
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def city(self, city):
|
|
18
|
+
self.parts.append(f'city:"{city}"')
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def postal_code(self, postal_code):
|
|
22
|
+
self.parts.append(f'postalCode:"{postal_code}"')
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def postal_codes(self, postal_codes):
|
|
26
|
+
values = " OR ".join([f'"{code}"' for code in postal_codes])
|
|
27
|
+
self.parts.append(f"postalCode:({values})")
|
|
28
|
+
return self
|
|
29
|
+
|
|
30
|
+
def address(self, address):
|
|
31
|
+
self.parts.append(f'address:"{address}"')
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def most_recent_status(self, statuses):
|
|
35
|
+
if isinstance(statuses, str):
|
|
36
|
+
statuses = [statuses]
|
|
37
|
+
values = " OR ".join([f'"{status}"' for status in statuses])
|
|
38
|
+
self.parts.append(f"mostRecentStatus:({values})")
|
|
39
|
+
return self
|
|
40
|
+
|
|
41
|
+
def most_recent_rental_status(self, statuses):
|
|
42
|
+
if isinstance(statuses, str):
|
|
43
|
+
statuses = [statuses]
|
|
44
|
+
values = " OR ".join([f'"{status}"' for status in statuses])
|
|
45
|
+
self.parts.append(f"mostRecentRentalStatus:({values})")
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def property_types(self, property_types):
|
|
49
|
+
if isinstance(property_types, str):
|
|
50
|
+
property_types = [property_types]
|
|
51
|
+
values = " OR ".join([f'"{ptype}"' for ptype in property_types])
|
|
52
|
+
self.parts.append(f"propertyType:({values})")
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def updated_after(self, date_string):
|
|
56
|
+
self.parts.append(f"dateUpdated:[{date_string} TO *]")
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def has_field(self, field):
|
|
60
|
+
self.parts.append(f"{field}:*")
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
def build(self):
|
|
64
|
+
return " AND ".join(self.parts)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .._base import BaseDatafinitiSDK
|
|
2
|
+
from .queries import PropertyQuery
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class PropertyDataSDK(BaseDatafinitiSDK):
|
|
6
|
+
BASE_URL = "https://api.datafiniti.co/v4/properties"
|
|
7
|
+
|
|
8
|
+
def query(self):
|
|
9
|
+
return PropertyQuery()
|
|
10
|
+
|
|
11
|
+
def search_by_address(self, address, postal_code=None, country="US", num_records=1):
|
|
12
|
+
parts = [f'address:"{address}"']
|
|
13
|
+
if postal_code:
|
|
14
|
+
parts.append(f'postalCode:"{postal_code}"')
|
|
15
|
+
if country:
|
|
16
|
+
parts.append(f"country:{country}")
|
|
17
|
+
return self.search(" AND ".join(parts), num_records=num_records)
|
|
18
|
+
|
|
19
|
+
def for_sale(self, postal_codes=None, property_types=None, num_records=10):
|
|
20
|
+
builder = (
|
|
21
|
+
self.query()
|
|
22
|
+
.country("US")
|
|
23
|
+
.most_recent_status(["For Sale", "Coming Soon", "Pending"])
|
|
24
|
+
)
|
|
25
|
+
if postal_codes:
|
|
26
|
+
builder.postal_codes(postal_codes)
|
|
27
|
+
if property_types:
|
|
28
|
+
builder.property_types(property_types)
|
|
29
|
+
return self.search(builder.build(), num_records=num_records)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datafiniti-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for Datafiniti Data APIs (Business, People, Property, Product)
|
|
5
|
+
Author-email: Leonard Trahan <leonard@datafiniti.co>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: datafiniti,api,sdk,business,property,people,product
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
18
|
+
|
|
19
|
+
# Datafiniti SDK
|
|
20
|
+
|
|
21
|
+
Python SDK for the [Datafiniti](https://datafiniti.co) Data APIs — Business, People, Property, and Product data.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install datafiniti-sdk
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Authentication
|
|
30
|
+
|
|
31
|
+
All SDK classes require a Datafiniti API key. You can pass it directly or set it as an environment variable:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export DATAFINITI_API_KEY="your_api_key_here"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from datafiniti import DatafinitiBusinessSDK, DatafinitiPeopleSDK, PropertyDataSDK, DatafinitiProductSDK
|
|
41
|
+
|
|
42
|
+
# Initialize from environment variable
|
|
43
|
+
sdk = PropertyDataSDK.from_env()
|
|
44
|
+
|
|
45
|
+
# Or pass the key directly
|
|
46
|
+
sdk = PropertyDataSDK(api_key="your_api_key_here")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Business Data
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from datafiniti import DatafinitiBusinessSDK
|
|
53
|
+
|
|
54
|
+
sdk = DatafinitiBusinessSDK.from_env()
|
|
55
|
+
|
|
56
|
+
# Search by name
|
|
57
|
+
results = sdk.search_by_name("Starbucks", city="Seattle", num_records=5)
|
|
58
|
+
|
|
59
|
+
# Count matching records
|
|
60
|
+
count = sdk.count('name:"Starbucks" AND country:US')
|
|
61
|
+
|
|
62
|
+
# Build a custom query
|
|
63
|
+
query = sdk.query().country("US").build()
|
|
64
|
+
results = sdk.search(query, num_records=10)
|
|
65
|
+
|
|
66
|
+
# Paginate through large result sets
|
|
67
|
+
for record in sdk.paginate('name:"Starbucks"', page_size=100, max_records=500):
|
|
68
|
+
print(record)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## People Data
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from datafiniti import DatafinitiPeopleSDK
|
|
75
|
+
|
|
76
|
+
sdk = DatafinitiPeopleSDK.from_env()
|
|
77
|
+
|
|
78
|
+
# Search by name
|
|
79
|
+
results = sdk.search_by_name("John Smith", city="Austin", num_records=10)
|
|
80
|
+
|
|
81
|
+
# Count matching records
|
|
82
|
+
count = sdk.count('name:"John Smith" AND country:US')
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Property Data
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from datafiniti import PropertyDataSDK
|
|
89
|
+
|
|
90
|
+
sdk = PropertyDataSDK.from_env()
|
|
91
|
+
|
|
92
|
+
# Search by address
|
|
93
|
+
results = sdk.search_by_address("123 Main St", postal_code="78701", num_records=1)
|
|
94
|
+
|
|
95
|
+
# Find properties for sale
|
|
96
|
+
results = sdk.for_sale(
|
|
97
|
+
postal_codes=["78701", "78702"],
|
|
98
|
+
property_types=["Single Family Residential"],
|
|
99
|
+
num_records=20
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Count matching records
|
|
103
|
+
count = sdk.count('country:US AND mostRecentStatus:"For Sale"')
|
|
104
|
+
|
|
105
|
+
# Paginate through results
|
|
106
|
+
for record in sdk.paginate('country:US AND mostRecentStatus:"For Sale"', page_size=100):
|
|
107
|
+
print(record["address"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Product Data
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from datafiniti import DatafinitiProductSDK
|
|
114
|
+
|
|
115
|
+
sdk = DatafinitiProductSDK.from_env()
|
|
116
|
+
|
|
117
|
+
# Search by product name
|
|
118
|
+
results = sdk.search_by_name("iPhone 15", brand="Apple", num_records=10)
|
|
119
|
+
|
|
120
|
+
# Count matching records
|
|
121
|
+
count = sdk.count('brand:"Apple"')
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Common Methods
|
|
125
|
+
|
|
126
|
+
All four SDK classes inherit these methods:
|
|
127
|
+
|
|
128
|
+
| Method | Description |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `search(query, num_records)` | Search and return up to `num_records` results |
|
|
131
|
+
| `count(query)` | Return the total number of matching records |
|
|
132
|
+
| `paginate(query, page_size, max_records)` | Generator that yields records page by page |
|
|
133
|
+
| `query()` | Returns a query builder for the data type |
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from datafiniti import DatafinitiAPIError
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
results = sdk.search("country:US", num_records=10)
|
|
142
|
+
except DatafinitiAPIError as e:
|
|
143
|
+
print(f"API error {e.status_code}: {e.message}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Requirements
|
|
147
|
+
|
|
148
|
+
- Python 3.9+
|
|
149
|
+
- `requests >= 2.31.0`
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
datafiniti/__init__.py
|
|
4
|
+
datafiniti/_base.py
|
|
5
|
+
datafiniti/errors.py
|
|
6
|
+
datafiniti/business/__init__.py
|
|
7
|
+
datafiniti/business/queries.py
|
|
8
|
+
datafiniti/business/sdk.py
|
|
9
|
+
datafiniti/people/__init__.py
|
|
10
|
+
datafiniti/people/queries.py
|
|
11
|
+
datafiniti/people/sdk.py
|
|
12
|
+
datafiniti/product/__init__.py
|
|
13
|
+
datafiniti/product/queries.py
|
|
14
|
+
datafiniti/product/sdk.py
|
|
15
|
+
datafiniti/property/__init__.py
|
|
16
|
+
datafiniti/property/queries.py
|
|
17
|
+
datafiniti/property/sdk.py
|
|
18
|
+
datafiniti_sdk.egg-info/PKG-INFO
|
|
19
|
+
datafiniti_sdk.egg-info/SOURCES.txt
|
|
20
|
+
datafiniti_sdk.egg-info/dependency_links.txt
|
|
21
|
+
datafiniti_sdk.egg-info/requires.txt
|
|
22
|
+
datafiniti_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
datafiniti
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "datafiniti-sdk"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python SDK for Datafiniti Data APIs (Business, People, Property, Product)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
authors = [{ name = "Leonard Trahan", email = "leonard@datafiniti.co" }]
|
|
8
|
+
keywords = ["datafiniti", "api", "sdk", "business", "property", "people", "product"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.9"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"requests>=2.31.0"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=8.0.0"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["setuptools>=61"]
|
|
28
|
+
build-backend = "setuptools.build_meta"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["."]
|
|
32
|
+
include = ["datafiniti*"]
|