tango-python 0.4.4__py3-none-any.whl → 0.5.0__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.
tango/__init__.py CHANGED
@@ -10,6 +10,7 @@ from .exceptions import (
10
10
  )
11
11
  from .models import (
12
12
  GsaElibraryContract,
13
+ ITDashboardInvestment,
13
14
  PaginatedResponse,
14
15
  RateLimitInfo,
15
16
  SearchFilters,
@@ -28,7 +29,7 @@ from .shapes import (
28
29
  TypeGenerator,
29
30
  )
30
31
 
31
- __version__ = "0.4.3"
32
+ __version__ = "0.5.0"
32
33
  __all__ = [
33
34
  "TangoClient",
34
35
  "TangoAPIError",
@@ -38,6 +39,7 @@ __all__ = [
38
39
  "TangoRateLimitError",
39
40
  "RateLimitInfo",
40
41
  "GsaElibraryContract",
42
+ "ITDashboardInvestment",
41
43
  "PaginatedResponse",
42
44
  "SearchFilters",
43
45
  "ShapeConfig",
tango/client.py CHANGED
@@ -26,6 +26,7 @@ from tango.models import (
26
26
  Forecast,
27
27
  Grant,
28
28
  GsaElibraryContract,
29
+ ITDashboardInvestment,
29
30
  Location,
30
31
  Notice,
31
32
  Opportunity,
@@ -1336,6 +1337,112 @@ class TangoClient:
1336
1337
  data, shape, GsaElibraryContract, flat, flat_lists, joiner=joiner
1337
1338
  )
1338
1339
 
1340
+ # ============================================================================
1341
+ # IT Dashboard Investments
1342
+ # ============================================================================
1343
+
1344
+ def list_itdashboard_investments(
1345
+ self,
1346
+ page: int = 1,
1347
+ limit: int = 25,
1348
+ shape: str | None = None,
1349
+ flat: bool = False,
1350
+ flat_lists: bool = False,
1351
+ joiner: str = ".",
1352
+ search: str | None = None,
1353
+ agency_code: int | None = None,
1354
+ agency_name: str | None = None,
1355
+ type_of_investment: str | None = None,
1356
+ updated_time_after: str | date | datetime | None = None,
1357
+ updated_time_before: str | date | datetime | None = None,
1358
+ cio_rating: int | None = None,
1359
+ cio_rating_max: int | None = None,
1360
+ performance_risk: bool | None = None,
1361
+ ) -> PaginatedResponse:
1362
+ """List federal IT investments from the IT Dashboard (`/api/itdashboard/`).
1363
+
1364
+ Filters are tier-gated by the API:
1365
+
1366
+ - **Free**: ``search`` (full-text across UII, title, description, agency, bureau)
1367
+ - **Pro**: ``agency_code``, ``type_of_investment``,
1368
+ ``updated_time_after`` / ``updated_time_before``
1369
+ - **Business+**: ``agency_name`` (text), ``cio_rating``,
1370
+ ``cio_rating_max``, ``performance_risk``
1371
+
1372
+ Hitting a gated filter on a lower tier returns a 403 with upgrade info.
1373
+
1374
+ CIO ratings: 1=High Risk, 2=Moderately High, 3=Medium, 4=Moderately Low, 5=Low.
1375
+ ``performance_risk=True`` returns investments with at least one NOT MET metric.
1376
+ """
1377
+ params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
1378
+ if shape is None:
1379
+ shape = ShapeConfig.ITDASHBOARD_INVESTMENTS_MINIMAL
1380
+ if shape:
1381
+ params["shape"] = shape
1382
+ if flat:
1383
+ params["flat"] = "true"
1384
+ if joiner:
1385
+ params["joiner"] = joiner
1386
+ if flat_lists:
1387
+ params["flat_lists"] = "true"
1388
+ for k, val in (
1389
+ ("search", search),
1390
+ ("agency_code", agency_code),
1391
+ ("agency_name", agency_name),
1392
+ ("type_of_investment", type_of_investment),
1393
+ ("updated_time_after", updated_time_after),
1394
+ ("updated_time_before", updated_time_before),
1395
+ ("cio_rating", cio_rating),
1396
+ ("cio_rating_max", cio_rating_max),
1397
+ ("performance_risk", performance_risk),
1398
+ ):
1399
+ if val is None:
1400
+ continue
1401
+ if isinstance(val, bool):
1402
+ params[k] = "true" if val else "false"
1403
+ elif isinstance(val, (date, datetime)):
1404
+ params[k] = val.isoformat()
1405
+ else:
1406
+ params[k] = val
1407
+ data = self._get("/api/itdashboard/", params)
1408
+ results = [
1409
+ self._parse_response_with_shape(
1410
+ obj, shape, ITDashboardInvestment, flat, flat_lists, joiner=joiner
1411
+ )
1412
+ for obj in data.get("results", [])
1413
+ ]
1414
+ return PaginatedResponse(
1415
+ count=data.get("count", 0),
1416
+ next=data.get("next"),
1417
+ previous=data.get("previous"),
1418
+ results=results,
1419
+ )
1420
+
1421
+ def get_itdashboard_investment(
1422
+ self,
1423
+ uii: str,
1424
+ shape: str | None = None,
1425
+ flat: bool = False,
1426
+ flat_lists: bool = False,
1427
+ joiner: str = ".",
1428
+ ) -> Any:
1429
+ """Get a single IT Dashboard investment by UII (`/api/itdashboard/{uii}/`)."""
1430
+ params: dict[str, Any] = {}
1431
+ if shape is None:
1432
+ shape = ShapeConfig.ITDASHBOARD_INVESTMENTS_COMPREHENSIVE
1433
+ if shape:
1434
+ params["shape"] = shape
1435
+ if flat:
1436
+ params["flat"] = "true"
1437
+ if joiner:
1438
+ params["joiner"] = joiner
1439
+ if flat_lists:
1440
+ params["flat_lists"] = "true"
1441
+ data = self._get(f"/api/itdashboard/{uii}/", params)
1442
+ return self._parse_response_with_shape(
1443
+ data, shape, ITDashboardInvestment, flat, flat_lists, joiner=joiner
1444
+ )
1445
+
1339
1446
  # ============================================================================
1340
1447
  # Vehicles (Awards)
1341
1448
  # ============================================================================
tango/models.py CHANGED
@@ -367,6 +367,45 @@ class GsaElibraryContract:
367
367
  sins: list[str] | None = None
368
368
 
369
369
 
370
+ @dataclass
371
+ class ITDashboardInvestment:
372
+ """Schema definition for IT Dashboard Investment (not used for instances)
373
+
374
+ Federal IT investment from itdashboard.gov, exposed at /api/itdashboard/.
375
+ Identified by ``uii`` (Unique Investment Identifier).
376
+
377
+ Tier-gated shape expansions:
378
+ Free base fields only
379
+ Pro+ ``funding`` and ``details`` expansions
380
+ Business+ nested sub-tables (``cio_evaluation``, ``contracts``,
381
+ ``projects``, ``cost_pools_towers``, ``funding_sources``,
382
+ ``performance_metrics``, ``performance_actual``,
383
+ ``operational_analysis``) and ``business_case_html``
384
+ """
385
+
386
+ uii: str
387
+ agency_code: int | None = None
388
+ agency_name: str | None = None
389
+ bureau_code: int | None = None
390
+ bureau_name: str | None = None
391
+ investment_title: str | None = None
392
+ type_of_investment: str | None = None
393
+ part_of_it_portfolio: str | None = None
394
+ updated_time: datetime | None = None
395
+ url: str | None = None
396
+ business_case_html: str | None = None
397
+ funding: dict[str, Any] | None = None
398
+ details: dict[str, Any] | None = None
399
+ cio_evaluation: list[dict[str, Any]] | None = None
400
+ contracts: list[dict[str, Any]] | None = None
401
+ projects: list[dict[str, Any]] | None = None
402
+ cost_pools_towers: list[dict[str, Any]] | None = None
403
+ funding_sources: list[dict[str, Any]] | None = None
404
+ performance_metrics: list[dict[str, Any]] | None = None
405
+ performance_actual: list[dict[str, Any]] | None = None
406
+ operational_analysis: list[dict[str, Any]] | None = None
407
+
408
+
370
409
  @dataclass
371
410
  class Vehicle:
372
411
  """Schema definition for Vehicle (not used for instances)"""
@@ -687,3 +726,18 @@ class ShapeConfig:
687
726
  GSA_ELIBRARY_CONTRACTS_MINIMAL: Final = (
688
727
  "uuid,contract_number,schedule,recipient(display_name,uei),idv(key,award_date)"
689
728
  )
729
+
730
+ # Default for list_itdashboard_investments()
731
+ # Free-tier safe: matches the API's INVESTMENT_LIST_DEFAULT_SHAPE.
732
+ ITDASHBOARD_INVESTMENTS_MINIMAL: Final = (
733
+ "uii,agency_name,bureau_name,investment_title,"
734
+ "type_of_investment,part_of_it_portfolio,updated_time,url"
735
+ )
736
+
737
+ # Default for get_itdashboard_investment()
738
+ # Free-tier safe: matches the API's INVESTMENT_RETRIEVE_DEFAULT_SHAPE.
739
+ ITDASHBOARD_INVESTMENTS_COMPREHENSIVE: Final = (
740
+ "uii,agency_code,agency_name,bureau_code,bureau_name,"
741
+ "investment_title,type_of_investment,part_of_it_portfolio,"
742
+ "updated_time,url"
743
+ )
@@ -1132,6 +1132,67 @@ GSA_ELIBRARY_CONTRACT_SCHEMA: dict[str, FieldSchema] = {
1132
1132
  ),
1133
1133
  }
1134
1134
 
1135
+ # IT Dashboard Investment
1136
+ ITDASHBOARD_INVESTMENT_SCHEMA: dict[str, FieldSchema] = {
1137
+ "uii": FieldSchema(name="uii", type=str, is_optional=False, is_list=False),
1138
+ "agency_code": FieldSchema(
1139
+ name="agency_code", type=int, is_optional=True, is_list=False
1140
+ ),
1141
+ "agency_name": FieldSchema(
1142
+ name="agency_name", type=str, is_optional=True, is_list=False
1143
+ ),
1144
+ "bureau_code": FieldSchema(
1145
+ name="bureau_code", type=int, is_optional=True, is_list=False
1146
+ ),
1147
+ "bureau_name": FieldSchema(
1148
+ name="bureau_name", type=str, is_optional=True, is_list=False
1149
+ ),
1150
+ "investment_title": FieldSchema(
1151
+ name="investment_title", type=str, is_optional=True, is_list=False
1152
+ ),
1153
+ "type_of_investment": FieldSchema(
1154
+ name="type_of_investment", type=str, is_optional=True, is_list=False
1155
+ ),
1156
+ "part_of_it_portfolio": FieldSchema(
1157
+ name="part_of_it_portfolio", type=str, is_optional=True, is_list=False
1158
+ ),
1159
+ "updated_time": FieldSchema(
1160
+ name="updated_time", type=datetime, is_optional=True, is_list=False
1161
+ ),
1162
+ "url": FieldSchema(name="url", type=str, is_optional=True, is_list=False),
1163
+ "business_case_html": FieldSchema(
1164
+ name="business_case_html", type=str, is_optional=True, is_list=False
1165
+ ),
1166
+ # Expansions: dict (funding/details) and list-of-dict (nested sub-tables).
1167
+ # Modeled as opaque dict/list since their inner shapes are dynamic.
1168
+ "funding": FieldSchema(name="funding", type=dict, is_optional=True, is_list=False),
1169
+ "details": FieldSchema(name="details", type=dict, is_optional=True, is_list=False),
1170
+ "cio_evaluation": FieldSchema(
1171
+ name="cio_evaluation", type=list, is_optional=True, is_list=True
1172
+ ),
1173
+ "contracts": FieldSchema(
1174
+ name="contracts", type=list, is_optional=True, is_list=True
1175
+ ),
1176
+ "projects": FieldSchema(
1177
+ name="projects", type=list, is_optional=True, is_list=True
1178
+ ),
1179
+ "cost_pools_towers": FieldSchema(
1180
+ name="cost_pools_towers", type=list, is_optional=True, is_list=True
1181
+ ),
1182
+ "funding_sources": FieldSchema(
1183
+ name="funding_sources", type=list, is_optional=True, is_list=True
1184
+ ),
1185
+ "performance_metrics": FieldSchema(
1186
+ name="performance_metrics", type=list, is_optional=True, is_list=True
1187
+ ),
1188
+ "performance_actual": FieldSchema(
1189
+ name="performance_actual", type=list, is_optional=True, is_list=True
1190
+ ),
1191
+ "operational_analysis": FieldSchema(
1192
+ name="operational_analysis", type=list, is_optional=True, is_list=True
1193
+ ),
1194
+ }
1195
+
1135
1196
  # ============================================================================
1136
1197
  # SCHEMA REGISTRY MAPPING
1137
1198
  # ============================================================================
@@ -1176,6 +1237,8 @@ EXPLICIT_SCHEMAS: dict[str, dict[str, FieldSchema]] = {
1176
1237
  # GSA eLibrary
1177
1238
  "GsaElibraryContract": GSA_ELIBRARY_CONTRACT_SCHEMA,
1178
1239
  "GsaElibraryIdvRef": GSA_ELIBRARY_IDV_REF_SCHEMA,
1240
+ # IT Dashboard
1241
+ "ITDashboardInvestment": ITDASHBOARD_INVESTMENT_SCHEMA,
1179
1242
  }
1180
1243
 
1181
1244
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tango-python
3
- Version: 0.4.4
3
+ Version: 0.5.0
4
4
  Summary: Python SDK for the Tango API
5
5
  Project-URL: Homepage, https://github.com/makegov/tango-python
6
6
  Project-URL: Documentation, https://docs.makegov.com/tango-python
@@ -1,16 +1,16 @@
1
- tango/__init__.py,sha256=MkZ4VcgIB5WFfCaxK6XXrK25icagQ2o9ZNta64xsqgw,1142
2
- tango/client.py,sha256=Dm8v3xMO3uX-GGM1ioLySzjLavPmlxhkrqZw4q6wTWw,88396
1
+ tango/__init__.py,sha256=jhfw7xJNlVM9N2MjC42_i-mGFMgvKlKOZuC-4-d8z_I,1198
2
+ tango/client.py,sha256=Pr9lYMj5diZ2ecyrHL88y2B_WF41tYZ9Qm2S2xT3NPU,92545
3
3
  tango/exceptions.py,sha256=aRvDm0dUCEtNDfRVYCX7SEDdd1WlIVVY6sN78Tzo-a0,3114
4
- tango/models.py,sha256=EDKsZ4fsxkAbDhX5roOfiKYyPjZaTV-5QKrPaQCsb0Y,20959
4
+ tango/models.py,sha256=fe8SDB1n8sG4DgVnCYnpkrWpY6Kz4CE475lh8MCCJfs,23207
5
5
  tango/shapes/__init__.py,sha256=7ea1WU74jp4znhNw-gXruag6m6eyPZtbVgbDFmFUWro,1072
6
- tango/shapes/explicit_schemas.py,sha256=H4pYs0LCTSV5msRCxftmgiM_-3sc4LsqpDPgj36DkPY,55202
6
+ tango/shapes/explicit_schemas.py,sha256=_99ywtXRQoJ6KxesVTzWjOWMzxE1h9f2YGx19kW0FnA,57834
7
7
  tango/shapes/factory.py,sha256=ytpMi5Uw72XZ8MimhuSsLDVXF3zO_Zt3_tAL6NF7LnU,34318
8
8
  tango/shapes/generator.py,sha256=61V1T3lm8Ps_KSMJAezQJLQVFbNKt1jtoLyhiqNtFTs,23380
9
9
  tango/shapes/models.py,sha256=h3pIhOqrrdlN953Y6r0oney5HFbKPOD-frRndRWimJ0,3018
10
10
  tango/shapes/parser.py,sha256=k6OsI2w3GH6-IBbc-XTLgL1mWH7bMf7A_dA6pr1xKfw,24619
11
11
  tango/shapes/schema.py,sha256=VRPOB1sBdjFyimNchrZKIpTHn83CyX4RfU9077aQtIU,14136
12
12
  tango/shapes/types.py,sha256=27jrAE0VIdrKaLjR_FK71hfIIGX2Tg3ex7REEBV1TFE,1301
13
- tango_python-0.4.4.dist-info/METADATA,sha256=QGTVP5M9kTipieVrII_ocUU061ukbG61bgDfscHIpgg,17595
14
- tango_python-0.4.4.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
15
- tango_python-0.4.4.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
16
- tango_python-0.4.4.dist-info/RECORD,,
13
+ tango_python-0.5.0.dist-info/METADATA,sha256=Ht7PpwMjaP7R4yX-tWAvdvzsRIzlfFu5ZPwJybmH04s,17595
14
+ tango_python-0.5.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
15
+ tango_python-0.5.0.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
16
+ tango_python-0.5.0.dist-info/RECORD,,