orionapi 2.28.0__tar.gz → 2.30.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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.30.0"
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
@@ -332,6 +332,25 @@ class BaseAPI:
|
|
|
332
332
|
|
|
333
333
|
return data
|
|
334
334
|
|
|
335
|
+
def _request_kwargs(self, timeout=None):
|
|
336
|
+
"""Timeout + SSL kwargs for unauthenticated (pre-token) requests.
|
|
337
|
+
|
|
338
|
+
``login()`` cannot go through :meth:`api_request` — there is no token yet,
|
|
339
|
+
so header construction would raise — but it still needs the same timeout
|
|
340
|
+
and SSL defaults applied here.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
timeout: Per-call timeout in seconds. When None, falls back to the
|
|
344
|
+
client's ``self.timeout``.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
dict: ``timeout`` and ``verify`` kwargs for ``requests``.
|
|
348
|
+
"""
|
|
349
|
+
return {
|
|
350
|
+
"timeout": self.timeout if timeout is None else timeout,
|
|
351
|
+
"verify": self.ca_bundle if self.ca_bundle else self.verify_ssl,
|
|
352
|
+
}
|
|
353
|
+
|
|
335
354
|
def api_request(self, url, req_func=None, timeout=None, **kwargs):
|
|
336
355
|
"""Make an authenticated API request with error handling.
|
|
337
356
|
|
|
@@ -440,18 +459,24 @@ class OrionAPI(BaseAPI):
|
|
|
440
459
|
self.login(usr, pwd)
|
|
441
460
|
# Credentials are not stored to prevent memory exposure
|
|
442
461
|
|
|
443
|
-
def login(self, usr=None, pwd=None):
|
|
462
|
+
def login(self, usr=None, pwd=None, timeout=None):
|
|
444
463
|
"""Authenticate with the Orion API.
|
|
445
464
|
|
|
446
465
|
Args:
|
|
447
466
|
usr: Username for authentication
|
|
448
467
|
pwd: Password for authentication
|
|
468
|
+
timeout: Request timeout in seconds. When None (default), falls back
|
|
469
|
+
to the client's ``self.timeout`` (constructor default 30).
|
|
449
470
|
|
|
450
471
|
Raises:
|
|
451
472
|
AuthenticationError: If credentials are invalid
|
|
452
473
|
"""
|
|
453
474
|
with self._token_lock:
|
|
454
|
-
res = requests.get(
|
|
475
|
+
res = requests.get(
|
|
476
|
+
f"{self.base_url}/security/token",
|
|
477
|
+
auth=(usr, pwd),
|
|
478
|
+
**self._request_kwargs(timeout),
|
|
479
|
+
)
|
|
455
480
|
if not res.ok:
|
|
456
481
|
raise AuthenticationError(f"Login failed: {res.status_code} {res.reason}")
|
|
457
482
|
try:
|
|
@@ -2275,13 +2300,15 @@ class EclipseBase(BaseAPI):
|
|
|
2275
2300
|
elif orion_token is not None:
|
|
2276
2301
|
self.login(orion_token=orion_token)
|
|
2277
2302
|
|
|
2278
|
-
def login(self, usr=None, pwd=None, orion_token=None):
|
|
2303
|
+
def login(self, usr=None, pwd=None, orion_token=None, timeout=None):
|
|
2279
2304
|
"""Authenticate with the Eclipse API.
|
|
2280
2305
|
|
|
2281
2306
|
Args:
|
|
2282
2307
|
usr: Username for authentication
|
|
2283
2308
|
pwd: Password for authentication
|
|
2284
2309
|
orion_token: Orion session token (alternative to usr/pwd)
|
|
2310
|
+
timeout: Request timeout in seconds. When None (default), falls back
|
|
2311
|
+
to the client's ``self.timeout`` (constructor default 30).
|
|
2285
2312
|
|
|
2286
2313
|
Raises:
|
|
2287
2314
|
AuthenticationError: If credentials are invalid or missing
|
|
@@ -2291,7 +2318,11 @@ class EclipseBase(BaseAPI):
|
|
|
2291
2318
|
|
|
2292
2319
|
with self._token_lock:
|
|
2293
2320
|
if usr is not None:
|
|
2294
|
-
res = requests.get(
|
|
2321
|
+
res = requests.get(
|
|
2322
|
+
f"{self.base_url}/admin/token",
|
|
2323
|
+
auth=(usr, pwd),
|
|
2324
|
+
**self._request_kwargs(timeout),
|
|
2325
|
+
)
|
|
2295
2326
|
if not res.ok:
|
|
2296
2327
|
raise AuthenticationError(f"Login failed: {res.status_code} {res.reason}")
|
|
2297
2328
|
try:
|
|
@@ -2303,6 +2334,7 @@ class EclipseBase(BaseAPI):
|
|
|
2303
2334
|
res = requests.get(
|
|
2304
2335
|
f"{self.base_url}/admin/token",
|
|
2305
2336
|
headers={"Authorization": "Session " + orion_token},
|
|
2337
|
+
**self._request_kwargs(timeout),
|
|
2306
2338
|
)
|
|
2307
2339
|
if not res.ok:
|
|
2308
2340
|
raise AuthenticationError(
|
|
@@ -2778,6 +2810,11 @@ class EclipseV1(EclipseBase):
|
|
|
2778
2810
|
"""Get portfolio details by ID.
|
|
2779
2811
|
|
|
2780
2812
|
Returns dict with 'general', 'teams', 'issues', 'summary' sections.
|
|
2813
|
+
The 'general' section carries ``teamIds`` / ``primaryTeamId``. The
|
|
2814
|
+
'teams' section's per-record field spelling is unspecified upstream
|
|
2815
|
+
(``id`` vs ``teamId``, ``name`` vs ``teamName``) — accept both, or use
|
|
2816
|
+
:meth:`get_portfolio_teams` for a normalized ``{id, name, is_primary}``
|
|
2817
|
+
view.
|
|
2781
2818
|
"""
|
|
2782
2819
|
res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}")
|
|
2783
2820
|
return res.json()
|
|
@@ -6631,6 +6668,108 @@ class EclipseV1(EclipseBase):
|
|
|
6631
6668
|
json=payload,
|
|
6632
6669
|
).json()
|
|
6633
6670
|
|
|
6671
|
+
# --- Admin: Teams (v1 reads) ---
|
|
6672
|
+
# The v1 team routes are the reliable team read on tenants where teams were
|
|
6673
|
+
# not created with an externalId — EclipseV2.get_teams returns [] there
|
|
6674
|
+
# (see its docstring). teamIds/primaryTeamId are required on the portfolio
|
|
6675
|
+
# record, so these are the reads that unblock create_portfolio.
|
|
6676
|
+
|
|
6677
|
+
def get_teams(self, is_active=None):
|
|
6678
|
+
"""Get all teams (``GET /admin/teams``).
|
|
6679
|
+
|
|
6680
|
+
Args:
|
|
6681
|
+
is_active: When set, passed as ``isActive`` (e.g. ``True`` returns
|
|
6682
|
+
active teams only). When None (default), the parameter is
|
|
6683
|
+
omitted and all teams are returned.
|
|
6684
|
+
|
|
6685
|
+
Returns:
|
|
6686
|
+
list: Team dicts. At minimum ``{id, name}``; the full record also
|
|
6687
|
+
carries ``portfolioAccess``, ``modelAccess``, ``status``,
|
|
6688
|
+
``numberOfUsers``, ``numberOfModels``, ``numberOfPortfolios``,
|
|
6689
|
+
``numberOfAdvisors``, ``isDeleted``, ``createdOn/By``,
|
|
6690
|
+
``editedOn/By``.
|
|
6691
|
+
"""
|
|
6692
|
+
params = {}
|
|
6693
|
+
if is_active is not None:
|
|
6694
|
+
params["isActive"] = str(bool(is_active)).lower()
|
|
6695
|
+
res = self.api_request(f"{self.base_url}/admin/teams", params=params)
|
|
6696
|
+
return res.json()
|
|
6697
|
+
|
|
6698
|
+
def get_team(self, team_id):
|
|
6699
|
+
"""Get details for a single team (``GET /admin/teams/{id}``).
|
|
6700
|
+
|
|
6701
|
+
Args:
|
|
6702
|
+
team_id: Eclipse team ID
|
|
6703
|
+
|
|
6704
|
+
Returns:
|
|
6705
|
+
dict: Team detail record
|
|
6706
|
+
"""
|
|
6707
|
+
res = self.api_request(f"{self.base_url}/admin/teams/{team_id}")
|
|
6708
|
+
return res.json()
|
|
6709
|
+
|
|
6710
|
+
def get_team_portfolios(self, team_id):
|
|
6711
|
+
"""Get portfolios associated with a team (``GET /admin/teams/{id}/portfolios``).
|
|
6712
|
+
|
|
6713
|
+
Args:
|
|
6714
|
+
team_id: Eclipse team ID
|
|
6715
|
+
|
|
6716
|
+
Returns:
|
|
6717
|
+
list: Portfolio dicts (``{id, name, isDeleted, createdOn/By, editedOn/By}``)
|
|
6718
|
+
"""
|
|
6719
|
+
res = self.api_request(f"{self.base_url}/admin/teams/{team_id}/portfolios")
|
|
6720
|
+
return res.json()
|
|
6721
|
+
|
|
6722
|
+
def get_team_primary_portfolios(self, team_id):
|
|
6723
|
+
"""Get portfolios where the team is set as primary
|
|
6724
|
+
(``GET /admin/teams/{id}/primaryPortfolios``).
|
|
6725
|
+
|
|
6726
|
+
Args:
|
|
6727
|
+
team_id: Eclipse team ID
|
|
6728
|
+
|
|
6729
|
+
Returns:
|
|
6730
|
+
list: Portfolio dicts (``{id, name, createdOn/By, editedOn/By}``)
|
|
6731
|
+
"""
|
|
6732
|
+
res = self.api_request(f"{self.base_url}/admin/teams/{team_id}/primaryPortfolios")
|
|
6733
|
+
return res.json()
|
|
6734
|
+
|
|
6735
|
+
def get_portfolio_teams(self, portfolio_id):
|
|
6736
|
+
"""Get the teams stamped on a portfolio, normalized.
|
|
6737
|
+
|
|
6738
|
+
Reads the ``teams`` section of ``GET /portfolio/portfolios/{id}``
|
|
6739
|
+
(see :meth:`get_portfolio`) and normalizes each record to
|
|
6740
|
+
``{id, name, is_primary}``. Unlike the ``/admin/teams`` routes, this
|
|
6741
|
+
works for a credential that is on no team at all, so it is the team
|
|
6742
|
+
read of last resort for service accounts.
|
|
6743
|
+
|
|
6744
|
+
The upstream field spelling is unspecified (``id`` vs ``teamId``,
|
|
6745
|
+
``name`` vs ``teamName``), so both are accepted. ``is_primary`` is
|
|
6746
|
+
derived from ``general.primaryTeamId`` when present, falling back to
|
|
6747
|
+
the record's own ``isPrimaryTeam`` flag.
|
|
6748
|
+
|
|
6749
|
+
Args:
|
|
6750
|
+
portfolio_id: Portfolio ID
|
|
6751
|
+
|
|
6752
|
+
Returns:
|
|
6753
|
+
list: ``{id, name, is_primary}`` dicts, one per team on the portfolio
|
|
6754
|
+
"""
|
|
6755
|
+
portfolio = self.get_portfolio(portfolio_id)
|
|
6756
|
+
primary_team_id = portfolio.get("general", {}).get("primaryTeamId")
|
|
6757
|
+
normalized = []
|
|
6758
|
+
for team in portfolio.get("teams") or []:
|
|
6759
|
+
team_id = team.get("id", team.get("teamId"))
|
|
6760
|
+
if primary_team_id is not None:
|
|
6761
|
+
is_primary = team_id == primary_team_id
|
|
6762
|
+
else:
|
|
6763
|
+
is_primary = bool(team.get("isPrimaryTeam"))
|
|
6764
|
+
normalized.append(
|
|
6765
|
+
{
|
|
6766
|
+
"id": team_id,
|
|
6767
|
+
"name": team.get("name", team.get("teamName")),
|
|
6768
|
+
"is_primary": is_primary,
|
|
6769
|
+
}
|
|
6770
|
+
)
|
|
6771
|
+
return normalized
|
|
6772
|
+
|
|
6634
6773
|
# --- Admin token (v1) ---
|
|
6635
6774
|
|
|
6636
6775
|
def get_firm_token(self, payload):
|
|
@@ -10075,13 +10214,19 @@ class EclipseV2(EclipseBase):
|
|
|
10075
10214
|
# --- Team / ServiceTeams / User ---
|
|
10076
10215
|
|
|
10077
10216
|
def get_teams(self, external_id=None):
|
|
10078
|
-
"""Get teams.
|
|
10217
|
+
"""Get teams (v2 ``Team/Team/GetTeams``).
|
|
10218
|
+
|
|
10219
|
+
Warning:
|
|
10220
|
+
Returns ``[]`` (HTTP 200) on tenants where teams were not created
|
|
10221
|
+
with an ``externalId`` — live-verified 2026-08-17. Prefer the v1
|
|
10222
|
+
route (:meth:`EclipseV1.get_teams`, ``GET /admin/teams``), which
|
|
10223
|
+
the :class:`Eclipse` unifier resolves to.
|
|
10079
10224
|
|
|
10080
10225
|
Args:
|
|
10081
10226
|
external_id: Optional external ID filter (maps to ``externalId``)
|
|
10082
10227
|
|
|
10083
10228
|
Returns:
|
|
10084
|
-
list: Team dicts
|
|
10229
|
+
list: Team dicts (``{teamId, teamName, isPrimaryTeam, ...}``)
|
|
10085
10230
|
"""
|
|
10086
10231
|
params = {}
|
|
10087
10232
|
if external_id is not None:
|
|
@@ -11681,6 +11826,21 @@ class Eclipse(EclipseBase):
|
|
|
11681
11826
|
internal_account_id=internal_account_id,
|
|
11682
11827
|
)
|
|
11683
11828
|
|
|
11829
|
+
# --- v1-preferred overrides (documented best-of-both choices) ----------------
|
|
11830
|
+
|
|
11831
|
+
def get_teams(self, is_active=None):
|
|
11832
|
+
"""Teams via the v1 route (best: v2 ``GetTeams`` is empty on some tenants).
|
|
11833
|
+
|
|
11834
|
+
Delegates to :meth:`EclipseV1.get_teams` (``GET /admin/teams``),
|
|
11835
|
+
returning ``{id, name, ...}`` records. **Behavior change in 2.30.0:**
|
|
11836
|
+
this name previously resolved to :meth:`EclipseV2.get_teams`, which
|
|
11837
|
+
returns ``[]`` (with differently spelled ``{teamId, teamName,
|
|
11838
|
+
isPrimaryTeam}`` records) on tenants where teams were not created with
|
|
11839
|
+
an ``externalId``. Use ``self.v2.get_teams(external_id=...)`` for the
|
|
11840
|
+
v2 surface explicitly.
|
|
11841
|
+
"""
|
|
11842
|
+
return self.v1.get_teams(is_active=is_active)
|
|
11843
|
+
|
|
11684
11844
|
|
|
11685
11845
|
class EclipseAPI(Eclipse):
|
|
11686
11846
|
"""Deprecated alias of :class:`Eclipse` (best-of-both client).
|
|
File without changes
|