shield-python 0.1.4__py3-none-any.whl → 0.1.6__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.
shield/__init__.py CHANGED
@@ -2,4 +2,4 @@ from .client import Client
2
2
  from .exceptions import ShieldError
3
3
 
4
4
  __all__ = ["Client", "ShieldError"]
5
- __version__ = "0.1.4"
5
+ __version__ = "0.1.5"
shield/client.py CHANGED
@@ -3,7 +3,7 @@ import hmac as hmac_mod
3
3
  import json
4
4
  import time
5
5
  from typing import Any, Dict, Optional
6
- from urllib.parse import urlparse
6
+ from urllib.parse import urlencode
7
7
 
8
8
  import requests
9
9
 
@@ -12,6 +12,10 @@ from .resources.sessions import Sessions
12
12
  from .resources.events import Events
13
13
  from .resources.verify import Verify
14
14
 
15
+ # ARCH-019: kept static to avoid leaking Python version / OS into server logs.
16
+ # Bumped alongside setup.py version on each release.
17
+ SDK_USER_AGENT = "shield-python/0.1.6"
18
+
15
19
 
16
20
  class Client:
17
21
  """Official Shield Python SDK client.
@@ -64,12 +68,29 @@ class Client:
64
68
  Raises:
65
69
  ShieldError: On non-2xx responses or request failures.
66
70
  """
67
- url = f"{self.base_url}{path}"
68
71
  method = method.upper()
69
72
 
73
+ # C-1 (v0.1.6): fold query params into `path` BEFORE signing so the
74
+ # canonical message matches what the server validates against
75
+ # http.Request.URL.RequestURI(). Previously `params` was passed to
76
+ # requests.request() separately — requests would append them to the
77
+ # wire URL, but the SDK signed only the bare path, guaranteeing a
78
+ # signature mismatch on any request with query params. We pre-encode
79
+ # here and pass params=None to requests so there is exactly one
80
+ # canonical query string, controlled by this SDK, for both signing
81
+ # and transmission. doseq=True matches requests' own encoding for
82
+ # list-valued params (?tag=a&tag=b).
83
+ if params:
84
+ path_with_query = f"{path}?{urlencode(params, doseq=True)}"
85
+ else:
86
+ path_with_query = path
87
+
88
+ url = f"{self.base_url}{path_with_query}"
89
+
70
90
  headers = {
71
91
  "X-Shield-Key": self.api_key,
72
92
  "Content-Type": "application/json",
93
+ "User-Agent": SDK_USER_AGENT,
73
94
  }
74
95
 
75
96
  # Serialize body
@@ -83,7 +104,7 @@ class Client:
83
104
  timestamp = str(int(time.time()))
84
105
  nonce = str(uuid.uuid4())
85
106
  body_hash = hashlib.sha256(body).hexdigest()
86
- message = f"{timestamp}.{method}.{path}.{body_hash}"
107
+ message = f"{timestamp}.{method}.{path_with_query}.{body_hash}"
87
108
  signature = hmac_mod.new(
88
109
  self.hmac_secret.encode("utf-8"),
89
110
  message.encode("utf-8"),
@@ -99,29 +120,31 @@ class Client:
99
120
  url=url,
100
121
  headers=headers,
101
122
  data=body if body else None,
102
- params=params,
123
+ params=None,
103
124
  timeout=self.timeout,
104
125
  )
105
126
  except requests.RequestException as e:
106
127
  raise ShieldError(
107
128
  message=f"Request failed: {str(e)}",
108
129
  status_code=0,
109
- code="request_error",
110
130
  )
111
131
 
112
132
  if not (200 <= response.status_code < 300):
113
- error_message = response.text
114
- error_code = "api_error"
133
+ # Backend returns {"error": "..."} (always) and sometimes
134
+ # {"message": "..."} with extra detail. Prefer message when present.
135
+ error_message = response.text or response.reason
115
136
  try:
116
137
  error_body = response.json()
117
- error_message = error_body.get("error", error_message)
118
- error_code = error_body.get("code", error_code)
119
- except (ValueError, KeyError):
138
+ error_message = (
139
+ error_body.get("message")
140
+ or error_body.get("error")
141
+ or error_message
142
+ )
143
+ except ValueError:
120
144
  pass
121
145
  raise ShieldError(
122
146
  message=error_message,
123
147
  status_code=response.status_code,
124
- code=error_code,
125
148
  )
126
149
 
127
150
  if raw_response:
shield/exceptions.py CHANGED
@@ -1,23 +1,15 @@
1
1
  class ShieldError(Exception):
2
2
  """Base exception for Shield SDK errors."""
3
3
 
4
- def __init__(self, message: str, status_code: int = None, code: str = None):
4
+ def __init__(self, message: str, status_code: int = None):
5
5
  super().__init__(message)
6
6
  self.message = message
7
7
  self.status_code = status_code
8
- self.code = code
9
8
 
10
9
  def __str__(self):
11
- parts = []
12
10
  if self.status_code:
13
- parts.append(f"[{self.status_code}]")
14
- if self.code:
15
- parts.append(f"({self.code})")
16
- parts.append(self.message)
17
- return " ".join(parts)
11
+ return f"[{self.status_code}] {self.message}"
12
+ return self.message
18
13
 
19
14
  def __repr__(self):
20
- return (
21
- f"ShieldError(message={self.message!r}, "
22
- f"status_code={self.status_code!r}, code={self.code!r})"
23
- )
15
+ return f"ShieldError(message={self.message!r}, status_code={self.status_code!r})"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shield-python
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: Official Shield SDK for Python
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://getshield.dev
@@ -264,3 +264,12 @@ Shield Standard Event Taxonomy v1.0 ??37 event types across 7 categories:
264
264
  | `shield.evidence.exported` | Evidence was exported |
265
265
  | `shield.evidence.verified` | Evidence was verified |
266
266
  | `shield.evidence.tampered_detected` | Evidence tampering was detected |
267
+
268
+ ## Versioning & API compatibility
269
+
270
+ This SDK follows [Semantic Versioning](https://semver.org/).
271
+
272
+ - **Pre-1.0** (current): minor-version bumps may ship breaking changes. Pin the full version in `requirements.txt`.
273
+ - **1.0 and later**: the public API is stable within a major version. Breaking changes require a major-version bump.
274
+
275
+ The Shield HTTP API is versioned at the URL path (`/api/v1`). This SDK targets `/api/v1` and will not transparently follow a server-side version bump — a new server major version will be delivered as a new SDK major version so callers opt in explicitly.
@@ -0,0 +1,11 @@
1
+ shield/__init__.py,sha256=Uktse_UZlAOJnYVIKLKzeXFL6kiSJEEqNy0BMDHIYM4,122
2
+ shield/client.py,sha256=1bVkTyvgtCQX4vxpAOPTFE6z5OEPMHoKR4PHw90l0wE,5281
3
+ shield/exceptions.py,sha256=GgC2gtEvgCTxqekJyu7BJdHGi3sY1NzGWOEWkARpci8,500
4
+ shield/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ shield/resources/events.py,sha256=uH0TG235kRsoS6sOp-zPIcy5WbbtaPNAhC8aFMy00Po,1093
6
+ shield/resources/sessions.py,sha256=qOnJlpCx2Zk5nEh5TCFkf6v05l4M5SKAeF7klQ9YCQM,1268
7
+ shield/resources/verify.py,sha256=vGN51EWU56jOl8BVfNlfiP0ivkNvkLykicupGIGcpAw,498
8
+ shield_python-0.1.6.dist-info/METADATA,sha256=GvbinXKIOVhxlBz5irtD6pcFvadRMy_qwd8hPw2F-AI,8641
9
+ shield_python-0.1.6.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ shield_python-0.1.6.dist-info/top_level.txt,sha256=XumpIzJqChucal6arFU8u2d4ua7RA9yPcBSJFUi0JDU,7
11
+ shield_python-0.1.6.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- shield/__init__.py,sha256=nXw1gar8DlzsZvb057b9Z3Gn4Fuudow06cbyizD4yfM,122
2
- shield/client.py,sha256=bTt-QylDQ92sIVBo3Q5aBe9aCDNmWmlQDYby8s5dGl4,4091
3
- shield/exceptions.py,sha256=PunpNnkqyP-9ZhLwPAwH4ZqpxL6rg_KxwaQi9_hdo9I,717
4
- shield/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- shield/resources/events.py,sha256=uH0TG235kRsoS6sOp-zPIcy5WbbtaPNAhC8aFMy00Po,1093
6
- shield/resources/sessions.py,sha256=qOnJlpCx2Zk5nEh5TCFkf6v05l4M5SKAeF7klQ9YCQM,1268
7
- shield/resources/verify.py,sha256=vGN51EWU56jOl8BVfNlfiP0ivkNvkLykicupGIGcpAw,498
8
- shield_python-0.1.4.dist-info/METADATA,sha256=fUMubnzO_KaApcKE6hLQJzbgveIocWYNwO_v_itkXYI,8044
9
- shield_python-0.1.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
- shield_python-0.1.4.dist-info/top_level.txt,sha256=XumpIzJqChucal6arFU8u2d4ua7RA9yPcBSJFUi0JDU,7
11
- shield_python-0.1.4.dist-info/RECORD,,