coconnect 1.0.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.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: coconnect
3
+ Version: 1.0.0
4
+ Summary: Send your platform's events to CoConnect Hub, in your own words. Server side.
5
+ License: MIT
6
+ Keywords: coconnect,quests,rewards,partner,events
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+
10
+ # coconnect
11
+
12
+ Send what users do on your platform to CoConnect Hub, named the way your platform already names it. CoConnect matches each event to the right task and rewards the member.
13
+
14
+ ```bash
15
+ pip install coconnect
16
+ ```
17
+
18
+ ```python
19
+ import os
20
+ from coconnect import CoConnect
21
+
22
+ hub = CoConnect(token=os.environ["COCONNECT_TOKEN"])
23
+
24
+ # When a user signs up (cc_id is the cookie set by https://hub.coconnect.co/sdk.js on your site):
25
+ hub.send("user_registered", user="u_1001", cc_id=request.cookies.get("cc_id"))
26
+
27
+ # Anything else, in your own words, with whatever details you have:
28
+ hub.send("ftd", user="u_1001", amount=75, currency="USD", id="dep_10482")
29
+ hub.send("kyc_failed", user="u_1001", status="failed", reason="Document unreadable")
30
+ ```
31
+
32
+ - **`user`** is required: your own ID for the user.
33
+ - **`cc_id`** links the user to their CoConnect account. Send it at least once per user.
34
+ - **`id`** is your reference for the event. If you leave it out, the SDK generates one, so a retried request is never counted twice.
35
+ - **Any other keyword arguments** are kept as details of the event. Emails and phone numbers are hashed by CoConnect on arrival.
36
+ - **Batching:** `send_batch([...])` sends up to 50 events at once.
37
+
38
+ The SDK also handles:
39
+ - **Retries:** network errors, 429 and 5xx responses are retried with backoff.
40
+ - **Errors:** a bad token or a malformed event raises `CoConnectError`.
41
+ - **No dependencies** beyond the standard library.
42
+
43
+ Keep your token on the server.
@@ -0,0 +1,34 @@
1
+ # coconnect
2
+
3
+ Send what users do on your platform to CoConnect Hub, named the way your platform already names it. CoConnect matches each event to the right task and rewards the member.
4
+
5
+ ```bash
6
+ pip install coconnect
7
+ ```
8
+
9
+ ```python
10
+ import os
11
+ from coconnect import CoConnect
12
+
13
+ hub = CoConnect(token=os.environ["COCONNECT_TOKEN"])
14
+
15
+ # When a user signs up (cc_id is the cookie set by https://hub.coconnect.co/sdk.js on your site):
16
+ hub.send("user_registered", user="u_1001", cc_id=request.cookies.get("cc_id"))
17
+
18
+ # Anything else, in your own words, with whatever details you have:
19
+ hub.send("ftd", user="u_1001", amount=75, currency="USD", id="dep_10482")
20
+ hub.send("kyc_failed", user="u_1001", status="failed", reason="Document unreadable")
21
+ ```
22
+
23
+ - **`user`** is required: your own ID for the user.
24
+ - **`cc_id`** links the user to their CoConnect account. Send it at least once per user.
25
+ - **`id`** is your reference for the event. If you leave it out, the SDK generates one, so a retried request is never counted twice.
26
+ - **Any other keyword arguments** are kept as details of the event. Emails and phone numbers are hashed by CoConnect on arrival.
27
+ - **Batching:** `send_batch([...])` sends up to 50 events at once.
28
+
29
+ The SDK also handles:
30
+ - **Retries:** network errors, 429 and 5xx responses are retried with backoff.
31
+ - **Errors:** a bad token or a malformed event raises `CoConnectError`.
32
+ - **No dependencies** beyond the standard library.
33
+
34
+ Keep your token on the server.
@@ -0,0 +1,146 @@
1
+ """CoConnect Hub SDK for Python (3.8+), server side only.
2
+
3
+ Partners send what happened on their platform, in their own words::
4
+
5
+ from coconnect import CoConnect
6
+ hub = CoConnect(token=os.environ["COCONNECT_TOKEN"])
7
+ hub.send("ftd", user="u_1001", cc_id=cc_id, amount=75, currency="USD")
8
+
9
+ CoConnect matches each event to the right task. The SDK adds a unique id to
10
+ every event (so a retry is never counted twice), retries when the hub is
11
+ briefly unreachable, and skips events no task needs once the hub says so.
12
+ No dependencies beyond the standard library.
13
+ """
14
+
15
+ import json
16
+ import random
17
+ import re
18
+ import time
19
+ import urllib.error
20
+ import urllib.request
21
+ import uuid
22
+
23
+ __all__ = ["CoConnect", "CoConnectError"]
24
+ __version__ = "1.0.0"
25
+
26
+ _KNOWN = {"user", "cc_id", "status", "id", "amount", "currency", "reason", "wallet", "email", "x", "discord", "properties"}
27
+ _RETRY = {408, 425, 429, 500, 502, 503, 504}
28
+
29
+
30
+ class CoConnectError(Exception):
31
+ def __init__(self, message, status=None, body=None):
32
+ super().__init__(message)
33
+ self.status = status
34
+ self.body = body
35
+
36
+
37
+ def _event_key(name):
38
+ s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", str(name or "")).lower()
39
+ return re.sub(r"[^a-z0-9]+", "_", s).strip("_")
40
+
41
+
42
+ class CoConnect:
43
+ def __init__(self, token, base_url="https://hub.coconnect.co", max_retries=4, timeout=10, filter=True):
44
+ """token: your partner token from CoConnect (keep it on the server)."""
45
+ if not token or not isinstance(token, str):
46
+ raise CoConnectError("A token is required.")
47
+ self.token = token
48
+ self.base_url = base_url.rstrip("/")
49
+ self.max_retries = max_retries
50
+ self.timeout = timeout
51
+ self.filter = filter
52
+ self._config = None
53
+ self._config_at = 0.0
54
+
55
+ def send(self, event, user, **details):
56
+ """Send one event, named the way your platform names it (e.g. "ftd").
57
+
58
+ user: your own ID for the user (required). Optional: cc_id, status
59
+ ("completed" | "failed" | "reversed"), id (your reference), amount,
60
+ currency, reason, wallet, email, x, discord, and any other details.
61
+ """
62
+ out = self.send_batch([dict(details, event=event, user=user)])
63
+ results = out.get("results") or []
64
+ return results[0] if results else out
65
+
66
+ def send_batch(self, events):
67
+ """Up to 50 events in one request, each a dict with event, user and details."""
68
+ if not events:
69
+ raise CoConnectError("No events.")
70
+ if len(events) > 50:
71
+ raise CoConnectError("At most 50 events per batch.")
72
+ wanted = [self._shape(e) for e in events if self._wanted(e.get("event"))]
73
+ if not wanted:
74
+ return {"ok": True, "results": [], "skipped": len(events)}
75
+ return self._request("POST", "/api/partner/events", {"events": wanted})
76
+
77
+ def _shape(self, e):
78
+ event = e.get("event")
79
+ if not event:
80
+ raise CoConnectError("Each event needs a name.")
81
+ if e.get("user") in (None, ""):
82
+ raise CoConnectError('"%s" needs user: your own ID for the user.' % event)
83
+ properties = dict(e.get("properties") or {})
84
+ for k, v in e.items():
85
+ if k not in _KNOWN and k != "event" and v is not None:
86
+ properties[k] = v
87
+ body = {
88
+ "event": str(event),
89
+ "user": str(e["user"]),
90
+ "status": e.get("status", "completed"),
91
+ # A unique reference per event: a retried request is then counted once.
92
+ "id": str(e["id"]) if e.get("id") is not None else str(uuid.uuid4()),
93
+ "properties": properties,
94
+ }
95
+ for k in ("cc_id", "amount", "currency", "reason", "wallet", "email", "x", "discord"):
96
+ if e.get(k) is not None:
97
+ body[k] = e[k]
98
+ return body
99
+
100
+ def _wanted(self, name):
101
+ if not self.filter:
102
+ return True
103
+ cfg = self._get_config()
104
+ key = _event_key(name)
105
+ if not cfg or key not in (cfg.get("quiet") or []) or key in (cfg.get("events") or []):
106
+ return True
107
+ return random.random() < float(cfg.get("sample", 0.05))
108
+
109
+ def _get_config(self):
110
+ age = float((self._config or {}).get("refreshSeconds", 300))
111
+ if self._config is not None and time.time() - self._config_at < age:
112
+ return self._config
113
+ try:
114
+ self._config = self._request("GET", "/api/partner/config")
115
+ except CoConnectError:
116
+ # Without advice, everything is sent.
117
+ self._config = None
118
+ self._config_at = time.time()
119
+ return self._config
120
+
121
+ def _request(self, method, path, body=None):
122
+ last = None
123
+ for attempt in range(self.max_retries + 1):
124
+ if attempt:
125
+ time.sleep(min(8.0, 0.5 * 2 ** (attempt - 1)) + random.random() * 0.25)
126
+ data = json.dumps(body).encode() if body is not None else None
127
+ req = urllib.request.Request(
128
+ self.base_url + path,
129
+ data=data,
130
+ method=method,
131
+ headers={"content-type": "application/json", "x-cc-token": self.token, "user-agent": "coconnect-sdk-python/" + __version__},
132
+ )
133
+ try:
134
+ with urllib.request.urlopen(req, timeout=self.timeout) as res:
135
+ return json.loads(res.read() or b"{}")
136
+ except urllib.error.HTTPError as e:
137
+ try:
138
+ payload = json.loads(e.read() or b"{}")
139
+ except ValueError:
140
+ payload = {}
141
+ last = CoConnectError(payload.get("error") or "CoConnect answered %s" % e.code, e.code, payload)
142
+ if e.code not in _RETRY:
143
+ raise last
144
+ except (urllib.error.URLError, TimeoutError, OSError) as e:
145
+ last = CoConnectError("Couldn't reach CoConnect: %s" % e)
146
+ raise last
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: coconnect
3
+ Version: 1.0.0
4
+ Summary: Send your platform's events to CoConnect Hub, in your own words. Server side.
5
+ License: MIT
6
+ Keywords: coconnect,quests,rewards,partner,events
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+
10
+ # coconnect
11
+
12
+ Send what users do on your platform to CoConnect Hub, named the way your platform already names it. CoConnect matches each event to the right task and rewards the member.
13
+
14
+ ```bash
15
+ pip install coconnect
16
+ ```
17
+
18
+ ```python
19
+ import os
20
+ from coconnect import CoConnect
21
+
22
+ hub = CoConnect(token=os.environ["COCONNECT_TOKEN"])
23
+
24
+ # When a user signs up (cc_id is the cookie set by https://hub.coconnect.co/sdk.js on your site):
25
+ hub.send("user_registered", user="u_1001", cc_id=request.cookies.get("cc_id"))
26
+
27
+ # Anything else, in your own words, with whatever details you have:
28
+ hub.send("ftd", user="u_1001", amount=75, currency="USD", id="dep_10482")
29
+ hub.send("kyc_failed", user="u_1001", status="failed", reason="Document unreadable")
30
+ ```
31
+
32
+ - **`user`** is required: your own ID for the user.
33
+ - **`cc_id`** links the user to their CoConnect account. Send it at least once per user.
34
+ - **`id`** is your reference for the event. If you leave it out, the SDK generates one, so a retried request is never counted twice.
35
+ - **Any other keyword arguments** are kept as details of the event. Emails and phone numbers are hashed by CoConnect on arrival.
36
+ - **Batching:** `send_batch([...])` sends up to 50 events at once.
37
+
38
+ The SDK also handles:
39
+ - **Retries:** network errors, 429 and 5xx responses are retried with backoff.
40
+ - **Errors:** a bad token or a malformed event raises `CoConnectError`.
41
+ - **No dependencies** beyond the standard library.
42
+
43
+ Keep your token on the server.
@@ -0,0 +1,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ coconnect/__init__.py
4
+ coconnect.egg-info/PKG-INFO
5
+ coconnect.egg-info/SOURCES.txt
6
+ coconnect.egg-info/dependency_links.txt
7
+ coconnect.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ coconnect
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "coconnect"
7
+ version = "1.0.0"
8
+ description = "Send your platform's events to CoConnect Hub, in your own words. Server side."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ keywords = ["coconnect", "quests", "rewards", "partner", "events"]
13
+ dependencies = []
14
+
15
+ [tool.setuptools]
16
+ packages = ["coconnect"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+