boarddata 4.0.2__tar.gz → 4.1.2__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.
- {boarddata-4.0.2 → boarddata-4.1.2}/PKG-INFO +1 -1
- {boarddata-4.0.2 → boarddata-4.1.2}/__init__.py +1 -1
- {boarddata-4.0.2 → boarddata-4.1.2}/_base.py +14 -6
- {boarddata-4.0.2 → boarddata-4.1.2}/_companies.py +3 -0
- boarddata-4.1.2/_indexes.py +127 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/boarddata.egg-info/PKG-INFO +1 -1
- {boarddata-4.0.2 → boarddata-4.1.2}/boarddata.egg-info/SOURCES.txt +4 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/cache.py +3 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/client.py +2 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/pyproject.toml +1 -1
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_config.py +2 -2
- boarddata-4.1.2/tests/test_indexes.py +112 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/__init__.py +15 -0
- boarddata-4.1.2/types/indexes.py +67 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/CLAUDE.md +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/README.md +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_assemblies.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_auditors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_comex.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_criteria.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_directors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_documents.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_esg.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_persons.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_sentinel.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/_utilities.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/boarddata.egg-info/dependency_links.txt +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/boarddata.egg-info/requires.txt +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/boarddata.egg-info/top_level.txt +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/errors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/py.typed +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/setup.cfg +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/__init__.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/conftest.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_assemblies.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_auditors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_base.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_build_payload.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_cache.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_comex.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_companies.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_criteria.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_directors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_documents.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_esg.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_persons.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_sentinel.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/tests/test_utilities.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/assemblies.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/auditors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/comex.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/companies.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/core.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/criteria.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/directors.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/documents.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/esg.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/persons.py +0 -0
- {boarddata-4.0.2 → boarddata-4.1.2}/types/sentinel.py +0 -0
|
@@ -105,15 +105,20 @@ class Base:
|
|
|
105
105
|
timeout: int = 30,
|
|
106
106
|
token_cache: TokenCache | None = None,
|
|
107
107
|
) -> None:
|
|
108
|
+
if not base_url.startswith("https://"):
|
|
109
|
+
logger.warning("base_url does not use HTTPS — credentials will be sent in plaintext")
|
|
108
110
|
self.base_url = base_url.rstrip("/")
|
|
109
|
-
self.
|
|
110
|
-
self.
|
|
111
|
+
self._client_id = client_id
|
|
112
|
+
self._client_secret = client_secret
|
|
111
113
|
self.timeout = timeout
|
|
112
114
|
self._token_cache = token_cache
|
|
113
115
|
|
|
114
116
|
self._token: str | None = None
|
|
115
117
|
self._token_expires_at: float = 0
|
|
116
118
|
|
|
119
|
+
def __repr__(self) -> str:
|
|
120
|
+
return f"<{type(self).__name__} base_url={self.base_url!r}>"
|
|
121
|
+
|
|
117
122
|
# ------------------------------------------------------------------
|
|
118
123
|
# Auth
|
|
119
124
|
# ------------------------------------------------------------------
|
|
@@ -132,13 +137,13 @@ class Base:
|
|
|
132
137
|
logger.debug("Loaded cached OAuth2 token from storage")
|
|
133
138
|
return self._token
|
|
134
139
|
except Exception:
|
|
135
|
-
logger.
|
|
140
|
+
logger.warning("Could not read token from cache", exc_info=True)
|
|
136
141
|
|
|
137
142
|
resp = requests.post(
|
|
138
143
|
f"{self.base_url}/api/oauth/token/",
|
|
139
144
|
data={
|
|
140
|
-
"client_id": self.
|
|
141
|
-
"client_secret": self.
|
|
145
|
+
"client_id": self._client_id,
|
|
146
|
+
"client_secret": self._client_secret,
|
|
142
147
|
"grant_type": "client_credentials",
|
|
143
148
|
"scope": "read write",
|
|
144
149
|
},
|
|
@@ -157,7 +162,7 @@ class Base:
|
|
|
157
162
|
})
|
|
158
163
|
logger.debug("Persisted OAuth2 token to storage")
|
|
159
164
|
except Exception:
|
|
160
|
-
logger.
|
|
165
|
+
logger.warning("Could not persist token to cache", exc_info=True)
|
|
161
166
|
|
|
162
167
|
return self._token # type: ignore[return-value]
|
|
163
168
|
|
|
@@ -173,6 +178,9 @@ class Base:
|
|
|
173
178
|
|
|
174
179
|
def _url(self, path: str) -> str:
|
|
175
180
|
path = path.lstrip("/")
|
|
181
|
+
if ".." in path.split("/"):
|
|
182
|
+
msg = f"Path traversal detected in URL path: {path!r}"
|
|
183
|
+
raise ValueError(msg)
|
|
176
184
|
return f"{self.base_url}/api/{path}"
|
|
177
185
|
|
|
178
186
|
def _request(
|
|
@@ -58,6 +58,7 @@ class CompanyMixin:
|
|
|
58
58
|
isin: str | None = None,
|
|
59
59
|
country: str | None = None,
|
|
60
60
|
sector: str | None = None,
|
|
61
|
+
ticker: str | None = None,
|
|
61
62
|
page: int | None = None,
|
|
62
63
|
page_size: int | None = None,
|
|
63
64
|
) -> PaginatedResponse:
|
|
@@ -68,6 +69,7 @@ class CompanyMixin:
|
|
|
68
69
|
isin: Filter by exact ISIN code.
|
|
69
70
|
country: Filter by ISO country code (e.g. ``"FR"``).
|
|
70
71
|
sector: Filter by sector name(s), comma-separated.
|
|
72
|
+
ticker: Filter by exact stock ticker symbol.
|
|
71
73
|
page: Page number (1-indexed).
|
|
72
74
|
page_size: Results per page (default 25).
|
|
73
75
|
|
|
@@ -88,6 +90,7 @@ class CompanyMixin:
|
|
|
88
90
|
isin=isin,
|
|
89
91
|
country=country,
|
|
90
92
|
sector=sector,
|
|
93
|
+
ticker=ticker,
|
|
91
94
|
page=page,
|
|
92
95
|
page_size=page_size,
|
|
93
96
|
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Index and IndexMembership API methods."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IndexMixin:
|
|
9
|
+
"""Index and IndexMembership CRUD.
|
|
10
|
+
|
|
11
|
+
Requires ``Base`` in the MRO (provides ``_get``, ``_post``, ``_patch``,
|
|
12
|
+
``_delete``, ``_build_payload``).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# -- Index CRUD --
|
|
16
|
+
|
|
17
|
+
def list_indexes(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
page: int | None = None,
|
|
21
|
+
page_size: int | None = None,
|
|
22
|
+
) -> Any:
|
|
23
|
+
return self._get( # type: ignore[attr-defined]
|
|
24
|
+
"indexes/",
|
|
25
|
+
page=page,
|
|
26
|
+
page_size=page_size,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def get_index(self, index_id: str) -> Any:
|
|
30
|
+
return self._get(f"indexes/{index_id}/") # type: ignore[attr-defined]
|
|
31
|
+
|
|
32
|
+
def create_index(
|
|
33
|
+
self,
|
|
34
|
+
name: str,
|
|
35
|
+
*,
|
|
36
|
+
description: str | None = None,
|
|
37
|
+
data_source_url: str | None = None,
|
|
38
|
+
data_source_type: str | None = None,
|
|
39
|
+
data_source_config: dict[str, Any] | None = None,
|
|
40
|
+
display_source_url: str | None = None,
|
|
41
|
+
) -> Any:
|
|
42
|
+
payload = self._build_payload( # type: ignore[attr-defined]
|
|
43
|
+
name=name,
|
|
44
|
+
description=description,
|
|
45
|
+
data_source_url=data_source_url,
|
|
46
|
+
data_source_type=data_source_type,
|
|
47
|
+
data_source_config=data_source_config,
|
|
48
|
+
display_source_url=display_source_url,
|
|
49
|
+
)
|
|
50
|
+
return self._post("indexes/", payload) # type: ignore[attr-defined]
|
|
51
|
+
|
|
52
|
+
def update_index(
|
|
53
|
+
self,
|
|
54
|
+
index_id: str,
|
|
55
|
+
*,
|
|
56
|
+
name: str | None = None,
|
|
57
|
+
description: str | None = None,
|
|
58
|
+
data_source_url: str | None = None,
|
|
59
|
+
data_source_type: str | None = None,
|
|
60
|
+
data_source_config: dict[str, Any] | None = None,
|
|
61
|
+
display_source_url: str | None = None,
|
|
62
|
+
) -> Any:
|
|
63
|
+
payload = self._build_payload( # type: ignore[attr-defined]
|
|
64
|
+
name=name,
|
|
65
|
+
description=description,
|
|
66
|
+
data_source_url=data_source_url,
|
|
67
|
+
data_source_type=data_source_type,
|
|
68
|
+
data_source_config=data_source_config,
|
|
69
|
+
display_source_url=display_source_url,
|
|
70
|
+
)
|
|
71
|
+
return self._patch(f"indexes/{index_id}/", payload) # type: ignore[attr-defined]
|
|
72
|
+
|
|
73
|
+
# -- IndexMembership CRUD --
|
|
74
|
+
|
|
75
|
+
def list_index_memberships(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
index_id: str | None = None,
|
|
79
|
+
company_id: str | None = None,
|
|
80
|
+
active_only: bool | None = None,
|
|
81
|
+
page: int | None = None,
|
|
82
|
+
page_size: int | None = None,
|
|
83
|
+
) -> Any:
|
|
84
|
+
params: dict[str, Any] = {}
|
|
85
|
+
if index_id is not None:
|
|
86
|
+
params["index"] = index_id
|
|
87
|
+
if company_id is not None:
|
|
88
|
+
params["company"] = company_id
|
|
89
|
+
if active_only is not None:
|
|
90
|
+
params["active_only"] = "true" if active_only else "false"
|
|
91
|
+
if page is not None:
|
|
92
|
+
params["page"] = page
|
|
93
|
+
if page_size is not None:
|
|
94
|
+
params["page_size"] = page_size
|
|
95
|
+
return self._get("index-memberships/", **params) # type: ignore[attr-defined]
|
|
96
|
+
|
|
97
|
+
def create_index_membership(
|
|
98
|
+
self,
|
|
99
|
+
company_id: str,
|
|
100
|
+
index_id: str,
|
|
101
|
+
*,
|
|
102
|
+
valid_from: str | None = None,
|
|
103
|
+
valid_to: str | None = None,
|
|
104
|
+
) -> Any:
|
|
105
|
+
payload: dict[str, Any] = {
|
|
106
|
+
"company": company_id,
|
|
107
|
+
"index": index_id,
|
|
108
|
+
"valid_from": valid_from,
|
|
109
|
+
"valid_to": valid_to,
|
|
110
|
+
}
|
|
111
|
+
return self._post("index-memberships/", payload) # type: ignore[attr-defined]
|
|
112
|
+
|
|
113
|
+
def update_index_membership(
|
|
114
|
+
self,
|
|
115
|
+
membership_id: str,
|
|
116
|
+
*,
|
|
117
|
+
valid_from: str | None = None,
|
|
118
|
+
valid_to: str | None = None,
|
|
119
|
+
) -> Any:
|
|
120
|
+
payload = self._build_payload( # type: ignore[attr-defined]
|
|
121
|
+
valid_from=valid_from,
|
|
122
|
+
valid_to=valid_to,
|
|
123
|
+
)
|
|
124
|
+
return self._patch( # type: ignore[attr-defined]
|
|
125
|
+
f"index-memberships/{membership_id}/",
|
|
126
|
+
payload,
|
|
127
|
+
)
|
|
@@ -10,6 +10,7 @@ _criteria.py
|
|
|
10
10
|
_directors.py
|
|
11
11
|
_documents.py
|
|
12
12
|
_esg.py
|
|
13
|
+
_indexes.py
|
|
13
14
|
_persons.py
|
|
14
15
|
_sentinel.py
|
|
15
16
|
_utilities.py
|
|
@@ -29,6 +30,7 @@ pyproject.toml
|
|
|
29
30
|
./_directors.py
|
|
30
31
|
./_documents.py
|
|
31
32
|
./_esg.py
|
|
33
|
+
./_indexes.py
|
|
32
34
|
./_persons.py
|
|
33
35
|
./_sentinel.py
|
|
34
36
|
./_utilities.py
|
|
@@ -55,6 +57,7 @@ tests/test_criteria.py
|
|
|
55
57
|
tests/test_directors.py
|
|
56
58
|
tests/test_documents.py
|
|
57
59
|
tests/test_esg.py
|
|
60
|
+
tests/test_indexes.py
|
|
58
61
|
tests/test_persons.py
|
|
59
62
|
tests/test_sentinel.py
|
|
60
63
|
tests/test_utilities.py
|
|
@@ -68,5 +71,6 @@ types/criteria.py
|
|
|
68
71
|
types/directors.py
|
|
69
72
|
types/documents.py
|
|
70
73
|
types/esg.py
|
|
74
|
+
types/indexes.py
|
|
71
75
|
types/persons.py
|
|
72
76
|
types/sentinel.py
|
|
@@ -34,9 +34,12 @@ class FileTokenCache:
|
|
|
34
34
|
|
|
35
35
|
def write(self, data: dict[str, Any]) -> None:
|
|
36
36
|
"""Persist token data."""
|
|
37
|
+
import os
|
|
38
|
+
|
|
37
39
|
p = Path(self._path)
|
|
38
40
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
39
41
|
p.write_text(json.dumps(data))
|
|
42
|
+
os.chmod(p, 0o600)
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
class ScalewaySecretTokenCache:
|
|
@@ -11,6 +11,7 @@ from ._criteria import CriteriaMixin
|
|
|
11
11
|
from ._directors import DirectorMixin
|
|
12
12
|
from ._documents import DocumentMixin
|
|
13
13
|
from ._esg import ESGMixin
|
|
14
|
+
from ._indexes import IndexMixin
|
|
14
15
|
from ._persons import PersonMixin
|
|
15
16
|
from ._sentinel import SentinelMixin
|
|
16
17
|
from ._utilities import UtilitiesMixin
|
|
@@ -27,6 +28,7 @@ class BoardDataClient(
|
|
|
27
28
|
ESGMixin,
|
|
28
29
|
SentinelMixin,
|
|
29
30
|
CriteriaMixin,
|
|
31
|
+
IndexMixin,
|
|
30
32
|
UtilitiesMixin,
|
|
31
33
|
Base,
|
|
32
34
|
):
|
|
@@ -20,8 +20,8 @@ class TestFromEnv:
|
|
|
20
20
|
def test_creates_client_from_env(self) -> None:
|
|
21
21
|
client = BoardDataClient.from_env()
|
|
22
22
|
assert client.base_url == "https://api.example.com"
|
|
23
|
-
assert client.
|
|
24
|
-
assert client.
|
|
23
|
+
assert client._client_id == "cid"
|
|
24
|
+
assert client._client_secret == "csecret"
|
|
25
25
|
|
|
26
26
|
@patch.dict(os.environ, {}, clear=True)
|
|
27
27
|
def test_raises_when_env_missing(self) -> None:
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Tests for IndexMixin methods.
|
|
2
|
+
|
|
3
|
+
Uses the shared ``client`` fixture from ``conftest.py`` which creates a
|
|
4
|
+
``BoardDataClient`` with mocked ``_request`` and ``_get_token``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestListIndexes:
|
|
13
|
+
def test_calls_correct_path(self, client):
|
|
14
|
+
client._request.return_value = {"count": 0, "results": []}
|
|
15
|
+
client.list_indexes()
|
|
16
|
+
args, kwargs = client._request.call_args
|
|
17
|
+
assert args == ("GET", "indexes/")
|
|
18
|
+
|
|
19
|
+
def test_returns_paginated_response(self, client):
|
|
20
|
+
client._request.return_value = {
|
|
21
|
+
"count": 1,
|
|
22
|
+
"next": None,
|
|
23
|
+
"previous": None,
|
|
24
|
+
"results": [{"id": "uuid1", "name": "CAC 40"}],
|
|
25
|
+
}
|
|
26
|
+
result = client.list_indexes()
|
|
27
|
+
assert result["count"] == 1
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestGetIndex:
|
|
31
|
+
def test_calls_correct_path(self, client):
|
|
32
|
+
client._request.return_value = {"id": "uuid1", "name": "CAC 40"}
|
|
33
|
+
client.get_index("uuid1")
|
|
34
|
+
args, kwargs = client._request.call_args
|
|
35
|
+
assert args == ("GET", "indexes/uuid1/")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestCreateIndex:
|
|
39
|
+
def test_sends_payload(self, client):
|
|
40
|
+
client._request.return_value = {"id": "uuid1", "name": "CAC 40"}
|
|
41
|
+
client.create_index(name="CAC 40", data_source_type="wikipedia")
|
|
42
|
+
args, kwargs = client._request.call_args
|
|
43
|
+
assert args == ("POST", "indexes/")
|
|
44
|
+
assert kwargs["json"]["name"] == "CAC 40"
|
|
45
|
+
assert kwargs["json"]["data_source_type"] == "wikipedia"
|
|
46
|
+
|
|
47
|
+
def test_excludes_none_values(self, client):
|
|
48
|
+
client._request.return_value = {"id": "uuid1", "name": "CAC 40"}
|
|
49
|
+
client.create_index(name="CAC 40")
|
|
50
|
+
_, kwargs = client._request.call_args
|
|
51
|
+
assert "data_source_type" not in kwargs["json"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestUpdateIndex:
|
|
55
|
+
def test_sends_patch(self, client):
|
|
56
|
+
client._request.return_value = {"id": "uuid1", "name": "CAC 40"}
|
|
57
|
+
client.update_index("uuid1", description="French blue-chip")
|
|
58
|
+
args, kwargs = client._request.call_args
|
|
59
|
+
assert args == ("PATCH", "indexes/uuid1/")
|
|
60
|
+
assert kwargs["json"]["description"] == "French blue-chip"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TestListIndexMemberships:
|
|
64
|
+
def test_calls_correct_path(self, client):
|
|
65
|
+
client._request.return_value = {"count": 0, "results": []}
|
|
66
|
+
client.list_index_memberships()
|
|
67
|
+
args, kwargs = client._request.call_args
|
|
68
|
+
assert args == ("GET", "index-memberships/")
|
|
69
|
+
|
|
70
|
+
def test_passes_filters(self, client):
|
|
71
|
+
client._request.return_value = {"count": 0, "results": []}
|
|
72
|
+
client.list_index_memberships(index_id="uuid1", active_only=True)
|
|
73
|
+
_, kwargs = client._request.call_args
|
|
74
|
+
assert kwargs["params"]["index"] == "uuid1"
|
|
75
|
+
assert kwargs["params"]["active_only"] == "true"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestCreateIndexMembership:
|
|
79
|
+
def test_sends_payload(self, client):
|
|
80
|
+
client._request.return_value = {"id": "m-uuid"}
|
|
81
|
+
client.create_index_membership(
|
|
82
|
+
company_id="c-uuid", index_id="i-uuid"
|
|
83
|
+
)
|
|
84
|
+
args, kwargs = client._request.call_args
|
|
85
|
+
assert args == ("POST", "index-memberships/")
|
|
86
|
+
assert kwargs["json"]["company"] == "c-uuid"
|
|
87
|
+
assert kwargs["json"]["index"] == "i-uuid"
|
|
88
|
+
|
|
89
|
+
def test_sends_valid_from_none(self, client):
|
|
90
|
+
client._request.return_value = {"id": "m-uuid"}
|
|
91
|
+
client.create_index_membership(
|
|
92
|
+
company_id="c-uuid", index_id="i-uuid", valid_from=None
|
|
93
|
+
)
|
|
94
|
+
_, kwargs = client._request.call_args
|
|
95
|
+
assert kwargs["json"]["valid_from"] is None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class TestUpdateIndexMembership:
|
|
99
|
+
def test_sends_patch(self, client):
|
|
100
|
+
client._request.return_value = {"id": "m-uuid"}
|
|
101
|
+
client.update_index_membership("m-uuid", valid_to="2026-04-07")
|
|
102
|
+
args, kwargs = client._request.call_args
|
|
103
|
+
assert args == ("PATCH", "index-memberships/m-uuid/")
|
|
104
|
+
assert kwargs["json"]["valid_to"] == "2026-04-07"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestListCompaniesTickerFilter:
|
|
108
|
+
def test_passes_ticker_param(self, client):
|
|
109
|
+
client._request.return_value = {"count": 0, "results": []}
|
|
110
|
+
client.list_companies(ticker="AI")
|
|
111
|
+
_, kwargs = client._request.call_args
|
|
112
|
+
assert kwargs["params"]["ticker"] == "AI"
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
"""BoardData API type definitions."""
|
|
2
2
|
|
|
3
|
+
from .indexes import (
|
|
4
|
+
CreateIndexMembershipPayload,
|
|
5
|
+
CreateIndexPayload,
|
|
6
|
+
IndexItem,
|
|
7
|
+
IndexMembershipItem,
|
|
8
|
+
UpdateIndexMembershipPayload,
|
|
9
|
+
UpdateIndexPayload,
|
|
10
|
+
)
|
|
3
11
|
from .esg import (
|
|
4
12
|
CompanyESGBenchmarkItem,
|
|
5
13
|
IROBatchItemPayload,
|
|
@@ -117,6 +125,13 @@ from .sentinel import (
|
|
|
117
125
|
)
|
|
118
126
|
|
|
119
127
|
__all__ = [
|
|
128
|
+
# indexes
|
|
129
|
+
"CreateIndexMembershipPayload",
|
|
130
|
+
"CreateIndexPayload",
|
|
131
|
+
"IndexItem",
|
|
132
|
+
"IndexMembershipItem",
|
|
133
|
+
"UpdateIndexMembershipPayload",
|
|
134
|
+
"UpdateIndexPayload",
|
|
120
135
|
# esg
|
|
121
136
|
"CompanyESGBenchmarkItem",
|
|
122
137
|
"IROBatchItemPayload",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""TypedDict definitions for Index and IndexMembership API types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from typing import NotRequired, TypedDict
|
|
9
|
+
except ImportError:
|
|
10
|
+
from typing_extensions import NotRequired, TypedDict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# -- Response types --
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IndexItem(TypedDict):
|
|
17
|
+
id: str
|
|
18
|
+
name: str
|
|
19
|
+
description: str
|
|
20
|
+
data_source_url: str
|
|
21
|
+
data_source_type: str
|
|
22
|
+
data_source_config: dict[str, Any]
|
|
23
|
+
display_source_url: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class IndexMembershipItem(TypedDict):
|
|
27
|
+
id: str
|
|
28
|
+
company: str
|
|
29
|
+
company_name: str
|
|
30
|
+
company_ticker: str
|
|
31
|
+
index: str
|
|
32
|
+
index_name: str
|
|
33
|
+
valid_from: str | None
|
|
34
|
+
valid_to: str | None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# -- Create/Update payloads --
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CreateIndexPayload(TypedDict):
|
|
41
|
+
name: str
|
|
42
|
+
description: NotRequired[str]
|
|
43
|
+
data_source_url: NotRequired[str]
|
|
44
|
+
data_source_type: NotRequired[str]
|
|
45
|
+
data_source_config: NotRequired[dict[str, Any]]
|
|
46
|
+
display_source_url: NotRequired[str]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class UpdateIndexPayload(TypedDict, total=False):
|
|
50
|
+
name: str
|
|
51
|
+
description: str
|
|
52
|
+
data_source_url: str
|
|
53
|
+
data_source_type: str
|
|
54
|
+
data_source_config: dict[str, Any]
|
|
55
|
+
display_source_url: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class CreateIndexMembershipPayload(TypedDict):
|
|
59
|
+
company: str
|
|
60
|
+
index: str
|
|
61
|
+
valid_from: NotRequired[str | None]
|
|
62
|
+
valid_to: NotRequired[str | None]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class UpdateIndexMembershipPayload(TypedDict, total=False):
|
|
66
|
+
valid_from: str | None
|
|
67
|
+
valid_to: str | None
|
|
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
|
|
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
|