boarddata 3.0.2__tar.gz → 3.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.
Files changed (54) hide show
  1. {boarddata-3.0.2 → boarddata-3.1.0}/PKG-INFO +1 -1
  2. {boarddata-3.0.2 → boarddata-3.1.0}/__init__.py +3 -2
  3. {boarddata-3.0.2 → boarddata-3.1.0}/_base.py +17 -4
  4. {boarddata-3.0.2 → boarddata-3.1.0}/boarddata.egg-info/PKG-INFO +1 -1
  5. boarddata-3.1.0/cache.py +167 -0
  6. {boarddata-3.0.2 → boarddata-3.1.0}/pyproject.toml +1 -1
  7. boarddata-3.0.2/cache.py +0 -39
  8. {boarddata-3.0.2 → boarddata-3.1.0}/CLAUDE.md +0 -0
  9. {boarddata-3.0.2 → boarddata-3.1.0}/README.md +0 -0
  10. {boarddata-3.0.2 → boarddata-3.1.0}/_assemblies.py +0 -0
  11. {boarddata-3.0.2 → boarddata-3.1.0}/_auditors.py +0 -0
  12. {boarddata-3.0.2 → boarddata-3.1.0}/_comex.py +0 -0
  13. {boarddata-3.0.2 → boarddata-3.1.0}/_companies.py +0 -0
  14. {boarddata-3.0.2 → boarddata-3.1.0}/_directors.py +0 -0
  15. {boarddata-3.0.2 → boarddata-3.1.0}/_documents.py +0 -0
  16. {boarddata-3.0.2 → boarddata-3.1.0}/_esg.py +0 -0
  17. {boarddata-3.0.2 → boarddata-3.1.0}/_persons.py +0 -0
  18. {boarddata-3.0.2 → boarddata-3.1.0}/_sentinel.py +0 -0
  19. {boarddata-3.0.2 → boarddata-3.1.0}/_utilities.py +0 -0
  20. {boarddata-3.0.2 → boarddata-3.1.0}/boarddata.egg-info/SOURCES.txt +0 -0
  21. {boarddata-3.0.2 → boarddata-3.1.0}/boarddata.egg-info/dependency_links.txt +0 -0
  22. {boarddata-3.0.2 → boarddata-3.1.0}/boarddata.egg-info/requires.txt +0 -0
  23. {boarddata-3.0.2 → boarddata-3.1.0}/boarddata.egg-info/top_level.txt +0 -0
  24. {boarddata-3.0.2 → boarddata-3.1.0}/client.py +0 -0
  25. {boarddata-3.0.2 → boarddata-3.1.0}/errors.py +0 -0
  26. {boarddata-3.0.2 → boarddata-3.1.0}/py.typed +0 -0
  27. {boarddata-3.0.2 → boarddata-3.1.0}/setup.cfg +0 -0
  28. {boarddata-3.0.2 → boarddata-3.1.0}/tests/__init__.py +0 -0
  29. {boarddata-3.0.2 → boarddata-3.1.0}/tests/conftest.py +0 -0
  30. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_assemblies.py +0 -0
  31. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_auditors.py +0 -0
  32. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_base.py +0 -0
  33. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_build_payload.py +0 -0
  34. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_cache.py +0 -0
  35. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_comex.py +0 -0
  36. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_companies.py +0 -0
  37. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_config.py +0 -0
  38. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_directors.py +0 -0
  39. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_documents.py +0 -0
  40. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_esg.py +0 -0
  41. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_persons.py +0 -0
  42. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_sentinel.py +0 -0
  43. {boarddata-3.0.2 → boarddata-3.1.0}/tests/test_utilities.py +0 -0
  44. {boarddata-3.0.2 → boarddata-3.1.0}/types/__init__.py +0 -0
  45. {boarddata-3.0.2 → boarddata-3.1.0}/types/assemblies.py +0 -0
  46. {boarddata-3.0.2 → boarddata-3.1.0}/types/auditors.py +0 -0
  47. {boarddata-3.0.2 → boarddata-3.1.0}/types/comex.py +0 -0
  48. {boarddata-3.0.2 → boarddata-3.1.0}/types/companies.py +0 -0
  49. {boarddata-3.0.2 → boarddata-3.1.0}/types/core.py +0 -0
  50. {boarddata-3.0.2 → boarddata-3.1.0}/types/directors.py +0 -0
  51. {boarddata-3.0.2 → boarddata-3.1.0}/types/documents.py +0 -0
  52. {boarddata-3.0.2 → boarddata-3.1.0}/types/esg.py +0 -0
  53. {boarddata-3.0.2 → boarddata-3.1.0}/types/persons.py +0 -0
  54. {boarddata-3.0.2 → boarddata-3.1.0}/types/sentinel.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boarddata
3
- Version: 3.0.2
3
+ Version: 3.1.0
4
4
  Summary: Python SDK for the BoardData V2 REST API
5
5
  License: MIT
6
6
  Classifier: Development Status :: 4 - Beta
@@ -1,18 +1,19 @@
1
1
  """BoardData API Python SDK."""
2
2
 
3
- from .cache import FileTokenCache
3
+ from .cache import FileTokenCache, ScalewaySecretTokenCache
4
4
  from ._base import Base, TokenCache
5
5
  from .client import BoardDataClient
6
6
  from .errors import BoardDataError
7
7
 
8
8
  from . import types as types # noqa: F401 — makes types accessible as boarddata.types
9
9
 
10
- __version__ = "3.0.2"
10
+ __version__ = "3.1.0"
11
11
  __all__ = [
12
12
  "Base",
13
13
  "BoardDataClient",
14
14
  "BoardDataError",
15
15
  "FileTokenCache",
16
+ "ScalewaySecretTokenCache",
16
17
  "TokenCache",
17
18
  "__version__",
18
19
  ]
@@ -35,11 +35,13 @@ class Base:
35
35
 
36
36
  DEFAULT_AUDIENCE = "https://api.boarddata.scalens.com"
37
37
 
38
+ _DEFAULT_CACHE = object() # sentinel for auto-detection
39
+
38
40
  @classmethod
39
41
  def from_env(
40
42
  cls,
41
43
  *,
42
- token_cache: TokenCache | str | None = None,
44
+ token_cache: TokenCache | str | None | object = _DEFAULT_CACHE,
43
45
  timeout: int = 30,
44
46
  ) -> Base:
45
47
  """Create a client from environment variables.
@@ -53,7 +55,10 @@ class Base:
53
55
  Args:
54
56
  token_cache: File path string (e.g. ``"~/.boarddata/token.json"``)
55
57
  or a ``TokenCache`` instance. When a string is passed, a
56
- ``FileTokenCache`` is created automatically.
58
+ ``FileTokenCache`` is created automatically. Defaults to
59
+ auto-detection: uses Scaleway Secret Manager if ``SCW_SECRET_KEY``
60
+ and ``SCW_PROJECT_ID`` are set, otherwise a local file cache at
61
+ ``/tmp/boarddata_token.json``. Pass ``None`` to disable caching.
57
62
  timeout: HTTP request timeout in seconds.
58
63
 
59
64
  Returns:
@@ -64,7 +69,7 @@ class Base:
64
69
  """
65
70
  import os
66
71
 
67
- from .cache import FileTokenCache
72
+ from .cache import FileTokenCache, ScalewaySecretTokenCache
68
73
 
69
74
  env_vars = {
70
75
  "BOARDDATA_BACKEND_URL": "base_url",
@@ -80,7 +85,15 @@ class Base:
80
85
  raise ValueError(msg)
81
86
  kwargs[param_name] = value
82
87
 
83
- cache = FileTokenCache(token_cache) if isinstance(token_cache, str) else token_cache
88
+ if token_cache is cls._DEFAULT_CACHE:
89
+ if os.environ.get("SCW_SECRET_KEY") and os.environ.get("SCW_PROJECT_ID"):
90
+ cache: TokenCache | None = ScalewaySecretTokenCache()
91
+ else:
92
+ cache = FileTokenCache("/tmp/boarddata_token.json")
93
+ elif isinstance(token_cache, str):
94
+ cache = FileTokenCache(token_cache)
95
+ else:
96
+ cache = token_cache # type: ignore[assignment]
84
97
 
85
98
  return cls(
86
99
  **kwargs,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boarddata
3
- Version: 3.0.2
3
+ Version: 3.1.0
4
4
  Summary: Python SDK for the BoardData V2 REST API
5
5
  License: MIT
6
6
  Classifier: Development Status :: 4 - Beta
@@ -0,0 +1,167 @@
1
+ """Token cache implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ logger = logging.getLogger("boarddata")
11
+
12
+
13
+ class FileTokenCache:
14
+ """Persists Auth0 tokens to a local JSON file.
15
+
16
+ Creates parent directories automatically. Reads return None
17
+ if the file is missing or contains invalid JSON.
18
+
19
+ Usage::
20
+
21
+ cache = FileTokenCache("~/.boarddata/token.json")
22
+ bd = BoardDataClient.from_env(token_cache=cache)
23
+ """
24
+
25
+ def __init__(self, path: str) -> None:
26
+ self._path = str(Path(path).expanduser().resolve())
27
+
28
+ def read(self) -> dict[str, Any] | None:
29
+ """Return cached token data or None."""
30
+ try:
31
+ return json.loads(Path(self._path).read_text()) # type: ignore[no-any-return]
32
+ except (FileNotFoundError, json.JSONDecodeError):
33
+ return None
34
+
35
+ def write(self, data: dict[str, Any]) -> None:
36
+ """Persist token data."""
37
+ p = Path(self._path)
38
+ p.parent.mkdir(parents=True, exist_ok=True)
39
+ p.write_text(json.dumps(data))
40
+
41
+
42
+ class ScalewaySecretTokenCache:
43
+ """Persists Auth0 tokens to Scaleway Secret Manager.
44
+
45
+ Shares a single cached token across all job runs, so only one
46
+ Auth0 M2M token request is made per token lifetime.
47
+
48
+ Requires ``SCW_SECRET_KEY`` and ``SCW_PROJECT_ID`` env vars (already
49
+ available in Scaleway Job containers).
50
+
51
+ Usage::
52
+
53
+ cache = ScalewaySecretTokenCache(secret_name="boarddata-m2m-token")
54
+ bd = BoardDataClient.from_env(token_cache=cache)
55
+ """
56
+
57
+ _API_BASE = "https://api.scaleway.com/secret-manager/v1beta1/regions/{region}"
58
+
59
+ def __init__(
60
+ self,
61
+ secret_name: str = "boarddata-m2m-token",
62
+ region: str | None = None,
63
+ ) -> None:
64
+ import os
65
+
66
+ self._secret_name = secret_name
67
+ self._region = region or os.environ.get("SCW_REGION", "fr-par")
68
+ self._base_url = self._API_BASE.format(region=self._region)
69
+
70
+ def _headers(self) -> dict[str, str]:
71
+ import os
72
+
73
+ secret_key = os.environ.get("SCW_SECRET_KEY", "")
74
+ return {"X-Auth-Token": secret_key}
75
+
76
+ def _project_id(self) -> str:
77
+ import os
78
+
79
+ return os.environ.get("SCW_PROJECT_ID", "")
80
+
81
+ def _find_secret_id(self) -> str | None:
82
+ """Find the secret ID by name, or None if it doesn't exist."""
83
+ import requests as _requests
84
+
85
+ try:
86
+ resp = _requests.get(
87
+ f"{self._base_url}/secrets",
88
+ headers=self._headers(),
89
+ params={"name": self._secret_name, "project_id": self._project_id()},
90
+ timeout=10,
91
+ )
92
+ if not resp.ok:
93
+ return None
94
+ secrets = resp.json().get("secrets", [])
95
+ return secrets[0]["id"] if secrets else None
96
+ except Exception:
97
+ logger.debug("Failed to look up secret %s", self._secret_name)
98
+ return None
99
+
100
+ def _create_secret(self) -> str | None:
101
+ """Create the secret and return its ID."""
102
+ import requests as _requests
103
+
104
+ try:
105
+ resp = _requests.post(
106
+ f"{self._base_url}/secrets",
107
+ headers=self._headers(),
108
+ json={
109
+ "name": self._secret_name,
110
+ "project_id": self._project_id(),
111
+ "description": "Cached Auth0 M2M token for BoardData client",
112
+ },
113
+ timeout=10,
114
+ )
115
+ if not resp.ok:
116
+ return None
117
+ return resp.json().get("id")
118
+ except Exception:
119
+ logger.debug("Failed to create secret %s", self._secret_name)
120
+ return None
121
+
122
+ def read(self) -> dict[str, Any] | None:
123
+ """Read the latest secret version from Scaleway Secret Manager."""
124
+ import base64
125
+
126
+ import requests as _requests
127
+
128
+ secret_id = self._find_secret_id()
129
+ if not secret_id:
130
+ return None
131
+
132
+ try:
133
+ resp = _requests.get(
134
+ f"{self._base_url}/secrets/{secret_id}/versions/latest/access",
135
+ headers=self._headers(),
136
+ timeout=10,
137
+ )
138
+ if not resp.ok:
139
+ return None
140
+ raw = base64.b64decode(resp.json()["data"]).decode()
141
+ return json.loads(raw) # type: ignore[no-any-return]
142
+ except Exception:
143
+ logger.debug("Failed to read token from Scaleway Secret Manager")
144
+ return None
145
+
146
+ def write(self, data: dict[str, Any]) -> None:
147
+ """Write a new secret version to Scaleway Secret Manager."""
148
+ import base64
149
+
150
+ import requests as _requests
151
+
152
+ secret_id = self._find_secret_id()
153
+ if not secret_id:
154
+ secret_id = self._create_secret()
155
+ if not secret_id:
156
+ return
157
+
158
+ try:
159
+ encoded = base64.b64encode(json.dumps(data).encode()).decode()
160
+ _requests.post(
161
+ f"{self._base_url}/secrets/{secret_id}/versions",
162
+ headers=self._headers(),
163
+ json={"data": encoded},
164
+ timeout=10,
165
+ )
166
+ except Exception:
167
+ logger.debug("Failed to write token to Scaleway Secret Manager")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "boarddata"
7
- version = "3.0.2"
7
+ version = "3.1.0"
8
8
  description = "Python SDK for the BoardData V2 REST API"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
boarddata-3.0.2/cache.py DELETED
@@ -1,39 +0,0 @@
1
- """Token cache implementations."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- from pathlib import Path
8
- from typing import Any
9
-
10
- logger = logging.getLogger("boarddata")
11
-
12
-
13
- class FileTokenCache:
14
- """Persists Auth0 tokens to a local JSON file.
15
-
16
- Creates parent directories automatically. Reads return None
17
- if the file is missing or contains invalid JSON.
18
-
19
- Usage::
20
-
21
- cache = FileTokenCache("~/.boarddata/token.json")
22
- bd = BoardDataClient.from_env(token_cache=cache)
23
- """
24
-
25
- def __init__(self, path: str) -> None:
26
- self._path = str(Path(path).expanduser().resolve())
27
-
28
- def read(self) -> dict[str, Any] | None:
29
- """Return cached token data or None."""
30
- try:
31
- return json.loads(Path(self._path).read_text()) # type: ignore[no-any-return]
32
- except (FileNotFoundError, json.JSONDecodeError):
33
- return None
34
-
35
- def write(self, data: dict[str, Any]) -> None:
36
- """Persist token data."""
37
- p = Path(self._path)
38
- p.parent.mkdir(parents=True, exist_ok=True)
39
- p.write_text(json.dumps(data))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes