dataproduct-cli 0.0.1__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.
@@ -0,0 +1,23 @@
1
+ from dataproduct.model.run import ResultEnum
2
+
3
+
4
+ class DataProductException(Exception):
5
+ """A handled error surfaced as a check in the run result (no traceback)."""
6
+
7
+ def __init__(
8
+ self,
9
+ type: str,
10
+ name: str,
11
+ reason: str,
12
+ engine: str = "dataproduct",
13
+ result: ResultEnum = ResultEnum.error,
14
+ ):
15
+ self.type = type
16
+ self.name = name
17
+ self.reason = reason
18
+ self.engine = engine
19
+ self.result = result
20
+ super().__init__(reason)
21
+
22
+ def __str__(self) -> str:
23
+ return f"{self.name}: {self.reason}"
@@ -0,0 +1,69 @@
1
+ """Result model for a CLI run (lint, publish, …).
2
+
3
+ Mirrors datacontract-cli's ``Run``/``Check`` shape closely so the two tools feel
4
+ like siblings and share output tooling. The ``warning`` result level exists for
5
+ parity and forward-compat; 0.1 emits only ``passed`` / ``error``.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime, timezone
10
+ from enum import Enum
11
+ from typing import List, Optional
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class ResultEnum(str, Enum):
17
+ passed = "passed"
18
+ warning = "warning"
19
+ error = "error"
20
+ failed = "failed"
21
+
22
+
23
+ class Check(BaseModel):
24
+ type: str = "lint"
25
+ result: ResultEnum
26
+ name: str
27
+ reason: Optional[str] = None
28
+ engine: str = "dataproduct"
29
+ field: Optional[str] = None
30
+
31
+
32
+ class Run(BaseModel):
33
+ result: ResultEnum = ResultEnum.passed
34
+ checks: List[Check] = Field(default_factory=list)
35
+ dataProductId: Optional[str] = None
36
+ dataProductVersion: Optional[str] = None
37
+ timestampStart: Optional[datetime] = None
38
+ timestampEnd: Optional[datetime] = None
39
+ logs: List[str] = Field(default_factory=list)
40
+
41
+ @classmethod
42
+ def create_run(cls) -> "Run":
43
+ return cls(timestampStart=datetime.now(timezone.utc))
44
+
45
+ def log_info(self, message: str) -> None:
46
+ logging.info(message)
47
+ self.logs.append(f"INFO {message}")
48
+
49
+ def log_warn(self, message: str) -> None:
50
+ logging.warning(message)
51
+ self.logs.append(f"WARN {message}")
52
+
53
+ def log_error(self, message: str) -> None:
54
+ logging.error(message)
55
+ self.logs.append(f"ERROR {message}")
56
+
57
+ def has_passed(self) -> bool:
58
+ return self.result == ResultEnum.passed
59
+
60
+ def finish(self) -> None:
61
+ """Compute the overall result and stamp the end time.
62
+
63
+ Any ``error`` check fails the run; ``warning`` checks do not.
64
+ """
65
+ self.timestampEnd = datetime.now(timezone.utc)
66
+ if any(c.result == ResultEnum.error for c in self.checks):
67
+ self.result = ResultEnum.failed
68
+ else:
69
+ self.result = ResultEnum.passed
File without changes
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class OutputFormat(str, Enum):
5
+ json = "json"
6
+ junit = "junit"
@@ -0,0 +1,77 @@
1
+ """Render a :class:`Run` to the console or a machine-readable file.
2
+
3
+ Console output uses ``rich``; ``--output-format json|junit`` writes a file (or
4
+ stdout). A failed run exits with code 1.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ from xml.etree.ElementTree import Element, SubElement, tostring
10
+
11
+ import typer
12
+
13
+ from dataproduct.model.run import ResultEnum, Run
14
+ from dataproduct.output.output_format import OutputFormat
15
+
16
+ _ICON = {
17
+ ResultEnum.passed: "✅",
18
+ ResultEnum.warning: "⚠️ ",
19
+ ResultEnum.error: "❌",
20
+ ResultEnum.failed: "❌",
21
+ }
22
+
23
+
24
+ def write_result(run: Run, console, output_format: Optional[OutputFormat], output: Optional[Path]) -> None:
25
+ if output_format == OutputFormat.json:
26
+ _write_or_print(run.model_dump_json(indent=2), output, console)
27
+ elif output_format == OutputFormat.junit:
28
+ _write_or_print(_to_junit(run), output, console)
29
+ else:
30
+ _print_console(run, console)
31
+
32
+ if not run.has_passed():
33
+ raise typer.Exit(code=1)
34
+
35
+
36
+ def _print_console(run: Run, console) -> None:
37
+ for check in run.checks:
38
+ icon = _ICON.get(check.result, "•")
39
+ line = f"{icon} {check.name}"
40
+ if check.reason:
41
+ line += f": {check.reason}"
42
+ console.print(line)
43
+ if run.has_passed():
44
+ console.print("[green]🟢 Data product is valid.[/green]")
45
+ else:
46
+ console.print("[red]🔴 Data product is invalid.[/red]")
47
+
48
+
49
+ def _write_or_print(content: str, output: Optional[Path], console) -> None:
50
+ if output is not None:
51
+ Path(output).write_text(content)
52
+ console.print(f"📝 results written to {output}")
53
+ else:
54
+ print(content)
55
+
56
+
57
+ def _to_junit(run: Run) -> str:
58
+ failures = sum(1 for c in run.checks if c.result in (ResultEnum.error, ResultEnum.failed))
59
+ testsuite = Element(
60
+ "testsuite",
61
+ {
62
+ "name": "dataproduct-lint",
63
+ "tests": str(len(run.checks)),
64
+ "failures": str(failures),
65
+ "errors": "0",
66
+ },
67
+ )
68
+ for check in run.checks:
69
+ testcase = SubElement(
70
+ testsuite,
71
+ "testcase",
72
+ {"classname": check.type, "name": check.name},
73
+ )
74
+ if check.result in (ResultEnum.error, ResultEnum.failed):
75
+ failure = SubElement(testcase, "failure", {"message": check.reason or check.name})
76
+ failure.text = check.reason or check.name
77
+ return '<?xml version="1.0" encoding="UTF-8"?>\n' + tostring(testsuite, encoding="unicode")
dataproduct/py.typed ADDED
File without changes
@@ -0,0 +1,37 @@
1
+ apiVersion: v1.0.0
2
+ kind: DataProduct
3
+ id: my-data-product-id
4
+ name: My Data Product
5
+ version: v1.0.0
6
+ status: draft
7
+
8
+ description:
9
+ purpose: Purpose of the data product.
10
+ usage: Intended usage of the data product.
11
+ limitations: Limitations of the data product.
12
+
13
+ # tags: ['example']
14
+
15
+ # inputPorts:
16
+ # - name: source-data
17
+ # version: 1.0.0
18
+ # contractId: 00000000-0000-0000-0000-000000000000
19
+
20
+ outputPorts:
21
+ - name: my-output-port
22
+ description: The data this product exposes.
23
+ type: tables
24
+ version: 1.0.0
25
+ # contractId: 00000000-0000-0000-0000-000000000000
26
+
27
+ # support:
28
+ # - channel: My Team Slack
29
+ # url: https://example.slack.com/archives/C0000000000
30
+ # tool: slack
31
+
32
+ # team:
33
+ # name: My Team
34
+ # members:
35
+ # - username: jane.doe@example.com
36
+ # name: Jane Doe
37
+ # role: owner