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 +3 -1
- tango/client.py +107 -0
- tango/models.py +54 -0
- tango/shapes/explicit_schemas.py +63 -0
- {tango_python-0.4.4.dist-info → tango_python-0.5.0.dist-info}/METADATA +1 -1
- {tango_python-0.4.4.dist-info → tango_python-0.5.0.dist-info}/RECORD +8 -8
- {tango_python-0.4.4.dist-info → tango_python-0.5.0.dist-info}/WHEEL +0 -0
- {tango_python-0.4.4.dist-info → tango_python-0.5.0.dist-info}/licenses/LICENSE +0 -0
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.
|
|
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
|
+
)
|
tango/shapes/explicit_schemas.py
CHANGED
|
@@ -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,16 +1,16 @@
|
|
|
1
|
-
tango/__init__.py,sha256=
|
|
2
|
-
tango/client.py,sha256=
|
|
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=
|
|
4
|
+
tango/models.py,sha256=fe8SDB1n8sG4DgVnCYnpkrWpY6Kz4CE475lh8MCCJfs,23207
|
|
5
5
|
tango/shapes/__init__.py,sha256=7ea1WU74jp4znhNw-gXruag6m6eyPZtbVgbDFmFUWro,1072
|
|
6
|
-
tango/shapes/explicit_schemas.py,sha256=
|
|
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.
|
|
14
|
-
tango_python-0.
|
|
15
|
-
tango_python-0.
|
|
16
|
-
tango_python-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|