orionapi 2.0.0__tar.gz → 2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionapi
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: A python interface for the Orion Advisors platform web API
5
5
  Author: spencerogden-dsam
6
6
  Author-email: 67068943+spencerogden-dsam@users.noreply.github.com
@@ -1,4 +1,4 @@
1
- __version__ = "2.0.0"
1
+ __version__ = "2.1.0"
2
2
 
3
3
  import logging
4
4
  import re
@@ -247,12 +247,15 @@ class BaseAPI:
247
247
 
248
248
  return data
249
249
 
250
- def api_request(self, url, req_func=requests.get, timeout=30, **kwargs):
250
+ def api_request(self, url, req_func=None, timeout=30, **kwargs):
251
251
  """Make an authenticated API request with error handling.
252
252
 
253
253
  Args:
254
254
  url: The API endpoint URL
255
- req_func: The requests function to use (get, post, put, delete)
255
+ req_func: The requests function to use (get, post, put, delete).
256
+ Defaults to ``requests.get``. Resolved at call time (not bound as a
257
+ default argument) so that patching ``requests.get`` in tests takes
258
+ effect for GET methods.
256
259
  timeout: Request timeout in seconds (default 30)
257
260
  **kwargs: Additional arguments passed to the request
258
261
 
@@ -264,6 +267,9 @@ class BaseAPI:
264
267
  NotFoundError: On 404 responses
265
268
  OrionAPIError: On other 4xx/5xx responses
266
269
  """
270
+ if req_func is None:
271
+ req_func = requests.get
272
+
267
273
  # Apply rate limiting
268
274
  self._rate_limiter.wait()
269
275
 
@@ -2479,6 +2485,54 @@ class EclipseV1(EclipseBase):
2479
2485
  res = self.api_request(f"{self.base_url}/account/accounts/{internal_id}")
2480
2486
  return res.json()
2481
2487
 
2488
+ def get_account_simple(self, account_id):
2489
+ """Get a lightweight account record by internal ID.
2490
+
2491
+ Hits ``/account/accounts/simple/{id}`` — a lighter payload than the full
2492
+ record returned by :meth:`get_account_details`.
2493
+
2494
+ Args:
2495
+ account_id: Internal Eclipse account ID
2496
+
2497
+ Returns:
2498
+ dict: Lightweight account record
2499
+ """
2500
+ res = self.api_request(f"{self.base_url}/account/accounts/simple/{account_id}")
2501
+ return res.json()
2502
+
2503
+ def get_account_holdings_detail(self, account_id):
2504
+ """Get holdings for an account via the account-path holdings endpoint.
2505
+
2506
+ Hits ``/account/accounts/{id}/holdings`` — a different path and shape than
2507
+ :meth:`get_account_holdings` (which uses ``/holding/holdings/simple``).
2508
+
2509
+ Args:
2510
+ account_id: Internal Eclipse account ID
2511
+
2512
+ Returns:
2513
+ list: Holding dicts as returned by the account-path endpoint
2514
+ """
2515
+ res = self.api_request(f"{self.base_url}/account/accounts/{account_id}/holdings")
2516
+ return res.json()
2517
+
2518
+ def get_out_of_tolerance_accounts(self, model_id, asset_id, asset_type="class"):
2519
+ """Get accounts out of tolerance for a model asset.
2520
+
2521
+ Args:
2522
+ model_id: Model ID (note: the API uses this value in the account path
2523
+ segment — kept as-is per the documented endpoint quirk)
2524
+ asset_id: Asset ID
2525
+ asset_type: Asset type (default "class")
2526
+
2527
+ Returns:
2528
+ list: Out-of-tolerance account dicts
2529
+ """
2530
+ res = self.api_request(
2531
+ f"{self.base_url}/account/accounts/{model_id}/outOfTolerance/{asset_id}",
2532
+ params={"assetType": asset_type},
2533
+ )
2534
+ return res.json()
2535
+
2482
2536
  def get_all_account_details(self):
2483
2537
  """Get detailed information for all accounts.
2484
2538
 
@@ -2508,12 +2562,20 @@ class EclipseV1(EclipseBase):
2508
2562
  res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}")
2509
2563
  return res.json()
2510
2564
 
2511
- def get_portfolio_accounts(self, portfolio_id):
2565
+ def get_portfolio_accounts(self, portfolio_id, simple=False):
2512
2566
  """Get list of accounts for a portfolio.
2513
2567
 
2568
+ Args:
2569
+ portfolio_id: Portfolio ID
2570
+ simple: When True, hit the lightweight ``/accounts/simple`` path
2571
+ (default False returns the full account records)
2572
+
2514
2573
  Returns list of accounts with cash targets, sleeve settings, and values.
2515
2574
  """
2516
- res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}/accounts")
2575
+ path = f"/portfolio/portfolios/{portfolio_id}/accounts"
2576
+ if simple:
2577
+ path += "/simple"
2578
+ res = self.api_request(f"{self.base_url}{path}")
2517
2579
  return res.json()
2518
2580
 
2519
2581
  def get_model_tolerance(self, portfolio_id, account_id, account_type="Normal"):
@@ -2532,18 +2594,21 @@ class EclipseV1(EclipseBase):
2532
2594
  )
2533
2595
  return res.json()
2534
2596
 
2535
- def get_all_portfolios(self, include_value=True, search=None):
2597
+ def get_all_portfolios(self, include_value=True, search=None, top=None):
2536
2598
  """Get list of all portfolios.
2537
2599
 
2538
2600
  Args:
2539
2601
  include_value: Include holding values (default True)
2540
2602
  search: Optional search string
2603
+ top: Optional max number of results (maps to ``$top``)
2541
2604
 
2542
2605
  Returns list of portfolios with basic info and optionally values.
2543
2606
  """
2544
2607
  params = {"includevalue": str(include_value).lower()}
2545
2608
  if search:
2546
2609
  params["search"] = search
2610
+ if top is not None:
2611
+ params["$top"] = top
2547
2612
  res = self.api_request(f"{self.base_url}/portfolio/portfolios/simple", params=params)
2548
2613
  return res.json()
2549
2614
 
@@ -2577,6 +2642,33 @@ class EclipseV1(EclipseBase):
2577
2642
  res = self.api_request(f"{self.base_url}/holding/holdings/simple", params=params)
2578
2643
  return res.json()
2579
2644
 
2645
+ def get_portfolio_holdings_detail(self, portfolio_id):
2646
+ """Get holdings for a portfolio via the portfolio-path holdings endpoint.
2647
+
2648
+ Hits ``/portfolio/portfolios/{id}/holdings`` — a different path and shape
2649
+ than :meth:`get_portfolio_holdings` (which uses ``/holding/holdings/simple``).
2650
+
2651
+ Args:
2652
+ portfolio_id: Portfolio ID
2653
+
2654
+ Returns:
2655
+ list: Holding dicts as returned by the portfolio-path endpoint
2656
+ """
2657
+ res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}/holdings")
2658
+ return res.json()
2659
+
2660
+ def get_taxlots(self, holding_id):
2661
+ """Get tax lots for a holding.
2662
+
2663
+ Args:
2664
+ holding_id: Holding ID
2665
+
2666
+ Returns:
2667
+ list: Tax-lot dicts (acquisition date, cost basis, quantity, etc.)
2668
+ """
2669
+ res = self.api_request(f"{self.base_url}/holding/holdings/{holding_id}/taxlots")
2670
+ return res.json()
2671
+
2580
2672
  def run_analytics(self):
2581
2673
  """Run analytics for portfolios that need it.
2582
2674
 
@@ -2644,6 +2736,32 @@ class EclipseV1(EclipseBase):
2644
2736
  """
2645
2737
  return self.api_request(f"{self.base_url}/tradeorder/trades?isPending=true").json()
2646
2738
 
2739
+ def get_trades(self, portfolio_id=None, top=None, is_pending=None):
2740
+ """Get trade orders with optional portfolio / paging / pending filters.
2741
+
2742
+ Hits the same ``/tradeorder/trades`` endpoint as :meth:`get_orders` /
2743
+ :meth:`get_orders_pending`, but lets the caller filter by portfolio and cap
2744
+ the result count.
2745
+
2746
+ Args:
2747
+ portfolio_id: Optional portfolio ID filter (maps to ``portfolioId``)
2748
+ top: Optional max number of results (maps to ``$top``)
2749
+ is_pending: Optional bool; when set, filter by pending status
2750
+ (maps to ``isPending``)
2751
+
2752
+ Returns:
2753
+ list: Trade order dicts
2754
+ """
2755
+ params = {}
2756
+ if portfolio_id is not None:
2757
+ params["portfolioId"] = portfolio_id
2758
+ if top is not None:
2759
+ params["$top"] = top
2760
+ if is_pending is not None:
2761
+ params["isPending"] = str(is_pending).lower()
2762
+ res = self.api_request(f"{self.base_url}/tradeorder/trades", params=params)
2763
+ return res.json()
2764
+
2647
2765
  def cash_needs_trade(
2648
2766
  self,
2649
2767
  portfolio_ids,
@@ -2692,6 +2810,9 @@ class EclipseV1(EclipseBase):
2692
2810
  reason="",
2693
2811
  is_excel_import=False,
2694
2812
  sync=True,
2813
+ selected_method_id=None,
2814
+ spend_full_amount=None,
2815
+ filter_type=None,
2695
2816
  ):
2696
2817
  """Generate Spend Cash trade for portfolios.
2697
2818
 
@@ -2703,6 +2824,12 @@ class EclipseV1(EclipseBase):
2703
2824
  is_excel_import: Whether this is from an Excel import (default False)
2704
2825
  sync: Wait for analytics to complete (default True)
2705
2826
 
2827
+ Args (additional):
2828
+ selected_method_id: Optional spend-cash calculation method id
2829
+ (see ``get_spend_cash_methods``); added to the body only when provided
2830
+ spend_full_amount: Optional bool; added to the body only when provided
2831
+ filter_type: Optional filter type; added to the body only when provided
2832
+
2706
2833
  Returns:
2707
2834
  dict with 'issues', 'success', and 'instanceId' fields
2708
2835
  """
@@ -2716,6 +2843,12 @@ class EclipseV1(EclipseBase):
2716
2843
  "reason": reason,
2717
2844
  "isExcelImport": is_excel_import,
2718
2845
  }
2846
+ if selected_method_id is not None:
2847
+ payload["selectedMethodId"] = selected_method_id
2848
+ if spend_full_amount is not None:
2849
+ payload["spendFullAmount"] = spend_full_amount
2850
+ if filter_type is not None:
2851
+ payload["filterType"] = filter_type
2719
2852
 
2720
2853
  res = self.api_request(
2721
2854
  f"{self.base_url}/tradetool/spendcash/action/generatetrade", requests.post, json=payload
@@ -2724,6 +2857,148 @@ class EclipseV1(EclipseBase):
2724
2857
  self._maybe_wait_for_analytics(sync)
2725
2858
  return result
2726
2859
 
2860
+ def get_raise_cash_methods(self):
2861
+ """Get the available raise-cash calculation methods.
2862
+
2863
+ Returns:
2864
+ list: Calculation-method dicts (id, name, etc.)
2865
+ """
2866
+ res = self.api_request(f"{self.base_url}/tradetool/raisecash/calculation_methods")
2867
+ return res.json()
2868
+
2869
+ def get_spend_cash_methods(self):
2870
+ """Get the available spend-cash calculation methods.
2871
+
2872
+ Returns:
2873
+ list: Calculation-method dicts (id, name, etc.)
2874
+ """
2875
+ res = self.api_request(f"{self.base_url}/tradetool/spendcash/calculation_methods")
2876
+ return res.json()
2877
+
2878
+ @staticmethod
2879
+ def _tlh_id_body(portfolio_ids, account_ids):
2880
+ """Build a tax-loss-harvesting request body, omitting empty id lists."""
2881
+ body = {}
2882
+ if portfolio_ids is not None:
2883
+ body["portfolioIds"] = portfolio_ids
2884
+ if account_ids is not None:
2885
+ body["accountIds"] = account_ids
2886
+ return body
2887
+
2888
+ def get_tlh_securities(self, portfolio_ids=None, account_ids=None):
2889
+ """Get tax-loss-harvesting candidate securities (preview only).
2890
+
2891
+ Args:
2892
+ portfolio_ids: Optional list of portfolio IDs
2893
+ account_ids: Optional list of account IDs
2894
+
2895
+ Returns:
2896
+ list: Candidate security dicts
2897
+ """
2898
+ res = self.api_request(
2899
+ f"{self.base_url}/tradetool/taxLossHarvesting/securities",
2900
+ requests.post,
2901
+ json=self._tlh_id_body(portfolio_ids, account_ids),
2902
+ )
2903
+ return res.json()
2904
+
2905
+ def check_tlh_gain_loss(self, portfolio_ids=None, account_ids=None):
2906
+ """Check projected gain/loss for tax-loss harvesting (preview only).
2907
+
2908
+ Args:
2909
+ portfolio_ids: Optional list of portfolio IDs
2910
+ account_ids: Optional list of account IDs
2911
+
2912
+ Returns:
2913
+ dict: Projected gain/loss summary
2914
+ """
2915
+ res = self.api_request(
2916
+ f"{self.base_url}/tradetool/taxLossHarvesting/action/checkGainLoss",
2917
+ requests.post,
2918
+ json=self._tlh_id_body(portfolio_ids, account_ids),
2919
+ )
2920
+ return res.json()
2921
+
2922
+ def tlh_trade(self, portfolio_ids=None, account_ids=None, is_view_only=True, sync=False):
2923
+ """Generate a tax-loss-harvesting trade (preview/staging by default).
2924
+
2925
+ Args:
2926
+ portfolio_ids: Optional list of portfolio IDs
2927
+ account_ids: Optional list of account IDs
2928
+ is_view_only: If True (default), preview trades without executing
2929
+ sync: Wait for analytics to complete (default False)
2930
+
2931
+ Returns:
2932
+ dict: Generated trade preview (instanceId, trades, etc.)
2933
+ """
2934
+ payload = self._tlh_id_body(portfolio_ids, account_ids)
2935
+ payload["isViewOnly"] = is_view_only
2936
+ res = self.api_request(
2937
+ f"{self.base_url}/tradetool/taxLossHarvesting/action/generateTrade",
2938
+ requests.post,
2939
+ json=payload,
2940
+ )
2941
+ result = res.json()
2942
+ self._maybe_wait_for_analytics(sync)
2943
+ return result
2944
+
2945
+ def rebalance_trade(
2946
+ self,
2947
+ portfolio_ids=None,
2948
+ account_ids=None,
2949
+ filter_type=None,
2950
+ is_view_only=True,
2951
+ max_gain_amount=0,
2952
+ minimum_trade_amount=0,
2953
+ minimum_trade_amount_type="$",
2954
+ allow_wash_sale=False,
2955
+ rounding=0,
2956
+ sync=False,
2957
+ ):
2958
+ """Generate a rebalance trade (preview/staging by default).
2959
+
2960
+ Args:
2961
+ portfolio_ids: Optional list of portfolio IDs
2962
+ account_ids: Optional list of account IDs
2963
+ filter_type: Optional filter type (maps to ``filterType``)
2964
+ is_view_only: If True (default), preview trades without executing
2965
+ max_gain_amount: Max gain amount (default 0)
2966
+ minimum_trade_amount: Minimum trade amount (default 0)
2967
+ minimum_trade_amount_type: Minimum trade amount type, "$" or "%" (default "$")
2968
+ allow_wash_sale: Whether to allow wash sales (default False)
2969
+ rounding: Rounding setting (default 0)
2970
+ sync: Wait for analytics to complete (default False)
2971
+
2972
+ Returns:
2973
+ dict: Generated trade preview (instanceId, trades, etc.)
2974
+ """
2975
+ payload = {
2976
+ "filterType": filter_type,
2977
+ "isViewOnly": is_view_only,
2978
+ "isExcelImport": False,
2979
+ "maxGainAmount": max_gain_amount,
2980
+ "minimumTradeAmount": {
2981
+ "amount": minimum_trade_amount,
2982
+ "type": minimum_trade_amount_type,
2983
+ },
2984
+ "allowWashSale": allow_wash_sale,
2985
+ "allowShortTermGain": None,
2986
+ "priorityRanking": [],
2987
+ "rounding": rounding,
2988
+ }
2989
+ if portfolio_ids is not None:
2990
+ payload["portfolioIds"] = portfolio_ids
2991
+ if account_ids is not None:
2992
+ payload["accountIds"] = account_ids
2993
+ res = self.api_request(
2994
+ f"{self.base_url}/tradetool/rebalancer/action/generatetrade",
2995
+ requests.post,
2996
+ json=payload,
2997
+ )
2998
+ result = res.json()
2999
+ self._maybe_wait_for_analytics(sync)
3000
+ return result
3001
+
2727
3002
  def get_closed_trades(self):
2728
3003
  """Get all closed/executed trade orders.
2729
3004
 
@@ -2732,21 +3007,27 @@ class EclipseV1(EclipseBase):
2732
3007
  """
2733
3008
  return self.api_request(f"{self.base_url}/tradeorder/closedtrades").json()
2734
3009
 
2735
- def get_trade_instances(self, start_date, end_date):
3010
+ def get_trade_instances(self, start_date, end_date, normalize=True):
2736
3011
  """Get trade instances (batches of trades) within a date range.
2737
3012
 
2738
3013
  Args:
2739
3014
  start_date: Start date (YYYY-MM-DD format)
2740
3015
  end_date: End date (YYYY-MM-DD format)
3016
+ normalize: When True (default), map the numeric tradeInstanceType /
3017
+ tradeInstanceSubType IDs to friendly names. When False, return the
3018
+ raw response unchanged (matching the API output).
2741
3019
 
2742
3020
  Returns:
2743
3021
  list: List of trade instance dicts with id, orderCount, executeStatus,
2744
- tradeInstanceType (string), tradeInstanceSubType (string), etc.
3022
+ tradeInstanceType, tradeInstanceSubType, etc.
2745
3023
  """
2746
3024
  instances = self.api_request(
2747
3025
  f"{self.base_url}/tradeorder/instances?startDate={start_date}&endDate={end_date}"
2748
3026
  ).json()
2749
3027
 
3028
+ if not normalize:
3029
+ return instances
3030
+
2750
3031
  # Convert type/subtype IDs to friendly names
2751
3032
  for inst in instances:
2752
3033
  type_id = inst.get("tradeInstanceType")
@@ -2760,13 +3041,22 @@ class EclipseV1(EclipseBase):
2760
3041
 
2761
3042
  # Model Maintenance
2762
3043
 
2763
- def get_all_models(self):
3044
+ def get_all_models(self, name=None, top=None):
2764
3045
  """Get all investment models.
2765
3046
 
3047
+ Args:
3048
+ name: Optional name filter
3049
+ top: Optional max number of results (maps to ``$top``)
3050
+
2766
3051
  Returns:
2767
3052
  list: List of model dicts with id, name, status, etc.
2768
3053
  """
2769
- res = self.api_request(f"{self.base_url}/modeling/models")
3054
+ params = {}
3055
+ if name is not None:
3056
+ params["name"] = name
3057
+ if top is not None:
3058
+ params["$top"] = top
3059
+ res = self.api_request(f"{self.base_url}/modeling/models", params=params)
2770
3060
  return res.json()
2771
3061
 
2772
3062
  def get_model(self, id):
@@ -2781,20 +3071,116 @@ class EclipseV1(EclipseBase):
2781
3071
  res = self.api_request(f"{self.base_url}/modeling/models/{id}")
2782
3072
  return res.json()
2783
3073
 
2784
- def get_model_allocations(self, id):
2785
- """Get aggregated allocations for a model.
3074
+ def get_model_allocations(self, id, aggregate=True):
3075
+ """Get allocations for a model.
2786
3076
 
2787
3077
  Args:
2788
3078
  id: Model ID
3079
+ aggregate: When True (default), aggregate allocations across the model
3080
+ tree (``aggregateAllocations=true``); when False, return per-node
3081
+ allocations.
2789
3082
 
2790
3083
  Returns:
2791
3084
  list: List of allocation dicts with target percentages
2792
3085
  """
2793
3086
  res = self.api_request(
2794
- f"{self.base_url}/modeling/models/{id}/allocations?aggregateAllocations=true"
3087
+ f"{self.base_url}/modeling/models/{id}/allocations",
3088
+ params={"aggregateAllocations": str(aggregate).lower()},
3089
+ )
3090
+ return res.json()
3091
+
3092
+ def get_model_nodes(self, model_id):
3093
+ """Get the node tree for a model.
3094
+
3095
+ Args:
3096
+ model_id: Model ID
3097
+
3098
+ Returns:
3099
+ dict: Node tree with ``levels``, ``modelId`` and ``preSelectedNodeId``
3100
+ """
3101
+ res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/Model/nodes")
3102
+ return res.json()
3103
+
3104
+ def get_model_portfolios(self, model_id):
3105
+ """Get portfolios assigned to a model.
3106
+
3107
+ Args:
3108
+ model_id: Model ID
3109
+
3110
+ Returns:
3111
+ list: Portfolio dicts assigned to the model
3112
+ """
3113
+ res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/portfolios")
3114
+ return res.json()
3115
+
3116
+ def get_model_pending(self, model_id):
3117
+ """Get pending changes for a model.
3118
+
3119
+ Args:
3120
+ model_id: Model ID
3121
+
3122
+ Returns:
3123
+ dict: Pending-change details
3124
+ """
3125
+ res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/pending")
3126
+ return res.json()
3127
+
3128
+ def get_model_analysis(self, model_id, asset_type="securityset"):
3129
+ """Get model analysis for a model.
3130
+
3131
+ Args:
3132
+ model_id: Model ID
3133
+ asset_type: Asset type to analyze (default "securityset")
3134
+
3135
+ Returns:
3136
+ dict: Model analysis results
3137
+ """
3138
+ res = self.api_request(
3139
+ f"{self.base_url}/modeling/models/{model_id}/modelAnalysis",
3140
+ params={
3141
+ "assetType": asset_type,
3142
+ "isIncludeTradeBlockAccount": 0,
3143
+ "isExcludeAsset": 0,
3144
+ },
2795
3145
  )
2796
3146
  return res.json()
2797
3147
 
3148
+ def get_model_status(self):
3149
+ """Get the list of model statuses.
3150
+
3151
+ Returns:
3152
+ list: Model-status dicts
3153
+ """
3154
+ res = self.api_request(f"{self.base_url}/modeling/models/modelStatus")
3155
+ return res.json()
3156
+
3157
+ def get_model_types(self):
3158
+ """Get the list of model types.
3159
+
3160
+ Returns:
3161
+ list: Model-type dicts
3162
+ """
3163
+ res = self.api_request(f"{self.base_url}/modeling/models/modelTypes")
3164
+ return res.json()
3165
+
3166
+ def get_submodels(self, model_type=None, search=None):
3167
+ """Get submodels, optionally filtered by type and name.
3168
+
3169
+ Args:
3170
+ model_type: Optional model-type filter (maps to ``modelType``)
3171
+ search: Optional name filter (maps to ``name``)
3172
+
3173
+ Returns:
3174
+ list: Submodel dicts
3175
+ """
3176
+ params = {}
3177
+ if model_type is not None:
3178
+ params["modelType"] = model_type
3179
+ if search is not None:
3180
+ params["name"] = search
3181
+ res = self.api_request(f"{self.base_url}/modeling/models/submodels", params=params)
3182
+ return res.json()
3183
+
2798
3184
  def get_all_security_sets(self):
2799
3185
  """Get all security sets.
2800
3186
 
@@ -2816,6 +3202,41 @@ class EclipseV1(EclipseBase):
2816
3202
  res = self.api_request(f"{self.base_url}/security/securityset/details/{id}")
2817
3203
  return res.json()
2818
3204
 
3205
+ def get_security_set_summary(self, id):
3206
+ """Get a security set by ID via the summary endpoint.
3207
+
3208
+ Hits ``/security/securityset/{id}`` — a different path than
3209
+ :meth:`get_security_set` (which uses ``/security/securityset/details/{id}``).
3210
+
3211
+ Args:
3212
+ id: Security set ID
3213
+
3214
+ Returns:
3215
+ dict: Security set summary
3216
+ """
3217
+ res = self.api_request(f"{self.base_url}/security/securityset/{id}")
3218
+ return res.json()
3219
+
3220
+ def get_security_set_details(self):
3221
+ """Get the full security-set detail list.
3222
+
3223
+ Hits ``/security/securityset/detail`` (documented as a large list across all
3224
+ sets).
3225
+
3226
+ .. note::
3227
+ As of 2026-05 this endpoint returns ``400 'id is not numeric string'`` on
3228
+ live Eclipse — the server routes the ``detail`` segment into the
3229
+ ``/security/securityset/{id}`` param route instead. Kept faithful to the
3230
+ documented endpoint (and the Eclipse MCP, which calls the same path), but
3231
+ the upstream route appears broken. Use :meth:`get_all_security_sets` plus
3232
+ :meth:`get_security_set` per id as a working alternative.
3233
+
3234
+ Returns:
3235
+ list: Security-set detail dicts
3236
+ """
3237
+ res = self.api_request(f"{self.base_url}/security/securityset/detail")
3238
+ return res.json()
3239
+
2819
3240
  def add_model(
2820
3241
  self,
2821
3242
  name,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "orionapi"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  description = "A python interface for the Orion Advisors platform web API"
5
5
  authors = ["spencerogden-dsam <67068943+spencerogden-dsam@users.noreply.github.com>"]
6
6