diff --git a/.license-header.txt b/.license-header.txt deleted file mode 100644 index 69e1060..0000000 --- a/.license-header.txt +++ /dev/null @@ -1,2 +0,0 @@ -Copyright (c) 2026 DataRobot, Inc. -SPDX-License-Identifier: Apache-2.0 diff --git a/.licenserc.yaml b/.licenserc.yaml new file mode 100644 index 0000000..dbbd7b3 --- /dev/null +++ b/.licenserc.yaml @@ -0,0 +1,44 @@ +# Copyright/license header enforcement via skywalking-eyes. +# Docs: https://github.com/apache/skywalking-eyes +# +# Run locally (same image the pre-commit hook uses): +# docker run --rm -v "$PWD:/src" --workdir /src \ +# apache/skywalking-eyes:0.8.0 -c .licenserc.yaml header check +# docker run --rm -v "$PWD:/src" --workdir /src \ +# apache/skywalking-eyes:0.8.0 -c .licenserc.yaml header fix +# +# Notebooks (*.ipynb) are handled by the companion `insert-license-notebooks` +# pre-commit hook: skywalking-eyes cannot process notebooks, so license-eye +# ignores them here. +# +# The `pattern` matches the previous two-line SPDX header so `header fix` +# rewrites it to the full Apache-2.0 notice instead of double-stamping. +header: + license: + spdx-id: Apache-2.0 + copyright-owner: DataRobot, Inc. + content: | + Copyright 2026 DataRobot, Inc. and its affiliates. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + pattern: | + Copyright \(c\) \d{4} DataRobot, Inc\. + SPDX-License-Identifier: Apache-2\.0 + paths: + - '**/*.py' + paths-ignore: + - '**/*.ipynb' + - '.venv/**' + - '**/.venv/**' + - 'build/**' + - 'dist/**' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30ad7f6..cc0257a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,10 +6,17 @@ repos: - repo: local hooks: - id: insert-license - name: insert license header (python + notebooks) - entry: uv run python scripts/insert_license.py --license-filepath .license-header.txt + name: insert license header (python) + entry: apache/skywalking-eyes:0.8.0 -c .licenserc.yaml header fix + language: docker_image + pass_filenames: false + require_serial: true + always_run: true + - id: insert-license-notebooks + name: insert license header (notebooks) + entry: uv run python scripts/insert_license_notebooks.py language: system - types_or: [python, jupyter] + types: [jupyter] - id: commitizen name: conventional commit message entry: uv run cz check --commit-msg-file diff --git a/README.md b/README.md index 67525de..d067308 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ Use `forecast_samples(...)` for sampled trajectories or `forecast_quantiles(...) - `task release`: cut a SemVer release with Commitizen (writes `CHANGELOG.md`, bumps versions, creates tag) - `task pre-commit`: run every configured pre-commit hook -Contributors do not need to add copyright or license headers to new Python files manually. The `insert-license` pre-commit hook stamps the standard SPDX header (`Copyright (c) 2026 DataRobot, Inc.` + `SPDX-License-Identifier: Apache-2.0`) into every `.py` file the first time you run `task pre-commit`. Verify the headers are present at any time with `task license-check`. +Contributors do not need to add copyright or license headers manually. The `insert-license` pre-commit hook runs [skywalking-eyes](https://github.com/apache/skywalking-eyes) (via the `apache/skywalking-eyes` Docker image, so a running Docker daemon is required) to stamp the standard Apache-2.0 header (`Copyright 2026 DataRobot, Inc. and its affiliates.` followed by the standard "Licensed under the Apache License, Version 2.0" notice) into every `.py` file, and the companion `insert-license-notebooks` hook stamps the same notice into a leading markdown cell of every notebook the first time you run `task pre-commit`. Verify the headers are present at any time with `task license-check`. ## Versioning & Commits diff --git a/Taskfile.yaml b/Taskfile.yaml index 6c58018..4118d63 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -26,7 +26,7 @@ tasks: license-check: desc: Verify every Python source file has the required copyright/SPDX header - cmd: uv run ruff check --preview --select CPY001 --extend-exclude '*.ipynb' . + cmd: docker run --rm -v "$PWD:/src" --workdir /src apache/skywalking-eyes:0.8.0 -c .licenserc.yaml header check format: desc: Run Ruff formatter (rewrites files in place) diff --git a/notebooks/forecast_csv.ipynb b/notebooks/forecast_csv.ipynb index 5ba8d2a..7a9653e 100644 --- a/notebooks/forecast_csv.ipynb +++ b/notebooks/forecast_csv.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -129,7 +139,7 @@ ], "metadata": { "kernelspec": { - "display_name": "joint-client-python (3.13.3)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/forecast_mean.ipynb b/notebooks/forecast_mean.ipynb index bb49836..d309f58 100644 --- a/notebooks/forecast_mean.ipynb +++ b/notebooks/forecast_mean.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -94,7 +104,7 @@ ], "metadata": { "kernelspec": { - "display_name": "joint-client-python (3.13.3)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/forecast_quantiles.ipynb b/notebooks/forecast_quantiles.ipynb index a5e6a64..7d696ee 100644 --- a/notebooks/forecast_quantiles.ipynb +++ b/notebooks/forecast_quantiles.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -96,7 +106,7 @@ ], "metadata": { "kernelspec": { - "display_name": "joint-client-python (3.13.3)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/forecast_samples.ipynb b/notebooks/forecast_samples.ipynb index 37e1516..6788af3 100644 --- a/notebooks/forecast_samples.ipynb +++ b/notebooks/forecast_samples.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -96,7 +106,7 @@ ], "metadata": { "kernelspec": { - "display_name": "joint-client-python (3.13.3)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/forecast_trading.ipynb b/notebooks/forecast_trading.ipynb index f9142b9..b98f9c6 100644 --- a/notebooks/forecast_trading.ipynb +++ b/notebooks/forecast_trading.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -517,7 +527,7 @@ ], "metadata": { "kernelspec": { - "display_name": "joint-client-python (3.13.3)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, diff --git a/notebooks/pandas_result_conversion.ipynb b/notebooks/pandas_result_conversion.ipynb index 2d3f762..63e23bf 100644 --- a/notebooks/pandas_result_conversion.ipynb +++ b/notebooks/pandas_result_conversion.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { @@ -65,7 +75,7 @@ ], "metadata": { "kernelspec": { - "display_name": "jointfm-client (3.14.0)", + "display_name": "joint-client-python (3.13.3.final.0)", "language": "python", "name": "python3" }, @@ -79,7 +89,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.14.0" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/notebooks/predict_json.ipynb b/notebooks/predict_json.ipynb index e6c1dc0..3e914e4 100644 --- a/notebooks/predict_json.ipynb +++ b/notebooks/predict_json.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { diff --git a/notebooks/service_health.ipynb b/notebooks/service_health.ipynb index 982e9c0..2055c39 100644 --- a/notebooks/service_health.ipynb +++ b/notebooks/service_health.ipynb @@ -8,9 +8,19 @@ "language": "markdown" }, "source": [ - "Copyright (c) 2026 DataRobot, Inc.\n", + "Copyright 2026 DataRobot, Inc. and its affiliates.\n", "\n", - "SPDX-License-Identifier: Apache-2.0" + "Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "you may not use this file except in compliance with the License.\n", + "You may obtain a copy of the License at\n", + "\n", + " http://www.apache.org/licenses/LICENSE-2.0\n", + "\n", + "Unless required by applicable law or agreed to in writing, software\n", + "distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "See the License for the specific language governing permissions and\n", + "limitations under the License." ] }, { diff --git a/pyproject.toml b/pyproject.toml index 7cebf1f..179874c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,10 +92,6 @@ select = ["D100", "D101", "D102", "D103"] [tool.ruff.lint.per-file-ignores] "notebooks/**/tests/**" = ["D100", "D101", "D102", "D103"] -[tool.ruff.lint.flake8-copyright] -notice-rgx = "(?i)Copyright \\(c\\) \\d{4}" -author = "DataRobot, Inc." - [tool.commitizen] name = "cz_conventional_commits" version = "0.1.0" diff --git a/scripts/insert_license.py b/scripts/insert_license.py deleted file mode 100644 index 810d6a7..0000000 --- a/scripts/insert_license.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 -"""Insert the project license header at the top of every source file. - -Supported file types: - -- Python source (``.py``): prepend a ``#``-comment header (after the - shebang if one is present). -- Jupyter notebooks (``.ipynb``): insert a markdown cell at index 0 - containing the header as an HTML comment. The cell renders as - whitespace for human readers but is scannable by tooling and travels - with the notebook if it is downloaded standalone. Notebooks are - rewritten with ``nbformat.read`` / ``nbformat.write`` so cell outputs - are preserved unchanged. - -Per-file template overrides: if ``.license-overrides.toml`` exists at the -repository root it lists alternate templates for specific files (used, -for example, by Apache-2.0 derivative files that need dual-attribution -headers). Files not matched by any override use the default template -passed via ``--license-filepath``. - -Idempotent: files that already carry the correct header are skipped, so -the hook cannot double-stamp existing headers. Exits 1 (and lists the -modified files) if any file needed a header, 0 otherwise. Matches -pre-commit's expected autofix-hook behavior. -""" - -from __future__ import annotations - -import argparse -import fnmatch -import sys -import tomllib -from pathlib import Path - -DETECT_LINES = 5 -OVERRIDES_FILE = ".license-overrides.toml" -# Any SPDX-License-Identifier line counts as "notebook header present" — -# the exact text differs between default and derivative templates. -NOTEBOOK_MARKER = "SPDX-License-Identifier:" - - -def render_python_header(template: Path) -> tuple[str, str]: - """Return (rendered ``#``-comment block, marker line) for ``.py`` files.""" - raw = template.read_text(encoding="utf-8").rstrip("\n").splitlines() - if not raw: - raise SystemExit(f"empty license template: {template}") - rendered = "\n".join(f"# {line}".rstrip() for line in raw) + "\n\n" - marker = f"# {raw[0]}".rstrip() - return rendered, marker - - -def render_notebook_cell(template: Path) -> str: - """Return the visible markdown source for a notebook header cell. - - Each template line becomes its own paragraph (joined by blank lines) - so the SPDX attribution renders as readable text in the notebook UI, - not as a hidden HTML comment. - """ - raw = template.read_text(encoding="utf-8").rstrip("\n").splitlines() - if not raw: - raise SystemExit(f"empty license template: {template}") - return "\n\n".join(raw) - - -def load_overrides(root: Path) -> list[tuple[list[str], Path]]: - """Parse ``.license-overrides.toml`` into ``(patterns, template_path)`` rows.""" - path = root / OVERRIDES_FILE - if not path.exists(): - return [] - data = tomllib.loads(path.read_text(encoding="utf-8")) - result: list[tuple[list[str], Path]] = [] - for override in data.get("overrides", []): - patterns = override["patterns"] - template = root / override["template"] - if not template.exists(): - raise SystemExit(f"override template not found: {template}") - result.append((patterns, template)) - return result - - -def select_template( - file_path: str, - overrides: list[tuple[list[str], Path]], - default: Path, -) -> Path: - """Return the first override template matching ``file_path``, else default.""" - for patterns, template in overrides: - if any(fnmatch.fnmatch(file_path, pat) for pat in patterns): - return template - return default - - -def has_py_header(content: str, marker: str) -> bool: - """Return True if a line within DETECT_LINES starts with ``marker``. - - Uses startswith (not equality) so existing headers with trailing - inline comments such as ``# noqa`` still count as present. - """ - marker = marker.rstrip() - for line in content.splitlines()[:DETECT_LINES]: - if line.startswith(marker): - return True - return False - - -def insert_py_header(content: str, header: str) -> str: - """Prepend the header, preserving any leading shebang line.""" - if content.startswith("#!"): - shebang, _, rest = content.partition("\n") - return f"{shebang}\n{header}{rest}" - return f"{header}{content}" - - -def process_py(path: Path, template: Path) -> bool: - """Insert the ``.py`` header if missing; return True if file was modified.""" - header, marker = render_python_header(template) - content = path.read_text(encoding="utf-8") - if has_py_header(content, marker): - return False - path.write_text(insert_py_header(content, header), encoding="utf-8") - return True - - -def process_ipynb(path: Path, template: Path) -> bool: - """Insert the notebook header cell if missing; return True if modified. - - Uses ``nbformat.read`` / ``nbformat.write`` to round-trip cell outputs - unchanged — this repository deliberately commits notebook outputs. - """ - # Lazy import so .py-only invocations don't require nbformat. - import nbformat - - nb = nbformat.read(str(path), as_version=4) - for cell in nb.cells: - if cell.cell_type == "markdown" and NOTEBOOK_MARKER in cell.source: - return False - nb.cells.insert( - 0, - nbformat.v4.new_markdown_cell( - render_notebook_cell(template), - metadata={"id": "license-header", "language": "markdown"}, - ), - ) - nbformat.write(nb, str(path)) - return True - - -def process_file(path: Path, template: Path) -> bool: - """Dispatch to the per-extension handler. Return True if file modified.""" - if path.suffix == ".py": - return process_py(path, template) - if path.suffix == ".ipynb": - return process_ipynb(path, template) - return False - - -def main(argv: list[str] | None = None) -> int: - """Insert the configured header into any file that does not have it.""" - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--license-filepath", required=True, type=Path) - parser.add_argument("--repo-root", default=Path.cwd(), type=Path) - parser.add_argument("files", nargs="*", type=Path) - args = parser.parse_args(argv) - - default_template = args.license_filepath - overrides = load_overrides(args.repo_root) - repo_root = args.repo_root.resolve() - - modified = 0 - for path in args.files: - try: - rel = path.resolve().relative_to(repo_root).as_posix() - except ValueError: - rel = str(path) - template = select_template(rel, overrides, default_template) - if process_file(path, template): - print(f"inserted header: {rel} (template: {template.name})") - modified += 1 - - if modified: - print(f"insert-license: modified {modified} file(s)", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/insert_license_notebooks.py b/scripts/insert_license_notebooks.py new file mode 100644 index 0000000..4a077b3 --- /dev/null +++ b/scripts/insert_license_notebooks.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Insert/refresh the project license header cell in Jupyter notebooks. + +skywalking-eyes (the ``insert-license`` hook) stamps license headers into +source files but cannot process ``.ipynb`` notebooks. This companion hook +covers notebooks: it ensures the first cell is a markdown cell carrying the +same license text, deriving that text from ``.licenserc.yaml`` so there is a +single source of truth shared with skywalking-eyes. + +Cell outputs are preserved via ``nbformat.read`` / ``nbformat.write`` (some +repositories deliberately commit executed notebook outputs). + +Idempotent: exits 1 (and lists the modified files) only when a notebook +needed its header inserted or refreshed, 0 otherwise. Matches pre-commit's +expected autofix-hook behavior. +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import yaml + +LICENSERC = ".licenserc.yaml" +CELL_ID = "license-header" +# Any SPDX identifier line marks an existing (possibly stale) header cell. +MARKER = "SPDX-License-Identifier:" + + +def load_header_text(repo_root: Path) -> str: + """Return the default license header content from ``.licenserc.yaml``. + + ``header`` may be a single mapping or a list of per-path mappings (used + when some files carry a derivative header). The notebook header uses the + general entry: the one whose ``paths`` include ``**/*.py``, else the last. + """ + data = yaml.safe_load((repo_root / LICENSERC).read_text(encoding="utf-8")) + header = data["header"] + entries = header if isinstance(header, list) else [header] + chosen = next( + (entry for entry in entries if "**/*.py" in entry.get("paths", [])), + entries[-1], + ) + return str(chosen["license"]["content"]).rstrip("\n") + + +def cell_source(cell: object) -> str: + """Return a notebook cell's source as a single string.""" + source = cell.source + return "".join(source) if isinstance(source, list) else source + + +def process_notebook(path: Path, header_text: str) -> bool: + """Insert or refresh the license cell. Return True if the file changed.""" + import nbformat + + nb = nbformat.read(str(path), as_version=4) + for cell in nb.cells: + if cell.cell_type != "markdown": + continue + is_header = cell.get("metadata", {}).get("id") == CELL_ID + if is_header or MARKER in cell_source(cell): + if cell_source(cell) == header_text: + return False + cell.source = header_text + nbformat.write(nb, str(path)) + return True + nb.cells.insert( + 0, + nbformat.v4.new_markdown_cell( + header_text, metadata={"id": CELL_ID, "language": "markdown"} + ), + ) + nbformat.write(nb, str(path)) + return True + + +def main(argv: list[str] | None = None) -> int: + """Refresh the license cell in every notebook passed on the command line.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--repo-root", default=Path.cwd(), type=Path) + parser.add_argument("files", nargs="*", type=Path) + args = parser.parse_args(argv) + + header_text = load_header_text(args.repo_root) + + modified = 0 + for path in args.files: + if path.suffix != ".ipynb": + continue + if process_notebook(path, header_text): + print(f"updated notebook header: {path}") + modified += 1 + + if modified: + print(f"insert-license-notebooks: modified {modified} file(s)", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/validate_distribution.py b/scripts/validate_distribution.py index 844b8b7..94f0bca 100644 --- a/scripts/validate_distribution.py +++ b/scripts/validate_distribution.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Validate JointFM SDK source distribution and wheel artifacts.""" diff --git a/src/jointfm_client/__init__.py b/src/jointfm_client/__init__.py index 406935f..ae32746 100644 --- a/src/jointfm_client/__init__.py +++ b/src/jointfm_client/__init__.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Public API surface for the JointFM Python SDK.""" diff --git a/src/jointfm_client/adapters.py b/src/jointfm_client/adapters.py index 834c516..089d693 100644 --- a/src/jointfm_client/adapters.py +++ b/src/jointfm_client/adapters.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """DataFrame and array adapters for JointFM V1 forecast requests.""" diff --git a/src/jointfm_client/capabilities.py b/src/jointfm_client/capabilities.py index 5acf4bc..83ea600 100644 --- a/src/jointfm_client/capabilities.py +++ b/src/jointfm_client/capabilities.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Capability-aware forecast planning built from `/healthz` metadata. diff --git a/src/jointfm_client/cli.py b/src/jointfm_client/cli.py index f34df92..bab71f9 100644 --- a/src/jointfm_client/cli.py +++ b/src/jointfm_client/cli.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Command line interface for the JointFM Python SDK.""" diff --git a/src/jointfm_client/client.py b/src/jointfm_client/client.py index f59309a..6ae5b1f 100644 --- a/src/jointfm_client/client.py +++ b/src/jointfm_client/client.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Public synchronous client shape for JointFM V1.""" diff --git a/src/jointfm_client/configuration.py b/src/jointfm_client/configuration.py index 7e4779f..e9c88db 100644 --- a/src/jointfm_client/configuration.py +++ b/src/jointfm_client/configuration.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Structured configuration models and YAML loading for the JointFM SDK.""" diff --git a/src/jointfm_client/contract.py b/src/jointfm_client/contract.py index 134f48c..235132d 100644 --- a/src/jointfm_client/contract.py +++ b/src/jointfm_client/contract.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """JointFM V1 service contract constants, models, and compatibility checks.""" diff --git a/src/jointfm_client/exceptions.py b/src/jointfm_client/exceptions.py index 3ba4881..ee02ff1 100644 --- a/src/jointfm_client/exceptions.py +++ b/src/jointfm_client/exceptions.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """SDK exception hierarchy.""" diff --git a/src/jointfm_client/notebooks.py b/src/jointfm_client/notebooks.py index d996f6e..9b42e66 100644 --- a/src/jointfm_client/notebooks.py +++ b/src/jointfm_client/notebooks.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Notebook bootstrap helpers for src-layout Python workspaces.""" diff --git a/src/jointfm_client/settings.py b/src/jointfm_client/settings.py index f58ac8b..ce3b0c4 100644 --- a/src/jointfm_client/settings.py +++ b/src/jointfm_client/settings.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Settings and URL helpers for DataRobot-hosted JointFM calls.""" diff --git a/src/jointfm_client/transport.py b/src/jointfm_client/transport.py index c230fb9..c56b98b 100644 --- a/src/jointfm_client/transport.py +++ b/src/jointfm_client/transport.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Synchronous JSON transport for JointFM HTTP calls.""" diff --git a/tests/conftest.py b/tests/conftest.py index fe15c9c..b296564 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Shared pytest fixtures for the jointfm_client test suite.""" diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index 279387b..6057619 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the capabilities surface of jointfm_client.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index d3ea831..1d371af 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the cli surface of jointfm_client.""" diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 53abc3a..2506c20 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the configuration surface of jointfm_client.""" diff --git a/tests/test_contract.py b/tests/test_contract.py index fed0843..b6b6d6a 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the contract surface of jointfm_client.""" diff --git a/tests/test_contract_models.py b/tests/test_contract_models.py index a394982..09466f1 100644 --- a/tests/test_contract_models.py +++ b/tests/test_contract_models.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the contract models surface of jointfm_client.""" diff --git a/tests/test_fixture_compatibility.py b/tests/test_fixture_compatibility.py index 7e9266e..2e2ac25 100644 --- a/tests/test_fixture_compatibility.py +++ b/tests/test_fixture_compatibility.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the fixture compatibility surface of jointfm_client.""" diff --git a/tests/test_live_smoke.py b/tests/test_live_smoke.py index 600551c..4ba8c49 100644 --- a/tests/test_live_smoke.py +++ b/tests/test_live_smoke.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the live smoke surface of jointfm_client.""" diff --git a/tests/test_notebooks.py b/tests/test_notebooks.py index 346865e..1cd3376 100644 --- a/tests/test_notebooks.py +++ b/tests/test_notebooks.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the notebooks surface of jointfm_client.""" @@ -104,9 +115,9 @@ def test_resolve_notebook_project_root_raises_outside_workspace( def test_example_notebooks_start_with_bootstrap_cell() -> None: """The first code cell of every example notebook is the bootstrap snippet. - Notebooks may carry a leading markdown cell (e.g. the SPDX license - header inserted by the insert-license pre-commit hook), so the - invariant we check is on the first *code* cell rather than cell 0. + Notebooks may carry a leading markdown cell (e.g. an SPDX license + header cell), so the invariant we check is on the first *code* cell + rather than cell 0. """ repo_root = Path(__file__).resolve().parents[1] notebook_paths = sorted((repo_root / "notebooks").glob("*.ipynb")) diff --git a/tests/test_settings.py b/tests/test_settings.py index bd1bc57..fa2623e 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the settings surface of jointfm_client.""" diff --git a/tests/test_surfaces.py b/tests/test_surfaces.py index 68275f0..d3cab6c 100644 --- a/tests/test_surfaces.py +++ b/tests/test_surfaces.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the surfaces surface of jointfm_client.""" diff --git a/tests/test_transport.py b/tests/test_transport.py index 37f6cf9..0f86e07 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -1,5 +1,16 @@ -# Copyright (c) 2026 DataRobot, Inc. -# SPDX-License-Identifier: Apache-2.0 +# Copyright 2026 DataRobot, Inc. and its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. """Tests for the transport surface of jointfm_client."""