dataproduct-cli 0.0.1__tar.gz

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.
Files changed (41) hide show
  1. dataproduct_cli-0.0.1/CHANGELOG.md +16 -0
  2. dataproduct_cli-0.0.1/LICENSE +21 -0
  3. dataproduct_cli-0.0.1/MANIFEST.in +3 -0
  4. dataproduct_cli-0.0.1/PKG-INFO +126 -0
  5. dataproduct_cli-0.0.1/README.md +84 -0
  6. dataproduct_cli-0.0.1/dataproduct/__init__.py +5 -0
  7. dataproduct_cli-0.0.1/dataproduct/cli.py +81 -0
  8. dataproduct_cli-0.0.1/dataproduct/command_init.py +33 -0
  9. dataproduct_cli-0.0.1/dataproduct/command_lint.py +58 -0
  10. dataproduct_cli-0.0.1/dataproduct/command_publish.py +47 -0
  11. dataproduct_cli-0.0.1/dataproduct/config/__init__.py +67 -0
  12. dataproduct_cli-0.0.1/dataproduct/data_product.py +93 -0
  13. dataproduct_cli-0.0.1/dataproduct/init/__init__.py +0 -0
  14. dataproduct_cli-0.0.1/dataproduct/init/init_template.py +26 -0
  15. dataproduct_cli-0.0.1/dataproduct/integration/__init__.py +0 -0
  16. dataproduct_cli-0.0.1/dataproduct/integration/entropy_data.py +87 -0
  17. dataproduct_cli-0.0.1/dataproduct/lint/__init__.py +0 -0
  18. dataproduct_cli-0.0.1/dataproduct/lint/files.py +38 -0
  19. dataproduct_cli-0.0.1/dataproduct/lint/resolve.py +11 -0
  20. dataproduct_cli-0.0.1/dataproduct/lint/schema.py +44 -0
  21. dataproduct_cli-0.0.1/dataproduct/lint/validate.py +61 -0
  22. dataproduct_cli-0.0.1/dataproduct/model/__init__.py +4 -0
  23. dataproduct_cli-0.0.1/dataproduct/model/exceptions.py +23 -0
  24. dataproduct_cli-0.0.1/dataproduct/model/run.py +69 -0
  25. dataproduct_cli-0.0.1/dataproduct/output/__init__.py +0 -0
  26. dataproduct_cli-0.0.1/dataproduct/output/output_format.py +6 -0
  27. dataproduct_cli-0.0.1/dataproduct/output/result_writer.py +77 -0
  28. dataproduct_cli-0.0.1/dataproduct/py.typed +0 -0
  29. dataproduct_cli-0.0.1/dataproduct/schemas/odps-1.0.0.init.yaml +37 -0
  30. dataproduct_cli-0.0.1/dataproduct/schemas/odps-1.0.0.schema.json +517 -0
  31. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/PKG-INFO +126 -0
  32. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/SOURCES.txt +39 -0
  33. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/dependency_links.txt +1 -0
  34. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/entry_points.txt +2 -0
  35. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/requires.txt +15 -0
  36. dataproduct_cli-0.0.1/dataproduct_cli.egg-info/top_level.txt +1 -0
  37. dataproduct_cli-0.0.1/pyproject.toml +94 -0
  38. dataproduct_cli-0.0.1/setup.cfg +4 -0
  39. dataproduct_cli-0.0.1/tests/test_init.py +57 -0
  40. dataproduct_cli-0.0.1/tests/test_lint.py +98 -0
  41. dataproduct_cli-0.0.1/tests/test_publish.py +85 -0
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Each entry is one line:
4
+ what changed (user-facing).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.0.1]
9
+
10
+ - Test release to validate the PyPI publishing pipeline
11
+
12
+ ## [0.1.0]
13
+
14
+ - `init` command: create a valid `dataproduct.odps.yaml` from a bundled ODPS v1.0.0 template
15
+ - `lint` command: validate a data product against the ODPS v1.0.0 JSON Schema (json/junit output)
16
+ - `publish` command: publish a data product to Entropy Data (`PUT /api/dataproducts/{id}`)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Data Contract
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md LICENSE CHANGELOG.md
2
+ recursive-include dataproduct/schemas *.json *.yaml
3
+ include dataproduct/py.typed
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.4
2
+ Name: dataproduct-cli
3
+ Version: 0.0.1
4
+ Summary: The dataproduct CLI is an open source command-line tool for working with data products. It uses data product YAML files following the Open Data Product Standard (ODPS) to lint the definition against the standard and publish it to Entropy Data. The tool is written in Python. It can be used as a standalone CLI tool, in a CI/CD pipeline, or directly as a Python library.
5
+ Author-email: Michael Kutz <michael.kutz@entropy-data.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/datacontract/dataproduct-cli
8
+ Project-URL: Repository, https://github.com/datacontract/dataproduct-cli
9
+ Project-URL: Issues, https://github.com/datacontract/dataproduct-cli/issues
10
+ Keywords: data-product,data-products,odps,open-data-product-standard,data-governance,data-mesh,data-engineering
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Database
23
+ Classifier: Topic :: Software Development :: Quality Assurance
24
+ Requires-Python: <3.15,>=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: typer<0.28,>=0.18.0
28
+ Requires-Dist: click<9.0.0,>=8.1.0
29
+ Requires-Dist: pydantic<2.14.0,>=2.8.2
30
+ Requires-Dist: pyyaml~=6.0.1
31
+ Requires-Dist: requests<2.35,>=2.31
32
+ Requires-Dist: jsonschema<5.0.0,>=4.23.0
33
+ Requires-Dist: rich<16.0,>=13.7
34
+ Requires-Dist: python-dotenv<2.0.0,>=1.0.0
35
+ Requires-Dist: typing-extensions>=4.7.0
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest; extra == "dev"
38
+ Requires-Dist: pytest-xdist; extra == "dev"
39
+ Requires-Dist: ruff==0.16.1; extra == "dev"
40
+ Requires-Dist: pre-commit<4.7.0,>=3.7.1; extra == "dev"
41
+ Dynamic: license-file
42
+
43
+ # Data Product CLI
44
+
45
+ The `dataproduct` CLI is an open-source command-line tool for working with
46
+ **data products** defined with the
47
+ [Open Data Product Standard (ODPS)](https://bitol-io.github.io/open-data-product-standard/v1.0.0/).
48
+
49
+ It is the data-product sibling of
50
+ [`datacontract-cli`](https://github.com/datacontract/datacontract-cli) (which
51
+ targets the Open Data **Contract** Standard) and mirrors its technology and
52
+ release mechanism. The tool is written in Python and can be used standalone, in
53
+ CI/CD, or as a Python library.
54
+
55
+ > The behavior of each command is specified in [`specs/`](specs/).
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ uv tool install --python python3.11 dataproduct-cli
61
+ # or
62
+ pip install dataproduct-cli
63
+ ```
64
+
65
+ ## Commands
66
+
67
+ ### `init` — create a data product
68
+
69
+ ```bash
70
+ dataproduct init # writes ./dataproduct.odps.yaml
71
+ dataproduct init my.odps.yaml # custom path
72
+ dataproduct init --overwrite # replace an existing file
73
+ dataproduct init --template <url|path> # seed from a template or existing product
74
+ ```
75
+
76
+ ### `lint` — validate against the ODPS standard
77
+
78
+ ```bash
79
+ dataproduct lint # lints ./dataproduct.odps.yaml
80
+ dataproduct lint my.odps.yaml
81
+ dataproduct lint --all-errors # report every schema violation
82
+ dataproduct lint --output-format junit --output TEST-dataproduct.xml
83
+ dataproduct lint --json-schema ./odps.schema.json # validate against a custom schema
84
+ ```
85
+
86
+ Validation is schema-only in 0.1: the data product is checked against the
87
+ bundled ODPS v1.0.0 JSON Schema. Exit code is `0` when valid, `1` otherwise.
88
+
89
+ ### `publish` — publish to Entropy Data
90
+
91
+ ```bash
92
+ export ENTROPY_DATA_API_KEY=... # from Entropy Data → Settings → API Keys
93
+ dataproduct publish my.odps.yaml
94
+ ```
95
+
96
+ `publish` sends `PUT {host}/api/dataproducts/{id}` with an `x-api-key` header.
97
+ The host defaults to `https://api.entropy-data.com` and can be overridden with
98
+ `ENTROPY_DATA_HOST`. The referenced `team` must already exist in your org.
99
+
100
+ ## Configuration
101
+
102
+ | Purpose | Variable (with fallbacks) |
103
+ |---|---|
104
+ | API host | `ENTROPY_DATA_HOST` → `DATAMESH_MANAGER_HOST` → `DATACONTRACT_MANAGER_HOST` → `https://api.entropy-data.com` |
105
+ | API key | `ENTROPY_DATA_API_KEY` → `DATAMESH_MANAGER_API_KEY` → `DATACONTRACT_MANAGER_API_KEY` |
106
+
107
+ `.env` files are loaded automatically.
108
+
109
+ ## Use as a library
110
+
111
+ ```python
112
+ from dataproduct.data_product import DataProduct
113
+
114
+ run = DataProduct(data_product_file="dataproduct.odps.yaml").lint()
115
+ assert run.result == "passed"
116
+
117
+ DataProduct(data_product_file="dataproduct.odps.yaml").publish()
118
+ ```
119
+
120
+ ## Development
121
+
122
+ See [AGENTS.md](AGENTS.md).
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,84 @@
1
+ # Data Product CLI
2
+
3
+ The `dataproduct` CLI is an open-source command-line tool for working with
4
+ **data products** defined with the
5
+ [Open Data Product Standard (ODPS)](https://bitol-io.github.io/open-data-product-standard/v1.0.0/).
6
+
7
+ It is the data-product sibling of
8
+ [`datacontract-cli`](https://github.com/datacontract/datacontract-cli) (which
9
+ targets the Open Data **Contract** Standard) and mirrors its technology and
10
+ release mechanism. The tool is written in Python and can be used standalone, in
11
+ CI/CD, or as a Python library.
12
+
13
+ > The behavior of each command is specified in [`specs/`](specs/).
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ uv tool install --python python3.11 dataproduct-cli
19
+ # or
20
+ pip install dataproduct-cli
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ ### `init` — create a data product
26
+
27
+ ```bash
28
+ dataproduct init # writes ./dataproduct.odps.yaml
29
+ dataproduct init my.odps.yaml # custom path
30
+ dataproduct init --overwrite # replace an existing file
31
+ dataproduct init --template <url|path> # seed from a template or existing product
32
+ ```
33
+
34
+ ### `lint` — validate against the ODPS standard
35
+
36
+ ```bash
37
+ dataproduct lint # lints ./dataproduct.odps.yaml
38
+ dataproduct lint my.odps.yaml
39
+ dataproduct lint --all-errors # report every schema violation
40
+ dataproduct lint --output-format junit --output TEST-dataproduct.xml
41
+ dataproduct lint --json-schema ./odps.schema.json # validate against a custom schema
42
+ ```
43
+
44
+ Validation is schema-only in 0.1: the data product is checked against the
45
+ bundled ODPS v1.0.0 JSON Schema. Exit code is `0` when valid, `1` otherwise.
46
+
47
+ ### `publish` — publish to Entropy Data
48
+
49
+ ```bash
50
+ export ENTROPY_DATA_API_KEY=... # from Entropy Data → Settings → API Keys
51
+ dataproduct publish my.odps.yaml
52
+ ```
53
+
54
+ `publish` sends `PUT {host}/api/dataproducts/{id}` with an `x-api-key` header.
55
+ The host defaults to `https://api.entropy-data.com` and can be overridden with
56
+ `ENTROPY_DATA_HOST`. The referenced `team` must already exist in your org.
57
+
58
+ ## Configuration
59
+
60
+ | Purpose | Variable (with fallbacks) |
61
+ |---|---|
62
+ | API host | `ENTROPY_DATA_HOST` → `DATAMESH_MANAGER_HOST` → `DATACONTRACT_MANAGER_HOST` → `https://api.entropy-data.com` |
63
+ | API key | `ENTROPY_DATA_API_KEY` → `DATAMESH_MANAGER_API_KEY` → `DATACONTRACT_MANAGER_API_KEY` |
64
+
65
+ `.env` files are loaded automatically.
66
+
67
+ ## Use as a library
68
+
69
+ ```python
70
+ from dataproduct.data_product import DataProduct
71
+
72
+ run = DataProduct(data_product_file="dataproduct.odps.yaml").lint()
73
+ assert run.result == "passed"
74
+
75
+ DataProduct(data_product_file="dataproduct.odps.yaml").publish()
76
+ ```
77
+
78
+ ## Development
79
+
80
+ See [AGENTS.md](AGENTS.md).
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,5 @@
1
+ """dataproduct-cli — a CLI for the Open Data Product Standard (ODPS).
2
+
3
+ Sister project of datacontract-cli. See the ``specs/`` directory for the
4
+ behavioral specification of each command.
5
+ """
@@ -0,0 +1,81 @@
1
+ import logging
2
+ import sys
3
+ from importlib import metadata
4
+ from pathlib import Path
5
+ from typing import Iterable, Optional
6
+
7
+ import typer
8
+ from click import Context
9
+ from dotenv import find_dotenv, load_dotenv
10
+ from rich.console import Console
11
+ from typer.core import TyperGroup
12
+ from typing_extensions import Annotated
13
+
14
+ from dataproduct.output.output_format import OutputFormat
15
+
16
+ console = Console()
17
+
18
+ debug_option = Annotated[bool, typer.Option(help="Enable debug logging")]
19
+
20
+ # Order in which top-level commands appear in `dataproduct --help`.
21
+ COMMAND_ORDER = ["init", "lint", "publish"]
22
+
23
+
24
+ class OrderedCommands(TyperGroup):
25
+ def list_commands(self, ctx: Context) -> Iterable[str]:
26
+ known = set(COMMAND_ORDER)
27
+ return [c for c in COMMAND_ORDER if c in self.commands] + [c for c in self.commands if c not in known]
28
+
29
+
30
+ app = typer.Typer(
31
+ cls=OrderedCommands,
32
+ no_args_is_help=True,
33
+ add_completion=False,
34
+ pretty_exceptions_show_locals=False,
35
+ help="CLI for data products following the Open Data Product Standard (ODPS).",
36
+ )
37
+
38
+
39
+ def version_callback(value: bool) -> None:
40
+ if value:
41
+ try:
42
+ version = metadata.version("dataproduct-cli")
43
+ except metadata.PackageNotFoundError:
44
+ version = "0.0.0"
45
+ console.print(version)
46
+ raise typer.Exit()
47
+
48
+
49
+ @app.callback()
50
+ def common(
51
+ ctx: typer.Context,
52
+ version: Annotated[
53
+ Optional[bool],
54
+ typer.Option("--version", callback=version_callback, is_eager=True, help="Print the version and exit."),
55
+ ] = None,
56
+ ) -> None:
57
+ """dataproduct CLI."""
58
+ load_dotenv(find_dotenv(usecwd=True))
59
+
60
+
61
+ def enable_debug_logging(debug: bool, otherwise_disable_stderr: bool = False) -> None:
62
+ if debug:
63
+ logging.basicConfig(level=logging.DEBUG, stream=sys.stderr, format="%(asctime)s %(levelname)s %(message)s")
64
+ elif otherwise_disable_stderr:
65
+ logging.disable(logging.CRITICAL)
66
+
67
+
68
+ def resolve_output_format(output_format: Optional[OutputFormat], output: Optional[Path]) -> Optional[OutputFormat]:
69
+ if output_format is not None:
70
+ return output_format
71
+ if output is not None:
72
+ return OutputFormat.junit if str(output).endswith(".xml") else OutputFormat.json
73
+ return None
74
+
75
+
76
+ # Register the commands (each module attaches itself to `app`).
77
+ from dataproduct import command_init, command_lint, command_publish # noqa: E402, F401
78
+
79
+
80
+ def main() -> None:
81
+ app()
@@ -0,0 +1,33 @@
1
+ import os
2
+
3
+ import typer
4
+ from typing_extensions import Annotated
5
+
6
+ from dataproduct.cli import app, console, debug_option, enable_debug_logging
7
+ from dataproduct.init.init_template import get_init_template
8
+
9
+
10
+ @app.command(
11
+ name="init",
12
+ epilog="Example: dataproduct init dataproduct.odps.yaml",
13
+ )
14
+ def init(
15
+ location: Annotated[
16
+ str, typer.Argument(help="The location of the data product file to create.")
17
+ ] = "dataproduct.odps.yaml",
18
+ template: Annotated[str, typer.Option(help="URL or path of a template or data product")] = None,
19
+ overwrite: Annotated[bool, typer.Option(help="Replace the existing data product file")] = False,
20
+ debug: debug_option = None,
21
+ ):
22
+ """
23
+ Create a new data product file.
24
+ """
25
+ enable_debug_logging(debug)
26
+
27
+ if not overwrite and os.path.exists(location):
28
+ console.print("File already exists, use --overwrite to overwrite")
29
+ raise typer.Exit(code=1)
30
+ template_str = get_init_template(template)
31
+ with open(location, "w") as f:
32
+ f.write(template_str)
33
+ console.print("📄 data product written to " + location)
@@ -0,0 +1,58 @@
1
+ from pathlib import Path
2
+
3
+ import typer
4
+ from typing_extensions import Annotated
5
+
6
+ from dataproduct.cli import app, console, debug_option, enable_debug_logging, resolve_output_format
7
+ from dataproduct.config import cli_config
8
+ from dataproduct.data_product import DataProduct
9
+ from dataproduct.output.output_format import OutputFormat
10
+ from dataproduct.output.result_writer import write_result
11
+
12
+
13
+ @app.command(
14
+ name="lint",
15
+ epilog="Example: dataproduct lint dataproduct.odps.yaml",
16
+ )
17
+ def lint(
18
+ location: Annotated[
19
+ str,
20
+ typer.Argument(help="The location (url or local path) of the data product yaml."),
21
+ ] = "dataproduct.odps.yaml",
22
+ schema: Annotated[
23
+ str,
24
+ typer.Option("--json-schema", help="The location (url or path) of the ODPS JSON Schema"),
25
+ ] = None,
26
+ output: Annotated[
27
+ Path,
28
+ typer.Option(
29
+ help="File path to write the results to (e.g. './TEST-dataproduct.xml'). "
30
+ "If omitted, results are printed to stdout."
31
+ ),
32
+ ] = None,
33
+ output_format: Annotated[
34
+ OutputFormat,
35
+ typer.Option(help="The result format. Accepted values: json, junit."),
36
+ ] = None,
37
+ all_errors: Annotated[
38
+ bool,
39
+ typer.Option(
40
+ "--all-errors",
41
+ help="Report all JSON Schema validation errors instead of stopping after the first one.",
42
+ ),
43
+ ] = False,
44
+ debug: debug_option = None,
45
+ ):
46
+ """
47
+ Validate that the data product is correctly formatted (against the ODPS JSON Schema).
48
+ """
49
+ enable_debug_logging(debug, otherwise_disable_stderr=True)
50
+
51
+ output_format = resolve_output_format(output_format, output)
52
+ run = DataProduct(
53
+ config=cli_config(),
54
+ data_product_file=location,
55
+ schema_location=schema,
56
+ all_errors=all_errors,
57
+ ).lint()
58
+ write_result(run, console, output_format, output)
@@ -0,0 +1,47 @@
1
+ import typer
2
+ from typing_extensions import Annotated
3
+
4
+ from dataproduct.cli import app, console, debug_option, enable_debug_logging
5
+ from dataproduct.config import cli_config
6
+ from dataproduct.integration.entropy_data import (
7
+ DataProductPublishError,
8
+ publish_data_product_to_entropy_data,
9
+ )
10
+ from dataproduct.lint.resolve import resolve_data_product_dict
11
+ from dataproduct.model.exceptions import DataProductException
12
+
13
+
14
+ @app.command(
15
+ name="publish",
16
+ epilog="Example: dataproduct publish dataproduct.odps.yaml",
17
+ )
18
+ def publish(
19
+ location: Annotated[
20
+ str,
21
+ typer.Argument(help="The location (url or local path) of the data product yaml."),
22
+ ] = "dataproduct.odps.yaml",
23
+ schema: Annotated[
24
+ str,
25
+ typer.Option("--json-schema", help="The location (url or path) of the ODPS JSON Schema"),
26
+ ] = None,
27
+ ssl_verification: Annotated[
28
+ bool,
29
+ typer.Option(help="SSL verification when publishing the data product."),
30
+ ] = True,
31
+ debug: debug_option = None,
32
+ ):
33
+ """
34
+ Publish the data product to Entropy Data.
35
+ """
36
+ enable_debug_logging(debug)
37
+
38
+ try:
39
+ data_product_dict = resolve_data_product_dict(location, config=cli_config())
40
+ publish_data_product_to_entropy_data(
41
+ data_product_dict=data_product_dict,
42
+ ssl_verification=ssl_verification,
43
+ config=cli_config(),
44
+ )
45
+ except (DataProductPublishError, DataProductException) as e:
46
+ console.print(f"[red]Failed publishing data product. Error: {e}[/red]")
47
+ raise typer.Exit(code=1)
@@ -0,0 +1,67 @@
1
+ """Programmatic configuration for hosts and API keys.
2
+
3
+ Every value the CLI reads from ``ENTROPY_DATA_*`` (and the legacy
4
+ ``DATAMESH_MANAGER_*`` / ``DATACONTRACT_MANAGER_*``) environment variables can
5
+ also be provided programmatically via :class:`Config` (or a plain dict keyed by
6
+ the env var names). Reads fall back to the process environment.
7
+ """
8
+
9
+ import os
10
+ from typing import Optional, Union
11
+
12
+
13
+ class Config:
14
+ def __init__(self, values: Optional[dict] = None):
15
+ self._values = dict(values or {})
16
+
17
+ def getenv(self, key: str) -> Optional[str]:
18
+ value = self._values.get(key)
19
+ if value is not None:
20
+ return value
21
+ return os.environ.get(key)
22
+
23
+ @classmethod
24
+ def resolve(cls, config: "Optional[Union[Config, dict]]" = None) -> "Config":
25
+ if config is None:
26
+ return cls()
27
+ if isinstance(config, Config):
28
+ return config
29
+ if isinstance(config, dict):
30
+ return cls(config)
31
+ raise TypeError(f"Unsupported config type: {type(config)!r}")
32
+
33
+ # Hosts
34
+ def get_entropy_data_host(self) -> Optional[str]:
35
+ return self.getenv("ENTROPY_DATA_HOST")
36
+
37
+ def get_datamesh_manager_host(self) -> Optional[str]:
38
+ return self.getenv("DATAMESH_MANAGER_HOST")
39
+
40
+ def get_datacontract_manager_host(self) -> Optional[str]:
41
+ return self.getenv("DATACONTRACT_MANAGER_HOST")
42
+
43
+ # API keys
44
+ def get_entropy_data_api_key(self) -> Optional[str]:
45
+ return self.getenv("ENTROPY_DATA_API_KEY")
46
+
47
+ def get_datamesh_manager_api_key(self) -> Optional[str]:
48
+ return self.getenv("DATAMESH_MANAGER_API_KEY")
49
+
50
+ def get_datacontract_manager_api_key(self) -> Optional[str]:
51
+ return self.getenv("DATACONTRACT_MANAGER_API_KEY")
52
+
53
+
54
+ # Simple process-wide "current" config set from the CLI entry point.
55
+ _cli_config = Config()
56
+
57
+
58
+ def set_cli_config(config: Config) -> None:
59
+ global _cli_config
60
+ _cli_config = config
61
+
62
+
63
+ def cli_config() -> Config:
64
+ return _cli_config
65
+
66
+
67
+ __all__ = ["Config", "cli_config", "set_cli_config"]
@@ -0,0 +1,93 @@
1
+ """Core library entry point, parallel to datacontract-cli's ``DataContract``.
2
+
3
+ from dataproduct.data_product import DataProduct
4
+
5
+ run = DataProduct(data_product_file="dataproduct.odps.yaml").lint()
6
+ DataProduct(data_product_file="dataproduct.odps.yaml").publish()
7
+ """
8
+
9
+ from typing import Optional, Union
10
+
11
+ from dataproduct.config import Config
12
+ from dataproduct.integration.entropy_data import publish_data_product_to_entropy_data
13
+ from dataproduct.lint.files import read_resource
14
+ from dataproduct.lint.schema import fetch_schema
15
+ from dataproduct.lint.validate import parse_yaml, validate_against_schema
16
+ from dataproduct.model.exceptions import DataProductException
17
+ from dataproduct.model.run import Check, ResultEnum, Run
18
+
19
+
20
+ class DataProduct:
21
+ def __init__(
22
+ self,
23
+ data_product_file: Optional[str] = None,
24
+ data_product_str: Optional[str] = None,
25
+ schema_location: Optional[str] = None,
26
+ all_errors: bool = False,
27
+ config: "Optional[Union[Config, dict]]" = None,
28
+ ):
29
+ self._data_product_file = data_product_file
30
+ self._data_product_str = data_product_str
31
+ self._schema_location = schema_location
32
+ self._all_errors = all_errors
33
+ self._config = Config.resolve(config)
34
+
35
+ def _load_dict(self) -> dict:
36
+ if self._data_product_file is not None:
37
+ content = read_resource(self._data_product_file, self._config)
38
+ elif self._data_product_str is not None:
39
+ content = self._data_product_str
40
+ else:
41
+ raise DataProductException(
42
+ type="lint",
43
+ name="Load data product",
44
+ reason="No data product provided (file or string).",
45
+ )
46
+ return parse_yaml(content)
47
+
48
+ def lint(self) -> Run:
49
+ """Validate the data product against the ODPS JSON Schema (schema-only)."""
50
+ run = Run.create_run()
51
+ run.log_info("Linting data product")
52
+ try:
53
+ data = self._load_dict()
54
+ run.dataProductId = data.get("id")
55
+ run.dataProductVersion = data.get("version")
56
+ schema = fetch_schema(self._schema_location)
57
+ checks = validate_against_schema(data, schema, self._all_errors)
58
+ if checks:
59
+ run.checks.extend(checks)
60
+ for check in checks:
61
+ run.log_error(str(check.reason))
62
+ else:
63
+ run.checks.append(
64
+ Check(
65
+ type="lint",
66
+ result=ResultEnum.passed,
67
+ name="Data product is syntactically valid",
68
+ )
69
+ )
70
+ except DataProductException as e:
71
+ run.checks.append(Check(type=e.type, result=e.result, name=e.name, reason=e.reason, engine=e.engine))
72
+ run.log_error(str(e))
73
+ except Exception as e:
74
+ run.checks.append(
75
+ Check(
76
+ type="general",
77
+ result=ResultEnum.error,
78
+ name="Check Data Product",
79
+ reason=str(e),
80
+ )
81
+ )
82
+ run.log_error(str(e))
83
+ run.finish()
84
+ return run
85
+
86
+ def publish(self, ssl_verification: bool = True) -> None:
87
+ """Publish the data product to Entropy Data (no client-side lint in 0.1)."""
88
+ data = self._load_dict()
89
+ publish_data_product_to_entropy_data(
90
+ data_product_dict=data,
91
+ ssl_verification=ssl_verification,
92
+ config=self._config,
93
+ )
File without changes
@@ -0,0 +1,26 @@
1
+ import importlib.resources as resources
2
+ import logging
3
+
4
+ import requests
5
+
6
+ DEFAULT_DATA_PRODUCT_INIT_TEMPLATE = "odps-1.0.0.init.yaml"
7
+
8
+
9
+ def get_init_template(location: str = None) -> str:
10
+ """Return the contents of an init template.
11
+
12
+ - ``None`` -> the bundled default template.
13
+ - an ``http(s)://`` URL -> the fetched body.
14
+ - anything else -> read as a local file path.
15
+ """
16
+ if location is None:
17
+ logging.info("Use default bundled template " + DEFAULT_DATA_PRODUCT_INIT_TEMPLATE)
18
+ schemas = resources.files("dataproduct")
19
+ template = schemas.joinpath("schemas", DEFAULT_DATA_PRODUCT_INIT_TEMPLATE)
20
+ with template.open("r") as file:
21
+ return file.read()
22
+ elif location.startswith("http://") or location.startswith("https://"):
23
+ return requests.get(location).text
24
+ else:
25
+ with open(location, "r") as file:
26
+ return file.read()