Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ build/

# Example workspaces created for testing
examples/

src/create_dlthub_workspace/_telemetry_key.py
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- The minimal workspace scaffold now ships an interactive onboarding notebook (`notebooks/onboarding_success/`) — a marimo app deployed alongside the pipeline (via `__deployment__.py`) that reads the loaded `sample_shop` data back from `playground` to browse the schema and run a first query.
- Anonymous usage telemetry: `dlthub-start` now emits a telemetry event on a command execution. Opt out with `--no-telemetry`, `DLTHUB_START_TELEMETRY=0`, `DO_NOT_TRACK=1`, or an existing dlt opt-out. See the README "Telemetry" section.

### Changed
- Refreshed the bundled minimal workspace `uv.lock` (`dlthub-client` 0.28.0, `marimo` 0.23.11) and added the notebook's dependencies (`anywidget`, `pandas`, `numpy`).
Expand Down
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,33 @@ When running `dlthub` commands **by hand** in a local workspace, set
stack also needs the runtime's mock host resolvable — add `127.0.0.1 dev-mock-services`
to `/etc/hosts`.

## Telemetry

The CLI sends anonymous usage events to PostHog. Users opt out with `--no-telemetry`,
`DLTHUB_START_TELEMETRY=0`, or `DO_NOT_TRACK=1`, and an existing dlt opt-out
(`runtime.dlthub_telemetry = false` in dlt's global `config.toml`, or
`RUNTIME__DLTHUB_TELEMETRY=0`) is honored.

For development and testing, three environment variables override the defaults:

| Variable | Effect |
|---|---|
| `DLTHUB_START_TELEMETRY` | Force telemetry on (`1`/`true`/`yes`/`on`) or off (any other value). |
| `DLTHUB_START_POSTHOG_KEY` | Override the bundled PostHog project key. |
| `DLTHUB_START_POSTHOG_HOST` | Override the PostHog host (default `https://eu.i.posthog.com`). |

Released builds bake the project key into a gitignored `_telemetry_key.py`; a dev
checkout has no key, so telemetry stays disabled until you set
`DLTHUB_START_POSTHOG_KEY`. To exercise the full path against a throwaway PostHog
project:

```bash
DLTHUB_START_TELEMETRY=1 \
DLTHUB_START_POSTHOG_KEY=phc_your_test_key \
DLTHUB_START_POSTHOG_HOST=https://eu.i.posthog.com \
uv run dlthub-start my-workspace --setup-only
```

## Tests

Run the fast unit test suite:
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ uvx dlthub-start@latest my-workspace --verbose
If the scaffold was created successfully, you can also enter the workspace and
run `uv sync` directly after fixing the underlying dependency or network issue.

## Telemetry

`dlthub-start` sends anonymous usage events so we can improve our onboarding and
refine the user experience.

No personal data is collected, no workspace is ever sent.

Telemetry is controlled by the following, in order of precedence:

1. the `--no-telemetry` flag,
2. `DLTHUB_START_TELEMETRY=0` (or `false`/`off`),
3. `DO_NOT_TRACK=1`,
4. an existing dlt opt-out (`~/.dlt/config.toml` `[runtime] dlthub_telemetry = false`,
or `RUNTIME__DLTHUB_TELEMETRY=false`).

## Development

For local setup, tests, build commands, `make workspace`, and AI workbench
Expand Down
30 changes: 30 additions & 0 deletions hatch_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Bake the telemetry ingest key into the distribution at build time."""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

from hatchling.builders.hooks.plugin.interface import BuildHookInterface

_KEY_MODULE = "src/create_dlthub_workspace/_telemetry_key.py"


class CustomBuildHook(BuildHookInterface): # type: ignore[type-arg]
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
key = os.environ.get("DLTHUB_START_POSTHOG_KEY", "").strip()
if not key:
self.app.display_warning("DLTHUB_START_POSTHOG_KEY is not set; building with telemetry disabled.")
return
if not key.startswith("phc_"):
raise ValueError("DLTHUB_START_POSTHOG_KEY must be a PostHog project key starting with 'phc_'.")
self._module.write_text(f"POSTHOG_PROJECT_KEY = {key!r}\n", encoding="utf-8")
build_data["artifacts"].append(_KEY_MODULE)

def finalize(self, version: str, build_data: dict[str, Any], artifact_path: str) -> None:
self._module.unlink(missing_ok=True)

@property
def _module(self) -> Path:
return Path(self.root) / _KEY_MODULE
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ classifiers = [
dependencies = [
"rich>=13.7",
"beaupy>=3.9",
"posthog>=3.7",
"tomli>=2.0; python_version < '3.11'",
]

Expand Down Expand Up @@ -72,6 +73,10 @@ warn_return_any = false
module = "beaupy.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "posthog.*"
ignore_missing_imports = true

# `tomli` is only installed on Python <3.11 (we fall back to stdlib `tomllib`
# on >=3.11), so it's absent from the dev env when running on newer pythons.
# Suppress missing-stub errors there; the runtime path is short and stable.
Expand All @@ -82,5 +87,8 @@ ignore_missing_imports = true
[project.scripts]
dlthub-start = "create_dlthub_workspace.cli:main"

[tool.hatch.build.hooks.custom]
path = "hatch_build.py"

[tool.hatch.build.targets.wheel]
packages = ["src/create_dlthub_workspace"]
15 changes: 14 additions & 1 deletion src/create_dlthub_workspace/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import sys
from pathlib import Path

from . import strings
from . import strings, telemetry
from .config import (
AGENT_LAUNCH_COMMANDS,
AGENT_SKILLS_DIR,
Expand Down Expand Up @@ -111,6 +111,11 @@ def build_parser() -> argparse.ArgumentParser:
action="store_true",
help="Stream output from underlying subprocesses (uv, dlthub).",
)
parser.add_argument(
"--no-telemetry",
action="store_true",
help="Disable usage telemetry for this run.",
)
# Dev/CI only; hidden from --help. Both skip login + playground connection.
# --setup-only installs deps; --scaffold-only skips deps too.
parser.add_argument(
Expand All @@ -130,25 +135,31 @@ def main(argv: list[str] | None = None) -> int:
_ensure_utf8_io_on_windows()
parser = build_parser()
args = parser.parse_args(argv)
telemetry.init(no_telemetry=args.no_telemetry, interactive=stdin_is_interactive())

try:
run(args)
except KeyboardInterrupt:
telemetry.track_run("cancelled")
console.print(strings.MSG_CANCELLED)
return 130
except WorkspaceDirectoryNotEmptyError as exc:
telemetry.track_run("failed", error_code=type(exc).__name__)
print_dir_not_empty(exc.project_dir)
return 2
except WorkspaceError as exc:
telemetry.track_run("failed", error_code=type(exc).__name__)
console.print(strings.MSG_ERROR_PREFIX.format(message=exc))
return 1
except Exception as exc:
telemetry.track_run("failed", error_code=type(exc).__name__)
console.print(strings.MSG_UNEXPECTED_ERROR.format(message=exc))
if args.verbose:
console.print_exception()
else:
console.print(strings.MSG_UNEXPECTED_ERROR_HINT)
return 1
telemetry.track_run("success")
return 0


Expand All @@ -159,6 +170,8 @@ def run(args: argparse.Namespace) -> None:
print_banner()
console.print()

telemetry.show_first_run_notice()

if args.setup_only or args.scaffold_only:
err_console.print(strings.MSG_TESTING_SHORTCUT_NOTE)

Expand Down
2 changes: 2 additions & 0 deletions src/create_dlthub_workspace/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
except PackageNotFoundError:
VERSION = "0.0.0+unknown"

POSTHOG_HOST = "https://eu.i.posthog.com"

GITHUB_ORG = "dlt-hub"

AGENTS = ("claude", "codex", "cursor")
Expand Down
7 changes: 7 additions & 0 deletions src/create_dlthub_workspace/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,10 @@
f"`{config.ONE_SHOT_ENTRY_SKILL}` skill to deploy and run the sample pipeline. "
"The skill is located at {skill_path}."
)


# Telemetry -------------------------------------------------------------
MSG_TELEMETRY_NOTICE = (
"[dim]dlthub-start sends anonymous usage events to help us improve user experience. "
"Opt out with --no-telemetry, DLTHUB_START_TELEMETRY=0, or DO_NOT_TRACK=1."
)
Loading
Loading