Skip to content

Commit 58a1530

Browse files
authored
Updates to CLI and programatic API (#2474)
* Updated contract API to allow only a single contract file. Includes changes to publish API * More updates to contract API * Implemented PR feedback: re-introduced old functions, changed dataset identifier. Exposed publish_contract to API * Fix rogue dataset_identifiers arguments * Change deprecate import * Fix dataset_id being `None` in the results * PR feedback
1 parent f5a0cc9 commit 58a1530

File tree

13 files changed

+216
-107
lines changed

13 files changed

+216
-107
lines changed

soda-core/src/soda_core/cli/cli.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,13 @@ def _setup_contract_verify_command(contract_parsers) -> None:
110110
"-c",
111111
"--contract",
112112
type=str,
113-
nargs="+",
114-
help="One or more contract file paths. Use this to work with local contracts.",
113+
help="Contract file path to verify. Use this to work with local contracts.",
115114
)
116115
verify_parser.add_argument(
117116
"-d",
118117
"--dataset",
119118
type=str,
120-
nargs="+",
121-
help="Names of datasets to verify. Use this to work with remote contracts present in Soda Cloud.",
119+
help="Name of dataset to verify. Use this to work with remote contracts present in Soda Cloud.",
122120
)
123121

124122
verify_parser.add_argument(
@@ -180,8 +178,8 @@ def _setup_contract_verify_command(contract_parsers) -> None:
180178
)
181179

182180
def handle(args):
183-
contract_file_paths = args.contract
184-
dataset_identifiers = args.dataset
181+
contract_file_path = args.contract
182+
dataset_identifier = args.dataset
185183
data_source_file_paths = args.data_source
186184
soda_cloud_file_path = args.soda_cloud
187185
variables = _parse_variables(args.set)
@@ -195,8 +193,8 @@ def handle(args):
195193
diagnostics_warehouse_file_path = args.diagnostics_warehouse
196194

197195
exit_code = handle_verify_contract(
198-
contract_file_paths,
199-
dataset_identifiers,
196+
contract_file_path,
197+
dataset_identifier,
200198
data_source_file_paths,
201199
soda_cloud_file_path,
202200
variables,
@@ -236,7 +234,7 @@ def _parse_variables(variables: Optional[List[str]]) -> Optional[Dict[str, str]]
236234

237235
def _setup_contract_publish_command(contract_parsers) -> None:
238236
publish_parser = contract_parsers.add_parser("publish", help="Publish a contract")
239-
publish_parser.add_argument("-c", "--contract", type=str, nargs="+", help="One or more contract file paths.")
237+
publish_parser.add_argument("-c", "--contract", type=str, help="Contract file path to publish.")
240238

241239
publish_parser.add_argument(
242240
"-sc",
@@ -256,17 +254,17 @@ def _setup_contract_publish_command(contract_parsers) -> None:
256254
)
257255

258256
def handle(args):
259-
contract_file_paths = args.contract
257+
contract_file_path = args.contract
260258
soda_cloud_file_path = args.soda_cloud
261-
exit_code = handle_publish_contract(contract_file_paths, soda_cloud_file_path)
259+
exit_code = handle_publish_contract(contract_file_path, soda_cloud_file_path)
262260
exit_with_code(exit_code)
263261

264262
publish_parser.set_defaults(handler_func=handle)
265263

266264

267265
def _setup_contract_test_command(contract_parsers) -> None:
268266
test_contract_parser = contract_parsers.add_parser(name="test", help="Test a contract syntax without executing it")
269-
test_contract_parser.add_argument("-c", "--contract", type=str, nargs="+", help="One or more contract file paths.")
267+
test_contract_parser.add_argument("-c", "--contract", type=str, help="Contract file path to test.")
270268

271269
test_contract_parser.add_argument(
272270
"-v",
@@ -278,9 +276,9 @@ def _setup_contract_test_command(contract_parsers) -> None:
278276
)
279277

280278
def handle(args):
281-
contract_file_paths = args.contract
279+
contract_file_path = args.contract
282280

283-
exit_code = handle_test_contract(contract_file_paths, {})
281+
exit_code = handle_test_contract(contract_file_path, {})
284282
exit_with_code(exit_code)
285283

286284
test_contract_parser.set_defaults(handler_func=handle)

soda-core/src/soda_core/cli/handlers/contract.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
)
1111
from soda_core.common.logging_constants import Emoticons, soda_logger
1212
from soda_core.common.yaml import ContractYamlSource
13-
from soda_core.contracts.api import test_contracts, verify_contracts
14-
from soda_core.contracts.contract_publication import ContractPublication
13+
from soda_core.contracts.api import test_contract, verify_contract
14+
from soda_core.contracts.api.publish_api import publish_contract
1515
from soda_core.contracts.contract_verification import ContractVerificationSessionResult
1616

1717

1818
def handle_verify_contract(
19-
contract_file_paths: Optional[list[str]],
20-
dataset_identifiers: Optional[list[str]],
19+
contract_file_path: Optional[str],
20+
dataset_identifier: Optional[str],
2121
data_source_file_paths: Optional[str],
2222
soda_cloud_file_path: Optional[str],
2323
variables: Optional[Dict[str, str]],
@@ -29,9 +29,9 @@ def handle_verify_contract(
2929
diagnostics_warehouse_file_path: Optional[str],
3030
) -> ExitCode:
3131
try:
32-
contract_verification_result = verify_contracts(
33-
contract_file_paths=contract_file_paths,
34-
dataset_identifiers=dataset_identifiers,
32+
contract_verification_result = verify_contract(
33+
contract_file_path=contract_file_path,
34+
dataset_identifier=dataset_identifier,
3535
data_source_file_path=None,
3636
data_source_file_paths=data_source_file_paths,
3737
soda_cloud_file_path=soda_cloud_file_path,
@@ -74,19 +74,11 @@ def interpret_contract_verification_result(verification_result: ContractVerifica
7474

7575

7676
def handle_publish_contract(
77-
contract_file_paths: Optional[list[str]],
77+
contract_file_path: Optional[str],
7878
soda_cloud_file_path: Optional[str],
7979
) -> ExitCode:
8080
try:
81-
contract_publication_builder = ContractPublication.builder()
82-
83-
for contract_file_path in contract_file_paths:
84-
contract_publication_builder.with_contract_yaml_file(contract_file_path)
85-
86-
if soda_cloud_file_path:
87-
contract_publication_builder.with_soda_cloud_yaml_file(soda_cloud_file_path)
88-
89-
contract_publication_result = contract_publication_builder.build().execute()
81+
contract_publication_result = publish_contract(contract_file_path, soda_cloud_file_path)
9082
if contract_publication_result.has_errors:
9183
# TODO: detect/deal with exit code 4?
9284
return ExitCode.LOG_ERRORS
@@ -101,18 +93,15 @@ def handle_publish_contract(
10193

10294

10395
def handle_test_contract(
104-
contract_file_paths: Optional[list[str]],
96+
contract_file_path: Optional[str],
10597
variables: Optional[Dict[str, str]],
10698
) -> ExitCode:
107-
for contract_file_path in contract_file_paths:
108-
contract_verification_result = test_contracts(contract_file_paths=[contract_file_path], variables=variables)
109-
if contract_verification_result.has_errors:
110-
return ExitCode.LOG_ERRORS
111-
else:
112-
soda_logger.info(f"{Emoticons.WHITE_CHECK_MARK} {contract_file_path} is valid")
113-
return ExitCode.OK
114-
115-
return ExitCode.OK
99+
contract_verification_result = test_contract(contract_file_path=contract_file_path, variables=variables)
100+
if contract_verification_result.has_errors:
101+
return ExitCode.LOG_ERRORS
102+
else:
103+
soda_logger.info(f"{Emoticons.WHITE_CHECK_MARK} {contract_file_path} is valid")
104+
return ExitCode.OK
116105

117106

118107
def handle_fetch_contract(

soda-core/src/soda_core/common/soda_cloud.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,7 @@ def publish_contract(self, contract_yaml: Optional[ContractYaml]) -> ContractPub
478478
soda_cloud_file_id=response_json.get("fileId", None),
479479
),
480480
soda_qualified_dataset_name=contract_yaml.dataset,
481+
dataset_id=response_json.get("publishedContract", {}).get("datasetId", None),
481482
),
482483
)
483484

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
from soda_core.contracts.api import (
2-
test_contracts,
3-
verify_contracts,
2+
publish_contract,
3+
test_contract,
4+
verify_contract,
5+
verify_contract_locally,
6+
verify_contract_on_agent,
47
verify_contracts_locally,
58
verify_contracts_on_agent,
69
)
710

811
__all__ = [
9-
"test_contracts",
10-
"verify_contracts",
12+
"test_contract",
13+
"verify_contract",
14+
"verify_contract_locally",
15+
"verify_contract_on_agent",
1116
"verify_contracts_locally",
1217
"verify_contracts_on_agent",
18+
"publish_contract",
1319
]
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1-
from soda_core.contracts.api.test_api import test_contracts
1+
from soda_core.contracts.api.publish_api import publish_contract
2+
from soda_core.contracts.api.test_api import test_contract
23
from soda_core.contracts.api.verify_api import (
3-
verify_contracts,
4+
verify_contract,
5+
verify_contract_locally,
6+
verify_contract_on_agent,
47
verify_contracts_locally,
58
verify_contracts_on_agent,
69
)
710

811
__all__ = [
9-
"test_contracts",
10-
"verify_contracts",
12+
"test_contract",
13+
"verify_contract",
14+
"verify_contract_locally",
15+
"verify_contract_on_agent",
1116
"verify_contracts_locally",
1217
"verify_contracts_on_agent",
18+
"publish_contract",
1319
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from soda_core.contracts.contract_publication import (
2+
ContractPublication,
3+
ContractPublicationResultList,
4+
)
5+
6+
7+
def publish_contract(contract_file_path: str, soda_cloud_file_path: str) -> ContractPublicationResultList:
8+
contract_publication_builder = ContractPublication.builder()
9+
10+
contract_publication_builder.with_contract_yaml_file(contract_file_path)
11+
contract_publication_builder.with_soda_cloud_yaml_file(soda_cloud_file_path)
12+
13+
contract_publication_result = contract_publication_builder.build().execute()
14+
15+
return contract_publication_result

soda-core/src/soda_core/contracts/api/test_api.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
from typing import Dict, Optional, Union
22

3+
from soda_core.common.exceptions import InvalidArgumentException
34
from soda_core.common.yaml import ContractYamlSource
45
from soda_core.contracts.contract_verification import (
56
ContractVerificationSession,
67
ContractVerificationSessionResult,
78
)
89
from soda_core.telemetry.soda_telemetry import SodaTelemetry
10+
from typing_extensions import deprecated
911

1012
soda_telemetry = SodaTelemetry()
1113

1214

15+
@deprecated("Use test_contract_locally instead")
1316
def test_contracts(
1417
contract_file_paths: Union[str, list[str]],
1518
variables: Optional[Dict[str, str]] = None,
1619
verbose: bool = False,
1720
) -> ContractVerificationSessionResult:
18-
if isinstance(contract_file_paths, str):
19-
contract_file_paths = [contract_file_paths]
21+
# Limit the contract file paths to only 1. Throw an error if more than 1 is provided.
22+
if isinstance(contract_file_paths, list) and len(contract_file_paths) > 1:
23+
raise InvalidArgumentException("Only one contract is allowed at a time")
2024

21-
contract_yaml_sources = [
22-
ContractYamlSource.from_file_path(contract_file_path) for contract_file_path in contract_file_paths
23-
]
25+
return test_contract(
26+
contract_file_path=contract_file_paths[0],
27+
variables=variables,
28+
verbose=verbose,
29+
)
30+
31+
32+
def test_contract(
33+
contract_file_path: str = None,
34+
variables: Optional[Dict[str, str]] = None,
35+
verbose: bool = False,
36+
) -> ContractVerificationSessionResult:
37+
contract_yaml_sources = [ContractYamlSource.from_file_path(contract_file_path)]
2438

2539
contract_verification_result = ContractVerificationSession.execute(
2640
contract_yaml_sources=contract_yaml_sources,

0 commit comments

Comments
 (0)