convexity-sdk 0.12.0.dev237__tar.gz → 0.12.0.dev239__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 (49) hide show
  1. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/PKG-INFO +1 -1
  2. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/organizations.py +3 -3
  3. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/projects.py +7 -1
  4. convexity_sdk-0.12.0.dev239/samples/README.md +31 -0
  5. convexity_sdk-0.12.0.dev239/samples/create_org_and_project.py +78 -0
  6. convexity_sdk-0.12.0.dev239/samples/quickbook/001_check_quickbook_connection.py +142 -0
  7. convexity_sdk-0.12.0.dev239/samples/quickbook/002_create_quickbook_datasets.py +266 -0
  8. convexity_sdk-0.12.0.dev239/samples/shopify/001_check_shopify_connection.py +133 -0
  9. convexity_sdk-0.12.0.dev239/samples/shopify/002_create_shopify_datasets.py +262 -0
  10. convexity_sdk-0.12.0.dev237/samples/README.md +0 -27
  11. convexity_sdk-0.12.0.dev237/samples/create_org_and_project.py +0 -51
  12. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/.gitignore +0 -0
  13. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/README.md +0 -0
  14. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/__init__.py +0 -0
  15. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/client.py +0 -0
  16. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/config.py +0 -0
  17. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/exceptions.py +0 -0
  18. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/http.py +0 -0
  19. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/models/__init__.py +0 -0
  20. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/__init__.py +0 -0
  21. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/api_keys.py +0 -0
  22. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/connections.py +0 -0
  23. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/dashboards.py +0 -0
  24. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/datalake.py +0 -0
  25. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/datasets.py +0 -0
  26. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/environments.py +0 -0
  27. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/issues.py +0 -0
  28. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/runs.py +0 -0
  29. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/tasks.py +0 -0
  30. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/usage.py +0 -0
  31. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/convexity_sdk/resources/workspace.py +0 -0
  32. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/pyproject.toml +0 -0
  33. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/.env.example +0 -0
  34. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/cancel_refresh_task.py +0 -0
  35. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/compare_api_key_scopes.py +0 -0
  36. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_csv_datasets.py +0 -0
  37. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_dashboards.py +0 -0
  38. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_databricks_connections.py +0 -0
  39. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_datasets.py +0 -0
  40. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_forecast_task.py +0 -0
  41. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/create_single_project_api_key.py +0 -0
  42. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/getting_started.py +0 -0
  43. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/list_orgs_and_projects.py +0 -0
  44. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/manage_environments.py +0 -0
  45. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/manage_issues.py +0 -0
  46. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/project_usage.py +0 -0
  47. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/query_datalake.py +0 -0
  48. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/share_dashboard.py +0 -0
  49. {convexity_sdk-0.12.0.dev237 → convexity_sdk-0.12.0.dev239}/samples/workspace_files.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: convexity-sdk
3
- Version: 0.12.0.dev237
3
+ Version: 0.12.0.dev239
4
4
  Summary: Convexity Python SDK - programmatic access to the Convexity platform
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: convexity-api-client<0.13.0,>=0.12.0.dev0
@@ -69,12 +69,12 @@ class Organizations:
69
69
  self,
70
70
  organization_id: str,
71
71
  *,
72
- user_id: str,
72
+ email: str,
73
73
  role: OrganizationRole = OrganizationRole.MEMBER,
74
74
  ) -> OrganizationMemberResponse:
75
- """Add a member to an organization."""
75
+ """Add a member to an organization by email."""
76
76
  body = AddOrganizationMemberRequest(
77
- user_id=user_id,
77
+ email=email,
78
78
  role=role,
79
79
  )
80
80
  result = _add_member.sync(organization_id, client=self._client, body=body)
@@ -18,6 +18,7 @@ from convexity_api_client.api.v1 import (
18
18
  from convexity_api_client.api.v1 import (
19
19
  list_user_projects_v1_projects_get as _list_projects,
20
20
  )
21
+ from convexity_api_client.errors import UnexpectedStatus
21
22
  from convexity_api_client.models.create_project_request import CreateProjectRequest
22
23
  from convexity_api_client.models.project_list_response import ProjectListResponse
23
24
  from convexity_api_client.models.project_response import ProjectResponse
@@ -56,7 +57,12 @@ class Projects:
56
57
 
57
58
  def get_by_slug(self, organization_id: str, slug: str) -> ProjectResponse | None:
58
59
  """Get a single project by slug within an organization."""
59
- result = _get_project_by_slug.sync(organization_id, slug, client=self._client)
60
+ try:
61
+ result = _get_project_by_slug.sync(organization_id, slug, client=self._client)
62
+ except UnexpectedStatus as exc:
63
+ if exc.status_code == 404:
64
+ return None
65
+ raise
60
66
  if isinstance(result, ProjectResponse):
61
67
  return result
62
68
  return None
@@ -0,0 +1,31 @@
1
+ git pull# Convexity SDK Samples
2
+
3
+ This directory contains sample scripts demonstrating how to use the Convexity SDK.
4
+
5
+ ## Samples
6
+
7
+ | Sample | Description |
8
+ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------- |
9
+ | [getting_started.py](getting_started.py) | Basic SDK setup and usage |
10
+ | [create_org_and_project.py](create_org_and_project.py) | Create an organization and project |
11
+ | [create_databricks_connections.py](create_databricks_connections.py) | Create Databricks connections |
12
+ | [create_datasets.py](create_datasets.py) | Create datasets from Databricks connections |
13
+ | [create_dashboards.py](create_dashboards.py) | Create analytic dashboards with widgets |
14
+ | [share_dashboard.py](share_dashboard.py) | Share a dashboard with other users |
15
+ | [list_orgs_and_projects.py](list_orgs_and_projects.py) | List organizations and projects |
16
+ | [query_datalake.py](query_datalake.py) | Query the DuckLake warehouse with DuckDB |
17
+ | [cancel_refresh_task.py](cancel_refresh_task.py) | Cancel a pending dataset refresh task |
18
+ | [create_forecast_task.py](create_forecast_task.py) | Create a demand-forecasting task and run it |
19
+ | [compare_api_key_scopes.py](compare_api_key_scopes.py) | Compare READ vs ADMIN API key behavior |
20
+ | [debug_api_key_tenant_context.py](debug_api_key_tenant_context.py) | Diagnose API key tenant/scope mismatch |
21
+ | [quickbook/001_check_quickbook_connection.py](quickbook/001_check_quickbook_connection.py) | Verify a QuickBooks connection before creating datasets |
22
+ | [quickbook/002_create_quickbook_datasets.py](quickbook/002_create_quickbook_datasets.py) | Create and refresh QuickBooks-backed datasets |
23
+ | [shopify/001_check_shopify_connection.py](shopify/001_check_shopify_connection.py) | Verify a Shopify connection before creating datasets |
24
+ | [shopify/002_create_shopify_datasets.py](shopify/002_create_shopify_datasets.py) | Create and refresh Shopify-backed datasets |
25
+
26
+ ## Running Samples
27
+
28
+ ```bash
29
+ # From the repo root
30
+ uv run python packages/convexity-sdk/samples/getting_started.py
31
+ ```
@@ -0,0 +1,78 @@
1
+ """
2
+ Convexity SDK - Create Organization & Project
3
+
4
+ This sample demonstrates how to:
5
+ - Create or reuse an organization
6
+ - Optionally add members to the organization as admins by email
7
+ - Create or reuse a project within the organization
8
+
9
+ Prerequisites:
10
+ - A valid API key with WRITE or ADMIN permission
11
+
12
+ Optional environment variables:
13
+ - CONVEXITY_SAMPLE_ADMIN_EMAILS: comma-separated emails to add as admins
14
+ """
15
+
16
+ import os
17
+
18
+ from convexity_api_client.errors import UnexpectedStatus
19
+ from convexity_api_client.models.organization_role import OrganizationRole
20
+
21
+ from convexity_sdk import ConvexityClient
22
+
23
+ ORG_SLUG = "convexity-sdk-samples"
24
+ PROJECT_SLUG = "griffin-it"
25
+ ADMIN_EMAILS = [email.strip() for email in os.getenv("CONVEXITY_SAMPLE_ADMIN_EMAILS", "").split(",") if email.strip()]
26
+
27
+ client = ConvexityClient()
28
+
29
+ try:
30
+ # ── Step 1: Create or reuse an organization ─────────────────────────
31
+ org = client.organizations.get_by_slug(ORG_SLUG)
32
+ if org is None:
33
+ org = client.organizations.create(
34
+ name="Convexity SDK samples",
35
+ slug=ORG_SLUG,
36
+ description="Organization created by the Convexity SDK sample scripts",
37
+ )
38
+ print(f"Created organization: {org.name} (id={org.id})")
39
+ else:
40
+ print(f"Found organization: {org.name} (id={org.id})")
41
+
42
+ # ── Step 2: Optionally add admin members ────────────────────────────
43
+ if not ADMIN_EMAILS:
44
+ print("No admin emails configured; skipping member invite step.")
45
+ print("Set CONVEXITY_SAMPLE_ADMIN_EMAILS=email1@example.com,email2@example.com to add admins.")
46
+
47
+ for email in ADMIN_EMAILS:
48
+ try:
49
+ member = client.organizations.add_member(
50
+ org.id,
51
+ email=email,
52
+ role=OrganizationRole.ADMIN,
53
+ )
54
+ except UnexpectedStatus as exc:
55
+ detail = exc.content.decode(errors="ignore")
56
+ if exc.status_code == 400 and "already a member" in detail:
57
+ print(f" Admin already exists: {email}")
58
+ continue
59
+ print(f" Skipped admin {email}: {detail}")
60
+ continue
61
+ print(f" Added admin: {email} (user_id={member.user_id}, role={member.role})")
62
+
63
+ # ── Step 3: Create or reuse a project in the organization ───────────
64
+ project = client.projects.get_by_slug(org.id, PROJECT_SLUG)
65
+ if project is None:
66
+ project = client.projects.create(
67
+ name="griffin it",
68
+ slug=PROJECT_SLUG,
69
+ organization_id=org.id,
70
+ description="Test project created by the Convexity SDK sample scripts",
71
+ )
72
+ print(f"Created project: {project.name} (id={project.id})")
73
+ else:
74
+ print(f"Found project: {project.name} (id={project.id})")
75
+
76
+ print("\nDone!")
77
+ finally:
78
+ client.close()
@@ -0,0 +1,142 @@
1
+ """
2
+ Convexity SDK - Check QuickBooks Connection
3
+
4
+ This sample verifies that a QuickBooks connection exists for a project and can
5
+ connect successfully. Run this before creating QuickBooks-backed datasets.
6
+
7
+ Prerequisites:
8
+ - A valid API key with READ permission for listing connections.
9
+ - WRITE or ADMIN permission if you want the sample to run the live verify step.
10
+ - An existing organization and project.
11
+ Run ../create_org_and_project.py first if needed.
12
+ - A QuickBooks connection created manually in Convexity.
13
+
14
+ Optional environment variables:
15
+ - QUICKBOOKS_ORG_SLUG: organization slug, defaults to convexity-sdk-samples
16
+ - QUICKBOOKS_PROJECT_SLUG: project slug, defaults to griffin-it
17
+ - QUICKBOOKS_CONNECTION_NAME: when set, require this exact connection name
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ import sys
24
+ from typing import Any
25
+
26
+ from convexity_sdk import ConvexityClient
27
+ from convexity_sdk.exceptions import ConvexityError
28
+
29
+ ORG_SLUG = os.getenv("QUICKBOOKS_ORG_SLUG", "convexity-sdk-samples")
30
+ PROJECT_SLUG = os.getenv("QUICKBOOKS_PROJECT_SLUG", "griffin-it")
31
+ QUICKBOOKS_CONNECTION_NAME = os.getenv("QUICKBOOKS_CONNECTION_NAME")
32
+
33
+
34
+ def find_project_id(client: ConvexityClient) -> str:
35
+ org = client.organizations.get_by_slug(ORG_SLUG)
36
+ if org is None:
37
+ print(f"Organization '{ORG_SLUG}' not found. Run ../create_org_and_project.py first.", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ project = client.projects.get_by_slug(org.id, PROJECT_SLUG)
41
+ if project is None:
42
+ print(f"Project '{PROJECT_SLUG}' not found in organization '{ORG_SLUG}'.", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ print(f"Using project: {project.name} ({project.id})")
46
+ return project.id
47
+
48
+
49
+ def connection_to_dict(connection: Any) -> dict[str, Any]:
50
+ if hasattr(connection, "to_dict"):
51
+ data = connection.to_dict()
52
+ if isinstance(data, dict):
53
+ return data
54
+ return {}
55
+
56
+
57
+ def find_quickbooks_connection(client: ConvexityClient, project_id: str) -> dict[str, Any] | None:
58
+ connections = client.connections.list(project_id=project_id).connections
59
+ quickbooks_connections = []
60
+ for connection in connections:
61
+ data = connection_to_dict(connection)
62
+ if data.get("type") == "QUICKBOOKS":
63
+ quickbooks_connections.append(data)
64
+
65
+ if QUICKBOOKS_CONNECTION_NAME is not None:
66
+ return next(
67
+ (connection for connection in quickbooks_connections if connection.get("name") == QUICKBOOKS_CONNECTION_NAME),
68
+ None,
69
+ )
70
+
71
+ return quickbooks_connections[0] if quickbooks_connections else None
72
+
73
+
74
+ def ask_user_to_create_connection(project_id: str) -> None:
75
+ print("QuickBooks connection is not available for this project.", file=sys.stderr)
76
+ print("Please create and connect a QuickBooks connection manually in Convexity, then rerun this sample.", file=sys.stderr)
77
+ print(f"Project ID: {project_id}", file=sys.stderr)
78
+ if QUICKBOOKS_CONNECTION_NAME is not None:
79
+ print(f"Expected connection name: {QUICKBOOKS_CONNECTION_NAME}", file=sys.stderr)
80
+
81
+
82
+ def verify_quickbooks_connection(client: ConvexityClient, connection_id: str) -> dict[str, Any]:
83
+ response = client._http.request("post", f"/v1/connections/quickbooks/{connection_id}/verify")
84
+ data = response.json()
85
+ if not isinstance(data, dict):
86
+ raise ValueError(f"Unexpected QuickBooks verify response: {data!r}")
87
+ return data
88
+
89
+
90
+ def main() -> None:
91
+ client = ConvexityClient()
92
+ try:
93
+ print(f"Connected to {client.base_url}")
94
+ project_id = find_project_id(client)
95
+
96
+ connection = find_quickbooks_connection(client, project_id)
97
+ if connection is None:
98
+ ask_user_to_create_connection(project_id)
99
+ sys.exit(1)
100
+
101
+ connection_id = connection.get("id")
102
+ if not isinstance(connection_id, str) or not connection_id:
103
+ print(f"QuickBooks connection '{connection.get('name')}' has no connection ID.", file=sys.stderr)
104
+ ask_user_to_create_connection(project_id)
105
+ sys.exit(1)
106
+
107
+ print(f"Found QuickBooks connection: {connection.get('name')} ({connection_id})")
108
+ if connection.get("realmId"):
109
+ print(f"Realm ID: {connection['realmId']}")
110
+ if connection.get("environment"):
111
+ print(f"Environment: {connection['environment']}")
112
+ if connection.get("status"):
113
+ print(f"Stored status: {connection['status']}")
114
+
115
+ try:
116
+ verified = verify_quickbooks_connection(client, connection_id)
117
+ except ConvexityError as exc:
118
+ print(f"QuickBooks verification failed: {exc}", file=sys.stderr)
119
+ print("Please reconnect or recreate the QuickBooks connection manually in Convexity, then rerun this sample.", file=sys.stderr)
120
+ sys.exit(1)
121
+
122
+ status = verified.get("status")
123
+ if status != "CONNECTED":
124
+ print(f"QuickBooks connection is not connected. Current status: {status or 'unknown'}", file=sys.stderr)
125
+ if verified.get("errorMessage"):
126
+ print(f"Error: {verified['errorMessage']}", file=sys.stderr)
127
+ print("Please reconnect or recreate the QuickBooks connection manually in Convexity, then rerun this sample.", file=sys.stderr)
128
+ sys.exit(1)
129
+
130
+ print("QuickBooks connection is available and connected.")
131
+ if verified.get("realmId"):
132
+ print(f"Realm ID: {verified['realmId']}")
133
+ if verified.get("oauthScopes"):
134
+ print("OAuth scopes:")
135
+ for scope in verified["oauthScopes"]:
136
+ print(f" - {scope}")
137
+ finally:
138
+ client.close()
139
+
140
+
141
+ if __name__ == "__main__":
142
+ main()
@@ -0,0 +1,266 @@
1
+ """
2
+ Convexity SDK - Create QuickBooks Datasets
3
+
4
+ This sample creates QuickBooks-backed datasets from an existing QuickBooks
5
+ connection, refreshes them, and prints a small sanity check for row count and
6
+ schema.
7
+
8
+ Prerequisites:
9
+ - Run 001_check_quickbook_connection.py first.
10
+ - A valid API key with WRITE or ADMIN permission.
11
+ - A connected QuickBooks connection in the target project.
12
+
13
+ Optional environment variables:
14
+ - QUICKBOOKS_ORG_SLUG: organization slug, defaults to convexity-sdk-samples
15
+ - QUICKBOOKS_PROJECT_SLUG: project slug, defaults to griffin-it
16
+ - QUICKBOOKS_CONNECTION_NAME: when set, require this exact connection name
17
+ - QUICKBOOKS_DATASET_ENTITIES: comma-separated QuickBooks entities, defaults to all supported entities
18
+ - QUICKBOOKS_REFRESH_TIMEOUT_SECONDS: refresh timeout per dataset, defaults to 180
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import os
24
+ import sys
25
+ import time
26
+ from typing import Any
27
+
28
+ from convexity_api_client.errors import UnexpectedStatus
29
+ from convexity_api_client.models.dataset_config import DatasetConfig
30
+ from convexity_api_client.models.dataset_definition import DatasetDefinition
31
+ from convexity_api_client.models.dataset_type import DatasetType
32
+ from convexity_api_client.models.quick_books_dataset_config import QuickBooksDatasetConfig
33
+ from convexity_api_client.models.quick_books_entity import QuickBooksEntity
34
+ from convexity_api_client.models.refresh_dataset_payload import RefreshDatasetPayload
35
+ from convexity_api_client.models.refresh_mode import RefreshMode
36
+ from convexity_api_client.models.worker_task_status import WorkerTaskStatus
37
+ from convexity_api_client.types import Unset
38
+
39
+ from convexity_sdk import ConvexityClient
40
+ from convexity_sdk.exceptions import ConvexityError
41
+
42
+ ORG_SLUG = os.getenv("QUICKBOOKS_ORG_SLUG", "convexity-sdk-samples")
43
+ PROJECT_SLUG = os.getenv("QUICKBOOKS_PROJECT_SLUG", "griffin-it")
44
+ QUICKBOOKS_CONNECTION_NAME = os.getenv("QUICKBOOKS_CONNECTION_NAME")
45
+ DEFAULT_QUICKBOOKS_DATASET_ENTITIES = ",".join(entity.value for entity in QuickBooksEntity)
46
+ QUICKBOOKS_DATASET_ENTITIES = os.getenv("QUICKBOOKS_DATASET_ENTITIES", DEFAULT_QUICKBOOKS_DATASET_ENTITIES)
47
+ REFRESH_TIMEOUT_SECONDS = int(os.getenv("QUICKBOOKS_REFRESH_TIMEOUT_SECONDS", "180"))
48
+ POLL_INTERVAL_SECONDS = 5
49
+
50
+
51
+ def parse_entities() -> list[QuickBooksEntity]:
52
+ entities: list[QuickBooksEntity] = []
53
+ for raw_entity in QUICKBOOKS_DATASET_ENTITIES.split(","):
54
+ entity_name = raw_entity.strip()
55
+ if not entity_name:
56
+ continue
57
+ try:
58
+ entities.append(QuickBooksEntity(entity_name))
59
+ except ValueError:
60
+ valid = ", ".join(entity.value for entity in QuickBooksEntity)
61
+ print(f"Unknown QuickBooks entity '{entity_name}'. Valid values: {valid}", file=sys.stderr)
62
+ sys.exit(1)
63
+ if not entities:
64
+ print("QUICKBOOKS_DATASET_ENTITIES did not include any entities.", file=sys.stderr)
65
+ sys.exit(1)
66
+ return entities
67
+
68
+
69
+ def find_project_id(client: ConvexityClient) -> str:
70
+ org = client.organizations.get_by_slug(ORG_SLUG)
71
+ if org is None:
72
+ print(f"Organization '{ORG_SLUG}' not found. Run ../create_org_and_project.py first.", file=sys.stderr)
73
+ sys.exit(1)
74
+
75
+ project = client.projects.get_by_slug(org.id, PROJECT_SLUG)
76
+ if project is None:
77
+ print(f"Project '{PROJECT_SLUG}' not found in organization '{ORG_SLUG}'.", file=sys.stderr)
78
+ sys.exit(1)
79
+
80
+ print(f"Using project: {project.name} ({project.id})")
81
+ return project.id
82
+
83
+
84
+ def connection_to_dict(connection: Any) -> dict[str, Any]:
85
+ if hasattr(connection, "to_dict"):
86
+ data = connection.to_dict()
87
+ if isinstance(data, dict):
88
+ return data
89
+ return {}
90
+
91
+
92
+ def find_quickbooks_connection(client: ConvexityClient, project_id: str) -> dict[str, Any] | None:
93
+ connections = client.connections.list(project_id=project_id).connections
94
+ quickbooks_connections = []
95
+ for connection in connections:
96
+ data = connection_to_dict(connection)
97
+ if data.get("type") == "QUICKBOOKS":
98
+ quickbooks_connections.append(data)
99
+
100
+ if QUICKBOOKS_CONNECTION_NAME is not None:
101
+ return next(
102
+ (connection for connection in quickbooks_connections if connection.get("name") == QUICKBOOKS_CONNECTION_NAME),
103
+ None,
104
+ )
105
+
106
+ return quickbooks_connections[0] if quickbooks_connections else None
107
+
108
+
109
+ def verify_quickbooks_connection(client: ConvexityClient, connection_id: str) -> dict[str, Any]:
110
+ response = client._http.request("post", f"/v1/connections/quickbooks/{connection_id}/verify")
111
+ data = response.json()
112
+ if not isinstance(data, dict):
113
+ raise ValueError(f"Unexpected QuickBooks verify response: {data!r}")
114
+ return data
115
+
116
+
117
+ def dataset_name_for(entity: QuickBooksEntity) -> str:
118
+ return f"quickbooks_{entity.value.lower()}"
119
+
120
+
121
+ def build_config(entity: QuickBooksEntity) -> DatasetConfig:
122
+ return DatasetConfig(quickbooks=QuickBooksDatasetConfig(entity=entity))
123
+
124
+
125
+ def find_existing_dataset(client: ConvexityClient, project_id: str, name: str) -> DatasetDefinition | None:
126
+ datasets = client.datasets.list(project_id).datasets
127
+ return next((dataset for dataset in datasets if dataset.name == name), None)
128
+
129
+
130
+ def create_or_update_dataset(
131
+ client: ConvexityClient,
132
+ *,
133
+ project_id: str,
134
+ connection_id: str,
135
+ entity: QuickBooksEntity,
136
+ ) -> DatasetDefinition:
137
+ name = dataset_name_for(entity)
138
+ config = build_config(entity)
139
+ existing = find_existing_dataset(client, project_id, name)
140
+ if existing is not None:
141
+ dataset = client.datasets.update(
142
+ existing.id,
143
+ connection_id=connection_id,
144
+ config=config,
145
+ refresh_mode=RefreshMode.MANUAL,
146
+ )
147
+ print(f"Updated dataset: {dataset.name} ({dataset.id})")
148
+ return dataset
149
+
150
+ dataset = client.datasets.create(
151
+ project_id=project_id,
152
+ name=name,
153
+ type_=DatasetType.QUICKBOOKS,
154
+ config=config,
155
+ connection_id=connection_id,
156
+ refresh_mode=RefreshMode.MANUAL,
157
+ description=f"QuickBooks {entity.value} dataset created by the SDK sample",
158
+ tags=["quickbooks", "sdk-sample"],
159
+ )
160
+ print(f"Created dataset: {dataset.name} ({dataset.id})")
161
+ return dataset
162
+
163
+
164
+ def refresh_and_wait(client: ConvexityClient, dataset: DatasetDefinition) -> RefreshDatasetPayload:
165
+ try:
166
+ task = client.datasets.refresh(dataset.id)
167
+ except (UnexpectedStatus, ConvexityError, ValueError) as exc:
168
+ raise RuntimeError(f"Failed to trigger refresh for '{dataset.name}': {exc}") from exc
169
+
170
+ print(f" Refresh started (task_id={task.id})", end="", flush=True)
171
+ deadline = time.monotonic() + REFRESH_TIMEOUT_SECONDS
172
+ while True:
173
+ if time.monotonic() > deadline:
174
+ raise TimeoutError(f"Refresh timed out for '{dataset.name}' after {REFRESH_TIMEOUT_SECONDS} seconds")
175
+
176
+ time.sleep(POLL_INTERVAL_SECONDS)
177
+ state = client.datasets.get_refresh_status(dataset.id, task.id)
178
+ payload = state.payload
179
+ if isinstance(payload, RefreshDatasetPayload):
180
+ message = payload.progress_message if not isinstance(payload.progress_message, Unset) else None
181
+ if message:
182
+ print(f" {message}", end="", flush=True)
183
+ else:
184
+ payload = None
185
+
186
+ if state.status == WorkerTaskStatus.COMPLETED:
187
+ print(" COMPLETED")
188
+ if payload is None:
189
+ raise ValueError(f"Refresh completed for '{dataset.name}' without a refresh payload")
190
+ return payload
191
+
192
+ if state.status == WorkerTaskStatus.FAILED:
193
+ error = state.error if not isinstance(state.error, Unset) else "unknown error"
194
+ raise RuntimeError(f"Refresh failed for '{dataset.name}': {error}")
195
+
196
+ if state.status == WorkerTaskStatus.CANCELLED:
197
+ raise RuntimeError(f"Refresh was cancelled for '{dataset.name}'")
198
+
199
+ print(".", end="", flush=True)
200
+
201
+
202
+ def print_dataset_sanity(client: ConvexityClient, dataset: DatasetDefinition, payload: RefreshDatasetPayload) -> None:
203
+ refreshed = client.datasets.get(dataset.id)
204
+ if refreshed is None:
205
+ raise ValueError(f"Could not fetch refreshed dataset '{dataset.name}'")
206
+
207
+ row_count = payload.row_count if not isinstance(payload.row_count, Unset) else None
208
+ schema = refreshed.schema
209
+ columns = [] if schema is None or isinstance(schema, Unset) else schema
210
+
211
+ print(f" Rows written: {row_count if row_count is not None else 'unknown'}")
212
+ print(f" Columns: {len(columns)}")
213
+ for column in columns[:8]:
214
+ column_type = getattr(column, "type_", getattr(column, "type", "unknown"))
215
+ print(f" - {column.name}: {column_type}")
216
+
217
+ if row_count is None:
218
+ raise ValueError(f"Refresh for '{dataset.name}' did not report row_count")
219
+ if not columns:
220
+ raise ValueError(f"Refresh for '{dataset.name}' did not produce a schema")
221
+
222
+
223
+ def main() -> None:
224
+ entities = parse_entities()
225
+ client = ConvexityClient()
226
+ try:
227
+ print(f"Connected to {client.base_url}")
228
+ project_id = find_project_id(client)
229
+
230
+ connection = find_quickbooks_connection(client, project_id)
231
+ if connection is None:
232
+ print("QuickBooks connection is not available for this project.", file=sys.stderr)
233
+ print("Run 001_check_quickbook_connection.py after creating the connection manually.", file=sys.stderr)
234
+ sys.exit(1)
235
+
236
+ connection_id = connection.get("id")
237
+ if not isinstance(connection_id, str) or not connection_id:
238
+ print(f"QuickBooks connection '{connection.get('name')}' has no connection ID.", file=sys.stderr)
239
+ sys.exit(1)
240
+
241
+ verified = verify_quickbooks_connection(client, connection_id)
242
+ if verified.get("status") != "CONNECTED":
243
+ print("QuickBooks connection is not connected. Run 001_check_quickbook_connection.py first.", file=sys.stderr)
244
+ sys.exit(1)
245
+
246
+ print(f"Using QuickBooks connection: {connection.get('name')} ({connection_id})")
247
+ print("Entities:", ", ".join(entity.value for entity in entities))
248
+
249
+ for entity in entities:
250
+ print(f"\n-- {entity.value} --")
251
+ dataset = create_or_update_dataset(
252
+ client,
253
+ project_id=project_id,
254
+ connection_id=connection_id,
255
+ entity=entity,
256
+ )
257
+ payload = refresh_and_wait(client, dataset)
258
+ print_dataset_sanity(client, dataset, payload)
259
+
260
+ print("\nDone!")
261
+ finally:
262
+ client.close()
263
+
264
+
265
+ if __name__ == "__main__":
266
+ main()
@@ -0,0 +1,133 @@
1
+ """
2
+ Convexity SDK - Check Shopify Connection
3
+
4
+ This sample verifies that a Shopify connection exists for a project and can
5
+ connect successfully. Run this before creating Shopify-backed datasets.
6
+
7
+ Prerequisites:
8
+ - A valid API key with READ permission for listing connections.
9
+ - WRITE or ADMIN permission if you want the sample to run the live verify step.
10
+ - An existing organization and project.
11
+ Run ../create_org_and_project.py first if needed.
12
+ - A Shopify connection created manually in Convexity.
13
+
14
+ Optional environment variables:
15
+ - SHOPIFY_ORG_SLUG: organization slug, defaults to convexity-sdk-samples
16
+ - SHOPIFY_PROJECT_SLUG: project slug, defaults to griffin-it
17
+ - SHOPIFY_CONNECTION_NAME: when set, require this exact connection name
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ import sys
24
+ from typing import Any
25
+
26
+ from convexity_sdk import ConvexityClient
27
+ from convexity_sdk.exceptions import ConvexityError
28
+
29
+ ORG_SLUG = os.getenv("SHOPIFY_ORG_SLUG", "convexity-sdk-samples")
30
+ PROJECT_SLUG = os.getenv("SHOPIFY_PROJECT_SLUG", "griffin-it")
31
+ SHOPIFY_CONNECTION_NAME = os.getenv("SHOPIFY_CONNECTION_NAME")
32
+
33
+
34
+ def find_project_id(client: ConvexityClient) -> str:
35
+ org = client.organizations.get_by_slug(ORG_SLUG)
36
+ if org is None:
37
+ print(f"Organization '{ORG_SLUG}' not found. Run ../create_org_and_project.py first.", file=sys.stderr)
38
+ sys.exit(1)
39
+
40
+ project = client.projects.get_by_slug(org.id, PROJECT_SLUG)
41
+ if project is None:
42
+ print(f"Project '{PROJECT_SLUG}' not found in organization '{ORG_SLUG}'.", file=sys.stderr)
43
+ sys.exit(1)
44
+
45
+ print(f"Using project: {project.name} ({project.id})")
46
+ return project.id
47
+
48
+
49
+ def connection_to_dict(connection: Any) -> dict[str, Any]:
50
+ if hasattr(connection, "to_dict"):
51
+ data = connection.to_dict()
52
+ if isinstance(data, dict):
53
+ return data
54
+ return {}
55
+
56
+
57
+ def find_shopify_connection(client: ConvexityClient, project_id: str) -> dict[str, Any] | None:
58
+ connections = client.connections.list(project_id=project_id).connections
59
+ shopify_connections = [connection_to_dict(connection) for connection in connections if connection_to_dict(connection).get("type") == "SHOPIFY"]
60
+
61
+ if SHOPIFY_CONNECTION_NAME is not None:
62
+ return next((connection for connection in shopify_connections if connection.get("name") == SHOPIFY_CONNECTION_NAME), None)
63
+
64
+ return shopify_connections[0] if shopify_connections else None
65
+
66
+
67
+ def ask_user_to_create_connection(project_id: str) -> None:
68
+ print("Shopify connection is not available for this project.", file=sys.stderr)
69
+ print("Please create and connect a Shopify connection manually in Convexity, then rerun this sample.", file=sys.stderr)
70
+ print(f"Project ID: {project_id}", file=sys.stderr)
71
+ if SHOPIFY_CONNECTION_NAME is not None:
72
+ print(f"Expected connection name: {SHOPIFY_CONNECTION_NAME}", file=sys.stderr)
73
+
74
+
75
+ def verify_shopify_connection(client: ConvexityClient, connection_id: str) -> dict[str, Any]:
76
+ response = client._http.request("post", f"/v1/connections/shopify/{connection_id}/verify")
77
+ data = response.json()
78
+ if not isinstance(data, dict):
79
+ raise ValueError(f"Unexpected Shopify verify response: {data!r}")
80
+ return data
81
+
82
+
83
+ def main() -> None:
84
+ client = ConvexityClient()
85
+ try:
86
+ print(f"Connected to {client.base_url}")
87
+ project_id = find_project_id(client)
88
+
89
+ connection = find_shopify_connection(client, project_id)
90
+ if connection is None:
91
+ ask_user_to_create_connection(project_id)
92
+ sys.exit(1)
93
+
94
+ connection_id = connection.get("id")
95
+ if not isinstance(connection_id, str) or not connection_id:
96
+ print(f"Shopify connection '{connection.get('name')}' has no connection ID.", file=sys.stderr)
97
+ ask_user_to_create_connection(project_id)
98
+ sys.exit(1)
99
+
100
+ print(f"Found Shopify connection: {connection.get('name')} ({connection_id})")
101
+ if connection.get("shopDomain"):
102
+ print(f"Shop domain: {connection['shopDomain']}")
103
+ if connection.get("status"):
104
+ print(f"Stored status: {connection['status']}")
105
+
106
+ try:
107
+ verified = verify_shopify_connection(client, connection_id)
108
+ except ConvexityError as exc:
109
+ print(f"Shopify verification failed: {exc}", file=sys.stderr)
110
+ print("Please reconnect or recreate the Shopify connection manually in Convexity, then rerun this sample.", file=sys.stderr)
111
+ sys.exit(1)
112
+
113
+ status = verified.get("status")
114
+ if status != "CONNECTED":
115
+ print(f"Shopify connection is not connected. Current status: {status or 'unknown'}", file=sys.stderr)
116
+ if verified.get("errorMessage"):
117
+ print(f"Error: {verified['errorMessage']}", file=sys.stderr)
118
+ print("Please reconnect or recreate the Shopify connection manually in Convexity, then rerun this sample.", file=sys.stderr)
119
+ sys.exit(1)
120
+
121
+ print("Shopify connection is available and connected.")
122
+ if verified.get("shopName"):
123
+ print(f"Shop name: {verified['shopName']}")
124
+ if verified.get("grantedScopes"):
125
+ print("Granted scopes:")
126
+ for scope in verified["grantedScopes"]:
127
+ print(f" - {scope}")
128
+ finally:
129
+ client.close()
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
@@ -0,0 +1,262 @@
1
+ """
2
+ Convexity SDK - Create Shopify Datasets
3
+
4
+ This sample creates Shopify-backed datasets from an existing Shopify connection,
5
+ refreshes them, and prints a small sanity check for row count and schema.
6
+
7
+ Prerequisites:
8
+ - Run 001_check_shopify_connection.py first.
9
+ - A valid API key with WRITE or ADMIN permission.
10
+ - A connected Shopify connection in the target project.
11
+
12
+ Optional environment variables:
13
+ - SHOPIFY_ORG_SLUG: organization slug, defaults to convexity-sdk-samples
14
+ - SHOPIFY_PROJECT_SLUG: project slug, defaults to griffin-it
15
+ - SHOPIFY_CONNECTION_NAME: when set, require this exact connection name
16
+ - SHOPIFY_DATASET_ENTITIES: comma-separated Shopify entities, defaults to all supported entities
17
+ - SHOPIFY_REFRESH_TIMEOUT_SECONDS: refresh timeout per dataset, defaults to 180
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ import sys
24
+ import time
25
+ from typing import Any
26
+
27
+ from convexity_api_client.errors import UnexpectedStatus
28
+ from convexity_api_client.models.dataset_config import DatasetConfig
29
+ from convexity_api_client.models.dataset_definition import DatasetDefinition
30
+ from convexity_api_client.models.dataset_type import DatasetType
31
+ from convexity_api_client.models.refresh_dataset_payload import RefreshDatasetPayload
32
+ from convexity_api_client.models.refresh_mode import RefreshMode
33
+ from convexity_api_client.models.shopify_dataset_config import ShopifyDatasetConfig
34
+ from convexity_api_client.models.shopify_entity import ShopifyEntity
35
+ from convexity_api_client.models.worker_task_status import WorkerTaskStatus
36
+ from convexity_api_client.types import Unset
37
+
38
+ from convexity_sdk import ConvexityClient
39
+ from convexity_sdk.exceptions import ConvexityError
40
+
41
+ ORG_SLUG = os.getenv("SHOPIFY_ORG_SLUG", "convexity-sdk-samples")
42
+ PROJECT_SLUG = os.getenv("SHOPIFY_PROJECT_SLUG", "griffin-it")
43
+ SHOPIFY_CONNECTION_NAME = os.getenv("SHOPIFY_CONNECTION_NAME")
44
+ DEFAULT_SHOPIFY_DATASET_ENTITIES = ",".join(entity.value for entity in ShopifyEntity)
45
+ SHOPIFY_DATASET_ENTITIES = os.getenv("SHOPIFY_DATASET_ENTITIES", DEFAULT_SHOPIFY_DATASET_ENTITIES)
46
+ REFRESH_TIMEOUT_SECONDS = int(os.getenv("SHOPIFY_REFRESH_TIMEOUT_SECONDS", "180"))
47
+ POLL_INTERVAL_SECONDS = 5
48
+
49
+
50
+ def parse_entities() -> list[ShopifyEntity]:
51
+ entities: list[ShopifyEntity] = []
52
+ for raw_entity in SHOPIFY_DATASET_ENTITIES.split(","):
53
+ entity_name = raw_entity.strip()
54
+ if not entity_name:
55
+ continue
56
+ try:
57
+ entities.append(ShopifyEntity(entity_name))
58
+ except ValueError:
59
+ valid = ", ".join(entity.value for entity in ShopifyEntity)
60
+ print(f"Unknown Shopify entity '{entity_name}'. Valid values: {valid}", file=sys.stderr)
61
+ sys.exit(1)
62
+ if not entities:
63
+ print("SHOPIFY_DATASET_ENTITIES did not include any entities.", file=sys.stderr)
64
+ sys.exit(1)
65
+ return entities
66
+
67
+
68
+ def find_project_id(client: ConvexityClient) -> str:
69
+ org = client.organizations.get_by_slug(ORG_SLUG)
70
+ if org is None:
71
+ print(f"Organization '{ORG_SLUG}' not found. Run ../create_org_and_project.py first.", file=sys.stderr)
72
+ sys.exit(1)
73
+
74
+ project = client.projects.get_by_slug(org.id, PROJECT_SLUG)
75
+ if project is None:
76
+ print(f"Project '{PROJECT_SLUG}' not found in organization '{ORG_SLUG}'.", file=sys.stderr)
77
+ sys.exit(1)
78
+
79
+ print(f"Using project: {project.name} ({project.id})")
80
+ return project.id
81
+
82
+
83
+ def connection_to_dict(connection: Any) -> dict[str, Any]:
84
+ if hasattr(connection, "to_dict"):
85
+ data = connection.to_dict()
86
+ if isinstance(data, dict):
87
+ return data
88
+ return {}
89
+
90
+
91
+ def find_shopify_connection(client: ConvexityClient, project_id: str) -> dict[str, Any] | None:
92
+ connections = client.connections.list(project_id=project_id).connections
93
+ shopify_connections = []
94
+ for connection in connections:
95
+ data = connection_to_dict(connection)
96
+ if data.get("type") == "SHOPIFY":
97
+ shopify_connections.append(data)
98
+
99
+ if SHOPIFY_CONNECTION_NAME is not None:
100
+ return next((connection for connection in shopify_connections if connection.get("name") == SHOPIFY_CONNECTION_NAME), None)
101
+
102
+ return shopify_connections[0] if shopify_connections else None
103
+
104
+
105
+ def verify_shopify_connection(client: ConvexityClient, connection_id: str) -> dict[str, Any]:
106
+ response = client._http.request("post", f"/v1/connections/shopify/{connection_id}/verify")
107
+ data = response.json()
108
+ if not isinstance(data, dict):
109
+ raise ValueError(f"Unexpected Shopify verify response: {data!r}")
110
+ return data
111
+
112
+
113
+ def dataset_name_for(entity: ShopifyEntity) -> str:
114
+ return f"shopify_{entity.value.lower()}"
115
+
116
+
117
+ def build_config(entity: ShopifyEntity) -> DatasetConfig:
118
+ return DatasetConfig(shopify=ShopifyDatasetConfig(entity=entity))
119
+
120
+
121
+ def find_existing_dataset(client: ConvexityClient, project_id: str, name: str) -> DatasetDefinition | None:
122
+ datasets = client.datasets.list(project_id).datasets
123
+ return next((dataset for dataset in datasets if dataset.name == name), None)
124
+
125
+
126
+ def create_or_update_dataset(
127
+ client: ConvexityClient,
128
+ *,
129
+ project_id: str,
130
+ connection_id: str,
131
+ entity: ShopifyEntity,
132
+ ) -> DatasetDefinition:
133
+ name = dataset_name_for(entity)
134
+ config = build_config(entity)
135
+ existing = find_existing_dataset(client, project_id, name)
136
+ if existing is not None:
137
+ dataset = client.datasets.update(
138
+ existing.id,
139
+ connection_id=connection_id,
140
+ config=config,
141
+ refresh_mode=RefreshMode.MANUAL,
142
+ )
143
+ print(f"Updated dataset: {dataset.name} ({dataset.id})")
144
+ return dataset
145
+
146
+ dataset = client.datasets.create(
147
+ project_id=project_id,
148
+ name=name,
149
+ type_=DatasetType.SHOPIFY,
150
+ config=config,
151
+ connection_id=connection_id,
152
+ refresh_mode=RefreshMode.MANUAL,
153
+ description=f"Shopify {entity.value} dataset created by the SDK sample",
154
+ tags=["shopify", "sdk-sample"],
155
+ )
156
+ print(f"Created dataset: {dataset.name} ({dataset.id})")
157
+ return dataset
158
+
159
+
160
+ def refresh_and_wait(client: ConvexityClient, dataset: DatasetDefinition) -> RefreshDatasetPayload:
161
+ try:
162
+ task = client.datasets.refresh(dataset.id)
163
+ except (UnexpectedStatus, ConvexityError, ValueError) as exc:
164
+ raise RuntimeError(f"Failed to trigger refresh for '{dataset.name}': {exc}") from exc
165
+
166
+ print(f" Refresh started (task_id={task.id})", end="", flush=True)
167
+ deadline = time.monotonic() + REFRESH_TIMEOUT_SECONDS
168
+ while True:
169
+ if time.monotonic() > deadline:
170
+ raise TimeoutError(f"Refresh timed out for '{dataset.name}' after {REFRESH_TIMEOUT_SECONDS} seconds")
171
+
172
+ time.sleep(POLL_INTERVAL_SECONDS)
173
+ state = client.datasets.get_refresh_status(dataset.id, task.id)
174
+ payload = state.payload
175
+ if isinstance(payload, RefreshDatasetPayload):
176
+ message = payload.progress_message if not isinstance(payload.progress_message, Unset) else None
177
+ if message:
178
+ print(f" {message}", end="", flush=True)
179
+ else:
180
+ payload = None
181
+
182
+ if state.status == WorkerTaskStatus.COMPLETED:
183
+ print(" COMPLETED")
184
+ if payload is None:
185
+ raise ValueError(f"Refresh completed for '{dataset.name}' without a refresh payload")
186
+ return payload
187
+
188
+ if state.status == WorkerTaskStatus.FAILED:
189
+ error = state.error if not isinstance(state.error, Unset) else "unknown error"
190
+ raise RuntimeError(f"Refresh failed for '{dataset.name}': {error}")
191
+
192
+ if state.status == WorkerTaskStatus.CANCELLED:
193
+ raise RuntimeError(f"Refresh was cancelled for '{dataset.name}'")
194
+
195
+ print(".", end="", flush=True)
196
+
197
+
198
+ def print_dataset_sanity(client: ConvexityClient, dataset: DatasetDefinition, payload: RefreshDatasetPayload) -> None:
199
+ refreshed = client.datasets.get(dataset.id)
200
+ if refreshed is None:
201
+ raise ValueError(f"Could not fetch refreshed dataset '{dataset.name}'")
202
+
203
+ row_count = payload.row_count if not isinstance(payload.row_count, Unset) else None
204
+ schema = refreshed.schema
205
+ columns = [] if schema is None or isinstance(schema, Unset) else schema
206
+
207
+ print(f" Rows written: {row_count if row_count is not None else 'unknown'}")
208
+ print(f" Columns: {len(columns)}")
209
+ for column in columns[:8]:
210
+ column_type = getattr(column, "type_", getattr(column, "type", "unknown"))
211
+ print(f" - {column.name}: {column_type}")
212
+
213
+ if row_count is None:
214
+ raise ValueError(f"Refresh for '{dataset.name}' did not report row_count")
215
+ if not columns:
216
+ raise ValueError(f"Refresh for '{dataset.name}' did not produce a schema")
217
+
218
+
219
+ def main() -> None:
220
+ entities = parse_entities()
221
+ client = ConvexityClient()
222
+ try:
223
+ print(f"Connected to {client.base_url}")
224
+ project_id = find_project_id(client)
225
+
226
+ connection = find_shopify_connection(client, project_id)
227
+ if connection is None:
228
+ print("Shopify connection is not available for this project.", file=sys.stderr)
229
+ print("Run 001_check_shopify_connection.py after creating the connection manually.", file=sys.stderr)
230
+ sys.exit(1)
231
+
232
+ connection_id = connection.get("id")
233
+ if not isinstance(connection_id, str) or not connection_id:
234
+ print(f"Shopify connection '{connection.get('name')}' has no connection ID.", file=sys.stderr)
235
+ sys.exit(1)
236
+
237
+ verified = verify_shopify_connection(client, connection_id)
238
+ if verified.get("status") != "CONNECTED":
239
+ print("Shopify connection is not connected. Run 001_check_shopify_connection.py first.", file=sys.stderr)
240
+ sys.exit(1)
241
+
242
+ print(f"Using Shopify connection: {connection.get('name')} ({connection_id})")
243
+ print("Entities:", ", ".join(entity.value for entity in entities))
244
+
245
+ for entity in entities:
246
+ print(f"\n-- {entity.value} --")
247
+ dataset = create_or_update_dataset(
248
+ client,
249
+ project_id=project_id,
250
+ connection_id=connection_id,
251
+ entity=entity,
252
+ )
253
+ payload = refresh_and_wait(client, dataset)
254
+ print_dataset_sanity(client, dataset, payload)
255
+
256
+ print("\nDone!")
257
+ finally:
258
+ client.close()
259
+
260
+
261
+ if __name__ == "__main__":
262
+ main()
@@ -1,27 +0,0 @@
1
- git pull# Convexity SDK Samples
2
-
3
- This directory contains sample scripts demonstrating how to use the Convexity SDK.
4
-
5
- ## Samples
6
-
7
- | Sample | Description |
8
- | -------------------------------------------------------------------- | ------------------------------------------- |
9
- | [getting_started.py](getting_started.py) | Basic SDK setup and usage |
10
- | [create_org_and_project.py](create_org_and_project.py) | Create an organization and project |
11
- | [create_databricks_connections.py](create_databricks_connections.py) | Create Databricks connections |
12
- | [create_datasets.py](create_datasets.py) | Create datasets from Databricks connections |
13
- | [create_dashboards.py](create_dashboards.py) | Create analytic dashboards with widgets |
14
- | [share_dashboard.py](share_dashboard.py) | Share a dashboard with other users |
15
- | [list_orgs_and_projects.py](list_orgs_and_projects.py) | List organizations and projects |
16
- | [query_datalake.py](query_datalake.py) | Query the DuckLake warehouse with DuckDB |
17
- | [cancel_refresh_task.py](cancel_refresh_task.py) | Cancel a pending dataset refresh task |
18
- | [create_forecast_task.py](create_forecast_task.py) | Create a demand-forecasting task and run it |
19
- | [compare_api_key_scopes.py](compare_api_key_scopes.py) | Compare READ vs ADMIN API key behavior |
20
- | [debug_api_key_tenant_context.py](debug_api_key_tenant_context.py) | Diagnose API key tenant/scope mismatch |
21
-
22
- ## Running Samples
23
-
24
- ```bash
25
- # From the repo root
26
- uv run python packages/convexity-sdk/samples/getting_started.py
27
- ```
@@ -1,51 +0,0 @@
1
- """
2
- Convexity SDK - Create Organization & Project
3
-
4
- This sample demonstrates how to:
5
- - Create a new organization
6
- - Add members to the organization as admins
7
- - Create a project within the organization
8
-
9
- Prerequisites:
10
- - A valid API key with WRITE or ADMIN permission
11
- """
12
-
13
- from convexity_api_client.models.organization_role import OrganizationRole
14
-
15
- from convexity_sdk import ConvexityClient
16
-
17
- client = ConvexityClient()
18
-
19
- # ── Step 1: Create a new organization ───────────────────────────────────
20
- org = client.organizations.create(
21
- name="Convexity SDK samples",
22
- slug="convexity-sdk-samples",
23
- description="Organization created by the Convexity SDK sample scripts",
24
- )
25
- print(f"Created organization: {org.name} (id={org.id})")
26
-
27
- # ── Step 2: Add admin members ──────────────────────────────────────────
28
- admin_user_ids = [
29
- "6eadd2f4-08aa-4017-9131-6ba87d066b12",
30
- "c572deb6-8c2e-4515-a23e-fb468a721048",
31
- ]
32
-
33
- for user_id in admin_user_ids:
34
- member = client.organizations.add_member(
35
- org.id,
36
- user_id=user_id,
37
- role=OrganizationRole.ADMIN,
38
- )
39
- print(f" Added admin: {member.user_id} (role={member.role})")
40
-
41
- # ── Step 3: Create a project in the organization ───────────────────────
42
- project = client.projects.create(
43
- name="griffin it",
44
- slug="griffin-it",
45
- organization_id=org.id,
46
- description="Test project created by the Convexity SDK sample scripts",
47
- )
48
- print(f"Created project: {project.name} (id={project.id})")
49
-
50
- print("\nDone!")
51
- client.close()