squid-crm 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.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: squid-crm
3
+ Version: 0.1.0
4
+ Summary: Python client for the Squid CRM REST and GraphQL APIs
5
+ Author: Squid-CRM contributors
6
+ License: AGPL-3.0-only
7
+ Project-URL: Repository, https://github.com/Mullassery/Squid-CRM
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "squid-crm"
7
+ version = "0.1.0"
8
+ description = "Python client for the Squid CRM REST and GraphQL APIs"
9
+ requires-python = ">=3.9"
10
+ license = { text = "AGPL-3.0-only" }
11
+ authors = [{ name = "Squid-CRM contributors" }]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: GNU Affero General Public License v3",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/Mullassery/Squid-CRM"
20
+
21
+ [tool.setuptools.packages.find]
22
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,116 @@
1
+ """Python client for the Squid CRM API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import urllib.error
7
+ import urllib.parse
8
+ import urllib.request
9
+ from typing import Any, Dict, Optional
10
+
11
+ __version__ = "0.1.0"
12
+
13
+ __all__ = ["SquidClient", "SquidError", "__version__"]
14
+
15
+
16
+ class SquidError(RuntimeError):
17
+ """Raised when the Squid API returns an error response."""
18
+
19
+ def __init__(self, message: str, status: Optional[int] = None, body: Any = None):
20
+ super().__init__(message)
21
+ self.status = status
22
+ self.body = body
23
+
24
+
25
+ class SquidClient:
26
+ """Small dependency-free client for the Squid REST/GraphQL API.
27
+
28
+ ``api_key`` is the bearer token returned by the API-key lifecycle
29
+ mutations. User access tokens are rejected by the data API, matching the
30
+ upstream contract.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ base_url: str,
36
+ api_key: Optional[str] = None,
37
+ timeout: float = 30.0,
38
+ ) -> None:
39
+ self.base_url = base_url.rstrip("/")
40
+ self.api_key = api_key
41
+ self.timeout = timeout
42
+
43
+ def _request(
44
+ self,
45
+ method: str,
46
+ path: str,
47
+ payload: Optional[Dict[str, Any]] = None,
48
+ params: Optional[Dict[str, Any]] = None,
49
+ ) -> Any:
50
+ url = f"{self.base_url}{path}"
51
+ if params:
52
+ query = urllib.parse.urlencode(
53
+ {key: value for key, value in params.items() if value is not None}
54
+ )
55
+ if query:
56
+ url = f"{url}?{query}"
57
+ data = None
58
+ headers = {"Accept": "application/json"}
59
+ if payload is not None:
60
+ data = json.dumps(payload).encode("utf-8")
61
+ headers["Content-Type"] = "application/json"
62
+ if self.api_key:
63
+ headers["Authorization"] = f"Bearer {self.api_key}"
64
+ request = urllib.request.Request(url, data=data, headers=headers, method=method)
65
+ try:
66
+ with urllib.request.urlopen(request, timeout=self.timeout) as response:
67
+ raw = response.read()
68
+ return json.loads(raw) if raw else None
69
+ except urllib.error.HTTPError as error:
70
+ raw = error.read()
71
+ try:
72
+ body = json.loads(raw)
73
+ except ValueError:
74
+ body = raw.decode("utf-8", errors="replace")
75
+ message = body.get("messages") if isinstance(body, dict) else body
76
+ raise SquidError(str(message), status=error.code, body=body) from error
77
+ except urllib.error.URLError as error:
78
+ raise SquidError(str(error.reason)) from error
79
+
80
+ def graphql(
81
+ self,
82
+ query: str,
83
+ variables: Optional[Dict[str, Any]] = None,
84
+ ) -> Any:
85
+ """Execute a GraphQL operation against ``/graphql``."""
86
+ response = self._request(
87
+ "POST",
88
+ "/graphql",
89
+ {"query": query, "variables": variables or {}},
90
+ )
91
+ if isinstance(response, dict) and response.get("errors"):
92
+ raise SquidError(str(response["errors"]), body=response)
93
+ return response.get("data") if isinstance(response, dict) else response
94
+
95
+ def list_people(self, limit: int = 20, offset: int = 0) -> Any:
96
+ """List people through the REST data API."""
97
+ return self._request(
98
+ "GET",
99
+ "/rest/people",
100
+ params={"limit": limit, "offset": offset},
101
+ )
102
+
103
+ def create_person(
104
+ self,
105
+ first_name: str,
106
+ last_name: str,
107
+ email: str,
108
+ **fields: Any,
109
+ ) -> Any:
110
+ """Create a person through the REST data API."""
111
+ payload: Dict[str, Any] = {
112
+ "name": {"firstName": first_name, "lastName": last_name},
113
+ "emails": {"primaryEmail": email},
114
+ }
115
+ payload.update(fields)
116
+ return self._request("POST", "/rest/people", payload)
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: squid-crm
3
+ Version: 0.1.0
4
+ Summary: Python client for the Squid CRM REST and GraphQL APIs
5
+ Author: Squid-CRM contributors
6
+ License: AGPL-3.0-only
7
+ Project-URL: Repository, https://github.com/Mullassery/Squid-CRM
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.9
@@ -0,0 +1,6 @@
1
+ pyproject.toml
2
+ src/squid_crm/__init__.py
3
+ src/squid_crm.egg-info/PKG-INFO
4
+ src/squid_crm.egg-info/SOURCES.txt
5
+ src/squid_crm.egg-info/dependency_links.txt
6
+ src/squid_crm.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ squid_crm