Skip to content

Commit 60d9fb2

Browse files
committed
lint + GA tweak
1 parent 7d27957 commit 60d9fb2

8 files changed

Lines changed: 123 additions & 83 deletions

File tree

.github/workflows/lint.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
name: Linting
22

3+
# Disabled: requires checkout of private makegov/tango repo for filter_shape conformance check.
4+
# Re-enable when that repo is accessible (e.g. add TANGO_API_REPO_ACCESS_TOKEN secret).
35
on:
4-
push:
5-
branches: [ main, develop ]
6-
pull_request:
7-
branches: [ main, develop ]
6+
workflow_dispatch:
7+
# push:
8+
# branches: [ main, develop ]
9+
# pull_request:
10+
# branches: [ main, develop ]
811

912
jobs:
1013
lint:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added
1313
- Protests endpoints: `list_protests`, `get_protest` with shaping and filter params (`source_system`, `outcome`, `case_type`, `agency`, `case_number`, `solicitation_number`, `protester`, `filed_date_after`, `filed_date_before`, `decision_date_after`, `decision_date_before`, `search`).
1414

15+
### Changed
16+
- Lint CI workflow disabled for push/PR (runs only on manual trigger) until the private `makegov/tango` repo is accessible to the workflow.
17+
1518
## [0.4.1] - 2026-03-03
1619

1720
### Added

docs/quick_start.ipynb

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,15 @@
171171
"print(f\"Found {contracts.count:,} total contracts\\n\")\n",
172172
"\n",
173173
"for contract in contracts.results:\n",
174-
" amount = f\"${contract['total_contract_value']:,.2f}\" if contract.get('total_contract_value') else \"N/A\"\n",
175-
" date_str = contract.get('award_date', 'N/A')\n",
176-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
177-
" description = contract.get('description', 'No description')[:100]\n",
178-
" \n",
174+
" amount = (\n",
175+
" f\"${contract['total_contract_value']:,.2f}\"\n",
176+
" if contract.get(\"total_contract_value\")\n",
177+
" else \"N/A\"\n",
178+
" )\n",
179+
" date_str = contract.get(\"award_date\", \"N/A\")\n",
180+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
181+
" description = contract.get(\"description\", \"No description\")[:100]\n",
182+
"\n",
179183
" print(f\"{recipient}\")\n",
180184
" print(f\" Amount: {amount}\")\n",
181185
" print(f\" Date: {date_str}\")\n",
@@ -223,8 +227,12 @@
223227
"\n",
224228
"print(f\"Contracts from last 30 days: {recent_contracts.count:,}\")\n",
225229
"for contract in recent_contracts.results:\n",
226-
" amount = f\"${contract['total_contract_value']:,.2f}\" if contract.get('total_contract_value') else \"N/A\"\n",
227-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
230+
" amount = (\n",
231+
" f\"${contract['total_contract_value']:,.2f}\"\n",
232+
" if contract.get(\"total_contract_value\")\n",
233+
" else \"N/A\"\n",
234+
" )\n",
235+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
228236
" print(f\"- {recipient}: {amount}\")"
229237
]
230238
},
@@ -255,8 +263,12 @@
255263
"\n",
256264
"print(f\"IT services contracts (NAICS 541511): {it_contracts.count:,}\")\n",
257265
"for contract in it_contracts.results:\n",
258-
" amount = f\"${contract['total_contract_value']:,.2f}\" if contract.get('total_contract_value') else \"N/A\"\n",
259-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
266+
" amount = (\n",
267+
" f\"${contract['total_contract_value']:,.2f}\"\n",
268+
" if contract.get(\"total_contract_value\")\n",
269+
" else \"N/A\"\n",
270+
" )\n",
271+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
260272
" print(f\"- {recipient}: {amount}\")"
261273
]
262274
},
@@ -283,8 +295,12 @@
283295
"gsa_contracts = client.list_contracts(awarding_agency=\"GSA\", limit=5)\n",
284296
"print(f\"GSA contracts: {gsa_contracts.count:,}\")\n",
285297
"for contract in gsa_contracts.results:\n",
286-
" amount = f\"${contract['total_contract_value']:,.2f}\" if contract.get('total_contract_value') else \"N/A\"\n",
287-
" description = contract.get('description', 'No description')[:80]\n",
298+
" amount = (\n",
299+
" f\"${contract['total_contract_value']:,.2f}\"\n",
300+
" if contract.get(\"total_contract_value\")\n",
301+
" else \"N/A\"\n",
302+
" )\n",
303+
" description = contract.get(\"description\", \"No description\")[:80]\n",
288304
" print(f\"- {description}... ({amount})\")"
289305
]
290306
},
@@ -345,15 +361,15 @@
345361
"print(f\"Found {results.count:,} contracts matching criteria\\n\")\n",
346362
"\n",
347363
"for contract in results.results:\n",
348-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
349-
" amount = contract.get('total_contract_value')\n",
350-
" date_str = contract.get('award_date', 'N/A')\n",
351-
" \n",
364+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
365+
" amount = contract.get(\"total_contract_value\")\n",
366+
" date_str = contract.get(\"award_date\", \"N/A\")\n",
367+
"\n",
352368
" print(f\"{recipient}\")\n",
353369
" print(f\" Amount: ${amount:,.2f}\" if amount else \" Amount: N/A\")\n",
354370
" print(f\" Date: {date_str}\")\n",
355-
" if contract.get('naics'):\n",
356-
" naics = contract['naics']\n",
371+
" if contract.get(\"naics\"):\n",
372+
" naics = contract[\"naics\"]\n",
357373
" print(f\" NAICS: {naics.get('code')} - {naics.get('description', '')}\")\n",
358374
" print()"
359375
]
@@ -386,14 +402,13 @@
386402
"source": [
387403
"# Use a custom shape to get only specific fields\n",
388404
"contracts_shaped = client.list_contracts(\n",
389-
" limit=3,\n",
390-
" shape=\"key,piid,recipient(display_name),total_contract_value\"\n",
405+
" limit=3, shape=\"key,piid,recipient(display_name),total_contract_value\"\n",
391406
")\n",
392407
"\n",
393408
"print(\"Custom shape (key, recipient, amount):\")\n",
394409
"for contract in contracts_shaped.results:\n",
395-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
396-
" amount = contract.get('total_contract_value')\n",
410+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
411+
" amount = contract.get(\"total_contract_value\")\n",
397412
" print(f\" {recipient}: ${amount:,.2f}\" if amount else f\" {recipient}: N/A\")"
398413
]
399414
},
@@ -423,13 +438,13 @@
423438
"print(\"Dictionary and attribute access both work:\")\n",
424439
"for contract in contracts.results:\n",
425440
" # Dictionary access (recommended) - works for all fields\n",
426-
" recipient_dict = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
427-
" piid_dict = contract.get('piid', 'N/A')\n",
428-
" \n",
441+
" recipient_dict = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
442+
" piid_dict = contract.get(\"piid\", \"N/A\")\n",
443+
"\n",
429444
" # Attribute access also works for top-level fields\n",
430445
" # Note: Nested objects are dicts, so use dictionary access for nested fields\n",
431-
" piid_attr = contract.piid if hasattr(contract, 'piid') else 'N/A'\n",
432-
" \n",
446+
" piid_attr = contract.piid if hasattr(contract, \"piid\") else \"N/A\"\n",
447+
"\n",
433448
" print(f\" PIID - Dictionary: {piid_dict}, Attribute: {piid_attr}\")\n",
434449
" print(f\" Recipient (dict access): {recipient_dict}\")\n",
435450
" print()"
@@ -463,8 +478,8 @@
463478
"\n",
464479
"print(\"Custom shape (only specific fields):\")\n",
465480
"for contract in contracts_custom.results:\n",
466-
" piid = contract.get('piid', 'N/A')\n",
467-
" recipient = contract.get('recipient', {}).get('display_name', 'Unknown')\n",
481+
" piid = contract.get(\"piid\", \"N/A\")\n",
482+
" recipient = contract.get(\"recipient\", {}).get(\"display_name\", \"Unknown\")\n",
468483
" print(f\" {piid}: {recipient}\")\n",
469484
" print(f\" Date: {contract.get('award_date', 'N/A')}\")"
470485
]
@@ -522,15 +537,15 @@
522537
"print(f\"Found {entities.count:,} entities\\n\")\n",
523538
"\n",
524539
"for entity in entities.results:\n",
525-
" display_name = entity.get('display_name', 'Unknown')\n",
540+
" display_name = entity.get(\"display_name\", \"Unknown\")\n",
526541
" print(f\"{display_name}\")\n",
527-
" if entity.get('uei'):\n",
542+
" if entity.get(\"uei\"):\n",
528543
" print(f\" UEI: {entity['uei']}\")\n",
529-
" if entity.get('physical_address'):\n",
530-
" addr = entity['physical_address']\n",
531-
" if addr.get('city') and addr.get('state_code'):\n",
544+
" if entity.get(\"physical_address\"):\n",
545+
" addr = entity[\"physical_address\"]\n",
546+
" if addr.get(\"city\") and addr.get(\"state_code\"):\n",
532547
" print(f\" Location: {addr['city']}, {addr['state_code']}\")\n",
533-
" if entity.get('business_types'):\n",
548+
" if entity.get(\"business_types\"):\n",
534549
" print(f\" Business Types: {entity['business_types'][:3]}\")\n",
535550
" print()"
536551
]
@@ -551,14 +566,14 @@
551566
"source": [
552567
"# Get specific entity by UEI or CAGE code\n",
553568
"if entities.results:\n",
554-
" entity_key = entities.results[0].get('uei') or entities.results[0].get('cage_code')\n",
569+
" entity_key = entities.results[0].get(\"uei\") or entities.results[0].get(\"cage_code\")\n",
555570
" if entity_key:\n",
556571
" entity = client.get_entity(entity_key)\n",
557-
" if entity.get('legal_business_name'):\n",
572+
" if entity.get(\"legal_business_name\"):\n",
558573
" print(f\"Legal Name: {entity['legal_business_name']}\")\n",
559-
" if entity.get('physical_address'):\n",
560-
" addr = entity['physical_address']\n",
561-
" if addr.get('city') and addr.get('state_code'):\n",
574+
" if entity.get(\"physical_address\"):\n",
575+
" addr = entity[\"physical_address\"]\n",
576+
" if addr.get(\"city\") and addr.get(\"state_code\"):\n",
562577
" print(f\"Location: {addr['city']}, {addr['state_code']}\")"
563578
]
564579
},
@@ -615,13 +630,13 @@
615630
"print(f\"Found {forecasts.count:,} contract forecasts\\n\")\n",
616631
"\n",
617632
"for forecast in forecasts.results:\n",
618-
" title = forecast.get('title', 'Untitled')\n",
633+
" title = forecast.get(\"title\", \"Untitled\")\n",
619634
" print(f\"{title}\")\n",
620-
" if forecast.get('anticipated_award_date'):\n",
635+
" if forecast.get(\"anticipated_award_date\"):\n",
621636
" print(f\" Anticipated Award: {forecast['anticipated_award_date']}\")\n",
622-
" if forecast.get('fiscal_year'):\n",
637+
" if forecast.get(\"fiscal_year\"):\n",
623638
" print(f\" Fiscal Year: {forecast['fiscal_year']}\")\n",
624-
" if forecast.get('naics_code'):\n",
639+
" if forecast.get(\"naics_code\"):\n",
625640
" print(f\" NAICS: {forecast['naics_code']}\")\n",
626641
" print()"
627642
]
@@ -680,13 +695,13 @@
680695
"print(f\"Found {opportunities.count:,} contract opportunities\\n\")\n",
681696
"\n",
682697
"for opp in opportunities.results:\n",
683-
" title = opp.get('title', 'Untitled')\n",
698+
" title = opp.get(\"title\", \"Untitled\")\n",
684699
" print(f\"{title}\")\n",
685-
" if opp.get('solicitation_number'):\n",
700+
" if opp.get(\"solicitation_number\"):\n",
686701
" print(f\" Solicitation #: {opp['solicitation_number']}\")\n",
687-
" if opp.get('response_deadline'):\n",
702+
" if opp.get(\"response_deadline\"):\n",
688703
" print(f\" Response Deadline: {opp['response_deadline']}\")\n",
689-
" if opp.get('active') is not None:\n",
704+
" if opp.get(\"active\") is not None:\n",
690705
" print(f\" Active: {opp['active']}\")\n",
691706
" print()"
692707
]
@@ -744,13 +759,13 @@
744759
"print(f\"Found {notices.count:,} contract notices\\n\")\n",
745760
"\n",
746761
"for notice in notices.results:\n",
747-
" title = notice.get('title', 'Untitled')\n",
762+
" title = notice.get(\"title\", \"Untitled\")\n",
748763
" print(f\"{title}\")\n",
749-
" if notice.get('solicitation_number'):\n",
764+
" if notice.get(\"solicitation_number\"):\n",
750765
" print(f\" Solicitation #: {notice['solicitation_number']}\")\n",
751-
" if notice.get('posted_date'):\n",
766+
" if notice.get(\"posted_date\"):\n",
752767
" print(f\" Posted: {notice['posted_date']}\")\n",
753-
" if notice.get('naics_code'):\n",
768+
" if notice.get(\"naics_code\"):\n",
754769
" print(f\" NAICS: {notice['naics_code']}\")\n",
755770
" print()"
756771
]

scripts/pr_review.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,22 @@ def get_pr_info_from_gh_cli(pr_number: int | None = None) -> PRInfo | None:
169169

170170
# Get current PR or specific PR number
171171
if pr_number:
172-
cmd = ["gh", "pr", "view", str(pr_number), "--json", "number,title,author,baseRefName,headRefName,url,state"]
172+
cmd = [
173+
"gh",
174+
"pr",
175+
"view",
176+
str(pr_number),
177+
"--json",
178+
"number,title,author,baseRefName,headRefName,url,state",
179+
]
173180
else:
174-
cmd = ["gh", "pr", "view", "--json", "number,title,author,baseRefName,headRefName,url,state"]
181+
cmd = [
182+
"gh",
183+
"pr",
184+
"view",
185+
"--json",
186+
"number,title,author,baseRefName,headRefName,url,state",
187+
]
175188

176189
result = subprocess.run(
177190
cmd,
@@ -345,12 +358,18 @@ def run_conformance_check() -> int:
345358
if not path.exists():
346359
print_warning("Conformance manifest not found - skipping filter/shape conformance")
347360
print(f" Expected: {path}")
348-
print(" Set TANGO_CONTRACT_MANIFEST or clone tango repo as tango-api/ (see scripts/README.md)")
361+
print(
362+
" Set TANGO_CONTRACT_MANIFEST or clone tango repo as tango-api/ (see scripts/README.md)"
363+
)
349364
return 0 # Don't fail when manifest is missing
350365

351366
cmd = [
352-
"uv", "run", "python", "scripts/check_filter_shape_conformance.py",
353-
"--manifest", manifest_path,
367+
"uv",
368+
"run",
369+
"python",
370+
"scripts/check_filter_shape_conformance.py",
371+
"--manifest",
372+
manifest_path,
354373
]
355374
return run_command(cmd, "Filter and shape conformance", check=False)
356375

tests/integration/conftest.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,54 +22,54 @@
2222
def path_transformer(path: str) -> str:
2323
"""
2424
Transform cassette path to shorten long filenames for Windows compatibility.
25-
25+
2626
Windows has a 260 character path limit. This function shortens long parameter
2727
values in test names by replacing them with a hash, keeping the filename
2828
under the limit while maintaining uniqueness.
29-
29+
3030
Args:
3131
path: Original cassette path (e.g., "tests/cassettes/TestClass.test_method[param1-param2].yaml")
32-
32+
3333
Returns:
3434
Shortened path with long parameters replaced by hash
3535
"""
3636
# Maximum safe filename length (accounting for path prefix)
3737
MAX_FILENAME_LENGTH = 200
38-
38+
3939
path_obj = Path(path)
4040
filename = path_obj.name
4141
directory = path_obj.parent
42-
42+
4343
# If filename is already short enough, return as-is
4444
if len(filename) <= MAX_FILENAME_LENGTH:
4545
return path
46-
46+
4747
# Extract base name and extension
48-
if filename.endswith('.yaml'):
48+
if filename.endswith(".yaml"):
4949
base_name = filename[:-5] # Remove .yaml
50-
ext = '.yaml'
51-
elif filename.endswith('.yml'):
50+
ext = ".yaml"
51+
elif filename.endswith(".yml"):
5252
base_name = filename[:-4] # Remove .yml
53-
ext = '.yml'
53+
ext = ".yml"
5454
else:
5555
# Unknown extension, return as-is
5656
return path
57-
57+
5858
# Check if this is a parameterized test (has brackets)
59-
if '[' not in base_name or ']' not in base_name:
59+
if "[" not in base_name or "]" not in base_name:
6060
# Not parameterized, just truncate if needed
6161
if len(filename) > MAX_FILENAME_LENGTH:
62-
truncated = base_name[:MAX_FILENAME_LENGTH - len(ext) - 8] + ext
62+
truncated = base_name[: MAX_FILENAME_LENGTH - len(ext) - 8] + ext
6363
return str(directory / truncated)
6464
return path
65-
65+
6666
# Split into test name and parameters
67-
test_name, params = base_name.split('[', 1)
68-
if not params.endswith(']'):
67+
test_name, params = base_name.split("[", 1)
68+
if not params.endswith("]"):
6969
return path # Malformed, return as-is
70-
70+
7171
params = params[:-1] # Remove trailing ]
72-
72+
7373
# If parameters are too long, hash them
7474
if len(params) > 100: # Threshold for hashing
7575
# Create a hash of the parameters (first 8 chars for readability)
@@ -87,8 +87,8 @@ def path_transformer(path: str) -> str:
8787
else:
8888
# Even test name is too long, use hash
8989
param_hash = hashlib.md5(params.encode()).hexdigest()[:8]
90-
new_filename = f"{test_name[:MAX_FILENAME_LENGTH - 15]}[{param_hash}]{ext}"
91-
90+
new_filename = f"{test_name[: MAX_FILENAME_LENGTH - 15]}[{param_hash}]{ext}"
91+
9292
return str(directory / new_filename)
9393

9494

tests/integration/test_entities_integration.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
TANGO_REFRESH_CASSETTES=true TANGO_API_KEY=xxx pytest tests/integration/
2222
"""
2323

24-
2524
import pytest
2625

2726
from tango import ShapeConfig

0 commit comments

Comments
 (0)