tango-python 1.7.0__py3-none-any.whl → 1.9.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 +11 -1
- tango/client.py +416 -51
- tango/exceptions.py +10 -0
- tango/models.py +188 -0
- tango/shapes/explicit_schemas.py +171 -0
- tango/shapes/generated_overlay.py +41 -19
- {tango_python-1.7.0.dist-info → tango_python-1.9.0.dist-info}/METADATA +10 -1
- {tango_python-1.7.0.dist-info → tango_python-1.9.0.dist-info}/RECORD +11 -11
- {tango_python-1.7.0.dist-info → tango_python-1.9.0.dist-info}/WHEEL +0 -0
- {tango_python-1.7.0.dist-info → tango_python-1.9.0.dist-info}/entry_points.txt +0 -0
- {tango_python-1.7.0.dist-info → tango_python-1.9.0.dist-info}/licenses/LICENSE +0 -0
tango/__init__.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from .client import TangoClient
|
|
4
4
|
from .exceptions import (
|
|
5
5
|
TangoAPIError,
|
|
6
|
+
TangoAttachmentLinkError,
|
|
6
7
|
TangoAuthError,
|
|
7
8
|
TangoNotFoundError,
|
|
8
9
|
TangoRateLimitError,
|
|
@@ -14,7 +15,11 @@ from .models import (
|
|
|
14
15
|
DibbsAward,
|
|
15
16
|
DibbsRfp,
|
|
16
17
|
DibbsRfq,
|
|
18
|
+
EbuyAccess,
|
|
19
|
+
EbuyAttachment,
|
|
20
|
+
EbuyRequest,
|
|
17
21
|
Exclusion,
|
|
22
|
+
FederalRegisterDocument,
|
|
18
23
|
GsaElibraryContract,
|
|
19
24
|
ITDashboardInvestment,
|
|
20
25
|
PaginatedResponse,
|
|
@@ -54,19 +59,24 @@ from .webhooks import (
|
|
|
54
59
|
)
|
|
55
60
|
from .webhooks.receiver import Delivery, WebhookReceiver
|
|
56
61
|
|
|
57
|
-
__version__ = "1.
|
|
62
|
+
__version__ = "1.9.0"
|
|
58
63
|
__all__ = [
|
|
59
64
|
"TangoClient",
|
|
60
65
|
"TangoAPIError",
|
|
61
66
|
"TangoAuthError",
|
|
62
67
|
"TangoNotFoundError",
|
|
63
68
|
"TangoValidationError",
|
|
69
|
+
"TangoAttachmentLinkError",
|
|
64
70
|
"TangoRateLimitError",
|
|
65
71
|
"RateLimitInfo",
|
|
66
72
|
"ResolveCandidate",
|
|
67
73
|
"ResolveResult",
|
|
68
74
|
"BudgetAccount",
|
|
69
75
|
"ContractAppeal",
|
|
76
|
+
"FederalRegisterDocument",
|
|
77
|
+
"EbuyAccess",
|
|
78
|
+
"EbuyAttachment",
|
|
79
|
+
"EbuyRequest",
|
|
70
80
|
"DibbsAward",
|
|
71
81
|
"DibbsRfp",
|
|
72
82
|
"DibbsRfq",
|
tango/client.py
CHANGED
|
@@ -5,12 +5,13 @@ import warnings
|
|
|
5
5
|
from datetime import date, datetime
|
|
6
6
|
from decimal import Decimal
|
|
7
7
|
from typing import Any, Literal, cast
|
|
8
|
-
from urllib.parse import urljoin
|
|
8
|
+
from urllib.parse import quote, urljoin
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
|
|
12
12
|
from tango.exceptions import (
|
|
13
13
|
TangoAPIError,
|
|
14
|
+
TangoAttachmentLinkError,
|
|
14
15
|
TangoAuthError,
|
|
15
16
|
TangoNotFoundError,
|
|
16
17
|
TangoRateLimitError,
|
|
@@ -28,8 +29,11 @@ from tango.models import (
|
|
|
28
29
|
DibbsAward,
|
|
29
30
|
DibbsRfp,
|
|
30
31
|
DibbsRfq,
|
|
32
|
+
EbuyAccess,
|
|
33
|
+
EbuyRequest,
|
|
31
34
|
Entity,
|
|
32
35
|
Exclusion,
|
|
36
|
+
FederalRegisterDocument,
|
|
33
37
|
Forecast,
|
|
34
38
|
Grant,
|
|
35
39
|
GsaElibraryContract,
|
|
@@ -174,55 +178,57 @@ class TangoClient:
|
|
|
174
178
|
self._last_response_headers = response.headers
|
|
175
179
|
self._last_rate_limit_info = self._parse_rate_limit_headers(response.headers)
|
|
176
180
|
|
|
177
|
-
|
|
178
|
-
raise TangoAuthError(
|
|
179
|
-
"Invalid API key or authentication required", response.status_code
|
|
180
|
-
)
|
|
181
|
-
elif response.status_code == 404:
|
|
182
|
-
raise TangoNotFoundError("Resource not found", response.status_code)
|
|
183
|
-
elif response.status_code == 400:
|
|
184
|
-
error_data = response.json() if response.content else {}
|
|
185
|
-
error_msg = "Invalid request parameters"
|
|
186
|
-
if error_data:
|
|
187
|
-
# Try to extract a more specific error message
|
|
188
|
-
if isinstance(error_data, dict):
|
|
189
|
-
detail = (
|
|
190
|
-
error_data.get("detail")
|
|
191
|
-
or error_data.get("message")
|
|
192
|
-
or error_data.get("error")
|
|
193
|
-
)
|
|
194
|
-
if detail:
|
|
195
|
-
error_msg = f"Invalid request parameters: {detail}"
|
|
196
|
-
issues = error_data.get("issues")
|
|
197
|
-
if isinstance(issues, list):
|
|
198
|
-
rejected = [
|
|
199
|
-
f"{issue['path']} ({issue['reason']})"
|
|
200
|
-
if issue.get("reason")
|
|
201
|
-
else str(issue["path"])
|
|
202
|
-
for issue in issues
|
|
203
|
-
if isinstance(issue, dict) and issue.get("path")
|
|
204
|
-
]
|
|
205
|
-
if rejected:
|
|
206
|
-
error_msg = f"{error_msg}: {', '.join(rejected)}"
|
|
207
|
-
raise TangoValidationError(
|
|
208
|
-
error_msg,
|
|
209
|
-
response.status_code,
|
|
210
|
-
error_data,
|
|
211
|
-
)
|
|
212
|
-
elif response.status_code == 429:
|
|
213
|
-
error_data = response.json() if response.content else {}
|
|
214
|
-
detail = error_data.get("detail", "Rate limit exceeded")
|
|
215
|
-
raise TangoRateLimitError(detail, response.status_code, error_data)
|
|
216
|
-
elif not response.is_success:
|
|
217
|
-
raise TangoAPIError(
|
|
218
|
-
f"API request failed with status {response.status_code}", response.status_code
|
|
219
|
-
)
|
|
181
|
+
self._raise_for_status(response)
|
|
220
182
|
|
|
221
183
|
return response.json() if response.content else {}
|
|
222
184
|
|
|
223
185
|
except httpx.HTTPError as e:
|
|
224
186
|
raise TangoAPIError(f"Request failed: {str(e)}") from e
|
|
225
187
|
|
|
188
|
+
def _raise_for_status(self, response: httpx.Response) -> None:
|
|
189
|
+
"""Raise the SDK exception matching a non-success response; return quietly on success."""
|
|
190
|
+
if response.status_code == 401:
|
|
191
|
+
raise TangoAuthError("Invalid API key or authentication required", response.status_code)
|
|
192
|
+
elif response.status_code == 404:
|
|
193
|
+
raise TangoNotFoundError("Resource not found", response.status_code)
|
|
194
|
+
elif response.status_code == 400:
|
|
195
|
+
error_data = response.json() if response.content else {}
|
|
196
|
+
error_msg = "Invalid request parameters"
|
|
197
|
+
if error_data:
|
|
198
|
+
# Try to extract a more specific error message
|
|
199
|
+
if isinstance(error_data, dict):
|
|
200
|
+
detail = (
|
|
201
|
+
error_data.get("detail")
|
|
202
|
+
or error_data.get("message")
|
|
203
|
+
or error_data.get("error")
|
|
204
|
+
)
|
|
205
|
+
if detail:
|
|
206
|
+
error_msg = f"Invalid request parameters: {detail}"
|
|
207
|
+
issues = error_data.get("issues")
|
|
208
|
+
if isinstance(issues, list):
|
|
209
|
+
rejected = [
|
|
210
|
+
f"{issue['path']} ({issue['reason']})"
|
|
211
|
+
if issue.get("reason")
|
|
212
|
+
else str(issue["path"])
|
|
213
|
+
for issue in issues
|
|
214
|
+
if isinstance(issue, dict) and issue.get("path")
|
|
215
|
+
]
|
|
216
|
+
if rejected:
|
|
217
|
+
error_msg = f"{error_msg}: {', '.join(rejected)}"
|
|
218
|
+
raise TangoValidationError(
|
|
219
|
+
error_msg,
|
|
220
|
+
response.status_code,
|
|
221
|
+
error_data,
|
|
222
|
+
)
|
|
223
|
+
elif response.status_code == 429:
|
|
224
|
+
error_data = response.json() if response.content else {}
|
|
225
|
+
detail = error_data.get("detail", "Rate limit exceeded")
|
|
226
|
+
raise TangoRateLimitError(detail, response.status_code, error_data)
|
|
227
|
+
elif not response.is_success:
|
|
228
|
+
raise TangoAPIError(
|
|
229
|
+
f"API request failed with status {response.status_code}", response.status_code
|
|
230
|
+
)
|
|
231
|
+
|
|
226
232
|
def _get(self, endpoint: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
227
233
|
"""Make a GET request"""
|
|
228
234
|
return self._request("GET", endpoint, params=params)
|
|
@@ -1601,18 +1607,18 @@ class TangoClient:
|
|
|
1601
1607
|
cio_rating_max: int | None = None,
|
|
1602
1608
|
performance_risk: bool | None = None,
|
|
1603
1609
|
previous_uii: str | None = None,
|
|
1610
|
+
agency: str | None = None,
|
|
1604
1611
|
) -> PaginatedResponse:
|
|
1605
1612
|
"""List federal IT investments from the IT Dashboard (`/api/itdashboard/`).
|
|
1606
1613
|
|
|
1607
1614
|
Filters are tier-gated by the API:
|
|
1608
1615
|
|
|
1609
|
-
- **Free**: ``search`` (full-text across UII, title, description, agency,
|
|
1610
|
-
bureau) and ``previous_uii`` — following a retired identifier forward to
|
|
1611
|
-
whatever superseded it is recovery, not analysis, so it is ungated
|
|
1616
|
+
- **Free**: ``search`` (full-text across UII, title, description, agency, bureau), ``agency_name`` (text), ``agency`` and ``previous_uii`` — following a retired identifier forward to whatever superseded it is recovery, not analysis, so it is ungated
|
|
1612
1617
|
- **Pro**: ``agency_code``, ``type_of_investment``,
|
|
1613
1618
|
``updated_time_after`` / ``updated_time_before``
|
|
1614
|
-
- **Business+**: ``
|
|
1615
|
-
|
|
1619
|
+
- **Business+**: ``cio_rating``, ``cio_rating_max``, ``performance_risk``
|
|
1620
|
+
|
|
1621
|
+
``agency`` takes a Tango agency name, abbreviation, code or organization key, e.g. ``DOT``, and matches the whole organization subtree, so a department includes its sub-agencies. OR several with ``|``.
|
|
1616
1622
|
|
|
1617
1623
|
Hitting a gated filter on a lower tier returns a 403 with upgrade info.
|
|
1618
1624
|
|
|
@@ -1632,6 +1638,7 @@ class TangoClient:
|
|
|
1632
1638
|
params["flat_lists"] = "true"
|
|
1633
1639
|
for k, val in (
|
|
1634
1640
|
("search", search),
|
|
1641
|
+
("agency", agency),
|
|
1635
1642
|
("agency_code", agency_code),
|
|
1636
1643
|
("agency_name", agency_name),
|
|
1637
1644
|
("type_of_investment", type_of_investment),
|
|
@@ -2818,6 +2825,358 @@ class TangoClient:
|
|
|
2818
2825
|
data = self._get(f"/api/contract_appeals/{appeal_id}/", params)
|
|
2819
2826
|
return self._parse_response_with_shape(data, shape, ContractAppeal, flat, flat_lists)
|
|
2820
2827
|
|
|
2828
|
+
# ============================================================================
|
|
2829
|
+
# Federal Register documents
|
|
2830
|
+
# ============================================================================
|
|
2831
|
+
|
|
2832
|
+
def list_federal_register_documents(
|
|
2833
|
+
self,
|
|
2834
|
+
page: int = 1,
|
|
2835
|
+
limit: int = 25,
|
|
2836
|
+
shape: str | None = None,
|
|
2837
|
+
flat: bool = False,
|
|
2838
|
+
flat_lists: bool = False,
|
|
2839
|
+
search: str | None = None,
|
|
2840
|
+
document_number: str | None = None,
|
|
2841
|
+
type: str | None = None,
|
|
2842
|
+
agency: str | None = None,
|
|
2843
|
+
fr_agency: str | None = None,
|
|
2844
|
+
publication_date_after: str | None = None,
|
|
2845
|
+
publication_date_before: str | None = None,
|
|
2846
|
+
effective_on_after: str | None = None,
|
|
2847
|
+
effective_on_before: str | None = None,
|
|
2848
|
+
comments_close_on_after: str | None = None,
|
|
2849
|
+
comments_close_on_before: str | None = None,
|
|
2850
|
+
comments_open: bool | None = None,
|
|
2851
|
+
cfr_title: str | None = None,
|
|
2852
|
+
cfr_part: str | None = None,
|
|
2853
|
+
significant: bool | None = None,
|
|
2854
|
+
rin: str | None = None,
|
|
2855
|
+
executive_order_number: str | None = None,
|
|
2856
|
+
ordering: str | None = None,
|
|
2857
|
+
) -> PaginatedResponse:
|
|
2858
|
+
"""
|
|
2859
|
+
List Federal Register documents.
|
|
2860
|
+
|
|
2861
|
+
Rules, proposed rules, notices and presidential documents published in the Federal Register since 1994.
|
|
2862
|
+
|
|
2863
|
+
Args:
|
|
2864
|
+
page: Page number
|
|
2865
|
+
limit: Results per page (max 100)
|
|
2866
|
+
shape: Response shape string (defaults to minimal shape)
|
|
2867
|
+
flat: If True, flatten nested objects in shaped response
|
|
2868
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
2869
|
+
search: Ranked full-text search over the title, abstract and action. Wrap in double quotes for a phrase
|
|
2870
|
+
document_number: Exact FR document number, e.g. ``2016-31922``. OR several with ``|``. A number the Federal Register reused before 2016 matches every document that carries it
|
|
2871
|
+
type: ``Notice``, ``Rule``, ``Proposed Rule``, ``Presidential Document``, ``Correction``, ``Sunshine Act Document`` or ``Uncategorized Document`` (case-insensitive). OR several with ``|``. Documents published before 2008 are mostly ``Uncategorized Document``
|
|
2872
|
+
agency: A Tango agency name, abbreviation, code or organization key, e.g. ``EPA``. Matches a document when any agency it lists falls within that organization, so a department includes its sub-agencies. OR several with ``|``
|
|
2873
|
+
fr_agency: The Federal Register's own agency slug, e.g. ``environmental-protection-agency``. OR several with ``|``
|
|
2874
|
+
publication_date_after: Published on or after (YYYY-MM-DD)
|
|
2875
|
+
publication_date_before: Published on or before (YYYY-MM-DD)
|
|
2876
|
+
effective_on_after: Effective on or after (YYYY-MM-DD)
|
|
2877
|
+
effective_on_before: Effective on or before (YYYY-MM-DD)
|
|
2878
|
+
comments_close_on_after: Comment period closes on or after (YYYY-MM-DD)
|
|
2879
|
+
comments_close_on_before: Comment period closes on or before (YYYY-MM-DD)
|
|
2880
|
+
comments_open: ``True`` for documents whose comment period closes today or later, ``False`` for those already closed. Documents with no comment deadline match neither
|
|
2881
|
+
cfr_title: A CFR title number, e.g. ``40``
|
|
2882
|
+
cfr_part: A CFR part number, e.g. ``52``. Requires ``cfr_title``, and both must match the same CFR reference
|
|
2883
|
+
significant: Significant under Executive Order 12866
|
|
2884
|
+
rin: A Regulation Identifier Number, e.g. ``2060-AV16``. OR several with ``|``
|
|
2885
|
+
executive_order_number: Exact executive order number
|
|
2886
|
+
ordering: Sort field — ``publication_date`` (the default, as ``-publication_date``), ``effective_on``, ``comments_close_on``, ``document_number`` or ``rank``. ``rank`` requires a non-empty ``search``
|
|
2887
|
+
"""
|
|
2888
|
+
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
2889
|
+
|
|
2890
|
+
if shape is None:
|
|
2891
|
+
shape = ShapeConfig.FEDERAL_REGISTER_MINIMAL
|
|
2892
|
+
if shape:
|
|
2893
|
+
params["shape"] = shape
|
|
2894
|
+
if flat:
|
|
2895
|
+
params["flat"] = "true"
|
|
2896
|
+
if flat_lists:
|
|
2897
|
+
params["flat_lists"] = "true"
|
|
2898
|
+
|
|
2899
|
+
for key, val in (
|
|
2900
|
+
("search", search),
|
|
2901
|
+
("document_number", document_number),
|
|
2902
|
+
("type", type),
|
|
2903
|
+
("agency", agency),
|
|
2904
|
+
("fr_agency", fr_agency),
|
|
2905
|
+
("publication_date_after", publication_date_after),
|
|
2906
|
+
("publication_date_before", publication_date_before),
|
|
2907
|
+
("effective_on_after", effective_on_after),
|
|
2908
|
+
("effective_on_before", effective_on_before),
|
|
2909
|
+
("comments_close_on_after", comments_close_on_after),
|
|
2910
|
+
("comments_close_on_before", comments_close_on_before),
|
|
2911
|
+
("comments_open", comments_open),
|
|
2912
|
+
("cfr_title", cfr_title),
|
|
2913
|
+
("cfr_part", cfr_part),
|
|
2914
|
+
("significant", significant),
|
|
2915
|
+
("rin", rin),
|
|
2916
|
+
("executive_order_number", executive_order_number),
|
|
2917
|
+
("ordering", ordering),
|
|
2918
|
+
):
|
|
2919
|
+
if val is not None:
|
|
2920
|
+
params[key] = val
|
|
2921
|
+
|
|
2922
|
+
data = self._get("/api/federal_register/", params)
|
|
2923
|
+
|
|
2924
|
+
results = [
|
|
2925
|
+
self._parse_response_with_shape(item, shape, FederalRegisterDocument, flat, flat_lists)
|
|
2926
|
+
for item in data["results"]
|
|
2927
|
+
]
|
|
2928
|
+
|
|
2929
|
+
return PaginatedResponse(
|
|
2930
|
+
count=data["count"],
|
|
2931
|
+
next=data.get("next"),
|
|
2932
|
+
previous=data.get("previous"),
|
|
2933
|
+
results=results,
|
|
2934
|
+
meta=data.get("meta"),
|
|
2935
|
+
)
|
|
2936
|
+
|
|
2937
|
+
def get_federal_register_document(
|
|
2938
|
+
self,
|
|
2939
|
+
document_uuid: str,
|
|
2940
|
+
shape: str | None = None,
|
|
2941
|
+
flat: bool = False,
|
|
2942
|
+
flat_lists: bool = False,
|
|
2943
|
+
) -> Any:
|
|
2944
|
+
"""
|
|
2945
|
+
Get a single Federal Register document by uuid.
|
|
2946
|
+
|
|
2947
|
+
The detail route takes the document's ``uuid``, not its ``document_number``, which is not unique on its own.
|
|
2948
|
+
To look a document up by number, use ``list_federal_register_documents(document_number=...)``.
|
|
2949
|
+
|
|
2950
|
+
Args:
|
|
2951
|
+
document_uuid: Document UUID
|
|
2952
|
+
shape: Response shape string (defaults to the comprehensive shape). Name ``full_text`` here to get the document's plain text, which can run to several MB
|
|
2953
|
+
flat: If True, flatten nested objects in shaped response
|
|
2954
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
2955
|
+
"""
|
|
2956
|
+
params: dict[str, Any] = {}
|
|
2957
|
+
if shape is None:
|
|
2958
|
+
shape = ShapeConfig.FEDERAL_REGISTER_COMPREHENSIVE
|
|
2959
|
+
if shape:
|
|
2960
|
+
params["shape"] = shape
|
|
2961
|
+
if flat:
|
|
2962
|
+
params["flat"] = "true"
|
|
2963
|
+
if flat_lists:
|
|
2964
|
+
params["flat_lists"] = "true"
|
|
2965
|
+
|
|
2966
|
+
data = self._get(f"/api/federal_register/{document_uuid}/", params)
|
|
2967
|
+
return self._parse_response_with_shape(
|
|
2968
|
+
data, shape, FederalRegisterDocument, flat, flat_lists
|
|
2969
|
+
)
|
|
2970
|
+
|
|
2971
|
+
# ============================================================================
|
|
2972
|
+
# GSA eBuy requests
|
|
2973
|
+
# ============================================================================
|
|
2974
|
+
|
|
2975
|
+
def list_ebuy_requests(
|
|
2976
|
+
self,
|
|
2977
|
+
page: int = 1,
|
|
2978
|
+
limit: int = 25,
|
|
2979
|
+
shape: str | None = None,
|
|
2980
|
+
flat: bool = False,
|
|
2981
|
+
flat_lists: bool = False,
|
|
2982
|
+
search: str | None = None,
|
|
2983
|
+
rfq_id: str | None = None,
|
|
2984
|
+
reference_number: str | None = None,
|
|
2985
|
+
request_type: str | None = None,
|
|
2986
|
+
status: str | None = None,
|
|
2987
|
+
sin: str | None = None,
|
|
2988
|
+
schedule: str | None = None,
|
|
2989
|
+
buyer_agency: str | None = None,
|
|
2990
|
+
agency: str | None = None,
|
|
2991
|
+
contract_number: str | None = None,
|
|
2992
|
+
issue_date_after: str | None = None,
|
|
2993
|
+
issue_date_before: str | None = None,
|
|
2994
|
+
close_date_after: str | None = None,
|
|
2995
|
+
close_date_before: str | None = None,
|
|
2996
|
+
ordering: str | None = None,
|
|
2997
|
+
) -> PaginatedResponse:
|
|
2998
|
+
"""
|
|
2999
|
+
List GSA eBuy requests (RFQs, RFPs and RFIs).
|
|
3000
|
+
|
|
3001
|
+
Requires the Pro tier or above; below it the API returns 403.
|
|
3002
|
+
Results are scoped to your account: you see only requests posted under the GSA schedule contracts linked to it.
|
|
3003
|
+
With no linked contract the page is empty rather than an error, so use ``get_ebuy_access()`` to tell "no access" from "no matches".
|
|
3004
|
+
|
|
3005
|
+
``status`` is frozen at the last state the request was seen in.
|
|
3006
|
+
Only currently-active requests are carried, so a request that closes stops appearing rather than getting a final row, and ``Open`` means "open the last time it was seen".
|
|
3007
|
+
Read ``last_seen`` for staleness.
|
|
3008
|
+
|
|
3009
|
+
Args:
|
|
3010
|
+
page: Page number
|
|
3011
|
+
limit: Results per page (max 100)
|
|
3012
|
+
shape: Response shape string (defaults to minimal shape)
|
|
3013
|
+
flat: If True, flatten nested objects in shaped response
|
|
3014
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
3015
|
+
search: Full-text search over the title, description, reference number, request id and attachment text. Results rank by relevance unless ``ordering`` is given
|
|
3016
|
+
rfq_id: Exact request id, e.g. ``RFQ1835158``. OR several with ``|``
|
|
3017
|
+
reference_number: The buyer's own solicitation number; dashes are ignored
|
|
3018
|
+
request_type: ``RFQ``, ``RFP`` or ``RFI``. OR several with ``|``
|
|
3019
|
+
status: ``Open`` or ``Cancelled``, as last seen. OR several with ``|``
|
|
3020
|
+
sin: Special Item Number, e.g. ``54151S``. OR several with ``|``
|
|
3021
|
+
schedule: GSA schedule. OR several with ``|``
|
|
3022
|
+
buyer_agency: The buyer agency as eBuy names it (free text). OR several with ``|``
|
|
3023
|
+
agency: A Tango agency name, abbreviation, code or organization key, e.g. ``GSA``. Matches the whole organization subtree, so a department includes its sub-agencies. OR several with ``|``. Requires Tango API 5.3.0
|
|
3024
|
+
contract_number: Narrow to requests posted under one of your own linked contracts. A contract not linked to your account returns an empty page, not an error
|
|
3025
|
+
issue_date_after: Issued on or after (YYYY-MM-DD)
|
|
3026
|
+
issue_date_before: Issued on or before (YYYY-MM-DD)
|
|
3027
|
+
close_date_after: Closes on or after (YYYY-MM-DD)
|
|
3028
|
+
close_date_before: Closes on or before (YYYY-MM-DD)
|
|
3029
|
+
ordering: Sort field — ``issue_date`` (the default, as ``-issue_date``), ``close_date``, ``last_seen`` or ``modified``. Prefix ``-`` for descending
|
|
3030
|
+
"""
|
|
3031
|
+
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
|
|
3032
|
+
|
|
3033
|
+
if shape is None:
|
|
3034
|
+
shape = ShapeConfig.EBUY_REQUESTS_MINIMAL
|
|
3035
|
+
if shape:
|
|
3036
|
+
params["shape"] = shape
|
|
3037
|
+
if flat:
|
|
3038
|
+
params["flat"] = "true"
|
|
3039
|
+
if flat_lists:
|
|
3040
|
+
params["flat_lists"] = "true"
|
|
3041
|
+
|
|
3042
|
+
for key, val in (
|
|
3043
|
+
("search", search),
|
|
3044
|
+
("rfq_id", rfq_id),
|
|
3045
|
+
("reference_number", reference_number),
|
|
3046
|
+
("request_type", request_type),
|
|
3047
|
+
("status", status),
|
|
3048
|
+
("sin", sin),
|
|
3049
|
+
("schedule", schedule),
|
|
3050
|
+
("buyer_agency", buyer_agency),
|
|
3051
|
+
("agency", agency),
|
|
3052
|
+
("contract_number", contract_number),
|
|
3053
|
+
("issue_date_after", issue_date_after),
|
|
3054
|
+
("issue_date_before", issue_date_before),
|
|
3055
|
+
("close_date_after", close_date_after),
|
|
3056
|
+
("close_date_before", close_date_before),
|
|
3057
|
+
("ordering", ordering),
|
|
3058
|
+
):
|
|
3059
|
+
if val is not None:
|
|
3060
|
+
params[key] = val
|
|
3061
|
+
|
|
3062
|
+
data = self._get("/api/ebuy/requests/", params)
|
|
3063
|
+
|
|
3064
|
+
results = [
|
|
3065
|
+
self._parse_response_with_shape(item, shape, EbuyRequest, flat, flat_lists)
|
|
3066
|
+
for item in data["results"]
|
|
3067
|
+
]
|
|
3068
|
+
|
|
3069
|
+
return PaginatedResponse(
|
|
3070
|
+
count=data["count"],
|
|
3071
|
+
next=data.get("next"),
|
|
3072
|
+
previous=data.get("previous"),
|
|
3073
|
+
results=results,
|
|
3074
|
+
meta=data.get("meta"),
|
|
3075
|
+
)
|
|
3076
|
+
|
|
3077
|
+
def get_ebuy_request(
|
|
3078
|
+
self,
|
|
3079
|
+
rfq_id: str,
|
|
3080
|
+
shape: str | None = None,
|
|
3081
|
+
flat: bool = False,
|
|
3082
|
+
flat_lists: bool = False,
|
|
3083
|
+
) -> Any:
|
|
3084
|
+
"""
|
|
3085
|
+
Get a single GSA eBuy request by its request id.
|
|
3086
|
+
|
|
3087
|
+
A request outside your account's scope raises ``TangoNotFoundError``, the same as an id that does not exist.
|
|
3088
|
+
|
|
3089
|
+
Args:
|
|
3090
|
+
rfq_id: Request id, e.g. ``RFQ1835158``
|
|
3091
|
+
shape: Response shape string (defaults to the comprehensive shape: every field plus the ``organization`` and ``attachments`` expands)
|
|
3092
|
+
flat: If True, flatten nested objects in shaped response
|
|
3093
|
+
flat_lists: If True, flatten arrays using indexed keys
|
|
3094
|
+
"""
|
|
3095
|
+
params: dict[str, Any] = {}
|
|
3096
|
+
if shape is None:
|
|
3097
|
+
shape = ShapeConfig.EBUY_REQUESTS_COMPREHENSIVE
|
|
3098
|
+
if shape:
|
|
3099
|
+
params["shape"] = shape
|
|
3100
|
+
if flat:
|
|
3101
|
+
params["flat"] = "true"
|
|
3102
|
+
if flat_lists:
|
|
3103
|
+
params["flat_lists"] = "true"
|
|
3104
|
+
|
|
3105
|
+
data = self._get(f"/api/ebuy/requests/{quote(rfq_id, safe='')}/", params)
|
|
3106
|
+
return self._parse_response_with_shape(data, shape, EbuyRequest, flat, flat_lists)
|
|
3107
|
+
|
|
3108
|
+
def get_ebuy_attachment_url(self, rfq_id: str, doc_seq_num: int) -> str:
|
|
3109
|
+
"""
|
|
3110
|
+
Get a short-lived download URL for one attachment on a GSA eBuy request.
|
|
3111
|
+
|
|
3112
|
+
The API answers with a redirect to a signed URL that expires after about five minutes; this returns that URL without downloading the document.
|
|
3113
|
+
Fetch it promptly, and call this again rather than storing it.
|
|
3114
|
+
|
|
3115
|
+
Args:
|
|
3116
|
+
rfq_id: Request id, e.g. ``RFQ1835158``
|
|
3117
|
+
doc_seq_num: The attachment's ``doc_seq_num`` from ``get_ebuy_request()``
|
|
3118
|
+
|
|
3119
|
+
Raises:
|
|
3120
|
+
TangoAttachmentLinkError: The entry is an external link (``is_link``), not a stored document. The link is on ``.url`` and in the message. Subclasses ``TangoValidationError``.
|
|
3121
|
+
TangoNotFoundError: The request is unknown or outside your scope, the attachment does not exist, or its document has not been captured yet.
|
|
3122
|
+
"""
|
|
3123
|
+
endpoint = (
|
|
3124
|
+
f"/api/ebuy/requests/{quote(rfq_id, safe='')}/attachments/{int(doc_seq_num)}/download/"
|
|
3125
|
+
)
|
|
3126
|
+
url = urljoin(f"{self.base_url}/", endpoint.lstrip("/"))
|
|
3127
|
+
|
|
3128
|
+
try:
|
|
3129
|
+
response = self.client.request(method="GET", url=url, follow_redirects=False)
|
|
3130
|
+
except httpx.HTTPError as e:
|
|
3131
|
+
raise TangoAPIError(f"Request failed: {str(e)}") from e
|
|
3132
|
+
self._last_response_headers = response.headers
|
|
3133
|
+
self._last_rate_limit_info = self._parse_rate_limit_headers(response.headers)
|
|
3134
|
+
|
|
3135
|
+
location = response.headers.get("Location")
|
|
3136
|
+
if response.is_redirect and location:
|
|
3137
|
+
return str(location)
|
|
3138
|
+
|
|
3139
|
+
if response.status_code in (400, 404):
|
|
3140
|
+
try:
|
|
3141
|
+
error_data = response.json() if response.content else {}
|
|
3142
|
+
except ValueError:
|
|
3143
|
+
error_data = {}
|
|
3144
|
+
if not isinstance(error_data, dict):
|
|
3145
|
+
error_data = {}
|
|
3146
|
+
if response.status_code == 400 and error_data.get("url"):
|
|
3147
|
+
raise TangoAttachmentLinkError(
|
|
3148
|
+
f"Attachment {doc_seq_num} on {rfq_id} is an external link, not a stored document: {error_data['url']}",
|
|
3149
|
+
response.status_code,
|
|
3150
|
+
error_data,
|
|
3151
|
+
)
|
|
3152
|
+
if response.status_code == 404:
|
|
3153
|
+
raise TangoNotFoundError(
|
|
3154
|
+
error_data.get("detail") or "Resource not found",
|
|
3155
|
+
response.status_code,
|
|
3156
|
+
error_data,
|
|
3157
|
+
)
|
|
3158
|
+
|
|
3159
|
+
self._raise_for_status(response)
|
|
3160
|
+
raise TangoAPIError(
|
|
3161
|
+
f"Expected a redirect to the attachment, got status {response.status_code}",
|
|
3162
|
+
response.status_code,
|
|
3163
|
+
)
|
|
3164
|
+
|
|
3165
|
+
def get_ebuy_access(self) -> EbuyAccess:
|
|
3166
|
+
"""
|
|
3167
|
+
Check whether your account can read GSA eBuy requests.
|
|
3168
|
+
|
|
3169
|
+
``list_ebuy_requests()`` returns an empty page, not an error, when no contract is linked; this tells the two apart.
|
|
3170
|
+
``reason`` is ``"tier_required"`` below the Pro tier (it wins when both apply), ``"no_contract_grant"`` when no contract is linked, and ``None`` when ``enabled`` is true.
|
|
3171
|
+
``contracts`` lists your own linked contracts, sorted.
|
|
3172
|
+
"""
|
|
3173
|
+
data = self._get("/api/ebuy/access/")
|
|
3174
|
+
return EbuyAccess(
|
|
3175
|
+
enabled=bool(data.get("enabled")),
|
|
3176
|
+
reason=data.get("reason"),
|
|
3177
|
+
contracts=list(data.get("contracts") or []),
|
|
3178
|
+
)
|
|
3179
|
+
|
|
2821
3180
|
# ============================================================================
|
|
2822
3181
|
# DLA DIBBS (RFQs, RFPs, awards)
|
|
2823
3182
|
# ============================================================================
|
|
@@ -3242,6 +3601,7 @@ class TangoClient:
|
|
|
3242
3601
|
update_date_before: str | None = None,
|
|
3243
3602
|
search: str | None = None,
|
|
3244
3603
|
ordering: str | None = None,
|
|
3604
|
+
agency: str | None = None,
|
|
3245
3605
|
) -> PaginatedResponse:
|
|
3246
3606
|
"""
|
|
3247
3607
|
List SAM.gov exclusion (debarment) records.
|
|
@@ -3261,6 +3621,7 @@ class TangoClient:
|
|
|
3261
3621
|
classification_type: Filter by classification (Firm, Individual, Vessel, ...)
|
|
3262
3622
|
exclusion_type: Filter by exclusion type
|
|
3263
3623
|
exclusion_program: Filter by exclusion program
|
|
3624
|
+
agency: Excluding agency as a Tango organization. A Tango agency name, abbreviation, code or organization key, e.g. ``EPA``. Matches the whole organization subtree, so a department includes its sub-agencies. OR several with ``|``
|
|
3264
3625
|
excluding_agency_code: Filter by excluding agency code
|
|
3265
3626
|
excluding_agency_name: Filter by excluding agency name
|
|
3266
3627
|
active: True returns only records currently in effect.
|
|
@@ -3296,6 +3657,7 @@ class TangoClient:
|
|
|
3296
3657
|
("classification_type", classification_type),
|
|
3297
3658
|
("exclusion_type", exclusion_type),
|
|
3298
3659
|
("exclusion_program", exclusion_program),
|
|
3660
|
+
("agency", agency),
|
|
3299
3661
|
("excluding_agency_code", excluding_agency_code),
|
|
3300
3662
|
("excluding_agency_name", excluding_agency_name),
|
|
3301
3663
|
("active", active),
|
|
@@ -4153,6 +4515,7 @@ class TangoClient:
|
|
|
4153
4515
|
actual_vs_requested_contract_capped_lte: float | None = None,
|
|
4154
4516
|
search: str | None = None,
|
|
4155
4517
|
ordering: str | None = None,
|
|
4518
|
+
agency: str | None = None,
|
|
4156
4519
|
) -> PaginatedResponse:
|
|
4157
4520
|
"""List budget accounts (`/api/budget/accounts/`).
|
|
4158
4521
|
|
|
@@ -4170,6 +4533,7 @@ class TangoClient:
|
|
|
4170
4533
|
fiscal_year: Fiscal year (exact).
|
|
4171
4534
|
fiscal_year_gte: Fiscal year >=.
|
|
4172
4535
|
fiscal_year_lte: Fiscal year <=.
|
|
4536
|
+
agency: A Tango agency name, abbreviation, code or organization key, e.g. ``EPA``. Matches the whole organization subtree, so a department includes its sub-agencies. OR several with ``|``.
|
|
4173
4537
|
agency_code: Agency code (exact).
|
|
4174
4538
|
bureau_name: Bureau name (exact).
|
|
4175
4539
|
account_title: Account title (icontains).
|
|
@@ -4234,6 +4598,7 @@ class TangoClient:
|
|
|
4234
4598
|
("fiscal_year", fiscal_year),
|
|
4235
4599
|
("fiscal_year__gte", fiscal_year_gte),
|
|
4236
4600
|
("fiscal_year__lte", fiscal_year_lte),
|
|
4601
|
+
("agency", agency),
|
|
4237
4602
|
("agency_code", agency_code),
|
|
4238
4603
|
("bureau_name", bureau_name),
|
|
4239
4604
|
("account_title__icontains", account_title),
|
|
@@ -4870,8 +5235,8 @@ class TangoClient:
|
|
|
4870
5235
|
name: Human-readable name for this alert.
|
|
4871
5236
|
query_type: One of ``opportunity``, ``contract``, ``idv``, ``ota``,
|
|
4872
5237
|
``otidv``, ``entity``, ``grant``, ``forecast``, ``exclusion``,
|
|
4873
|
-
``dibbs_rfq``, ``dibbs_rfp``, ``sled_opportunity
|
|
4874
|
-
``contract_appeal``. :meth:`list_webhook_event_types` is the
|
|
5238
|
+
``dibbs_rfq``, ``dibbs_rfp``, ``sled_opportunity``,
|
|
5239
|
+
``contract_appeal``, ``federal_register`` or ``ebuy_request``. :meth:`list_webhook_event_types` is the
|
|
4875
5240
|
current authority — this list can only go stale.
|
|
4876
5241
|
filters: Dict of query parameters that the alert matches against
|
|
4877
5242
|
(e.g. ``{"naics": "541330", "set_aside": "SBA"}``).
|
tango/exceptions.py
CHANGED
|
@@ -53,6 +53,16 @@ class TangoValidationError(TangoAPIError):
|
|
|
53
53
|
return val if isinstance(val, dict) else None
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
class TangoAttachmentLinkError(TangoValidationError):
|
|
57
|
+
"""The requested attachment is an external link, not a stored document, so there is nothing to download."""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def url(self) -> str | None:
|
|
61
|
+
"""The external link the entry points at."""
|
|
62
|
+
val = self.response_data.get("url")
|
|
63
|
+
return val if isinstance(val, str) else None
|
|
64
|
+
|
|
65
|
+
|
|
56
66
|
class TangoRateLimitError(TangoAPIError):
|
|
57
67
|
"""Rate limit exceeded error"""
|
|
58
68
|
|
tango/models.py
CHANGED
|
@@ -1213,6 +1213,156 @@ class ContractAppeal:
|
|
|
1213
1213
|
decision_text: str | None = None
|
|
1214
1214
|
|
|
1215
1215
|
|
|
1216
|
+
class FederalRegisterDocument:
|
|
1217
|
+
"""Schema definition for FederalRegisterDocument (not used for instances).
|
|
1218
|
+
|
|
1219
|
+
Federal Register documents at ``/api/federal_register/``: rules, proposed rules, notices and presidential documents published since 1994.
|
|
1220
|
+
Shape-on-demand, so every field is optional.
|
|
1221
|
+
|
|
1222
|
+
A document is identified by ``uuid``.
|
|
1223
|
+
``document_number`` alone is not unique: the Federal Register reused some numbers before 2016, so ``document_number=`` can match more than one document.
|
|
1224
|
+
|
|
1225
|
+
``agencies``, ``cfr_references``, ``dockets`` and ``topics`` are the Federal Register's own structures, served as published.
|
|
1226
|
+
``full_text`` can run to several MB, so it is available on the detail endpoint only and only when named in ``shape``.
|
|
1227
|
+
"""
|
|
1228
|
+
|
|
1229
|
+
uuid: str | None = None
|
|
1230
|
+
document_number: str | None = None
|
|
1231
|
+
publication_date: date | None = None
|
|
1232
|
+
citation: str | None = None
|
|
1233
|
+
title: str | None = None
|
|
1234
|
+
type: str | None = None
|
|
1235
|
+
subtype: str | None = None
|
|
1236
|
+
abstract: str | None = None
|
|
1237
|
+
action: str | None = None
|
|
1238
|
+
dates: str | None = None
|
|
1239
|
+
comments_close_on: date | None = None
|
|
1240
|
+
effective_on: date | None = None
|
|
1241
|
+
signing_date: date | None = None
|
|
1242
|
+
start_page: int | None = None
|
|
1243
|
+
end_page: int | None = None
|
|
1244
|
+
page_length: int | None = None
|
|
1245
|
+
volume: int | None = None
|
|
1246
|
+
significant: bool | None = None
|
|
1247
|
+
toc_doc: str | None = None
|
|
1248
|
+
toc_subject: str | None = None
|
|
1249
|
+
correction_of: str | None = None
|
|
1250
|
+
corrections: list[str] | None = None
|
|
1251
|
+
disposition_notes: str | None = None
|
|
1252
|
+
executive_order_notes: str | None = None
|
|
1253
|
+
executive_order_number: str | None = None
|
|
1254
|
+
presidential_document_number: str | None = None
|
|
1255
|
+
proclamation_number: str | None = None
|
|
1256
|
+
not_received_for_publication: str | None = None
|
|
1257
|
+
comment_url: str | None = None
|
|
1258
|
+
regulations_dot_gov_url: str | None = None
|
|
1259
|
+
html_url: str | None = None
|
|
1260
|
+
body_html_url: str | None = None
|
|
1261
|
+
pdf_url: str | None = None
|
|
1262
|
+
public_inspection_pdf_url: str | None = None
|
|
1263
|
+
raw_text_url: str | None = None
|
|
1264
|
+
agencies: list[dict[str, Any]] | None = None
|
|
1265
|
+
cfr_references: list[dict[str, Any]] | None = None
|
|
1266
|
+
dockets: list[dict[str, Any]] | None = None
|
|
1267
|
+
docket_ids: list[str] | None = None
|
|
1268
|
+
topics: list[str] | None = None
|
|
1269
|
+
regulation_id_numbers: list[str] | None = None
|
|
1270
|
+
regulation_id_number_info: dict[str, Any] | None = None
|
|
1271
|
+
regulations_dot_gov_info: dict[str, Any] | None = None
|
|
1272
|
+
full_text: str | None = None
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
class EbuyAttachment:
|
|
1276
|
+
"""Schema definition for one entry in an eBuy request's ``attachments`` (not used for instances).
|
|
1277
|
+
|
|
1278
|
+
``is_link=True`` means the entry is an outbound URL in ``doc_path`` with no stored document behind it, so ``get_ebuy_attachment_url()`` raises ``TangoAttachmentLinkError`` for it.
|
|
1279
|
+
"""
|
|
1280
|
+
|
|
1281
|
+
doc_seq_num: int | None = None
|
|
1282
|
+
doc_name: str | None = None
|
|
1283
|
+
doc_type: int | None = None
|
|
1284
|
+
doc_path: str | None = None
|
|
1285
|
+
is_link: bool | None = None
|
|
1286
|
+
doc_session_date: datetime | None = None
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
class EbuyRequest:
|
|
1290
|
+
"""Schema definition for EbuyRequest (not used for instances).
|
|
1291
|
+
|
|
1292
|
+
GSA eBuy requests (RFQs, RFPs, RFIs) at ``/api/ebuy/requests/``, keyed by ``rfq_id``.
|
|
1293
|
+
Shape-on-demand, so every field is optional.
|
|
1294
|
+
|
|
1295
|
+
The resource is scoped to your account: you see only requests posted under the GSA schedule contracts linked to it.
|
|
1296
|
+
|
|
1297
|
+
``status`` is frozen at the last state the request was seen in.
|
|
1298
|
+
Only currently-active requests are carried, so a request that closes stops appearing rather than getting a final row, and ``Open`` means "open the last time it was seen".
|
|
1299
|
+
Read ``last_seen`` for staleness.
|
|
1300
|
+
|
|
1301
|
+
``buyer_agency_code`` and some other buyer and contact fields are sparse on older requests.
|
|
1302
|
+
The contract number a request was posted under is never returned.
|
|
1303
|
+
"""
|
|
1304
|
+
|
|
1305
|
+
rfq_id: str | None = None
|
|
1306
|
+
request_type: str | None = None
|
|
1307
|
+
title: str | None = None
|
|
1308
|
+
description: str | None = None
|
|
1309
|
+
schedule: str | None = None
|
|
1310
|
+
sin: str | None = None
|
|
1311
|
+
status: str | None = None
|
|
1312
|
+
buyer_name: str | None = None
|
|
1313
|
+
buyer_agency: str | None = None
|
|
1314
|
+
buyer_agency_code: str | None = None
|
|
1315
|
+
buyer_email: str | None = None
|
|
1316
|
+
buyer_user_id: str | None = None
|
|
1317
|
+
reference_number: str | None = None
|
|
1318
|
+
award_method: str | None = None
|
|
1319
|
+
contract_type: str | None = None
|
|
1320
|
+
commercial_type: str | None = None
|
|
1321
|
+
follow_on: bool | None = None
|
|
1322
|
+
source_sought: bool | None = None
|
|
1323
|
+
issue_date: datetime | None = None
|
|
1324
|
+
close_date: datetime | None = None
|
|
1325
|
+
cancel_date: datetime | None = None
|
|
1326
|
+
last_mod_date: datetime | None = None
|
|
1327
|
+
pop_start_date: datetime | None = None
|
|
1328
|
+
pop_end_date: datetime | None = None
|
|
1329
|
+
oco_name: str | None = None
|
|
1330
|
+
oco_title: str | None = None
|
|
1331
|
+
oco_agency: str | None = None
|
|
1332
|
+
oco_phone: str | None = None
|
|
1333
|
+
oco_aac: str | None = None
|
|
1334
|
+
ocs_name: str | None = None
|
|
1335
|
+
ocs_title: str | None = None
|
|
1336
|
+
ocs_agency: str | None = None
|
|
1337
|
+
ocs_phone: str | None = None
|
|
1338
|
+
ocs_aac: str | None = None
|
|
1339
|
+
amendment_count: int | None = None
|
|
1340
|
+
mod_version: int | None = None
|
|
1341
|
+
qa_document_count: int | None = None
|
|
1342
|
+
attachment_count: int | None = None
|
|
1343
|
+
link_count: int | None = None
|
|
1344
|
+
amendments: list[dict[str, Any]] | None = None
|
|
1345
|
+
line_items: list[dict[str, Any]] | None = None
|
|
1346
|
+
addresses: list[dict[str, Any]] | None = None
|
|
1347
|
+
detail_fetched: bool | None = None
|
|
1348
|
+
first_seen: datetime | None = None
|
|
1349
|
+
last_seen: datetime | None = None
|
|
1350
|
+
organization: dict[str, Any] | None = None
|
|
1351
|
+
attachments: list[EbuyAttachment] | None = None
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
@dataclass
|
|
1355
|
+
class EbuyAccess:
|
|
1356
|
+
"""Result of ``GET /api/ebuy/access/``: whether your account can read eBuy requests.
|
|
1357
|
+
|
|
1358
|
+
``list_ebuy_requests()`` returns an empty page, not an error, when no contract is linked, so this is how to tell "no access" from "no matches".
|
|
1359
|
+
"""
|
|
1360
|
+
|
|
1361
|
+
enabled: bool
|
|
1362
|
+
reason: Literal["tier_required", "no_contract_grant"] | None
|
|
1363
|
+
contracts: list[str]
|
|
1364
|
+
|
|
1365
|
+
|
|
1216
1366
|
@dataclass
|
|
1217
1367
|
class PaginatedResponse[T]:
|
|
1218
1368
|
"""Paginated API response
|
|
@@ -1394,6 +1544,44 @@ class ShapeConfig:
|
|
|
1394
1544
|
"listing_year,first_listed_at,listed,text_status,text_char_count"
|
|
1395
1545
|
)
|
|
1396
1546
|
|
|
1547
|
+
# Default for list_federal_register_documents(). Mirrors the API's own list default.
|
|
1548
|
+
FEDERAL_REGISTER_MINIMAL: Final = (
|
|
1549
|
+
"uuid,document_number,publication_date,type,subtype,title,abstract,action,"
|
|
1550
|
+
"agencies,cfr_references,citation,significant,comments_close_on,effective_on,"
|
|
1551
|
+
"html_url,pdf_url"
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
# Default for get_federal_register_document(). Mirrors the API's own retrieve default.
|
|
1555
|
+
# `full_text` is left out on purpose: it can run to several MB.
|
|
1556
|
+
FEDERAL_REGISTER_COMPREHENSIVE: Final = (
|
|
1557
|
+
"uuid,document_number,publication_date,type,subtype,title,abstract,action,"
|
|
1558
|
+
"agencies,cfr_references,citation,significant,comments_close_on,effective_on,"
|
|
1559
|
+
"html_url,pdf_url,dates,signing_date,start_page,end_page,volume,docket_ids,"
|
|
1560
|
+
"dockets,regulation_id_numbers,topics,correction_of,corrections,"
|
|
1561
|
+
"executive_order_number,presidential_document_number,proclamation_number,"
|
|
1562
|
+
"comment_url,regulations_dot_gov_url,raw_text_url,body_html_url"
|
|
1563
|
+
)
|
|
1564
|
+
|
|
1565
|
+
# Default for list_ebuy_requests(). Mirrors the API's own list default.
|
|
1566
|
+
EBUY_REQUESTS_MINIMAL: Final = (
|
|
1567
|
+
"rfq_id,request_type,title,schedule,sin,status,buyer_name,buyer_agency,"
|
|
1568
|
+
"buyer_agency_code,reference_number,issue_date,close_date,attachment_count,"
|
|
1569
|
+
"link_count,last_seen"
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
# Default for get_ebuy_request(). Mirrors the API's own retrieve default; the attachment fields are named rather than `attachments(*)` so their dates parse.
|
|
1573
|
+
EBUY_REQUESTS_COMPREHENSIVE: Final = (
|
|
1574
|
+
"rfq_id,request_type,title,description,schedule,sin,status,buyer_name,"
|
|
1575
|
+
"buyer_agency,buyer_agency_code,buyer_email,buyer_user_id,reference_number,"
|
|
1576
|
+
"award_method,contract_type,commercial_type,follow_on,source_sought,issue_date,"
|
|
1577
|
+
"close_date,cancel_date,last_mod_date,pop_start_date,pop_end_date,oco_name,"
|
|
1578
|
+
"oco_title,oco_agency,oco_phone,oco_aac,ocs_name,ocs_title,ocs_agency,ocs_phone,"
|
|
1579
|
+
"ocs_aac,amendment_count,mod_version,qa_document_count,attachment_count,"
|
|
1580
|
+
"link_count,amendments,line_items,addresses,detail_fetched,first_seen,last_seen,"
|
|
1581
|
+
"organization(*),"
|
|
1582
|
+
"attachments(doc_seq_num,doc_name,doc_type,doc_path,is_link,doc_session_date)"
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1397
1585
|
# Default for list_dibbs_rfqs()
|
|
1398
1586
|
DIBBS_RFQS_MINIMAL: Final = (
|
|
1399
1587
|
"uuid,solicitation,nsn,part_number,nomenclature,quantity,issue_date,return_by_date,is_open"
|
tango/shapes/explicit_schemas.py
CHANGED
|
@@ -800,6 +800,174 @@ CONTRACT_APPEAL_SCHEMA: dict[str, FieldSchema] = {
|
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
|
|
803
|
+
FEDERAL_REGISTER_DOCUMENT_SCHEMA: dict[str, FieldSchema] = {
|
|
804
|
+
"uuid": FieldSchema(name="uuid", type=str, is_optional=True, is_list=False),
|
|
805
|
+
"document_number": FieldSchema(
|
|
806
|
+
name="document_number", type=str, is_optional=True, is_list=False
|
|
807
|
+
),
|
|
808
|
+
"publication_date": FieldSchema(
|
|
809
|
+
name="publication_date", type=date, is_optional=True, is_list=False
|
|
810
|
+
),
|
|
811
|
+
"citation": FieldSchema(name="citation", type=str, is_optional=True, is_list=False),
|
|
812
|
+
"title": FieldSchema(name="title", type=str, is_optional=True, is_list=False),
|
|
813
|
+
"type": FieldSchema(name="type", type=str, is_optional=True, is_list=False),
|
|
814
|
+
"subtype": FieldSchema(name="subtype", type=str, is_optional=True, is_list=False),
|
|
815
|
+
"abstract": FieldSchema(name="abstract", type=str, is_optional=True, is_list=False),
|
|
816
|
+
"action": FieldSchema(name="action", type=str, is_optional=True, is_list=False),
|
|
817
|
+
"dates": FieldSchema(name="dates", type=str, is_optional=True, is_list=False),
|
|
818
|
+
"comments_close_on": FieldSchema(
|
|
819
|
+
name="comments_close_on", type=date, is_optional=True, is_list=False
|
|
820
|
+
),
|
|
821
|
+
"effective_on": FieldSchema(name="effective_on", type=date, is_optional=True, is_list=False),
|
|
822
|
+
"signing_date": FieldSchema(name="signing_date", type=date, is_optional=True, is_list=False),
|
|
823
|
+
"start_page": FieldSchema(name="start_page", type=int, is_optional=True, is_list=False),
|
|
824
|
+
"end_page": FieldSchema(name="end_page", type=int, is_optional=True, is_list=False),
|
|
825
|
+
"page_length": FieldSchema(name="page_length", type=int, is_optional=True, is_list=False),
|
|
826
|
+
"volume": FieldSchema(name="volume", type=int, is_optional=True, is_list=False),
|
|
827
|
+
"significant": FieldSchema(name="significant", type=bool, is_optional=True, is_list=False),
|
|
828
|
+
"toc_doc": FieldSchema(name="toc_doc", type=str, is_optional=True, is_list=False),
|
|
829
|
+
"toc_subject": FieldSchema(name="toc_subject", type=str, is_optional=True, is_list=False),
|
|
830
|
+
"correction_of": FieldSchema(name="correction_of", type=str, is_optional=True, is_list=False),
|
|
831
|
+
"corrections": FieldSchema(name="corrections", type=str, is_optional=True, is_list=True),
|
|
832
|
+
"disposition_notes": FieldSchema(
|
|
833
|
+
name="disposition_notes", type=str, is_optional=True, is_list=False
|
|
834
|
+
),
|
|
835
|
+
"executive_order_notes": FieldSchema(
|
|
836
|
+
name="executive_order_notes", type=str, is_optional=True, is_list=False
|
|
837
|
+
),
|
|
838
|
+
"executive_order_number": FieldSchema(
|
|
839
|
+
name="executive_order_number", type=str, is_optional=True, is_list=False
|
|
840
|
+
),
|
|
841
|
+
"presidential_document_number": FieldSchema(
|
|
842
|
+
name="presidential_document_number", type=str, is_optional=True, is_list=False
|
|
843
|
+
),
|
|
844
|
+
"proclamation_number": FieldSchema(
|
|
845
|
+
name="proclamation_number", type=str, is_optional=True, is_list=False
|
|
846
|
+
),
|
|
847
|
+
"not_received_for_publication": FieldSchema(
|
|
848
|
+
name="not_received_for_publication", type=str, is_optional=True, is_list=False
|
|
849
|
+
),
|
|
850
|
+
"comment_url": FieldSchema(name="comment_url", type=str, is_optional=True, is_list=False),
|
|
851
|
+
"regulations_dot_gov_url": FieldSchema(
|
|
852
|
+
name="regulations_dot_gov_url", type=str, is_optional=True, is_list=False
|
|
853
|
+
),
|
|
854
|
+
"html_url": FieldSchema(name="html_url", type=str, is_optional=True, is_list=False),
|
|
855
|
+
"body_html_url": FieldSchema(name="body_html_url", type=str, is_optional=True, is_list=False),
|
|
856
|
+
"pdf_url": FieldSchema(name="pdf_url", type=str, is_optional=True, is_list=False),
|
|
857
|
+
"public_inspection_pdf_url": FieldSchema(
|
|
858
|
+
name="public_inspection_pdf_url", type=str, is_optional=True, is_list=False
|
|
859
|
+
),
|
|
860
|
+
"raw_text_url": FieldSchema(name="raw_text_url", type=str, is_optional=True, is_list=False),
|
|
861
|
+
"agencies": FieldSchema(name="agencies", type=dict, is_optional=True, is_list=True),
|
|
862
|
+
"cfr_references": FieldSchema(name="cfr_references", type=dict, is_optional=True, is_list=True),
|
|
863
|
+
"dockets": FieldSchema(name="dockets", type=dict, is_optional=True, is_list=True),
|
|
864
|
+
"docket_ids": FieldSchema(name="docket_ids", type=str, is_optional=True, is_list=True),
|
|
865
|
+
"topics": FieldSchema(name="topics", type=str, is_optional=True, is_list=True),
|
|
866
|
+
"regulation_id_numbers": FieldSchema(
|
|
867
|
+
name="regulation_id_numbers", type=str, is_optional=True, is_list=True
|
|
868
|
+
),
|
|
869
|
+
"regulation_id_number_info": FieldSchema(
|
|
870
|
+
name="regulation_id_number_info", type=dict, is_optional=True, is_list=False
|
|
871
|
+
),
|
|
872
|
+
"regulations_dot_gov_info": FieldSchema(
|
|
873
|
+
name="regulations_dot_gov_info", type=dict, is_optional=True, is_list=False
|
|
874
|
+
),
|
|
875
|
+
# Detail endpoint only, and only when named in the shape.
|
|
876
|
+
"full_text": FieldSchema(name="full_text", type=str, is_optional=True, is_list=False),
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
EBUY_ATTACHMENT_SCHEMA: dict[str, FieldSchema] = {
|
|
881
|
+
"doc_seq_num": FieldSchema(name="doc_seq_num", type=int, is_optional=True, is_list=False),
|
|
882
|
+
"doc_name": FieldSchema(name="doc_name", type=str, is_optional=True, is_list=False),
|
|
883
|
+
"doc_type": FieldSchema(name="doc_type", type=int, is_optional=True, is_list=False),
|
|
884
|
+
"doc_path": FieldSchema(name="doc_path", type=str, is_optional=True, is_list=False),
|
|
885
|
+
"is_link": FieldSchema(name="is_link", type=bool, is_optional=True, is_list=False),
|
|
886
|
+
"doc_session_date": FieldSchema(
|
|
887
|
+
name="doc_session_date", type=datetime, is_optional=True, is_list=False
|
|
888
|
+
),
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
EBUY_REQUEST_SCHEMA: dict[str, FieldSchema] = {
|
|
893
|
+
"rfq_id": FieldSchema(name="rfq_id", type=str, is_optional=True, is_list=False),
|
|
894
|
+
"request_type": FieldSchema(name="request_type", type=str, is_optional=True, is_list=False),
|
|
895
|
+
"title": FieldSchema(name="title", type=str, is_optional=True, is_list=False),
|
|
896
|
+
"description": FieldSchema(name="description", type=str, is_optional=True, is_list=False),
|
|
897
|
+
"schedule": FieldSchema(name="schedule", type=str, is_optional=True, is_list=False),
|
|
898
|
+
"sin": FieldSchema(name="sin", type=str, is_optional=True, is_list=False),
|
|
899
|
+
"status": FieldSchema(name="status", type=str, is_optional=True, is_list=False),
|
|
900
|
+
"buyer_name": FieldSchema(name="buyer_name", type=str, is_optional=True, is_list=False),
|
|
901
|
+
"buyer_agency": FieldSchema(name="buyer_agency", type=str, is_optional=True, is_list=False),
|
|
902
|
+
"buyer_agency_code": FieldSchema(
|
|
903
|
+
name="buyer_agency_code", type=str, is_optional=True, is_list=False
|
|
904
|
+
),
|
|
905
|
+
"buyer_email": FieldSchema(name="buyer_email", type=str, is_optional=True, is_list=False),
|
|
906
|
+
"buyer_user_id": FieldSchema(name="buyer_user_id", type=str, is_optional=True, is_list=False),
|
|
907
|
+
"reference_number": FieldSchema(
|
|
908
|
+
name="reference_number", type=str, is_optional=True, is_list=False
|
|
909
|
+
),
|
|
910
|
+
"award_method": FieldSchema(name="award_method", type=str, is_optional=True, is_list=False),
|
|
911
|
+
"contract_type": FieldSchema(name="contract_type", type=str, is_optional=True, is_list=False),
|
|
912
|
+
"commercial_type": FieldSchema(
|
|
913
|
+
name="commercial_type", type=str, is_optional=True, is_list=False
|
|
914
|
+
),
|
|
915
|
+
"follow_on": FieldSchema(name="follow_on", type=bool, is_optional=True, is_list=False),
|
|
916
|
+
"source_sought": FieldSchema(name="source_sought", type=bool, is_optional=True, is_list=False),
|
|
917
|
+
"issue_date": FieldSchema(name="issue_date", type=datetime, is_optional=True, is_list=False),
|
|
918
|
+
"close_date": FieldSchema(name="close_date", type=datetime, is_optional=True, is_list=False),
|
|
919
|
+
"cancel_date": FieldSchema(name="cancel_date", type=datetime, is_optional=True, is_list=False),
|
|
920
|
+
"last_mod_date": FieldSchema(
|
|
921
|
+
name="last_mod_date", type=datetime, is_optional=True, is_list=False
|
|
922
|
+
),
|
|
923
|
+
"pop_start_date": FieldSchema(
|
|
924
|
+
name="pop_start_date", type=datetime, is_optional=True, is_list=False
|
|
925
|
+
),
|
|
926
|
+
"pop_end_date": FieldSchema(
|
|
927
|
+
name="pop_end_date", type=datetime, is_optional=True, is_list=False
|
|
928
|
+
),
|
|
929
|
+
"oco_name": FieldSchema(name="oco_name", type=str, is_optional=True, is_list=False),
|
|
930
|
+
"oco_title": FieldSchema(name="oco_title", type=str, is_optional=True, is_list=False),
|
|
931
|
+
"oco_agency": FieldSchema(name="oco_agency", type=str, is_optional=True, is_list=False),
|
|
932
|
+
"oco_phone": FieldSchema(name="oco_phone", type=str, is_optional=True, is_list=False),
|
|
933
|
+
"oco_aac": FieldSchema(name="oco_aac", type=str, is_optional=True, is_list=False),
|
|
934
|
+
"ocs_name": FieldSchema(name="ocs_name", type=str, is_optional=True, is_list=False),
|
|
935
|
+
"ocs_title": FieldSchema(name="ocs_title", type=str, is_optional=True, is_list=False),
|
|
936
|
+
"ocs_agency": FieldSchema(name="ocs_agency", type=str, is_optional=True, is_list=False),
|
|
937
|
+
"ocs_phone": FieldSchema(name="ocs_phone", type=str, is_optional=True, is_list=False),
|
|
938
|
+
"ocs_aac": FieldSchema(name="ocs_aac", type=str, is_optional=True, is_list=False),
|
|
939
|
+
"amendment_count": FieldSchema(
|
|
940
|
+
name="amendment_count", type=int, is_optional=True, is_list=False
|
|
941
|
+
),
|
|
942
|
+
"mod_version": FieldSchema(name="mod_version", type=int, is_optional=True, is_list=False),
|
|
943
|
+
"qa_document_count": FieldSchema(
|
|
944
|
+
name="qa_document_count", type=int, is_optional=True, is_list=False
|
|
945
|
+
),
|
|
946
|
+
"attachment_count": FieldSchema(
|
|
947
|
+
name="attachment_count", type=int, is_optional=True, is_list=False
|
|
948
|
+
),
|
|
949
|
+
"link_count": FieldSchema(name="link_count", type=int, is_optional=True, is_list=False),
|
|
950
|
+
"amendments": FieldSchema(name="amendments", type=dict, is_optional=True, is_list=True),
|
|
951
|
+
"line_items": FieldSchema(name="line_items", type=dict, is_optional=True, is_list=True),
|
|
952
|
+
"addresses": FieldSchema(name="addresses", type=dict, is_optional=True, is_list=True),
|
|
953
|
+
"detail_fetched": FieldSchema(
|
|
954
|
+
name="detail_fetched", type=bool, is_optional=True, is_list=False
|
|
955
|
+
),
|
|
956
|
+
"first_seen": FieldSchema(name="first_seen", type=datetime, is_optional=True, is_list=False),
|
|
957
|
+
"last_seen": FieldSchema(name="last_seen", type=datetime, is_optional=True, is_list=False),
|
|
958
|
+
"organization": FieldSchema(
|
|
959
|
+
name="organization",
|
|
960
|
+
type=dict,
|
|
961
|
+
is_optional=True,
|
|
962
|
+
is_list=False,
|
|
963
|
+
nested_model="OrganizationOffice",
|
|
964
|
+
),
|
|
965
|
+
"attachments": FieldSchema(
|
|
966
|
+
name="attachments", type=dict, is_optional=True, is_list=True, nested_model="EbuyAttachment"
|
|
967
|
+
),
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
|
|
803
971
|
AGENCY_SCHEMA: dict[str, FieldSchema] = {
|
|
804
972
|
"abbreviation": FieldSchema(name="abbreviation", type=str, is_optional=True, is_list=False),
|
|
805
973
|
"code": FieldSchema(name="code", type=str, is_optional=False, is_list=False),
|
|
@@ -1545,6 +1713,9 @@ EXPLICIT_SCHEMAS: dict[str, dict[str, FieldSchema]] = {
|
|
|
1545
1713
|
"Protest": PROTEST_SCHEMA,
|
|
1546
1714
|
"ProtestDocket": PROTEST_DOCKET_SCHEMA,
|
|
1547
1715
|
"ContractAppeal": CONTRACT_APPEAL_SCHEMA,
|
|
1716
|
+
"FederalRegisterDocument": FEDERAL_REGISTER_DOCUMENT_SCHEMA,
|
|
1717
|
+
"EbuyRequest": EBUY_REQUEST_SCHEMA,
|
|
1718
|
+
"EbuyAttachment": EBUY_ATTACHMENT_SCHEMA,
|
|
1548
1719
|
"Agency": AGENCY_SCHEMA,
|
|
1549
1720
|
"Grant": GRANT_SCHEMA,
|
|
1550
1721
|
# Vehicles (Awards)
|
|
@@ -81,17 +81,6 @@ ARCHIVE_SCHEMA: dict[str, FieldSchema] = {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
ATTACHMENTS_SCHEMA: dict[str, FieldSchema] = {
|
|
84
|
-
"doc_name": FieldSchema(name="doc_name", type=str, is_optional=True, is_list=False),
|
|
85
|
-
"doc_path": FieldSchema(name="doc_path", type=str, is_optional=True, is_list=False),
|
|
86
|
-
"doc_seq_num": FieldSchema(name="doc_seq_num", type=str, is_optional=True, is_list=False),
|
|
87
|
-
"doc_session_date": FieldSchema(
|
|
88
|
-
name="doc_session_date", type=date, is_optional=True, is_list=False
|
|
89
|
-
),
|
|
90
|
-
"doc_type": FieldSchema(name="doc_type", type=str, is_optional=True, is_list=False),
|
|
91
|
-
"is_link": FieldSchema(name="is_link", type=bool, is_optional=True, is_list=False),
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
ATTACHMENTS2_SCHEMA: dict[str, FieldSchema] = {
|
|
95
84
|
"attachment_id": FieldSchema(name="attachment_id", type=str, is_optional=True, is_list=False),
|
|
96
85
|
"doc_role": FieldSchema(name="doc_role", type=str, is_optional=True, is_list=False),
|
|
97
86
|
"doc_role_alt": FieldSchema(name="doc_role_alt", type=str, is_optional=True, is_list=False),
|
|
@@ -105,7 +94,7 @@ ATTACHMENTS2_SCHEMA: dict[str, FieldSchema] = {
|
|
|
105
94
|
"url": FieldSchema(name="url", type=str, is_optional=True, is_list=False),
|
|
106
95
|
}
|
|
107
96
|
|
|
108
|
-
|
|
97
|
+
ATTACHMENTS2_SCHEMA: dict[str, FieldSchema] = {
|
|
109
98
|
"attachment_id": FieldSchema(name="attachment_id", type=str, is_optional=True, is_list=False),
|
|
110
99
|
"doc_role": FieldSchema(name="doc_role", type=str, is_optional=True, is_list=False),
|
|
111
100
|
"doc_role_alt": FieldSchema(name="doc_role_alt", type=str, is_optional=True, is_list=False),
|
|
@@ -119,7 +108,7 @@ ATTACHMENTS3_SCHEMA: dict[str, FieldSchema] = {
|
|
|
119
108
|
"url": FieldSchema(name="url", type=str, is_optional=True, is_list=False),
|
|
120
109
|
}
|
|
121
110
|
|
|
122
|
-
|
|
111
|
+
ATTACHMENTS3_SCHEMA: dict[str, FieldSchema] = {
|
|
123
112
|
"char_count": FieldSchema(name="char_count", type=int, is_optional=True, is_list=False),
|
|
124
113
|
"checksum": FieldSchema(name="checksum", type=str, is_optional=True, is_list=False),
|
|
125
114
|
"download_status": FieldSchema(
|
|
@@ -144,7 +133,7 @@ ATTACHMENTS4_SCHEMA: dict[str, FieldSchema] = {
|
|
|
144
133
|
"word_count": FieldSchema(name="word_count", type=int, is_optional=True, is_list=False),
|
|
145
134
|
}
|
|
146
135
|
|
|
147
|
-
|
|
136
|
+
ATTACHMENTS4_SCHEMA: dict[str, FieldSchema] = {
|
|
148
137
|
"attachment_id": FieldSchema(name="attachment_id", type=str, is_optional=True, is_list=False),
|
|
149
138
|
"file_size": FieldSchema(name="file_size", type=str, is_optional=True, is_list=False),
|
|
150
139
|
"mime_type": FieldSchema(name="mime_type", type=str, is_optional=True, is_list=False),
|
|
@@ -155,7 +144,7 @@ ATTACHMENTS5_SCHEMA: dict[str, FieldSchema] = {
|
|
|
155
144
|
"url": FieldSchema(name="url", type=str, is_optional=True, is_list=False),
|
|
156
145
|
}
|
|
157
146
|
|
|
158
|
-
|
|
147
|
+
ATTACHMENTS5_SCHEMA: dict[str, FieldSchema] = {
|
|
159
148
|
"attachment_id": FieldSchema(name="attachment_id", type=str, is_optional=True, is_list=False),
|
|
160
149
|
"doc_role": FieldSchema(name="doc_role", type=str, is_optional=True, is_list=False),
|
|
161
150
|
"doc_role_alt": FieldSchema(name="doc_role_alt", type=str, is_optional=True, is_list=False),
|
|
@@ -1119,7 +1108,6 @@ GENERATED_NESTED: dict[str, dict[str, FieldSchema]] = {
|
|
|
1119
1108
|
"Attachments3": ATTACHMENTS3_SCHEMA,
|
|
1120
1109
|
"Attachments4": ATTACHMENTS4_SCHEMA,
|
|
1121
1110
|
"Attachments5": ATTACHMENTS5_SCHEMA,
|
|
1122
|
-
"Attachments6": ATTACHMENTS6_SCHEMA,
|
|
1123
1111
|
"Awardee": AWARDEE_SCHEMA,
|
|
1124
1112
|
"AwardingOffice": AWARDING_OFFICE_SCHEMA,
|
|
1125
1113
|
"AwardingOffice2": AWARDING_OFFICE2_SCHEMA,
|
|
@@ -1382,6 +1370,16 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
1382
1370
|
"on_off_budget": FieldSchema(
|
|
1383
1371
|
name="on_off_budget", type=str, is_optional=True, is_list=False
|
|
1384
1372
|
),
|
|
1373
|
+
"organization": FieldSchema(
|
|
1374
|
+
name="organization",
|
|
1375
|
+
type=dict,
|
|
1376
|
+
is_optional=True,
|
|
1377
|
+
is_list=False,
|
|
1378
|
+
nested_model="AwardingOffice",
|
|
1379
|
+
),
|
|
1380
|
+
"organization_id": FieldSchema(
|
|
1381
|
+
name="organization_id", type=str, is_optional=True, is_list=False
|
|
1382
|
+
),
|
|
1385
1383
|
"outlayed_to_obligated_pct": FieldSchema(
|
|
1386
1384
|
name="outlayed_to_obligated_pct", type=str, is_optional=True, is_list=False
|
|
1387
1385
|
),
|
|
@@ -1911,6 +1909,16 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
1911
1909
|
name="more_locations", type=str, is_optional=True, is_list=False
|
|
1912
1910
|
),
|
|
1913
1911
|
"npi": FieldSchema(name="npi", type=str, is_optional=True, is_list=False),
|
|
1912
|
+
"organization": FieldSchema(
|
|
1913
|
+
name="organization",
|
|
1914
|
+
type=dict,
|
|
1915
|
+
is_optional=True,
|
|
1916
|
+
is_list=False,
|
|
1917
|
+
nested_model="AwardingOffice",
|
|
1918
|
+
),
|
|
1919
|
+
"organization_id": FieldSchema(
|
|
1920
|
+
name="organization_id", type=str, is_optional=True, is_list=False
|
|
1921
|
+
),
|
|
1914
1922
|
"prefix": FieldSchema(name="prefix", type=str, is_optional=True, is_list=False),
|
|
1915
1923
|
"primary_address": FieldSchema(
|
|
1916
1924
|
name="primary_address", type=str, is_optional=True, is_list=False
|
|
@@ -2218,7 +2226,7 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2218
2226
|
type=dict,
|
|
2219
2227
|
is_optional=True,
|
|
2220
2228
|
is_list=True,
|
|
2221
|
-
nested_model="
|
|
2229
|
+
nested_model="Attachments",
|
|
2222
2230
|
),
|
|
2223
2231
|
"meta": FieldSchema(
|
|
2224
2232
|
name="meta", type=dict, is_optional=True, is_list=False, nested_model="Meta"
|
|
@@ -2466,7 +2474,7 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2466
2474
|
type=dict,
|
|
2467
2475
|
is_optional=True,
|
|
2468
2476
|
is_list=False,
|
|
2469
|
-
nested_model="
|
|
2477
|
+
nested_model="Attachments5",
|
|
2470
2478
|
),
|
|
2471
2479
|
"department": FieldSchema(
|
|
2472
2480
|
name="department",
|
|
@@ -2683,6 +2691,13 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2683
2691
|
name="documents", type=dict, is_optional=True, is_list=True, nested_model="Documents"
|
|
2684
2692
|
),
|
|
2685
2693
|
"end_date": FieldSchema(name="end_date", type=date, is_optional=True, is_list=False),
|
|
2694
|
+
"organization": FieldSchema(
|
|
2695
|
+
name="organization",
|
|
2696
|
+
type=dict,
|
|
2697
|
+
is_optional=True,
|
|
2698
|
+
is_list=False,
|
|
2699
|
+
nested_model="AwardingOffice",
|
|
2700
|
+
),
|
|
2686
2701
|
"out_of_cycle": FieldSchema(
|
|
2687
2702
|
name="out_of_cycle", type=bool, is_optional=True, is_list=False
|
|
2688
2703
|
),
|
|
@@ -2736,6 +2751,13 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2736
2751
|
is_list=False,
|
|
2737
2752
|
nested_model="Opportunity2",
|
|
2738
2753
|
),
|
|
2754
|
+
"organization": FieldSchema(
|
|
2755
|
+
name="organization",
|
|
2756
|
+
type=dict,
|
|
2757
|
+
is_optional=True,
|
|
2758
|
+
is_list=False,
|
|
2759
|
+
nested_model="AwardingOffice",
|
|
2760
|
+
),
|
|
2739
2761
|
"release_date": FieldSchema(
|
|
2740
2762
|
name="release_date", type=date, is_optional=True, is_list=False
|
|
2741
2763
|
),
|
|
@@ -2839,7 +2861,7 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
|
|
|
2839
2861
|
type=dict,
|
|
2840
2862
|
is_optional=True,
|
|
2841
2863
|
is_list=True,
|
|
2842
|
-
nested_model="
|
|
2864
|
+
nested_model="Attachments3",
|
|
2843
2865
|
),
|
|
2844
2866
|
"bid_opening_date": FieldSchema(
|
|
2845
2867
|
name="bid_opening_date", type=datetime, is_optional=True, is_list=False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: tango-python
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.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
|
|
@@ -308,6 +308,15 @@ protests = client.list_protests(source_system="gao", outcome="Sustained", limit=
|
|
|
308
308
|
protest = client.get_protest("CASE_UUID")
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
+
### GSA eBuy
|
|
312
|
+
|
|
313
|
+
```python
|
|
314
|
+
access = client.get_ebuy_access() # scoped to the schedule contracts linked to your account
|
|
315
|
+
requests = client.list_ebuy_requests(status="Open", sin="54151S", limit=25)
|
|
316
|
+
request = client.get_ebuy_request("RFQ1835158")
|
|
317
|
+
url = client.get_ebuy_attachment_url("RFQ1835158", doc_seq_num=1) # short-lived signed URL
|
|
318
|
+
```
|
|
319
|
+
|
|
311
320
|
### GSA eLibrary Contracts
|
|
312
321
|
|
|
313
322
|
```python
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
tango/__init__.py,sha256=
|
|
2
|
-
tango/client.py,sha256=
|
|
3
|
-
tango/exceptions.py,sha256=
|
|
4
|
-
tango/models.py,sha256=
|
|
1
|
+
tango/__init__.py,sha256=jdqZItlsYrPAUN5SNr0jxMzKta8MVebhkafeJkatzyI,2534
|
|
2
|
+
tango/client.py,sha256=_QCR5rwYPHs1YHsuf9TCEJXQoNIcHshF5nRrArmuf7s,237760
|
|
3
|
+
tango/exceptions.py,sha256=oPiLCccBoWLK5SjHkzP-oI6mUD9twEeKSvEuLV5h01I,4224
|
|
4
|
+
tango/models.py,sha256=Ujvt-cUxfFA2VZoAfRZs77WPcwUdyb-Hw-EWkoa7mrw,66613
|
|
5
5
|
tango/shapes/__init__.py,sha256=7ea1WU74jp4znhNw-gXruag6m6eyPZtbVgbDFmFUWro,1072
|
|
6
|
-
tango/shapes/explicit_schemas.py,sha256=
|
|
6
|
+
tango/shapes/explicit_schemas.py,sha256=spYyMmWKOiMJygz9yViBQrjIxUPFfv7HpVkJcWy13x8,84021
|
|
7
7
|
tango/shapes/factory.py,sha256=uDPhnp6VVzrLBKodxteZy5AcU43juEv-qKVRWth51j0,31550
|
|
8
|
-
tango/shapes/generated_overlay.py,sha256=
|
|
8
|
+
tango/shapes/generated_overlay.py,sha256=X6VLGbO2sgh4X_u8hJmIqnB30Hct8XZDQfnSbIV7tBU,138368
|
|
9
9
|
tango/shapes/generator.py,sha256=xzakpwTwqTMUr89I06hp54-FIYZziviWHk2Glscg-LM,22775
|
|
10
10
|
tango/shapes/models.py,sha256=h3pIhOqrrdlN953Y6r0oney5HFbKPOD-frRndRWimJ0,3018
|
|
11
11
|
tango/shapes/parser.py,sha256=AecNx5oT_hV_pf2AWZnsR8Y2aLmCtjfgS9-znA2Pesc,27429
|
|
@@ -16,8 +16,8 @@ tango/webhooks/cli.py,sha256=f_vQbJ2AeSQjKnQo7MxHFyUB2SAKcgHYmGQULS0isqQ,13858
|
|
|
16
16
|
tango/webhooks/receiver.py,sha256=5yhsVhlLyoxmOCGvmbynWAIlDB2OaCPVf1H4GA1SxmU,9279
|
|
17
17
|
tango/webhooks/signing.py,sha256=92Ee-0B6PR7ZkvY3Np3gzl88-mtfKkh-I7lxqCe2lGw,2374
|
|
18
18
|
tango/webhooks/simulate.py,sha256=g2Osa0FYU5mJuon07T2aUCtmkUoTEzsY261tlp76fF0,3165
|
|
19
|
-
tango_python-1.
|
|
20
|
-
tango_python-1.
|
|
21
|
-
tango_python-1.
|
|
22
|
-
tango_python-1.
|
|
23
|
-
tango_python-1.
|
|
19
|
+
tango_python-1.9.0.dist-info/METADATA,sha256=FFL14hmyg_t__WWg6lSxVA3ydOSA8GwLoaIdysJsuog,22117
|
|
20
|
+
tango_python-1.9.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
21
|
+
tango_python-1.9.0.dist-info/entry_points.txt,sha256=kGLUbglUjuaAqEFvOZ1QuSW0vWb6VeSpCIFKaOFkKoQ,50
|
|
22
|
+
tango_python-1.9.0.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
|
|
23
|
+
tango_python-1.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|