Skip to content
Draft
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
89 changes: 87 additions & 2 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,32 @@ User Classes
content.metadata.Licensed
proj.ai.AIEnabled
proj.briefcase.Briefcase
proj.cicd.GitHubActions
proj.cicd.GitLabCI
proj.cicd.CircleCI
proj.cicd.Taskfile
proj.cicd.JustFile
proj.cicd.Tox
proj.conda_package.CondaRecipe
proj.conda_package.RattlerRecipe
proj.conda_project.CondaProject
proj.datapackage.DVCRepo
proj.datapackage.DataPackage
proj.dataworkflows.Dbt
proj.dataworkflows.Quarto
proj.dataworkflows.Prefect
proj.dataworkflows.Dagster
proj.dataworkflows.Kedro
proj.dataworkflows.Metaflow
proj.dataworkflows.MLFlow
proj.dataworkflows.Airflow
proj.dataworkflows.Snakemake
proj.dataworkflows.Nox
proj.documentation.Docusaurus
proj.documentation.MDBook
proj.documentation.MkDocs
proj.documentation.RTD
proj.documentation.Sphinx
proj.git.GitRepo
proj.golang.Golang
proj.helm.HelmChart
Expand All @@ -51,6 +70,21 @@ User Classes
proj.ide.NvidiaAIWorkbench
proj.ide.VSCode
proj.ide.Zed
proj.infra.DockerCompose
proj.infra.Terraform
proj.infra.Ansible
proj.infra.Pulumi
proj.infra.CDK
proj.infra.Earthfile
proj.infra.Nixpacks
proj.infra.Vagrant
proj.jsframeworks.NextJS
proj.jsframeworks.NuxtJS
proj.jsframeworks.SvelteKit
proj.jsframeworks.Vite
proj.jsframeworks.Pnpm
proj.jsframeworks.Bun
proj.jsframeworks.Deno
proj.node.JLabExtension
proj.node.Node
proj.node.Yarn
Expand All @@ -66,9 +100,12 @@ User Classes
proj.uv.Uv
proj.uv.UvScript
proj.webapp.Django
Comment thread
martindurant marked this conversation as resolved.
proj.webapp.Gradio
proj.webapp.Marimo
proj.webapp.Panel
proj.webapp.Shiny
proj.webapp.Streamlit
proj.workflows.MLFlow
proj.dataworkflows.MLFlow


.. autoclass:: projspec.artifact.container.Docker
Expand All @@ -78,13 +115,31 @@ User Classes
.. autoclass:: projspec.content.metadata.Licensed
.. autoclass:: projspec.proj.ai.AIEnabled
.. autoclass:: projspec.proj.briefcase.Briefcase
.. autoclass:: projspec.proj.cicd.GitHubActions
.. autoclass:: projspec.proj.cicd.GitLabCI
.. autoclass:: projspec.proj.cicd.CircleCI
.. autoclass:: projspec.proj.cicd.Taskfile
.. autoclass:: projspec.proj.cicd.JustFile
.. autoclass:: projspec.proj.cicd.Tox
.. autoclass:: projspec.proj.conda_package.CondaRecipe
.. autoclass:: projspec.proj.conda_package.RattlerRecipe
.. autoclass:: projspec.proj.conda_project.CondaProject
.. autoclass:: projspec.proj.datapackage.DVCRepo
.. autoclass:: projspec.proj.datapackage.DataPackage
.. autoclass:: projspec.proj.dataworkflows.Dbt
.. autoclass:: projspec.proj.dataworkflows.Quarto
.. autoclass:: projspec.proj.dataworkflows.Prefect
.. autoclass:: projspec.proj.dataworkflows.Dagster
.. autoclass:: projspec.proj.dataworkflows.Kedro
.. autoclass:: projspec.proj.dataworkflows.Metaflow
.. autoclass:: projspec.proj.dataworkflows.Airflow
.. autoclass:: projspec.proj.dataworkflows.Snakemake
.. autoclass:: projspec.proj.dataworkflows.Nox
.. autoclass:: projspec.proj.documentation.Docusaurus
.. autoclass:: projspec.proj.documentation.MDBook
.. autoclass:: projspec.proj.documentation.MkDocs
.. autoclass:: projspec.proj.documentation.RTD
.. autoclass:: projspec.proj.documentation.Sphinx
.. autoclass:: projspec.proj.git.GitRepo
.. autoclass:: projspec.proj.golang.Golang
.. autoclass:: projspec.proj.helm.HelmChart
Expand All @@ -94,6 +149,21 @@ User Classes
.. autoclass:: projspec.proj.ide.NvidiaAIWorkbench
.. autoclass:: projspec.proj.ide.VSCode
.. autoclass:: projspec.proj.ide.Zed
.. autoclass:: projspec.proj.infra.DockerCompose
.. autoclass:: projspec.proj.infra.Terraform
.. autoclass:: projspec.proj.infra.Ansible
.. autoclass:: projspec.proj.infra.Pulumi
.. autoclass:: projspec.proj.infra.CDK
.. autoclass:: projspec.proj.infra.Earthfile
.. autoclass:: projspec.proj.infra.Nixpacks
.. autoclass:: projspec.proj.infra.Vagrant
.. autoclass:: projspec.proj.jsframeworks.NextJS
.. autoclass:: projspec.proj.jsframeworks.NuxtJS
.. autoclass:: projspec.proj.jsframeworks.SvelteKit
.. autoclass:: projspec.proj.jsframeworks.Vite
.. autoclass:: projspec.proj.jsframeworks.Pnpm
.. autoclass:: projspec.proj.jsframeworks.Bun
.. autoclass:: projspec.proj.jsframeworks.Deno
.. autoclass:: projspec.proj.node.JLabExtension
.. autoclass:: projspec.proj.node.Node
.. autoclass:: projspec.proj.node.Yarn
Expand All @@ -109,9 +179,12 @@ User Classes
.. autoclass:: projspec.proj.uv.Uv
.. autoclass:: projspec.proj.uv.UvScript
.. autoclass:: projspec.proj.webapp.Django
.. autoclass:: projspec.proj.webapp.Gradio
.. autoclass:: projspec.proj.webapp.Marimo
.. autoclass:: projspec.proj.webapp.Panel
.. autoclass:: projspec.proj.webapp.Shiny
.. autoclass:: projspec.proj.webapp.Streamlit
.. autoclass:: projspec.proj.workflows.MLFlow
.. autoclass:: projspec.proj.dataworkflows.MLFlow


Contents
Expand All @@ -133,6 +206,9 @@ User Classes
~~~~~~~~~~~~

.. autosummary::
content.cicd.CIWorkflow
content.cicd.PipelineStage
content.cicd.ServiceDependency
content.data.IntakeSource
content.data.TabularData
content.data.DataResource
Expand All @@ -145,6 +221,9 @@ User Classes
content.package.PythonPackage
content.package.RustModule

.. autoclass:: projspec.content.cicd.CIWorkflow
.. autoclass:: projspec.content.cicd.PipelineStage
.. autoclass:: projspec.content.cicd.ServiceDependency
.. autoclass:: projspec.content.data.IntakeSource
.. autoclass:: projspec.content.data.DataResource
.. autoclass:: projspec.content.data.TabularData
Expand Down Expand Up @@ -182,6 +261,9 @@ User Classes
artifact.container.DockerRuntime
artifact.deployment.Deployment
artifact.deployment.HelmDeployment
artifact.infra.ComposeStack
artifact.infra.StaticSite
artifact.infra.TerraformPlan
artifact.installable.CondaPackage
artifact.installable.SystemInstallablePackage
artifact.installable.Wheel
Expand All @@ -199,6 +281,9 @@ User Classes
:members:
.. autoclass:: projspec.artifact.deployment.HelmDeployment
:members:
.. autoclass:: projspec.artifact.infra.ComposeStack
.. autoclass:: projspec.artifact.infra.StaticSite
.. autoclass:: projspec.artifact.infra.TerraformPlan
.. autoclass:: projspec.artifact.installable.CondaPackage
.. autoclass:: projspec.artifact.installable.SystemInstallablePackage
.. autoclass:: projspec.artifact.installable.Wheel
Expand Down
5 changes: 5 additions & 0 deletions src/projspec/artifact/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from projspec.artifact.base import BaseArtifact, FileArtifact
from projspec.artifact.container import DockerImage
from projspec.artifact.deployment import Deployment, HelmDeployment
from projspec.artifact.infra import ComposeStack, StaticSite, TerraformPlan
from projspec.artifact.installable import CondaPackage, Wheel
from projspec.artifact.linter import PreCommit
from projspec.artifact.process import Process
Expand All @@ -11,14 +12,18 @@
__all__ = [
"BaseArtifact",
"FileArtifact",
"ComposeStack",
"DockerImage",
"Deployment",
"HelmDeployment",
"StaticSite",
"TerraformPlan",
"CondaPackage",
"Wheel",
"Process",
"EnvPack",
"CondaEnv",
"VirtualEnv",
"LockFile",
"PreCommit",
]
71 changes: 71 additions & 0 deletions src/projspec/artifact/infra.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Infrastructure and deployment artifact types."""

from projspec.artifact.base import BaseArtifact, FileArtifact
from projspec.proj.base import Project
from projspec.utils import run_subprocess


class ComposeStack(BaseArtifact):
"""A multi-service stack managed by Docker Compose.

``make()`` runs ``docker compose up -d``
``clean()`` runs ``docker compose down``
``state`` is inferred by ``docker compose ps`` (checks for running services).
"""

def __init__(self, proj: Project, file: str = "docker-compose.yml", **kwargs):
self.compose_file = file
cmd = ["docker", "compose", "-f", file, "up", "-d"]
super().__init__(proj, cmd=cmd, **kwargs)

def _make(self, **kwargs):
run_subprocess(self.cmd, cwd=self.proj.url, output=False, **kwargs)

def clean(self):
run_subprocess(
["docker", "compose", "-f", self.compose_file, "down"],
cwd=self.proj.url,
output=False,
)

def _is_done(self) -> bool:
try:
result = run_subprocess(
["docker", "compose", "-f", self.compose_file, "ps", "-q"],
cwd=self.proj.url,
)
return bool(result.stdout.strip())
except Exception:
return False

def _is_clean(self) -> bool:
return not self._is_done()


class StaticSite(FileArtifact):
"""A static website produced by a build tool (MkDocs, Sphinx, Docusaurus, Quarto, etc.).

``fn`` should be the glob pattern for the output index file, e.g.
``<proj>/site/index.html``.
"""

pass


class TerraformPlan(FileArtifact):
"""A saved Terraform execution plan file (``terraform plan -out plan.tfplan``).

``make()`` runs ``terraform plan -out plan.tfplan``
``clean()`` deletes the plan file
"""

def __init__(self, proj: Project, plan_file: str = "plan.tfplan", **kwargs):
fn = f"{proj.url}/{plan_file}"
cmd = ["terraform", "plan", "-out", plan_file]
super().__init__(proj, fn=fn, cmd=cmd, **kwargs)

def clean(self):
try:
self.proj.fs.rm(self.fn)
except FileNotFoundError:
pass
5 changes: 5 additions & 0 deletions src/projspec/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def defaults():
"scan_max_size": 5 * 2**10,
"remote_artifact_status": False,
"capture_artifact_output": True,
"preferred_install_methods": ["conda", "pip"],
}


Expand All @@ -33,6 +34,10 @@ def defaults():
"if True, capture and enqueue output from spawned Process artifacts. "
"Otherwise, output appears on stdout/err."
),
"preferred_install_methods": (
"ordered list of preferred installer names for install_tool(), "
"e.g. ['uv', 'conda', 'pip']. Empty list uses the platform default."
),
}


Expand Down
10 changes: 10 additions & 0 deletions src/projspec/content/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"""Contents classes - information declared in project specs"""

from projspec.content.base import BaseContent
from projspec.content.cicd import (
CIWorkflow,
GithubAction,
PipelineStage,
ServiceDependency,
)
from projspec.content.data import TabularData, IntakeSource
from projspec.content.env_var import EnvironmentVariables
from projspec.content.environment import Environment, Stack, Precision
Expand All @@ -12,6 +18,10 @@

__all__ = [
"BaseContent",
"CIWorkflow",
"GithubAction",
"PipelineStage",
"ServiceDependency",
"TabularData",
"IntakeSource",
"EnvironmentVariables",
Expand Down
44 changes: 37 additions & 7 deletions src/projspec/content/cicd.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,45 @@
"""Run definitions that are part of code productionalisation"""

from dataclasses import dataclass, field

from projspec.content import BaseContent


class GithubAction(BaseContent):
"""A run prescription that runs in github on push/merge"""
@dataclass
class CIWorkflow(BaseContent):
"""A CI/CD workflow or pipeline definition.

Captures the name, triggering events, and high-level job/stage names from
CI configuration files (GitHub Actions, GitLab CI, CircleCI, etc.).
"""

name: str = ""
triggers: list = field(default_factory=list)
jobs: list = field(default_factory=list)
provider: str = "" # e.g. "github", "gitlab", "circleci"


# Keep legacy stub under old name for backwards compatibility
GithubAction = CIWorkflow


@dataclass
class PipelineStage(BaseContent):
"""A named stage or step in a data/ML/workflow pipeline."""

name: str = ""
cmd: list = field(default_factory=list)
depends_on: list = field(default_factory=list)


# TODO: we probably want to extract out the jobs and runs, maybe the steps.
# It may be interesting to provide links to the browser or API to view
# details.
...
@dataclass
class ServiceDependency(BaseContent):
"""An external service that a project depends on at runtime.

Typically exposed via an open TCP port, e.g., as used in container orchestration.
"""

# TODO: there are many of these, but we don't extract much information from them
name: str = ""
service_type: str = "" # e.g. "postgres", "redis", "kafka"
version: str = ""
image: str = ""
2 changes: 2 additions & 0 deletions src/projspec/content/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def __repr__(self) -> str:

def _repr_html_(self) -> str:
"""Jupyter rich display — returns cached HTML, rendering on first call."""
# TODO: this is probably not what we want jupyter to dysplay, but it's
# convenient for now.
if self._html is None:
from projspec.content.data_html import repr_html

Expand Down
Loading
Loading