dagster-evidence 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,28 @@
1
1
  from dagster._core.libraries import DagsterLibraryRegistry
2
2
 
3
+ from dagster_evidence.components.evidence_project_v2 import EvidenceProjectComponentV2
4
+ from dagster_evidence.components.sources import (
5
+ EvidenceProjectTranslatorData,
6
+ EvidenceSourceTranslatorData,
7
+ ProjectDagsterMetadata,
8
+ SourceDagsterMetadata,
9
+ )
10
+ from dagster_evidence.components.translator import DagsterEvidenceTranslator
3
11
  from dagster_evidence.lib.evidence_project import EvidenceProject
4
12
  from dagster_evidence.resource import EvidenceResource
5
13
 
6
- __version__ = "0.1.7"
14
+ __version__ = "0.2.0"
7
15
 
8
- __all__ = [EvidenceProject, EvidenceResource]
16
+ __all__ = [
17
+ "EvidenceProjectComponentV2",
18
+ "EvidenceProject",
19
+ "EvidenceResource",
20
+ "DagsterEvidenceTranslator",
21
+ "EvidenceSourceTranslatorData",
22
+ "EvidenceProjectTranslatorData",
23
+ "ProjectDagsterMetadata",
24
+ "SourceDagsterMetadata",
25
+ ]
9
26
 
10
27
  DagsterLibraryRegistry.register(
11
28
  "dagster-evidence", __version__, is_dagster_package=False
@@ -0,0 +1,17 @@
1
+ from dagster_evidence.components.evidence_project_v2 import (
2
+ EvidenceProjectComponentV2 as EvidenceProjectComponentV2,
3
+ )
4
+ from dagster_evidence.components.sources import (
5
+ EvidenceProjectTranslatorData as EvidenceProjectTranslatorData,
6
+ EvidenceSourceTranslatorData as EvidenceSourceTranslatorData,
7
+ )
8
+ from dagster_evidence.components.translator import (
9
+ DagsterEvidenceTranslator as DagsterEvidenceTranslator,
10
+ )
11
+
12
+ __all__ = [
13
+ "EvidenceProjectComponentV2",
14
+ "DagsterEvidenceTranslator",
15
+ "EvidenceSourceTranslatorData",
16
+ "EvidenceProjectTranslatorData",
17
+ ]
@@ -0,0 +1,514 @@
1
+ """Deployment classes for Evidence projects.
2
+
3
+ This module provides deployment targets for Evidence projects, including
4
+ GitHub Pages, Netlify, and custom deployment commands.
5
+ """
6
+
7
+ import os
8
+ from abc import abstractmethod
9
+ from pathlib import Path
10
+ from typing import Literal, Optional
11
+
12
+ import dagster as dg
13
+ import yaml
14
+ from dagster._annotations import beta, public
15
+ from dagster.components.resolved.base import resolve_fields
16
+ from pydantic import BaseModel, Field
17
+
18
+
19
+ @beta
20
+ @public
21
+ class BaseEvidenceProjectDeployment(dg.ConfigurableResource):
22
+ """Base class for Evidence project deployment configurations.
23
+
24
+ Subclass this to implement custom deployment targets.
25
+
26
+ Example:
27
+
28
+ Implementing a custom S3 deployment:
29
+
30
+ .. code-block:: python
31
+
32
+ from dagster_evidence.components.deployments import (
33
+ BaseEvidenceProjectDeployment,
34
+ )
35
+ import dagster as dg
36
+
37
+ class S3EvidenceProjectDeployment(BaseEvidenceProjectDeployment):
38
+ s3_bucket: str
39
+ s3_prefix: str = ""
40
+
41
+ def deploy_evidence_project(
42
+ self,
43
+ evidence_project_build_path: str,
44
+ context: dg.AssetExecutionContext,
45
+ pipes_subprocess_client: dg.PipesSubprocessClient,
46
+ env: dict[str, str] | None = None,
47
+ ) -> None:
48
+ context.log.info(
49
+ f"Deploying to s3://{self.s3_bucket}/{self.s3_prefix}"
50
+ )
51
+ import os
52
+ pipes_subprocess_client.run(
53
+ command=[
54
+ "aws", "s3", "sync",
55
+ evidence_project_build_path,
56
+ f"s3://{self.s3_bucket}/{self.s3_prefix}",
57
+ ],
58
+ cwd=evidence_project_build_path,
59
+ context=context,
60
+ env=env or os.environ.copy(),
61
+ )
62
+ """
63
+
64
+ @public
65
+ @abstractmethod
66
+ def deploy_evidence_project(
67
+ self,
68
+ evidence_project_build_path: str,
69
+ context: dg.AssetExecutionContext,
70
+ pipes_subprocess_client: dg.PipesSubprocessClient,
71
+ env: Optional[dict[str, str]] = None,
72
+ ) -> None:
73
+ """Deploy the built Evidence project to the target destination.
74
+
75
+ Args:
76
+ evidence_project_build_path: Path to the built Evidence project output.
77
+ context: The Dagster asset execution context.
78
+ pipes_subprocess_client: Client for running subprocess commands.
79
+ env: Optional environment variables for the deployment process.
80
+ """
81
+ raise NotImplementedError()
82
+
83
+ @public
84
+ def get_base_path(self, project_path: str) -> str:
85
+ """Get the build output path for this deployment.
86
+
87
+ Args:
88
+ project_path: Path to the Evidence project directory.
89
+
90
+ Returns:
91
+ The build output path. Default is 'build'.
92
+ """
93
+ return "build"
94
+
95
+
96
+ @beta
97
+ @public
98
+ class CustomEvidenceProjectDeployment(BaseEvidenceProjectDeployment):
99
+ """Custom deployment using a shell command.
100
+
101
+ Use this deployment type when you need to run a custom deployment script
102
+ or command that is not covered by the built-in deployment types.
103
+
104
+ Attributes:
105
+ deploy_command: The shell command to run for deployment.
106
+
107
+ Example:
108
+
109
+ .. code-block:: yaml
110
+
111
+ project_deployment:
112
+ type: custom
113
+ deploy_command: "rsync -avz ./ user@server:/var/www/dashboard/"
114
+
115
+ Or using a deployment script:
116
+
117
+ .. code-block:: yaml
118
+
119
+ project_deployment:
120
+ type: custom
121
+ deploy_command: "./deploy.sh production"
122
+ """
123
+
124
+ deploy_command: str
125
+
126
+ def deploy_evidence_project(
127
+ self,
128
+ evidence_project_build_path: str,
129
+ context: dg.AssetExecutionContext,
130
+ pipes_subprocess_client: dg.PipesSubprocessClient,
131
+ env: Optional[dict[str, str]] = None,
132
+ ) -> None:
133
+ if self.deploy_command is not None:
134
+ context.log.info(f"Running deploy command: {self.deploy_command}")
135
+ pipes_subprocess_client.run(
136
+ command=self.deploy_command,
137
+ cwd=evidence_project_build_path,
138
+ context=context,
139
+ env=env or os.environ.copy(),
140
+ )
141
+
142
+
143
+ @beta
144
+ @public
145
+ class CustomEvidenceProjectDeploymentArgs(dg.Model, dg.Resolvable):
146
+ """Arguments for configuring a custom deployment command.
147
+
148
+ Example:
149
+
150
+ .. code-block:: yaml
151
+
152
+ project_deployment:
153
+ type: custom
154
+ deploy_command: "aws s3 sync build/ s3://my-bucket/dashboard/"
155
+
156
+ Attributes:
157
+ type: Must be "custom" to use this deployment type.
158
+ deploy_command: Shell command to execute for deployment.
159
+ """
160
+
161
+ type: Literal["custom"] = Field(
162
+ default="custom",
163
+ description="Deployment type identifier.",
164
+ )
165
+ deploy_command: str = Field(
166
+ ...,
167
+ description="Custom command to run for deployment.",
168
+ )
169
+
170
+
171
+ @beta
172
+ @public
173
+ class GithubPagesEvidenceProjectDeployment(BaseEvidenceProjectDeployment):
174
+ """Deploy Evidence project to GitHub Pages.
175
+
176
+ This deployment type pushes the built Evidence project to a GitHub Pages
177
+ branch, enabling automatic hosting via GitHub.
178
+
179
+ Requirements:
180
+ - evidence.config.yaml must have deployment.basePath configured
181
+ - A GitHub token with repo write access
182
+
183
+ Attributes:
184
+ github_repo: Repository in "owner/repo" format.
185
+ branch: Target branch for deployment (default: "gh-pages").
186
+ github_token: GitHub token for authentication.
187
+
188
+ Example:
189
+
190
+ .. code-block:: yaml
191
+
192
+ project_deployment:
193
+ type: github_pages
194
+ github_repo: my-org/analytics-dashboard
195
+ branch: gh-pages
196
+ github_token: ${GITHUB_TOKEN}
197
+
198
+ Your evidence.config.yaml should include:
199
+
200
+ .. code-block:: yaml
201
+
202
+ deployment:
203
+ basePath: /analytics-dashboard
204
+ """
205
+
206
+ github_repo: str # e.g., "milicevica23/food-tracking-serving"
207
+ branch: str = "gh-pages"
208
+ github_token: str # Token resolved at component load time (from config or GITHUB_TOKEN env var)
209
+
210
+ def get_base_path(self, project_path: str) -> str:
211
+ """Get the build output path from evidence.config.yaml for GitHub Pages.
212
+
213
+ Args:
214
+ project_path: Path to the Evidence project directory.
215
+
216
+ Returns:
217
+ The build path in format 'build/{basePath}'.
218
+
219
+ Raises:
220
+ FileNotFoundError: If evidence.config.yaml is not found.
221
+ ValueError: If basePath is not configured in deployment section.
222
+ """
223
+ config_path = Path(project_path) / "evidence.config.yaml"
224
+ if not config_path.exists():
225
+ raise FileNotFoundError(
226
+ f"evidence.config.yaml not found at {config_path}. "
227
+ "See: https://docs.evidence.dev/deployment/self-host/github-pages/"
228
+ )
229
+
230
+ with open(config_path, "r") as f:
231
+ config = yaml.safe_load(f)
232
+
233
+ deployment_config = config.get("deployment", {})
234
+ if not deployment_config:
235
+ raise ValueError(
236
+ "Missing 'deployment' section in evidence.config.yaml. "
237
+ "GitHub Pages deployment requires 'deployment.basePath' to be configured. "
238
+ "See: https://docs.evidence.dev/deployment/self-host/github-pages/"
239
+ )
240
+
241
+ base_path = deployment_config.get("basePath")
242
+ if not base_path:
243
+ raise ValueError(
244
+ "Missing 'basePath' in deployment section of evidence.config.yaml. "
245
+ "GitHub Pages deployment requires 'deployment.basePath' to be configured. "
246
+ "See: https://docs.evidence.dev/deployment/self-host/github-pages/"
247
+ )
248
+
249
+ # Remove leading slash and prepend with build/
250
+ return f"build/{base_path.lstrip('/')}"
251
+
252
+ def deploy_evidence_project(
253
+ self,
254
+ evidence_project_build_path: str,
255
+ context: dg.AssetExecutionContext,
256
+ pipes_subprocess_client: dg.PipesSubprocessClient,
257
+ env: Optional[dict[str, str]] = None,
258
+ ) -> None:
259
+ """Deploy Evidence project build to GitHub Pages using GitPython."""
260
+ from datetime import datetime, timezone
261
+
262
+ try:
263
+ from git import Repo
264
+ except ImportError:
265
+ raise ImportError(
266
+ "GitPython is required for GitHub Pages deployment. "
267
+ "Install it with: pip install dagster-evidence[github-pages]"
268
+ ) from None
269
+
270
+ if not os.path.isdir(evidence_project_build_path):
271
+ raise FileNotFoundError(
272
+ f"Build directory not found: {evidence_project_build_path}"
273
+ )
274
+
275
+ context.log.info("Deploying to GitHub Pages...")
276
+ context.log.info(f" Repo: {self.github_repo}")
277
+ context.log.info(f" Branch: {self.branch}")
278
+ context.log.info(f" Build path: {evidence_project_build_path}")
279
+
280
+ # Initialize git repo directly in the build directory
281
+ repo = Repo.init(evidence_project_build_path, initial_branch="main")
282
+
283
+ # Add remote with token auth (token is never logged)
284
+ remote_url = f"https://x-access-token:{self.github_token}@github.com/{self.github_repo}.git"
285
+ origin = repo.create_remote("origin", remote_url)
286
+
287
+ # Disable Jekyll processing (required for files/folders starting with _)
288
+ Path(evidence_project_build_path, ".nojekyll").touch()
289
+
290
+ # Configure git user
291
+ repo.config_writer().set_value(
292
+ "user", "name", "Dagster Evidence Deploy"
293
+ ).release()
294
+ repo.config_writer().set_value("user", "email", "dagster@localhost").release()
295
+
296
+ # Add all files
297
+ repo.index.add("*")
298
+ repo.index.add(".nojekyll")
299
+
300
+ # Commit
301
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H:%M:%S")
302
+ repo.index.commit(f"Deploy Evidence dashboard {timestamp}")
303
+
304
+ # Create and checkout branch
305
+ new_branch = repo.create_head(self.branch)
306
+ new_branch.checkout()
307
+
308
+ # Force push
309
+ origin.push(refspec=f"{self.branch}:{self.branch}", force=True)
310
+
311
+ context.log.info("Deployment complete!")
312
+
313
+
314
+ @beta
315
+ @public
316
+ class GithubPagesEvidenceProjectDeploymentArgs(dg.Model, dg.Resolvable):
317
+ """Arguments for deploying Evidence project to GitHub Pages.
318
+
319
+ Example:
320
+
321
+ .. code-block:: yaml
322
+
323
+ project_deployment:
324
+ type: github_pages
325
+ github_repo: owner/repo-name
326
+ branch: gh-pages
327
+
328
+ With explicit token (not recommended - prefer env var):
329
+
330
+ .. code-block:: yaml
331
+
332
+ project_deployment:
333
+ type: github_pages
334
+ github_repo: owner/repo-name
335
+ github_token: ghp_xxxxxxxxxxxx
336
+
337
+ Attributes:
338
+ type: Must be "github_pages" to use this deployment type.
339
+ github_repo: Repository in "owner/repo" format.
340
+ branch: Target branch (default: "gh-pages").
341
+ github_token: GitHub token. Falls back to GITHUB_TOKEN env var if not provided.
342
+ """
343
+
344
+ type: Literal["github_pages"] = Field(
345
+ default="github_pages",
346
+ description="Deployment type identifier.",
347
+ )
348
+ github_repo: str = Field(
349
+ ...,
350
+ description="GitHub repository in format 'owner/repo' (e.g., 'milicevica23/my-dashboard').",
351
+ )
352
+ branch: str = Field(
353
+ default="gh-pages",
354
+ description="Branch to deploy to (default: gh-pages).",
355
+ )
356
+ github_token: Optional[str] = Field(
357
+ default=None,
358
+ description="GitHub token for authentication. If not provided, falls back to GITHUB_TOKEN env var.",
359
+ )
360
+
361
+
362
+ @beta
363
+ @public
364
+ class EvidenceProjectNetlifyDeployment(BaseEvidenceProjectDeployment):
365
+ """Deploy Evidence project to Netlify.
366
+
367
+ **Coming Soon** - This deployment type is planned but not yet implemented.
368
+
369
+ This deployment type will push the built Evidence project to Netlify,
370
+ enabling automatic hosting and CDN distribution.
371
+
372
+ Planned Features:
373
+ - Deploy via Netlify CLI or API
374
+ - Support for deploy previews
375
+ - Environment variable configuration
376
+ - Build hooks integration
377
+
378
+ Attributes:
379
+ netlify_project_url: URL of the Netlify project.
380
+
381
+ Example:
382
+
383
+ .. code-block:: yaml
384
+
385
+ project_deployment:
386
+ type: netlify
387
+ netlify_project_url: https://app.netlify.com/sites/my-dashboard
388
+ netlify_auth_token: ${NETLIFY_AUTH_TOKEN}
389
+
390
+ Note:
391
+ If you need Netlify deployment support, please open an issue on GitHub
392
+ to help prioritize this feature. In the meantime, you can use the
393
+ ``custom`` deployment type with the Netlify CLI.
394
+ """
395
+
396
+ netlify_project_url: str
397
+
398
+ def deploy_evidence_project(
399
+ self,
400
+ evidence_project_build_path: str,
401
+ context: dg.AssetExecutionContext,
402
+ pipes_subprocess_client: dg.PipesSubprocessClient,
403
+ env: Optional[dict[str, str]] = None,
404
+ ) -> None:
405
+ raise NotImplementedError()
406
+
407
+
408
+ @beta
409
+ @public
410
+ class EvidenceProjectNetlifyDeploymentArgs(dg.Model, dg.Resolvable):
411
+ """Arguments for deploying Evidence project to Netlify.
412
+
413
+ **Coming Soon** - This deployment type is planned but not yet implemented.
414
+
415
+ Example:
416
+
417
+ .. code-block:: yaml
418
+
419
+ project_deployment:
420
+ type: netlify
421
+ netlify_project_url: https://app.netlify.com/sites/my-dashboard
422
+
423
+ Attributes:
424
+ type: Must be "netlify" to use this deployment type.
425
+ netlify_project_url: URL of the Netlify project.
426
+
427
+ Note:
428
+ Use the ``custom`` deployment type with Netlify CLI as a workaround:
429
+
430
+ .. code-block:: yaml
431
+
432
+ project_deployment:
433
+ type: custom
434
+ deploy_command: "netlify deploy --prod --dir=."
435
+ """
436
+
437
+ type: Literal["netlify"] = Field(
438
+ default="netlify",
439
+ description="Deployment type identifier.",
440
+ )
441
+ netlify_project_url: str = Field(
442
+ ...,
443
+ description="Netlify project URL.",
444
+ )
445
+
446
+
447
+ @public
448
+ def resolve_evidence_project_deployment(
449
+ context: dg.ResolutionContext, model: BaseModel
450
+ ) -> BaseEvidenceProjectDeployment:
451
+ """Resolve deployment configuration to a concrete deployment instance.
452
+
453
+ This function is used internally by the component resolution system
454
+ to convert YAML configuration into deployment instances.
455
+
456
+ Args:
457
+ context: The resolution context.
458
+ model: The parsed configuration model.
459
+
460
+ Returns:
461
+ A BaseEvidenceProjectDeployment instance.
462
+
463
+ Raises:
464
+ NotImplementedError: If an unknown deployment type is specified.
465
+ ValueError: If required configuration is missing (e.g., github_token).
466
+ """
467
+ # First, check which type we're dealing with
468
+ deployment_type = (
469
+ model.get("type", "custom")
470
+ if isinstance(model, dict)
471
+ else getattr(model, "type", "custom")
472
+ )
473
+
474
+ if deployment_type == "github_pages":
475
+ resolved = resolve_fields(
476
+ model=model,
477
+ resolved_cls=GithubPagesEvidenceProjectDeploymentArgs,
478
+ context=context,
479
+ )
480
+ # Resolve token: use provided value or fall back to env var
481
+ github_token = resolved.get("github_token") or os.environ.get("GITHUB_TOKEN")
482
+ if not github_token:
483
+ raise ValueError(
484
+ "GitHub token is required for github_pages deployment. "
485
+ "Provide 'github_token' in config or set GITHUB_TOKEN environment variable."
486
+ )
487
+ return GithubPagesEvidenceProjectDeployment(
488
+ github_repo=resolved["github_repo"],
489
+ branch=resolved.get(
490
+ "branch",
491
+ GithubPagesEvidenceProjectDeploymentArgs.model_fields["branch"].default,
492
+ ),
493
+ github_token=github_token,
494
+ )
495
+ elif deployment_type == "netlify":
496
+ resolved = resolve_fields(
497
+ model=model,
498
+ resolved_cls=EvidenceProjectNetlifyDeploymentArgs,
499
+ context=context,
500
+ )
501
+ return EvidenceProjectNetlifyDeployment(
502
+ netlify_project_url=resolved["netlify_project_url"],
503
+ )
504
+ elif deployment_type == "custom":
505
+ resolved = resolve_fields(
506
+ model=model,
507
+ resolved_cls=CustomEvidenceProjectDeploymentArgs,
508
+ context=context,
509
+ )
510
+ return CustomEvidenceProjectDeployment(
511
+ deploy_command=resolved["deploy_command"],
512
+ )
513
+ else:
514
+ raise NotImplementedError(f"Unknown deployment type: {deployment_type}")