midbound-cloud 0.0.0.dev0__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.
- midbound_cloud-0.0.0.dev0/.gitignore +28 -0
- midbound_cloud-0.0.0.dev0/PKG-INFO +71 -0
- midbound_cloud-0.0.0.dev0/README.md +46 -0
- midbound_cloud-0.0.0.dev0/pyproject.toml +51 -0
- midbound_cloud-0.0.0.dev0/src/midbound_cloud/__init__.py +11 -0
- midbound_cloud-0.0.0.dev0/src/midbound_cloud/client.py +91 -0
- midbound_cloud-0.0.0.dev0/src/midbound_cloud/webhooks.py +125 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Fetched from live API
|
|
2
|
+
openapi.json
|
|
3
|
+
|
|
4
|
+
# Generated code
|
|
5
|
+
typescript/src/generated/
|
|
6
|
+
python/src/midbound_cloud/generated/
|
|
7
|
+
go/generated/
|
|
8
|
+
java/src/main/java/
|
|
9
|
+
php/src/
|
|
10
|
+
rust/src/
|
|
11
|
+
|
|
12
|
+
# Build artifacts
|
|
13
|
+
typescript/dist/
|
|
14
|
+
python/dist/
|
|
15
|
+
java/target/
|
|
16
|
+
rust/target/
|
|
17
|
+
|
|
18
|
+
# Dependencies
|
|
19
|
+
node_modules/
|
|
20
|
+
__pycache__/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
|
|
25
|
+
# IDE
|
|
26
|
+
.idea/
|
|
27
|
+
*.iml
|
|
28
|
+
.vscode/
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: midbound-cloud
|
|
3
|
+
Version: 0.0.0.dev0
|
|
4
|
+
Summary: Official Midbound Cloud SDK for Python
|
|
5
|
+
Project-URL: Homepage, https://midbound.cloud
|
|
6
|
+
Project-URL: Documentation, https://midbound.cloud/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/midbound/midbound-monorepo
|
|
8
|
+
Project-URL: Issues, https://github.com/midbound/midbound-monorepo/issues
|
|
9
|
+
Author-email: Midbound <support@midbound.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: api,b2b,enrichment,midbound,sdk,visitor-identification
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# midbound-cloud
|
|
27
|
+
|
|
28
|
+
Official Midbound Cloud SDK for Python.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install midbound-cloud
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from midbound_cloud import Midbound
|
|
40
|
+
|
|
41
|
+
client = Midbound(api_key="your-api-key")
|
|
42
|
+
|
|
43
|
+
# Enrich a person by LinkedIn URL
|
|
44
|
+
result = client.enrich.person(linkedin_url="https://linkedin.com/in/example")
|
|
45
|
+
print(result)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Webhook Verification
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from midbound_cloud import webhooks
|
|
52
|
+
|
|
53
|
+
# Verify a webhook signature
|
|
54
|
+
is_valid = webhooks.verify_signature(
|
|
55
|
+
payload=request.body,
|
|
56
|
+
signature=request.headers["x-midbound-signature"],
|
|
57
|
+
secret=os.environ["WEBHOOK_SECRET"]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Or construct a typed event
|
|
61
|
+
event = webhooks.construct_event(
|
|
62
|
+
payload=request.body,
|
|
63
|
+
signature=request.headers["x-midbound-signature"],
|
|
64
|
+
secret=os.environ["WEBHOOK_SECRET"]
|
|
65
|
+
)
|
|
66
|
+
print(event["type"]) # e.g., "identity.resolved"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Documentation
|
|
70
|
+
|
|
71
|
+
Full documentation available at [midbound.cloud/docs](https://midbound.cloud/docs).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# midbound-cloud
|
|
2
|
+
|
|
3
|
+
Official Midbound Cloud SDK for Python.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install midbound-cloud
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from midbound_cloud import Midbound
|
|
15
|
+
|
|
16
|
+
client = Midbound(api_key="your-api-key")
|
|
17
|
+
|
|
18
|
+
# Enrich a person by LinkedIn URL
|
|
19
|
+
result = client.enrich.person(linkedin_url="https://linkedin.com/in/example")
|
|
20
|
+
print(result)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Webhook Verification
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from midbound_cloud import webhooks
|
|
27
|
+
|
|
28
|
+
# Verify a webhook signature
|
|
29
|
+
is_valid = webhooks.verify_signature(
|
|
30
|
+
payload=request.body,
|
|
31
|
+
signature=request.headers["x-midbound-signature"],
|
|
32
|
+
secret=os.environ["WEBHOOK_SECRET"]
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Or construct a typed event
|
|
36
|
+
event = webhooks.construct_event(
|
|
37
|
+
payload=request.body,
|
|
38
|
+
signature=request.headers["x-midbound-signature"],
|
|
39
|
+
secret=os.environ["WEBHOOK_SECRET"]
|
|
40
|
+
)
|
|
41
|
+
print(event["type"]) # e.g., "identity.resolved"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Documentation
|
|
45
|
+
|
|
46
|
+
Full documentation available at [midbound.cloud/docs](https://midbound.cloud/docs).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "midbound-cloud"
|
|
7
|
+
version = "0.0.0.dev0"
|
|
8
|
+
description = "Official Midbound Cloud SDK for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Midbound", email = "support@midbound.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"midbound",
|
|
17
|
+
"api",
|
|
18
|
+
"sdk",
|
|
19
|
+
"enrichment",
|
|
20
|
+
"b2b",
|
|
21
|
+
"visitor-identification"
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Typing :: Typed"
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"httpx>=0.25.0",
|
|
36
|
+
"pydantic>=2.0.0"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://midbound.cloud"
|
|
41
|
+
Documentation = "https://midbound.cloud/docs"
|
|
42
|
+
Repository = "https://github.com/midbound/midbound-monorepo"
|
|
43
|
+
Issues = "https://github.com/midbound/midbound-monorepo/issues"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/midbound_cloud"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = [
|
|
50
|
+
"/src"
|
|
51
|
+
]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Midbound Cloud SDK client.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EnrichAPI:
|
|
10
|
+
"""Enrichment API for person and company data."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, client: httpx.Client):
|
|
13
|
+
self._client = client
|
|
14
|
+
|
|
15
|
+
def person(
|
|
16
|
+
self,
|
|
17
|
+
linkedin_url: Optional[str] = None,
|
|
18
|
+
email: Optional[str] = None,
|
|
19
|
+
webhook_url: Optional[str] = None,
|
|
20
|
+
webhook_secret: Optional[str] = None,
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""
|
|
23
|
+
Enrich a person by LinkedIn URL or email.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
linkedin_url: LinkedIn profile URL
|
|
27
|
+
email: Email address
|
|
28
|
+
webhook_url: Optional webhook URL for async results
|
|
29
|
+
webhook_secret: Optional webhook signing secret
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Enrichment result dictionary
|
|
33
|
+
"""
|
|
34
|
+
payload = {}
|
|
35
|
+
if linkedin_url:
|
|
36
|
+
payload["linkedinUrl"] = linkedin_url
|
|
37
|
+
if email:
|
|
38
|
+
payload["email"] = email
|
|
39
|
+
if webhook_url:
|
|
40
|
+
payload["webhookUrl"] = webhook_url
|
|
41
|
+
if webhook_secret:
|
|
42
|
+
payload["webhookSecret"] = webhook_secret
|
|
43
|
+
|
|
44
|
+
response = self._client.post("/v1/enrich/person", json=payload)
|
|
45
|
+
response.raise_for_status()
|
|
46
|
+
return response.json()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Midbound:
|
|
50
|
+
"""
|
|
51
|
+
Midbound Cloud SDK client.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> from midbound_cloud import Midbound
|
|
55
|
+
>>> client = Midbound(api_key="your-api-key")
|
|
56
|
+
>>> result = client.enrich.person(linkedin_url="https://linkedin.com/in/example")
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str,
|
|
62
|
+
base_url: str = "https://api.midbound.cloud",
|
|
63
|
+
timeout: float = 30.0,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the Midbound client.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
api_key: Your Midbound API key
|
|
70
|
+
base_url: Base URL for the API (default: production)
|
|
71
|
+
timeout: Request timeout in seconds
|
|
72
|
+
"""
|
|
73
|
+
self._client = httpx.Client(
|
|
74
|
+
base_url=base_url,
|
|
75
|
+
headers={
|
|
76
|
+
"x-api-key": api_key,
|
|
77
|
+
"Content-Type": "application/json",
|
|
78
|
+
},
|
|
79
|
+
timeout=timeout,
|
|
80
|
+
)
|
|
81
|
+
self.enrich = EnrichAPI(self._client)
|
|
82
|
+
|
|
83
|
+
def close(self) -> None:
|
|
84
|
+
"""Close the HTTP client."""
|
|
85
|
+
self._client.close()
|
|
86
|
+
|
|
87
|
+
def __enter__(self) -> "Midbound":
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def __exit__(self, *args) -> None:
|
|
91
|
+
self.close()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Webhook signature verification utilities.
|
|
3
|
+
|
|
4
|
+
Verify webhook payloads from Midbound to ensure they are authentic
|
|
5
|
+
and haven't been tampered with.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import hmac
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any, Dict, TypeVar, Type, Union
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def verify_signature(
|
|
19
|
+
payload: str,
|
|
20
|
+
signature: str,
|
|
21
|
+
secret: str,
|
|
22
|
+
max_age_seconds: int = 300,
|
|
23
|
+
) -> bool:
|
|
24
|
+
"""
|
|
25
|
+
Verify a webhook signature.
|
|
26
|
+
|
|
27
|
+
Validates that the webhook payload was signed by Midbound using your
|
|
28
|
+
webhook signing secret. Also checks that the signature hasn't expired.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
payload: The raw JSON payload string from the request body
|
|
32
|
+
signature: The signature header value (x-midbound-signature)
|
|
33
|
+
secret: Your webhook signing secret
|
|
34
|
+
max_age_seconds: Maximum age of signature in seconds (default: 300 = 5 minutes)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if signature is valid and not expired
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> from midbound_cloud import webhooks
|
|
41
|
+
>>>
|
|
42
|
+
>>> is_valid = webhooks.verify_signature(
|
|
43
|
+
... payload=request.body,
|
|
44
|
+
... signature=request.headers["x-midbound-signature"],
|
|
45
|
+
... secret=os.environ["WEBHOOK_SECRET"]
|
|
46
|
+
... )
|
|
47
|
+
"""
|
|
48
|
+
match = re.match(r"t=(\d+),v1=([a-f0-9]+)", signature)
|
|
49
|
+
if not match:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
timestamp_str, received_sig = match.groups()
|
|
53
|
+
timestamp = int(timestamp_str)
|
|
54
|
+
|
|
55
|
+
# Check expiry
|
|
56
|
+
if time.time() - timestamp > max_age_seconds:
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
# Verify signature
|
|
60
|
+
signed_payload = f"{timestamp}.{payload}"
|
|
61
|
+
expected_sig = hmac.new(
|
|
62
|
+
secret.encode(),
|
|
63
|
+
signed_payload.encode(),
|
|
64
|
+
hashlib.sha256,
|
|
65
|
+
).hexdigest()
|
|
66
|
+
|
|
67
|
+
# Use timing-safe comparison to prevent timing attacks
|
|
68
|
+
return hmac.compare_digest(expected_sig, received_sig)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def construct_event(
|
|
72
|
+
payload: str,
|
|
73
|
+
signature: str,
|
|
74
|
+
secret: str,
|
|
75
|
+
event_class: Type[T] = dict, # type: ignore
|
|
76
|
+
) -> Union[T, Dict[str, Any]]:
|
|
77
|
+
"""
|
|
78
|
+
Verify signature and construct a webhook event.
|
|
79
|
+
|
|
80
|
+
Combines signature verification with JSON parsing. Raises ValueError
|
|
81
|
+
if the signature is invalid.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
payload: The raw JSON payload string from the request body
|
|
85
|
+
signature: The signature header value (x-midbound-signature)
|
|
86
|
+
secret: Your webhook signing secret
|
|
87
|
+
event_class: Optional class to instantiate with event data
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
The parsed webhook event (dict or event_class instance)
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If signature is invalid
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
>>> from midbound_cloud import webhooks
|
|
97
|
+
>>>
|
|
98
|
+
>>> try:
|
|
99
|
+
... event = webhooks.construct_event(
|
|
100
|
+
... payload=request.body,
|
|
101
|
+
... signature=request.headers["x-midbound-signature"],
|
|
102
|
+
... secret=os.environ["WEBHOOK_SECRET"]
|
|
103
|
+
... )
|
|
104
|
+
... print(event["type"]) # e.g., "identity.resolved"
|
|
105
|
+
... except ValueError:
|
|
106
|
+
... print("Invalid webhook signature")
|
|
107
|
+
"""
|
|
108
|
+
if not verify_signature(payload, signature, secret):
|
|
109
|
+
raise ValueError("Invalid webhook signature")
|
|
110
|
+
|
|
111
|
+
data = json.loads(payload)
|
|
112
|
+
|
|
113
|
+
if event_class is dict:
|
|
114
|
+
return data
|
|
115
|
+
return event_class(**data)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Event type constants
|
|
119
|
+
EVENT_TYPES = (
|
|
120
|
+
"identity.resolved",
|
|
121
|
+
"identity.qualified",
|
|
122
|
+
"identity.enriched",
|
|
123
|
+
"identity.validated",
|
|
124
|
+
"identity.session.finalized",
|
|
125
|
+
)
|