deployml-core 0.0.59__tar.gz → 0.0.60__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.
- deployml_core-0.0.60/PKG-INFO +117 -0
- deployml_core-0.0.60/README.md +92 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/pyproject.toml +1 -1
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/cli/cli.py +35 -13
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/main.py +76 -36
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/requirements.txt +3 -1
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/mlflow/Dockerfile +1 -1
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2 +1 -0
- deployml_core-0.0.60/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json +12 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/main.tf +5 -1
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/variables.tf +7 -1
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/main.tf +4 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/helpers.py +4 -10
- deployml_core-0.0.59/PKG-INFO +0 -111
- deployml_core-0.0.59/README.md +0 -87
- deployml_core-0.0.59/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json +0 -7
- {deployml_core-0.0.59 → deployml_core-0.0.60}/LICENSE +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/api.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/cli/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/diagnostics/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/diagnostics/doctor.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/.dockerignore +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/fastapi/Dockerfile +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/Dockerfile +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/README.md +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/entrypoint.sh +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/mlflow/requirements.txt +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/cloud_provider.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/enum/deployment_type.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/deployment.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/display.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/docker.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/stack.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook/urls.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/notebook.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/base_main.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/main.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/terraform.tfvars.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/variables.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/main.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/terraform.tfvars.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/variables.tf.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/deployment.yaml.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/mlflow-deployment.yaml.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/mlflow-service.yaml.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/kubernetes_local/service.yaml.j2 +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/drift_metrics.json +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/ground_truth.json +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/predictions.json +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/bigquery/cloud/gcp/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cloud_sql_postgres/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/cron/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/explainability_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fairness_monitoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/fastapi/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/feast_env.tpl +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/feast/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/grafana/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/mlflow/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/offline_scoring/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/main.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloud_function/requirements.txt +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/cloudbuild.yaml +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/teardown_script.sh +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/teardown/cloud/gcp/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/outputs.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_run/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/main.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/terraform/modules/wandb/cloud/gcp/cloud_vm/variables.tf +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/__init__.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/banner.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/constants.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/infracost.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/kubernetes_gke.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/kubernetes_local.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/menu.py +0 -0
- {deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/utils/teardown.py +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deployml-core
|
|
3
|
+
Version: 0.0.60
|
|
4
|
+
Summary: Infra for academia
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Drew Hoang
|
|
7
|
+
Author-email: codentell@gmail.com
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Dist: google-cloud-storage (>=3.1.1,<4.0.0)
|
|
15
|
+
Requires-Dist: ipython (>=9.0.0)
|
|
16
|
+
Requires-Dist: jinja2
|
|
17
|
+
Requires-Dist: jupyter (>=1.0.0)
|
|
18
|
+
Requires-Dist: pandas (>=2.2.1)
|
|
19
|
+
Requires-Dist: pyyaml
|
|
20
|
+
Requires-Dist: requests (>=2.28.0)
|
|
21
|
+
Requires-Dist: rich
|
|
22
|
+
Requires-Dist: typer
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# deployml
|
|
26
|
+
|
|
27
|
+
A CLI tool that deploys a production MLOps stack on GCP with a single command. Built for academic ML courses so students can focus on building models, not infrastructure.
|
|
28
|
+
|
|
29
|
+
## What you get
|
|
30
|
+
|
|
31
|
+
- **MLflow** — experiment tracking, artifact storage, and model registry (backed by Cloud SQL Postgres + GCS)
|
|
32
|
+
- **FastAPI** — model serving endpoint that loads the latest registered model from MLflow automatically
|
|
33
|
+
- **Grafana** — monitoring dashboard connected to your metrics database
|
|
34
|
+
- **BigQuery** — `mlops` dataset with tables for features, predictions, ground truth, and drift metrics
|
|
35
|
+
|
|
36
|
+
All running on GCP Cloud Run — no servers to manage, scales to zero when idle.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
**1. Install**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install deployml-core
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**2. Initialize your GCP project** (enables APIs, creates Artifact Registry)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
deployml init --provider gcp --project-id YOUR_PROJECT_ID
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**3. Configure**
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cp config.example.yaml config.yaml
|
|
56
|
+
# Edit config.yaml and set your project_id
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**4. Build images**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
deployml build-images
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**5. Deploy**
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
deployml deploy --verbose
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
First deploy takes ~20 minutes (Cloud SQL provisioning). Subsequent deploys are 1–2 minutes.
|
|
72
|
+
|
|
73
|
+
**6. Get your URLs**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
deployml get-urls
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Prints service URLs and writes a `.env` file with `MLFLOW_URL`, `FASTAPI_URL`, `GRAFANA_URL`, `BIGQUERY_PROJECT`, and `BIGQUERY_DATASET`.
|
|
80
|
+
|
|
81
|
+
## End-to-End Example
|
|
82
|
+
|
|
83
|
+
Once deployed, the `example/` directory walks through a complete MLOps workflow using a synthetic housing price dataset:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install mlflow scikit-learn pandas numpy google-cloud-bigquery db-dtypes python-dotenv requests
|
|
87
|
+
|
|
88
|
+
python example/scripts/01_load_training_data.py # load 500 rows into BigQuery
|
|
89
|
+
python example/scripts/02_train_model.py # train RandomForest, log to MLflow
|
|
90
|
+
python example/scripts/03_register_model.py # register model as Production
|
|
91
|
+
python example/scripts/04_make_predictions.py # serve 50 predictions via FastAPI
|
|
92
|
+
python example/scripts/05_generate_ground_truth.py # simulate actual outcomes
|
|
93
|
+
python example/scripts/06_compute_drift_metrics.py # compute feature drift + MAE
|
|
94
|
+
python example/scripts/07_setup_grafana.py # provision monitoring dashboard
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
See [example/README.md](example/README.md) for details.
|
|
98
|
+
|
|
99
|
+
## Teardown
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
deployml destroy
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Deletes all Cloud Run services, Cloud SQL, GCS bucket, and BigQuery dataset. Does not delete Artifact Registry images or the GCP project.
|
|
106
|
+
|
|
107
|
+
## Full Tutorial
|
|
108
|
+
|
|
109
|
+
See [docs/tutorials/gcp-cloud-run.md](docs/tutorials/gcp-cloud-run.md) for a step-by-step walkthrough.
|
|
110
|
+
|
|
111
|
+
## Requirements
|
|
112
|
+
|
|
113
|
+
- Python 3.10+
|
|
114
|
+
- `gcloud` CLI (authenticated)
|
|
115
|
+
- Docker (running)
|
|
116
|
+
- Terraform
|
|
117
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# deployml
|
|
2
|
+
|
|
3
|
+
A CLI tool that deploys a production MLOps stack on GCP with a single command. Built for academic ML courses so students can focus on building models, not infrastructure.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
- **MLflow** — experiment tracking, artifact storage, and model registry (backed by Cloud SQL Postgres + GCS)
|
|
8
|
+
- **FastAPI** — model serving endpoint that loads the latest registered model from MLflow automatically
|
|
9
|
+
- **Grafana** — monitoring dashboard connected to your metrics database
|
|
10
|
+
- **BigQuery** — `mlops` dataset with tables for features, predictions, ground truth, and drift metrics
|
|
11
|
+
|
|
12
|
+
All running on GCP Cloud Run — no servers to manage, scales to zero when idle.
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
**1. Install**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install deployml-core
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**2. Initialize your GCP project** (enables APIs, creates Artifact Registry)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
deployml init --provider gcp --project-id YOUR_PROJECT_ID
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**3. Configure**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
cp config.example.yaml config.yaml
|
|
32
|
+
# Edit config.yaml and set your project_id
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**4. Build images**
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
deployml build-images
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**5. Deploy**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
deployml deploy --verbose
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
First deploy takes ~20 minutes (Cloud SQL provisioning). Subsequent deploys are 1–2 minutes.
|
|
48
|
+
|
|
49
|
+
**6. Get your URLs**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
deployml get-urls
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Prints service URLs and writes a `.env` file with `MLFLOW_URL`, `FASTAPI_URL`, `GRAFANA_URL`, `BIGQUERY_PROJECT`, and `BIGQUERY_DATASET`.
|
|
56
|
+
|
|
57
|
+
## End-to-End Example
|
|
58
|
+
|
|
59
|
+
Once deployed, the `example/` directory walks through a complete MLOps workflow using a synthetic housing price dataset:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install mlflow scikit-learn pandas numpy google-cloud-bigquery db-dtypes python-dotenv requests
|
|
63
|
+
|
|
64
|
+
python example/scripts/01_load_training_data.py # load 500 rows into BigQuery
|
|
65
|
+
python example/scripts/02_train_model.py # train RandomForest, log to MLflow
|
|
66
|
+
python example/scripts/03_register_model.py # register model as Production
|
|
67
|
+
python example/scripts/04_make_predictions.py # serve 50 predictions via FastAPI
|
|
68
|
+
python example/scripts/05_generate_ground_truth.py # simulate actual outcomes
|
|
69
|
+
python example/scripts/06_compute_drift_metrics.py # compute feature drift + MAE
|
|
70
|
+
python example/scripts/07_setup_grafana.py # provision monitoring dashboard
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [example/README.md](example/README.md) for details.
|
|
74
|
+
|
|
75
|
+
## Teardown
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
deployml destroy
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Deletes all Cloud Run services, Cloud SQL, GCS bucket, and BigQuery dataset. Does not delete Artifact Registry images or the GCP project.
|
|
82
|
+
|
|
83
|
+
## Full Tutorial
|
|
84
|
+
|
|
85
|
+
See [docs/tutorials/gcp-cloud-run.md](docs/tutorials/gcp-cloud-run.md) for a step-by-step walkthrough.
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
- Python 3.10+
|
|
90
|
+
- `gcloud` CLI (authenticated)
|
|
91
|
+
- Docker (running)
|
|
92
|
+
- Terraform
|
|
@@ -4,6 +4,7 @@ import typer
|
|
|
4
4
|
import shutil
|
|
5
5
|
import subprocess
|
|
6
6
|
import re
|
|
7
|
+
import importlib.resources as pkg_resources
|
|
7
8
|
from deployml.utils.banner import display_banner
|
|
8
9
|
from deployml.utils.menu import prompt, show_menu
|
|
9
10
|
from deployml.utils.constants import (
|
|
@@ -780,7 +781,7 @@ def terraform(
|
|
|
780
781
|
@cli.command()
|
|
781
782
|
def deploy(
|
|
782
783
|
config_path: Path = typer.Option(
|
|
783
|
-
|
|
784
|
+
Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
|
|
784
785
|
),
|
|
785
786
|
yes: bool = typer.Option(
|
|
786
787
|
False, "--yes", "-y", help="Skip confirmation prompts and deploy"
|
|
@@ -1456,7 +1457,7 @@ def deploy(
|
|
|
1456
1457
|
@cli.command()
|
|
1457
1458
|
def get_urls(
|
|
1458
1459
|
config_path: Path = typer.Option(
|
|
1459
|
-
|
|
1460
|
+
Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
|
|
1460
1461
|
),
|
|
1461
1462
|
env_path: Path = typer.Option(
|
|
1462
1463
|
Path(".env"), "--env-path", help="Path to write .env file"
|
|
@@ -1471,6 +1472,7 @@ def get_urls(
|
|
|
1471
1472
|
|
|
1472
1473
|
config = yaml.safe_load(config_path.read_text())
|
|
1473
1474
|
workspace_name = config.get("name") or "development"
|
|
1475
|
+
project_id = config.get("provider", {}).get("project_id", "")
|
|
1474
1476
|
terraform_dir = Path.cwd() / ".deployml" / workspace_name / "terraform"
|
|
1475
1477
|
|
|
1476
1478
|
if not terraform_dir.exists():
|
|
@@ -1527,6 +1529,10 @@ def get_urls(
|
|
|
1527
1529
|
else:
|
|
1528
1530
|
typer.echo(f" {key}: {output_val}")
|
|
1529
1531
|
|
|
1532
|
+
if project_id:
|
|
1533
|
+
env_lines.append(f"BIGQUERY_PROJECT={project_id}")
|
|
1534
|
+
typer.echo(f" bigquery_project: {project_id}")
|
|
1535
|
+
|
|
1530
1536
|
env_path.write_text("\n".join(env_lines) + "\n")
|
|
1531
1537
|
typer.echo(f"\n .env written to {env_path.resolve()}")
|
|
1532
1538
|
|
|
@@ -1534,7 +1540,7 @@ def get_urls(
|
|
|
1534
1540
|
@cli.command()
|
|
1535
1541
|
def destroy(
|
|
1536
1542
|
config_path: Path = typer.Option(
|
|
1537
|
-
|
|
1543
|
+
Path("config.yaml"), "--config-path", "-c", help="Path to YAML config file"
|
|
1538
1544
|
),
|
|
1539
1545
|
workspace: Optional[str] = typer.Option(
|
|
1540
1546
|
None, "--workspace", help="Override workspace name from config"
|
|
@@ -2434,15 +2440,17 @@ def gke_apply(
|
|
|
2434
2440
|
|
|
2435
2441
|
@cli.command("build-images")
|
|
2436
2442
|
def build_images_command(
|
|
2437
|
-
|
|
2438
|
-
|
|
2443
|
+
config_path: Path = typer.Option(
|
|
2444
|
+
Path("config.yaml"),
|
|
2445
|
+
"--config-path",
|
|
2446
|
+
"-c",
|
|
2447
|
+
help="Path to YAML config file. Used to infer project and region automatically.",
|
|
2448
|
+
),
|
|
2449
|
+
docker_root: Optional[Path] = typer.Option(
|
|
2450
|
+
None,
|
|
2439
2451
|
"--docker-root",
|
|
2440
2452
|
"-d",
|
|
2441
|
-
help="Path to folder containing subfolders with Dockerfiles.",
|
|
2442
|
-
exists=True,
|
|
2443
|
-
file_okay=False,
|
|
2444
|
-
dir_okay=True,
|
|
2445
|
-
resolve_path=True,
|
|
2453
|
+
help="Path to folder containing subfolders with Dockerfiles. Defaults to the built-in deployml docker directory.",
|
|
2446
2454
|
),
|
|
2447
2455
|
gcp_project: Optional[str] = typer.Option(
|
|
2448
2456
|
None,
|
|
@@ -2450,10 +2458,10 @@ def build_images_command(
|
|
|
2450
2458
|
"-p",
|
|
2451
2459
|
help="If provided, images will be built using Cloud Build in this GCP project.",
|
|
2452
2460
|
),
|
|
2453
|
-
region: str = typer.Option(
|
|
2454
|
-
|
|
2461
|
+
region: Optional[str] = typer.Option(
|
|
2462
|
+
None,
|
|
2455
2463
|
"--region",
|
|
2456
|
-
help="GCP region for Artifact Registry.",
|
|
2464
|
+
help="GCP region for Artifact Registry. Inferred from config if not set.",
|
|
2457
2465
|
),
|
|
2458
2466
|
repository: str = typer.Option(
|
|
2459
2467
|
"mlops-images",
|
|
@@ -2489,6 +2497,16 @@ def build_images_command(
|
|
|
2489
2497
|
--dry-run prints commands without executing them.
|
|
2490
2498
|
"""
|
|
2491
2499
|
|
|
2500
|
+
if config_path and config_path.exists():
|
|
2501
|
+
config = yaml.safe_load(config_path.read_text())
|
|
2502
|
+
if not gcp_project:
|
|
2503
|
+
gcp_project = config.get("provider", {}).get("project_id")
|
|
2504
|
+
if not region:
|
|
2505
|
+
region = config.get("provider", {}).get("region", "us-central1")
|
|
2506
|
+
|
|
2507
|
+
if not region:
|
|
2508
|
+
region = "us-central1"
|
|
2509
|
+
|
|
2492
2510
|
if create_repo and not gcp_project:
|
|
2493
2511
|
typer.secho(
|
|
2494
2512
|
"--create-repo can only be used with --gcp-project.",
|
|
@@ -2496,6 +2514,10 @@ def build_images_command(
|
|
|
2496
2514
|
)
|
|
2497
2515
|
raise typer.Exit(code=1)
|
|
2498
2516
|
|
|
2517
|
+
if docker_root is None:
|
|
2518
|
+
docker_root = Path(str(pkg_resources.files("deployml") / "docker"))
|
|
2519
|
+
typer.echo(f"Using built-in docker directory: {docker_root}")
|
|
2520
|
+
|
|
2499
2521
|
try:
|
|
2500
2522
|
build_images(
|
|
2501
2523
|
docker_root=docker_root,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from fastapi import FastAPI
|
|
1
|
+
from fastapi import FastAPI
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
from pydantic import BaseModel
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Dict, Optional
|
|
5
5
|
import os
|
|
6
|
-
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime, timezone
|
|
7
8
|
import pandas as pd
|
|
8
9
|
import mlflow
|
|
9
10
|
import mlflow.pyfunc
|
|
@@ -14,21 +15,28 @@ app = FastAPI(
|
|
|
14
15
|
version="1.0.0"
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
# MLflow configuration — override via MLFLOW_TRACKING_URI env var
|
|
18
18
|
MLFLOW_TRACKING_URI = os.getenv("MLFLOW_TRACKING_URI", "http://mlflow-service:5000")
|
|
19
19
|
MODEL_NAME = os.getenv("MODEL_NAME", "HousingPriceModel")
|
|
20
|
+
BIGQUERY_PROJECT = os.getenv("GOOGLE_CLOUD_PROJECT", "")
|
|
21
|
+
BIGQUERY_DATASET = os.getenv("BIGQUERY_DATASET", "mlops")
|
|
20
22
|
|
|
21
|
-
# Global model variable
|
|
22
23
|
model = None
|
|
24
|
+
bq_client = None
|
|
25
|
+
|
|
26
|
+
FEATURE_ORDER = ['bedrooms', 'bathrooms', 'area_sqft', 'lot_size', 'year_built', 'city', 'state']
|
|
27
|
+
|
|
23
28
|
|
|
24
|
-
# Pydantic models
|
|
25
29
|
class PredictionRequest(BaseModel):
|
|
30
|
+
entity_id: Optional[str] = None
|
|
26
31
|
features: Dict[str, float]
|
|
27
32
|
|
|
33
|
+
|
|
28
34
|
class PredictionResponse(BaseModel):
|
|
29
35
|
prediction: float
|
|
30
36
|
timestamp: str
|
|
31
37
|
model_used: Optional[str] = None
|
|
38
|
+
entity_id: Optional[str] = None
|
|
39
|
+
|
|
32
40
|
|
|
33
41
|
class HealthResponse(BaseModel):
|
|
34
42
|
status: str
|
|
@@ -37,10 +45,9 @@ class HealthResponse(BaseModel):
|
|
|
37
45
|
mlflow_connected: bool
|
|
38
46
|
model_loaded: bool
|
|
39
47
|
|
|
48
|
+
|
|
40
49
|
def load_model_from_mlflow():
|
|
41
|
-
"""Load model from MLflow Model Registry"""
|
|
42
50
|
global model
|
|
43
|
-
|
|
44
51
|
try:
|
|
45
52
|
mlflow.set_tracking_uri(MLFLOW_TRACKING_URI)
|
|
46
53
|
model = mlflow.pyfunc.load_model(f"models:/{MODEL_NAME}/Production")
|
|
@@ -51,32 +58,59 @@ def load_model_from_mlflow():
|
|
|
51
58
|
model = None
|
|
52
59
|
return False
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
|
|
62
|
+
def init_bigquery():
|
|
63
|
+
global bq_client
|
|
64
|
+
if not BIGQUERY_PROJECT:
|
|
65
|
+
return
|
|
66
|
+
try:
|
|
67
|
+
from google.cloud import bigquery
|
|
68
|
+
bq_client = bigquery.Client(project=BIGQUERY_PROJECT)
|
|
69
|
+
print(f"✓ BigQuery client initialized for project {BIGQUERY_PROJECT}")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"⚠️ Could not initialize BigQuery client: {e}")
|
|
72
|
+
bq_client = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def log_prediction_to_bigquery(entity_id: str, predicted_value: float, model_version: str):
|
|
76
|
+
if bq_client is None:
|
|
77
|
+
return
|
|
78
|
+
try:
|
|
79
|
+
table_id = f"{BIGQUERY_PROJECT}.{BIGQUERY_DATASET}.predictions"
|
|
80
|
+
row = {
|
|
81
|
+
"prediction_id": str(uuid.uuid4()),
|
|
82
|
+
"entity_id": entity_id,
|
|
83
|
+
"prediction_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
84
|
+
"predicted_value": predicted_value,
|
|
85
|
+
"model_version": model_version,
|
|
86
|
+
"feature_snapshot_timestamp": datetime.now(timezone.utc).isoformat(),
|
|
87
|
+
}
|
|
88
|
+
bq_client.insert_rows_json(table_id, [row])
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"⚠️ Could not log prediction to BigQuery: {e}")
|
|
91
|
+
|
|
92
|
+
|
|
55
93
|
@app.on_event("startup")
|
|
56
94
|
async def startup_event():
|
|
57
|
-
"""Load MLflow model on startup"""
|
|
58
95
|
load_model_from_mlflow()
|
|
96
|
+
init_bigquery()
|
|
97
|
+
|
|
59
98
|
|
|
60
99
|
@app.get("/")
|
|
61
100
|
async def root():
|
|
62
|
-
"""Root endpoint"""
|
|
63
101
|
return {
|
|
64
102
|
"service": "FastAPI Demo",
|
|
65
103
|
"version": "1.0.0",
|
|
66
104
|
"model_loaded": model is not None,
|
|
67
105
|
"model_name": MODEL_NAME,
|
|
68
106
|
"mlflow_uri": MLFLOW_TRACKING_URI,
|
|
69
|
-
"endpoints": {
|
|
70
|
-
"health": "/health",
|
|
71
|
-
"predict": "/predict",
|
|
72
|
-
"docs": "/docs"
|
|
73
|
-
}
|
|
107
|
+
"endpoints": {"health": "/health", "predict": "/predict", "docs": "/docs"}
|
|
74
108
|
}
|
|
75
109
|
|
|
110
|
+
|
|
76
111
|
@app.get("/health", response_model=HealthResponse)
|
|
77
112
|
async def health():
|
|
78
|
-
""
|
|
79
|
-
port = int(os.getenv("FASTAPI_PORT", "8000"))
|
|
113
|
+
port = int(os.getenv("PORT", "8000"))
|
|
80
114
|
return HealthResponse(
|
|
81
115
|
status="healthy",
|
|
82
116
|
timestamp=datetime.now().isoformat(),
|
|
@@ -85,43 +119,49 @@ async def health():
|
|
|
85
119
|
model_loaded=model is not None
|
|
86
120
|
)
|
|
87
121
|
|
|
122
|
+
|
|
88
123
|
@app.post("/predict", response_model=PredictionResponse)
|
|
89
124
|
async def predict(request: PredictionRequest):
|
|
90
|
-
"""Prediction endpoint using MLflow model"""
|
|
91
125
|
global model
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
|
|
127
|
+
entity_id = request.entity_id or str(uuid.uuid4())
|
|
128
|
+
|
|
129
|
+
if model is None:
|
|
130
|
+
load_model_from_mlflow()
|
|
131
|
+
|
|
94
132
|
if model is None:
|
|
95
133
|
return PredictionResponse(
|
|
96
134
|
prediction=-1.0,
|
|
97
135
|
timestamp=datetime.now().isoformat(),
|
|
98
|
-
model_used=None
|
|
136
|
+
model_used=None,
|
|
137
|
+
entity_id=entity_id
|
|
99
138
|
)
|
|
100
|
-
|
|
139
|
+
|
|
101
140
|
try:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
feature_values = [request.features.get(f, 0.0) for f in feature_order]
|
|
106
|
-
X = pd.DataFrame([feature_values], columns=feature_order)
|
|
107
|
-
|
|
141
|
+
feature_values = [request.features.get(f, 0.0) for f in FEATURE_ORDER]
|
|
142
|
+
X = pd.DataFrame([feature_values], columns=FEATURE_ORDER)
|
|
143
|
+
|
|
108
144
|
prediction = float(model.predict(X)[0])
|
|
109
|
-
|
|
110
|
-
|
|
145
|
+
model_version = f"MLflow: {MODEL_NAME}"
|
|
146
|
+
|
|
147
|
+
log_prediction_to_bigquery(entity_id, prediction, model_version)
|
|
148
|
+
|
|
111
149
|
return PredictionResponse(
|
|
112
150
|
prediction=prediction,
|
|
113
151
|
timestamp=datetime.now().isoformat(),
|
|
114
|
-
model_used=
|
|
152
|
+
model_used=model_version,
|
|
153
|
+
entity_id=entity_id
|
|
115
154
|
)
|
|
116
155
|
except Exception as e:
|
|
117
|
-
# On any error, return -1
|
|
118
156
|
return PredictionResponse(
|
|
119
157
|
prediction=-1.0,
|
|
120
158
|
timestamp=datetime.now().isoformat(),
|
|
121
|
-
model_used=None
|
|
159
|
+
model_used=None,
|
|
160
|
+
entity_id=entity_id
|
|
122
161
|
)
|
|
123
162
|
|
|
163
|
+
|
|
124
164
|
if __name__ == "__main__":
|
|
125
165
|
import uvicorn
|
|
126
|
-
port = int(os.getenv("
|
|
127
|
-
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
166
|
+
port = int(os.getenv("PORT", "8000"))
|
|
167
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
@@ -36,5 +36,5 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
|
|
36
36
|
|
|
37
37
|
# Default command - MLflow server
|
|
38
38
|
# Uses PORT env var so it works on Cloud Run (PORT=8080) and locally (PORT=5000)
|
|
39
|
-
CMD mlflow server --host 0.0.0.0 --port ${PORT:-5000} --dev
|
|
39
|
+
CMD mlflow server --host 0.0.0.0 --port ${PORT:-5000} --dev --allowed-hosts '*'
|
|
40
40
|
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/mlflow_main.tf.j2
RENAMED
|
@@ -151,6 +151,7 @@ module "{{ stage_name }}_{{ tool.name }}" {
|
|
|
151
151
|
{% endfor %}
|
|
152
152
|
feast_service_url = {{ 'length(module.feature_store_feast) > 0 ? module.feature_store_feast[0].service_url : ""' if feast_exists.value else '""' }}
|
|
153
153
|
enable_feast_connection = {{ 'true' if feast_exists.value else 'false' }}
|
|
154
|
+
bigquery_dataset = "mlops"
|
|
154
155
|
{% if feast_exists.value %}
|
|
155
156
|
depends_on = [module.feature_store_feast]
|
|
156
157
|
{% endif %}
|
deployml_core-0.0.60/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[
|
|
2
|
+
{"name": "entity_id", "type": "STRING", "mode": "REQUIRED"},
|
|
3
|
+
{"name": "event_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
|
|
4
|
+
{"name": "bedrooms", "type": "FLOAT", "mode": "NULLABLE"},
|
|
5
|
+
{"name": "bathrooms", "type": "FLOAT", "mode": "NULLABLE"},
|
|
6
|
+
{"name": "area_sqft", "type": "FLOAT", "mode": "NULLABLE"},
|
|
7
|
+
{"name": "lot_size", "type": "FLOAT", "mode": "NULLABLE"},
|
|
8
|
+
{"name": "year_built", "type": "FLOAT", "mode": "NULLABLE"},
|
|
9
|
+
{"name": "city", "type": "FLOAT", "mode": "NULLABLE"},
|
|
10
|
+
{"name": "state", "type": "FLOAT", "mode": "NULLABLE"},
|
|
11
|
+
{"name": "price", "type": "FLOAT", "mode": "NULLABLE"}
|
|
12
|
+
]
|
|
@@ -51,6 +51,10 @@ resource "google_cloud_run_service" "fastapi" {
|
|
|
51
51
|
name = "GOOGLE_CLOUD_PROJECT"
|
|
52
52
|
value = var.project_id
|
|
53
53
|
}
|
|
54
|
+
env {
|
|
55
|
+
name = "BIGQUERY_DATASET"
|
|
56
|
+
value = var.bigquery_dataset
|
|
57
|
+
}
|
|
54
58
|
resources {
|
|
55
59
|
limits = {
|
|
56
60
|
cpu = var.cpu_limit
|
|
@@ -116,7 +120,7 @@ resource "google_project_iam_member" "fastapi_storage_object_admin" {
|
|
|
116
120
|
|
|
117
121
|
resource "google_project_iam_member" "fastapi_bigquery_user" {
|
|
118
122
|
project = var.project_id
|
|
119
|
-
role = "roles/bigquery.
|
|
123
|
+
role = "roles/bigquery.dataEditor"
|
|
120
124
|
member = "serviceAccount:${data.google_project.current.number}-compute@developer.gserviceaccount.com"
|
|
121
125
|
}
|
|
122
126
|
|
|
@@ -93,4 +93,10 @@ variable "mlflow_bucket_exists" {
|
|
|
93
93
|
type = bool
|
|
94
94
|
description = "Whether the MLflow artifact bucket already exists"
|
|
95
95
|
default = false
|
|
96
|
-
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
variable "bigquery_dataset" {
|
|
99
|
+
type = string
|
|
100
|
+
description = "BigQuery dataset name for storing predictions"
|
|
101
|
+
default = "mlops"
|
|
102
|
+
}
|
|
@@ -196,22 +196,16 @@ def generate_unique_bucket_name(base_name: str, project_id: str) -> str:
|
|
|
196
196
|
|
|
197
197
|
def generate_bucket_name(project_id: str) -> str:
|
|
198
198
|
"""
|
|
199
|
-
Generate a
|
|
199
|
+
Generate a stable, deterministic GCS bucket name for the given project.
|
|
200
|
+
Uses project_id (globally unique on GCP) so the name is consistent across deploys.
|
|
200
201
|
|
|
201
202
|
Args:
|
|
202
203
|
project_id (str): The GCP project ID.
|
|
203
204
|
|
|
204
205
|
Returns:
|
|
205
|
-
str: A
|
|
206
|
+
str: A deterministic bucket name.
|
|
206
207
|
"""
|
|
207
|
-
|
|
208
|
-
word = random.choice(ANIMAL_NAMES)
|
|
209
|
-
else:
|
|
210
|
-
word = random.choice(FALLBACK_WORDS)
|
|
211
|
-
suffix = "".join(
|
|
212
|
-
random.choices(string.ascii_lowercase + string.digits, k=4)
|
|
213
|
-
)
|
|
214
|
-
return f"{word}-bucket-{project_id}-{suffix}".replace("_", "-")
|
|
208
|
+
return f"mlflow-artifacts-{project_id}".replace("_", "-")
|
|
215
209
|
|
|
216
210
|
|
|
217
211
|
def estimate_terraform_time(plan_output: str, operation: str = "apply") -> str:
|
deployml_core-0.0.59/PKG-INFO
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: deployml-core
|
|
3
|
-
Version: 0.0.59
|
|
4
|
-
Summary: Infra for academia
|
|
5
|
-
License-File: LICENSE
|
|
6
|
-
Author: Drew Hoang
|
|
7
|
-
Author-email: codentell@gmail.com
|
|
8
|
-
Requires-Python: >=3.11
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Requires-Dist: google-cloud-storage (>=3.1.1,<4.0.0)
|
|
15
|
-
Requires-Dist: ipython (>=9.0.0)
|
|
16
|
-
Requires-Dist: jinja2
|
|
17
|
-
Requires-Dist: jupyter (>=1.0.0)
|
|
18
|
-
Requires-Dist: pandas (>=2.2.1)
|
|
19
|
-
Requires-Dist: pyyaml
|
|
20
|
-
Requires-Dist: requests (>=2.28.0)
|
|
21
|
-
Requires-Dist: rich
|
|
22
|
-
Requires-Dist: typer
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
|
|
25
|
-
# deployml
|
|
26
|
-
Infrastructure for academia with cost analysis
|
|
27
|
-
|
|
28
|
-
## Features
|
|
29
|
-
|
|
30
|
-
- 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
|
|
31
|
-
- 💰 **Cost Analysis**: Integrated infracost analysis before deployment
|
|
32
|
-
- ☁️ **Multi-Cloud Support**: GCP, AWS, and more
|
|
33
|
-
- 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
|
|
34
|
-
- 🛡️ **Production Ready**: Security best practices and service account management
|
|
35
|
-
|
|
36
|
-
## Instructions
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
poetry install
|
|
40
|
-
poetry run deployml doctor
|
|
41
|
-
poetry run deployml deploy --config-path your-config.yaml
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
|
|
45
|
-
|
|
46
|
-
gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
|
|
47
|
-
|
|
48
|
-
## Cost Analysis Integration
|
|
49
|
-
|
|
50
|
-
deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
|
|
51
|
-
|
|
52
|
-
### Installation
|
|
53
|
-
```bash
|
|
54
|
-
brew install infracost
|
|
55
|
-
```
|
|
56
|
-
Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
|
|
57
|
-
|
|
58
|
-
To generate your infracost API key run the following command:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
infracost auth login
|
|
62
|
-
```
|
|
63
|
-
If you want to retrieve your API key use:
|
|
64
|
-
```bash
|
|
65
|
-
infracost configure get api_key
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Cost Analysis Configuration
|
|
69
|
-
Add cost analysis settings to your YAML configuration:
|
|
70
|
-
|
|
71
|
-
```yaml
|
|
72
|
-
name: "my-mlops-stack"
|
|
73
|
-
cost_analysis:
|
|
74
|
-
enabled: true # Enable/disable cost analysis (default: true)
|
|
75
|
-
warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
|
|
76
|
-
currency: "USD" # Currency for cost display
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Cloud Run Service Account Setup
|
|
80
|
-
|
|
81
|
-
When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
|
|
82
|
-
|
|
83
|
-
### How to get a service account email
|
|
84
|
-
|
|
85
|
-
1. List your service accounts:
|
|
86
|
-
```sh
|
|
87
|
-
gcloud iam service-accounts list
|
|
88
|
-
```
|
|
89
|
-
2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
|
|
90
|
-
```sh
|
|
91
|
-
gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
|
|
92
|
-
```
|
|
93
|
-
3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
|
|
94
|
-
|
|
95
|
-
### Grant the service account permissions
|
|
96
|
-
|
|
97
|
-
The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
|
|
98
|
-
|
|
99
|
-
### Deploying with Terraform
|
|
100
|
-
|
|
101
|
-
Pass the service account email as a variable:
|
|
102
|
-
|
|
103
|
-
```sh
|
|
104
|
-
terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Or, if using the CLI, ensure it passes this variable to Terraform.
|
|
108
|
-
|
|
109
|
-
### Why this is needed
|
|
110
|
-
|
|
111
|
-
The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
|
deployml_core-0.0.59/README.md
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# deployml
|
|
2
|
-
Infrastructure for academia with cost analysis
|
|
3
|
-
|
|
4
|
-
## Features
|
|
5
|
-
|
|
6
|
-
- 🏗️ **Infrastructure as Code**: Deploy ML infrastructure using Terraform
|
|
7
|
-
- 💰 **Cost Analysis**: Integrated infracost analysis before deployment
|
|
8
|
-
- ☁️ **Multi-Cloud Support**: GCP, AWS, and more
|
|
9
|
-
- 🔬 **ML-Focused**: Pre-configured for MLflow, experiment tracking, and model registry
|
|
10
|
-
- 🛡️ **Production Ready**: Security best practices and service account management
|
|
11
|
-
|
|
12
|
-
## Instructions
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
poetry install
|
|
16
|
-
poetry run deployml doctor
|
|
17
|
-
poetry run deployml deploy --config-path your-config.yaml
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
docker build --platform=linux/amd64 -t gcr.io/mlops-intro-461805/mlflow/mlflow:latest .
|
|
21
|
-
|
|
22
|
-
gcloud auth configure-docker docker push gcr.io/PROJECT_ID/mlflow-app:latest
|
|
23
|
-
|
|
24
|
-
## Cost Analysis Integration
|
|
25
|
-
|
|
26
|
-
deployml integrates with [infracost](https://www.infracost.io/) to provide cost estimates before deployment:
|
|
27
|
-
|
|
28
|
-
### Installation
|
|
29
|
-
```bash
|
|
30
|
-
brew install infracost
|
|
31
|
-
```
|
|
32
|
-
Once installed you will need to create a free [infracost](https://www.infracost.io/) account before creating your API key.
|
|
33
|
-
|
|
34
|
-
To generate your infracost API key run the following command:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
infracost auth login
|
|
38
|
-
```
|
|
39
|
-
If you want to retrieve your API key use:
|
|
40
|
-
```bash
|
|
41
|
-
infracost configure get api_key
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Cost Analysis Configuration
|
|
45
|
-
Add cost analysis settings to your YAML configuration:
|
|
46
|
-
|
|
47
|
-
```yaml
|
|
48
|
-
name: "my-mlops-stack"
|
|
49
|
-
cost_analysis:
|
|
50
|
-
enabled: true # Enable/disable cost analysis (default: true)
|
|
51
|
-
warning_threshold: 100.0 # Warn if monthly cost exceeds this amount
|
|
52
|
-
currency: "USD" # Currency for cost display
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Cloud Run Service Account Setup
|
|
56
|
-
|
|
57
|
-
When deploying MLflow, you must specify the service account email that Cloud Run will use. This service account must have permission to access the artifact bucket.
|
|
58
|
-
|
|
59
|
-
### How to get a service account email
|
|
60
|
-
|
|
61
|
-
1. List your service accounts:
|
|
62
|
-
```sh
|
|
63
|
-
gcloud iam service-accounts list
|
|
64
|
-
```
|
|
65
|
-
2. (Recommended) Create a dedicated service account for MLflow if you don't have one:
|
|
66
|
-
```sh
|
|
67
|
-
gcloud iam service-accounts create mlflow-cloudrun --display-name "MLflow Cloud Run Service Account"
|
|
68
|
-
```
|
|
69
|
-
3. Find the email for your service account (it will look like `mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com`).
|
|
70
|
-
|
|
71
|
-
### Grant the service account permissions
|
|
72
|
-
|
|
73
|
-
The Terraform module will automatically grant the service account the required permissions on the artifact bucket.
|
|
74
|
-
|
|
75
|
-
### Deploying with Terraform
|
|
76
|
-
|
|
77
|
-
Pass the service account email as a variable:
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
terraform apply -var="cloud_run_service_account=mlflow-cloudrun@YOUR_PROJECT.iam.gserviceaccount.com"
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Or, if using the CLI, ensure it passes this variable to Terraform.
|
|
84
|
-
|
|
85
|
-
### Why this is needed
|
|
86
|
-
|
|
87
|
-
The MLflow server (running on Cloud Run) needs permission to list and read artifacts in the GCS bucket. This setup ensures the MLflow UI works for all users without manual permission fixes.
|
deployml_core-0.0.59/src/deployml/terraform/modules/bigquery/cloud/gcp/schemas/offline_features.json
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{"name": "entity_id", "type": "STRING", "mode": "REQUIRED"},
|
|
3
|
-
{"name": "event_timestamp", "type": "TIMESTAMP", "mode": "REQUIRED"},
|
|
4
|
-
{"name": "feature_1", "type": "FLOAT", "mode": "NULLABLE"},
|
|
5
|
-
{"name": "feature_2", "type": "FLOAT", "mode": "NULLABLE"},
|
|
6
|
-
{"name": "feature_3", "type": "FLOAT", "mode": "NULLABLE"}
|
|
7
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/Dockerfile
RENAMED
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/README.md
RENAMED
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/docker/grafana-container/entrypoint.sh
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/base_main.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/main.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/variables.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_run/wandb_main.tf.j2
RENAMED
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/main.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
{deployml_core-0.0.59 → deployml_core-0.0.60}/src/deployml/templates/gcp/cloud_vm/variables.tf.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|