Skip to content

Commit 332402f

Browse files
vdavezclaude
andcommitted
feat(client): add shape/flat/flat_lists to reference-data endpoints
The tango API has always supported the shape system on NAICS, PSC, Assistance Listings, Business Types, and MAS SINs viewsets (all use `ShapeOnDemandMixin`), but the SDK's list/get methods for these resources didn't expose `shape` / `flat` / `flat_lists`. Adds the three params to: list_naics/get_naics, list_psc/get_psc, list_assistance_listings/ get_assistance_listing, list_business_types/get_business_type, and list_mas_sins/get_mas_sin. When `shape` is omitted, the API applies its own per-resource default — existing callers see no behavior change. `list_business_types` returns raw dicts when a shape is supplied so the caller gets exactly the shape requested (otherwise still yields `BusinessType` instances). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 135eae9 commit 332402f

2 files changed

Lines changed: 165 additions & 17 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- Reference-data list/get methods now accept `shape` (and the associated
12+
`flat` / `flat_lists`) parameters, matching the underlying API which has
13+
always supported the shape system via `ShapeOnDemandMixin`. Affected:
14+
`list_naics` / `get_naics`, `list_psc` / `get_psc`,
15+
`list_assistance_listings` / `get_assistance_listing`,
16+
`list_business_types` / `get_business_type`,
17+
`list_mas_sins` / `get_mas_sin`. When `shape` is omitted, behavior is
18+
unchanged — the API applies its own per-resource default.
19+
`list_business_types` returns raw dicts (instead of `BusinessType`
20+
instances) when a `shape` is supplied so the caller gets exactly the
21+
shape requested.
22+
1023
## [1.1.2] - 2026-06-04
1124

1225
### Changed

tango/client.py

Lines changed: 152 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1923,15 +1923,37 @@ def list_vehicle_orders(
19231923
)
19241924

19251925
# Business Types endpoints
1926-
def list_business_types(self, page: int = 1, limit: int = 25) -> PaginatedResponse:
1927-
"""List business types"""
1928-
params = {"page": page, "limit": min(limit, 100)}
1926+
def list_business_types(
1927+
self,
1928+
page: int = 1,
1929+
limit: int = 25,
1930+
shape: str | None = None,
1931+
flat: bool = False,
1932+
flat_lists: bool = False,
1933+
) -> PaginatedResponse:
1934+
"""List business types.
1935+
1936+
When ``shape`` is omitted the API applies its own default
1937+
(``name,code``) and results are returned as :class:`BusinessType`
1938+
instances. When ``shape`` is provided, raw dicts are returned so the
1939+
caller can rely on the exact shape requested.
1940+
"""
1941+
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
1942+
if shape:
1943+
params["shape"] = shape
1944+
if flat:
1945+
params["flat"] = "true"
1946+
if flat_lists:
1947+
params["flat_lists"] = "true"
19291948
data = self._get("/api/business_types/", params)
1949+
results: list[Any] = (
1950+
list(data["results"]) if shape else [BusinessType(**btype) for btype in data["results"]]
1951+
)
19301952
return PaginatedResponse(
19311953
count=data["count"],
19321954
next=data.get("next"),
19331955
previous=data.get("previous"),
1934-
results=[BusinessType(**btype) for btype in data["results"]],
1956+
results=results,
19351957
)
19361958

19371959
def list_naics(
@@ -1945,8 +1967,17 @@ def list_naics(
19451967
revenue_limit_gte: int | None = None,
19461968
revenue_limit_lte: int | None = None,
19471969
search: str | None = None,
1970+
shape: str | None = None,
1971+
flat: bool = False,
1972+
flat_lists: bool = False,
19481973
) -> PaginatedResponse:
1949-
"""List NAICS codes (`/api/naics/`)."""
1974+
"""List NAICS codes (`/api/naics/`).
1975+
1976+
When ``shape`` is omitted the API applies its own default. Passing any
1977+
of the ``revenue_limit*`` / ``employee_limit*`` filters causes the API
1978+
to widen the default shape to include ``size_standards`` and
1979+
``federal_obligations``.
1980+
"""
19501981
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
19511982
if employee_limit is not None:
19521983
params["employee_limit"] = employee_limit
@@ -1962,6 +1993,12 @@ def list_naics(
19621993
params["revenue_limit_lte"] = revenue_limit_lte
19631994
if search is not None:
19641995
params["search"] = search
1996+
if shape:
1997+
params["shape"] = shape
1998+
if flat:
1999+
params["flat"] = "true"
2000+
if flat_lists:
2001+
params["flat_lists"] = "true"
19652002
data = self._get("/api/naics/", params)
19662003
return PaginatedResponse(
19672004
count=data.get("count", 0),
@@ -3344,9 +3381,22 @@ def get_department(self, code: str) -> dict[str, Any]:
33443381
raise TangoValidationError("Department code is required")
33453382
return self._get(f"/api/departments/{code}/")
33463383

3347-
def list_psc(self, page: int = 1, limit: int = 25) -> PaginatedResponse[dict[str, Any]]:
3384+
def list_psc(
3385+
self,
3386+
page: int = 1,
3387+
limit: int = 25,
3388+
shape: str | None = None,
3389+
flat: bool = False,
3390+
flat_lists: bool = False,
3391+
) -> PaginatedResponse[dict[str, Any]]:
33483392
"""List Product Service Codes (`/api/psc/`)."""
33493393
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
3394+
if shape:
3395+
params["shape"] = shape
3396+
if flat:
3397+
params["flat"] = "true"
3398+
if flat_lists:
3399+
params["flat_lists"] = "true"
33503400
data = self._get("/api/psc/", params)
33513401
return PaginatedResponse(
33523402
count=int(data.get("count", 0)),
@@ -3355,11 +3405,24 @@ def list_psc(self, page: int = 1, limit: int = 25) -> PaginatedResponse[dict[str
33553405
results=list(data.get("results") or []),
33563406
)
33573407

3358-
def get_psc(self, code: str) -> dict[str, Any]:
3408+
def get_psc(
3409+
self,
3410+
code: str,
3411+
shape: str | None = None,
3412+
flat: bool = False,
3413+
flat_lists: bool = False,
3414+
) -> dict[str, Any]:
33593415
"""Get a Product Service Code by code (`/api/psc/{code}/`)."""
33603416
if not code:
33613417
raise TangoValidationError("PSC code is required")
3362-
return self._get(f"/api/psc/{code}/")
3418+
params: dict[str, Any] = {}
3419+
if shape:
3420+
params["shape"] = shape
3421+
if flat:
3422+
params["flat"] = "true"
3423+
if flat_lists:
3424+
params["flat_lists"] = "true"
3425+
return self._get(f"/api/psc/{code}/", params)
33633426

33643427
def get_psc_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
33653428
"""Get rolling PSC metrics (`/api/psc/{code}/metrics/{months}/{period_grouping}/`).
@@ -3374,29 +3437,66 @@ def get_psc_metrics(self, code: str, months: int, period_grouping: str) -> dict[
33743437
raise TangoValidationError("PSC code is required")
33753438
return self._get(f"/api/psc/{code}/metrics/{months}/{period_grouping}/")
33763439

3377-
def get_naics(self, code: str) -> dict[str, Any]:
3440+
def get_naics(
3441+
self,
3442+
code: str,
3443+
shape: str | None = None,
3444+
flat: bool = False,
3445+
flat_lists: bool = False,
3446+
) -> dict[str, Any]:
33783447
"""Get a NAICS code by code (`/api/naics/{code}/`)."""
33793448
if not code:
33803449
raise TangoValidationError("NAICS code is required")
3381-
return self._get(f"/api/naics/{code}/")
3450+
params: dict[str, Any] = {}
3451+
if shape:
3452+
params["shape"] = shape
3453+
if flat:
3454+
params["flat"] = "true"
3455+
if flat_lists:
3456+
params["flat_lists"] = "true"
3457+
return self._get(f"/api/naics/{code}/", params)
33823458

33833459
def get_naics_metrics(self, code: str, months: int, period_grouping: str) -> dict[str, Any]:
33843460
"""Get rolling NAICS metrics (`/api/naics/{code}/metrics/{months}/{period_grouping}/`)."""
33853461
if not code:
33863462
raise TangoValidationError("NAICS code is required")
33873463
return self._get(f"/api/naics/{code}/metrics/{months}/{period_grouping}/")
33883464

3389-
def get_business_type(self, code: str) -> dict[str, Any]:
3465+
def get_business_type(
3466+
self,
3467+
code: str,
3468+
shape: str | None = None,
3469+
flat: bool = False,
3470+
flat_lists: bool = False,
3471+
) -> dict[str, Any]:
33903472
"""Get a business type by code (`/api/business_types/{code}/`)."""
33913473
if not code:
33923474
raise TangoValidationError("Business type code is required")
3393-
return self._get(f"/api/business_types/{code}/")
3475+
params: dict[str, Any] = {}
3476+
if shape:
3477+
params["shape"] = shape
3478+
if flat:
3479+
params["flat"] = "true"
3480+
if flat_lists:
3481+
params["flat_lists"] = "true"
3482+
return self._get(f"/api/business_types/{code}/", params)
33943483

33953484
def list_assistance_listings(
3396-
self, page: int = 1, limit: int = 25
3485+
self,
3486+
page: int = 1,
3487+
limit: int = 25,
3488+
shape: str | None = None,
3489+
flat: bool = False,
3490+
flat_lists: bool = False,
33973491
) -> PaginatedResponse[dict[str, Any]]:
33983492
"""List Assistance Listings (CFDA programs) (`/api/assistance_listings/`)."""
33993493
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
3494+
if shape:
3495+
params["shape"] = shape
3496+
if flat:
3497+
params["flat"] = "true"
3498+
if flat_lists:
3499+
params["flat_lists"] = "true"
34003500
data = self._get("/api/assistance_listings/", params)
34013501
return PaginatedResponse(
34023502
count=int(data.get("count", 0)),
@@ -3405,22 +3505,44 @@ def list_assistance_listings(
34053505
results=list(data.get("results") or []),
34063506
)
34073507

3408-
def get_assistance_listing(self, number: str) -> dict[str, Any]:
3508+
def get_assistance_listing(
3509+
self,
3510+
number: str,
3511+
shape: str | None = None,
3512+
flat: bool = False,
3513+
flat_lists: bool = False,
3514+
) -> dict[str, Any]:
34093515
"""Get an Assistance Listing by CFDA number (`/api/assistance_listings/{number}/`)."""
34103516
if not number:
34113517
raise TangoValidationError("Assistance listing number is required")
3412-
return self._get(f"/api/assistance_listings/{number}/")
3518+
params: dict[str, Any] = {}
3519+
if shape:
3520+
params["shape"] = shape
3521+
if flat:
3522+
params["flat"] = "true"
3523+
if flat_lists:
3524+
params["flat_lists"] = "true"
3525+
return self._get(f"/api/assistance_listings/{number}/", params)
34133526

34143527
def list_mas_sins(
34153528
self,
34163529
page: int = 1,
34173530
limit: int = 25,
34183531
search: str | None = None,
3532+
shape: str | None = None,
3533+
flat: bool = False,
3534+
flat_lists: bool = False,
34193535
) -> PaginatedResponse[dict[str, Any]]:
34203536
"""List GSA MAS SINs (`/api/mas_sins/`)."""
34213537
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
34223538
if search is not None:
34233539
params["search"] = search
3540+
if shape:
3541+
params["shape"] = shape
3542+
if flat:
3543+
params["flat"] = "true"
3544+
if flat_lists:
3545+
params["flat_lists"] = "true"
34243546
data = self._get("/api/mas_sins/", params)
34253547
return PaginatedResponse(
34263548
count=int(data.get("count", 0)),
@@ -3429,11 +3551,24 @@ def list_mas_sins(
34293551
results=list(data.get("results") or []),
34303552
)
34313553

3432-
def get_mas_sin(self, sin: str) -> dict[str, Any]:
3554+
def get_mas_sin(
3555+
self,
3556+
sin: str,
3557+
shape: str | None = None,
3558+
flat: bool = False,
3559+
flat_lists: bool = False,
3560+
) -> dict[str, Any]:
34333561
"""Get a MAS SIN by code (`/api/mas_sins/{sin}/`)."""
34343562
if not sin:
34353563
raise TangoValidationError("MAS SIN is required")
3436-
return self._get(f"/api/mas_sins/{sin}/")
3564+
params: dict[str, Any] = {}
3565+
if shape:
3566+
params["shape"] = shape
3567+
if flat:
3568+
params["flat"] = "true"
3569+
if flat_lists:
3570+
params["flat_lists"] = "true"
3571+
return self._get(f"/api/mas_sins/{sin}/", params)
34373572

34383573
# ============================================================================
34393574
# Entity sub-resources

0 commit comments

Comments
 (0)