zen-temple 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Reliability and Risk Engineering, ETH Zurich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: zen_temple
3
+ Version: 0.0.1
4
+ Summary: The visualization plattform for ZEN Garden
5
+ Author-email: Vinzenz Muser <muserv@ethz.ch>
6
+ Project-URL: Homepage, https://github.com/ZEN-universe/ZEN-temple
7
+ Project-URL: Issues, https://github.com/ZEN-universe/ZEN-temple/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: uvicorn
15
+ Requires-Dist: fastapi
16
+ Requires-Dist: fastapi-cache2
17
+ Requires-Dist: python-dotenv
18
+ Requires-Dist: sqlmodel
19
+ Requires-Dist: python-multipart
20
+ Requires-Dist: zen-garden
21
+
22
+ ## ZEN Suite
23
+
24
+ Run with: `uvicorn src.main:app`
25
+ Develop with: `uvicorn src.main:app --reload --host 0.0.0.0`
26
+
27
+ If chnages to the models are made, alembic is responsible for the migrations. Use the following steps:
28
+
29
+ Create change-scripts: `alembic revision --autogenerate -m "<message>"`
30
+ Commit changes: `alembic upgrade head`
31
+
32
+ Build Docker-Container with `docker build -t example --build-arg ssh_prv_key="$(cat ~/.ssh/id_rsa)" --build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)" .`
33
+
34
+ Watch out, this copies your private ssh-key to the container because the required repository (ZEN garden) is private and therefore you need git-authentification.
@@ -0,0 +1,13 @@
1
+ ## ZEN Suite
2
+
3
+ Run with: `uvicorn src.main:app`
4
+ Develop with: `uvicorn src.main:app --reload --host 0.0.0.0`
5
+
6
+ If chnages to the models are made, alembic is responsible for the migrations. Use the following steps:
7
+
8
+ Create change-scripts: `alembic revision --autogenerate -m "<message>"`
9
+ Commit changes: `alembic upgrade head`
10
+
11
+ Build Docker-Container with `docker build -t example --build-arg ssh_prv_key="$(cat ~/.ssh/id_rsa)" --build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)" .`
12
+
13
+ Watch out, this copies your private ssh-key to the container because the required repository (ZEN garden) is private and therefore you need git-authentification.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zen_temple"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Vinzenz Muser", email="muserv@ethz.ch" },
10
+ ]
11
+ description = "The visualization plattform for ZEN Garden"
12
+ requires-python= ">=3.10"
13
+ readme = "README.md"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ dependencies = [
21
+ "uvicorn",
22
+ "fastapi",
23
+ "fastapi-cache2",
24
+ "python-dotenv",
25
+ "sqlmodel",
26
+ "python-multipart",
27
+ "zen-garden"
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/ZEN-universe/ZEN-temple"
32
+ Issues = "https://github.com/ZEN-universe/ZEN-temple/issues"
@@ -0,0 +1,17 @@
1
+ [flake8]
2
+ max-line-length = 90
3
+ exclude = migrations,src/config.py
4
+ ignore = E203
5
+
6
+ [black]
7
+ max-line-length = 90
8
+
9
+ [mypy]
10
+ python_executable = "venv/bin/python"
11
+ exclude = venv|migrations|tests|public
12
+ strict = True
13
+
14
+ [egg_info]
15
+ tag_build =
16
+ tag_date = 0
17
+
File without changes
@@ -0,0 +1,23 @@
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+
7
+ class Config:
8
+ def __init__(self) -> None:
9
+ self.SOLUTION_FOLDER: str = os.getenv("SOLUTION_FOLDER") # type: ignore
10
+
11
+ if self.SOLUTION_FOLDER is None:
12
+ self.SOLUTION_FOLDER = "../outputs"
13
+ print(f"Setting default solution folder {self.SOLUTION_FOLDER}")
14
+
15
+ self.check()
16
+
17
+ def check(self) -> None:
18
+ for key, val in self.__dict__.items():
19
+ if val is None:
20
+ raise Exception(f"Env-Variable {key} is missing!")
21
+
22
+
23
+ config = Config()
@@ -0,0 +1,44 @@
1
+ from src.routers import solutions
2
+ from collections.abc import AsyncIterator
3
+
4
+ import uvicorn
5
+
6
+ from contextlib import asynccontextmanager
7
+
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi import FastAPI
10
+ from fastapi.staticfiles import StaticFiles
11
+
12
+ from fastapi_cache import FastAPICache
13
+ from fastapi_cache.backends.inmemory import InMemoryBackend
14
+ import webbrowser
15
+
16
+
17
+ @asynccontextmanager
18
+ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
19
+ FastAPICache.init(InMemoryBackend())
20
+ yield
21
+
22
+
23
+ app = FastAPI(lifespan=lifespan)
24
+
25
+ origins = ["*"]
26
+
27
+ app.add_middleware(
28
+ CORSMiddleware,
29
+ allow_origins=origins, # type: ignore
30
+ allow_credentials=True, # type: ignore
31
+ allow_methods=["*"], # type: ignore
32
+ allow_headers=["*"], # type: ignore
33
+ )
34
+
35
+ app.include_router(solutions.router)
36
+ app.mount("/explorer", StaticFiles(directory="explorer", html=True), name="explorer")
37
+
38
+ if __name__ == "__main__":
39
+ config = uvicorn.Config("main:app", port=8000, log_level="info")
40
+ server = uvicorn.Server(config)
41
+ webbrowser.open('http://localhost:8000/explorer', new=2)
42
+
43
+ server.run()
44
+
@@ -0,0 +1 @@
1
+ from . import solution # noqa: F401
@@ -0,0 +1,222 @@
1
+ from sqlmodel import Field, Column, String
2
+ from sqlalchemy.dialects import postgresql
3
+ from pydantic import BaseModel
4
+ from typing import Optional
5
+ from enum import Enum
6
+ from src.config import config
7
+ import os
8
+ import json
9
+ from typing import Any
10
+ from zen_garden.model.default_config import System # type: ignore
11
+ from zen_garden.postprocess.results import Results # type: ignore
12
+
13
+
14
+ class SeriesBehaviour(Enum):
15
+ sum = "sum"
16
+ series = "series"
17
+
18
+
19
+ class ScenarioDetail(BaseModel):
20
+ system: System
21
+ reference_carrier: dict[str, str]
22
+ carriers_import: list[str]
23
+ carriers_export: list[str]
24
+ carriers_input: dict[str, list[str]]
25
+ carriers_output: dict[str, list[str]]
26
+ carriers_demand: list[str]
27
+ edges: dict[str, str]
28
+
29
+
30
+ class SolutionDetail(BaseModel):
31
+ name: str
32
+ folder_name: str
33
+ scenarios: dict[str, ScenarioDetail]
34
+
35
+ @staticmethod
36
+ def from_name(name: str) -> "SolutionDetail":
37
+ results = Results(os.path.join(config.SOLUTION_FOLDER, name))
38
+ reference_carriers = results.get_df("set_reference_carriers")
39
+
40
+ scenario_details = {}
41
+
42
+ for scenario_name, scenario in results.solution_loader.scenarios.items():
43
+ system = scenario.system
44
+ reference_carrier = reference_carriers[scenario_name].to_dict()
45
+ df_import = results.get_df("availability_import")[scenario_name]
46
+ df_export = results.get_df("availability_export")[scenario_name]
47
+ df_demand = results.get_df("demand")[scenario_name]
48
+ df_input_carriers = results.get_df("set_input_carriers")[scenario_name]
49
+ df_output_carriers = results.get_df("set_output_carriers")[scenario_name]
50
+ edges = results.get_df("set_nodes_on_edges")[scenario_name]
51
+ edges_dict = edges.to_dict()
52
+ carriers_input_dict = {
53
+ key: val.split(",") for key, val in df_input_carriers.to_dict().items()
54
+ }
55
+ carriers_output_dict = {
56
+ key: val.split(",") for key, val in df_output_carriers.to_dict().items()
57
+ }
58
+
59
+ for key in carriers_output_dict:
60
+ if carriers_output_dict[key] == [""]:
61
+ carriers_output_dict[key] = []
62
+
63
+ for key in carriers_input_dict:
64
+ if carriers_input_dict[key] == [""]:
65
+ carriers_input_dict[key] = []
66
+
67
+ carriers_import = list(
68
+ df_import.loc[df_import != 0].index.get_level_values("carrier").unique()
69
+ )
70
+ carriers_export = list(
71
+ df_export.loc[df_export != 0].index.get_level_values("carrier").unique()
72
+ )
73
+
74
+ carriers_demand = list(
75
+ df_demand.loc[df_demand != 0].index.get_level_values("carrier").unique()
76
+ )
77
+
78
+ scenario_details[scenario_name] = ScenarioDetail(
79
+ system=system,
80
+ reference_carrier=reference_carrier,
81
+ carriers_import=carriers_import,
82
+ carriers_export=carriers_export,
83
+ carriers_input=carriers_input_dict,
84
+ carriers_output=carriers_output_dict,
85
+ carriers_demand=carriers_demand,
86
+ edges=edges_dict
87
+ )
88
+
89
+ return SolutionDetail(
90
+ name=name.split("/")[-1],
91
+ folder_name=name.split("/")[-1],
92
+ scenarios=scenario_details,
93
+ )
94
+
95
+
96
+ class Solution(BaseModel):
97
+ folder_name: str
98
+ name: str
99
+ nodes: list[str] = Field(default=[], sa_column=Column(postgresql.ARRAY(String())))
100
+ total_hours_per_year: int
101
+ optimized_years: int
102
+ technologies: list[str] = Field(
103
+ default=[], sa_column=Column(postgresql.ARRAY(String()))
104
+ )
105
+ carriers: list[str] = Field(
106
+ default=[], sa_column=Column(postgresql.ARRAY(String()))
107
+ )
108
+ scenarios: list[str] = Field(
109
+ default=[], sa_column=Column(postgresql.ARRAY(String()))
110
+ )
111
+
112
+ @staticmethod
113
+ def from_name(name: str) -> "Solution":
114
+ scenarios = [
115
+ i
116
+ for i in os.listdir(os.path.join(config.SOLUTION_FOLDER, name))
117
+ if i.startswith("scenario_")
118
+ ]
119
+
120
+ if len(scenarios) == 0:
121
+ scenarios = ["scenario_"]
122
+
123
+ system_path = os.path.join(config.SOLUTION_FOLDER, name, "system.json")
124
+ if not os.path.exists(system_path):
125
+ system_path = os.path.join(
126
+ config.SOLUTION_FOLDER, name, "scenario_", "system.json"
127
+ )
128
+
129
+ with open(system_path) as f:
130
+ system: dict[str, Any] = json.load(f)
131
+
132
+ system["carriers"] = system["set_carriers"]
133
+ system["technologies"] = system["set_technologies"]
134
+ system["folder_name"] = name.split("/")[-1]
135
+ system["scenarios"] = scenarios
136
+ system["nodes"] = system["set_nodes"]
137
+ system["name"] = name.split("/")[-1]
138
+ solution = Solution(**system)
139
+
140
+ return solution
141
+
142
+
143
+ class IndexSet(BaseModel):
144
+ index_title: str
145
+ behaviour: SeriesBehaviour = SeriesBehaviour.series
146
+ indices: list[str] = []
147
+
148
+
149
+ class DataRequest(BaseModel):
150
+ default: SeriesBehaviour = SeriesBehaviour.series
151
+ index_sets: list[IndexSet] = []
152
+
153
+
154
+ class CompleteDataRequest(BaseModel):
155
+ solution_name: str
156
+ component: str
157
+ scenario: str = "scenario_"
158
+ data_request: DataRequest
159
+ aggregate_years: bool = False
160
+
161
+
162
+ class DataResult(BaseModel):
163
+ data_csv: str
164
+ unit: Optional[str]
165
+
166
+
167
+ class ResultsRequest(BaseModel):
168
+ component: str
169
+ yearly: bool = False
170
+ node_edit: Optional[str] = None
171
+ sum_techs: bool = False
172
+ tech_type: Optional[str] = None
173
+ reference_carrier: Optional[str] = None
174
+ scenario: Optional[str] = None
175
+
176
+ def to_data_request(self, solution_name: str) -> CompleteDataRequest:
177
+ data_request = DataRequest()
178
+ index_sets: list[IndexSet] = []
179
+
180
+ if self.node_edit is not None and self.node_edit != "all":
181
+ index_sets.append(
182
+ IndexSet(
183
+ index_title="node",
184
+ behaviour=SeriesBehaviour.series,
185
+ indices=[self.node_edit],
186
+ )
187
+ )
188
+
189
+ if (
190
+ self.sum_techs is not None or self.tech_type
191
+ ) is not None and self.tech_type != "all":
192
+ tech_index = IndexSet(
193
+ index_title="technology", behaviour=SeriesBehaviour.series
194
+ )
195
+ if self.sum_techs:
196
+ tech_index.behaviour = SeriesBehaviour.sum
197
+ if self.tech_type is not None:
198
+ tech_index.indices = [self.tech_type]
199
+ index_sets.append(tech_index)
200
+
201
+ if self.reference_carrier is not None and self.reference_carrier != "all":
202
+ index_sets.append(
203
+ IndexSet(
204
+ index_title="carrier",
205
+ behaviour=SeriesBehaviour.series,
206
+ indices=[self.reference_carrier],
207
+ )
208
+ )
209
+
210
+ data_request.index_sets = index_sets
211
+
212
+ request = CompleteDataRequest(
213
+ solution_name=solution_name,
214
+ component=self.component,
215
+ data_request=data_request,
216
+ aggregate_years=self.yearly,
217
+ )
218
+
219
+ if self.scenario is not None:
220
+ request.scenario = "scenario_" + self.scenario
221
+
222
+ return request
@@ -0,0 +1,134 @@
1
+ from ..config import config
2
+ from zen_garden.postprocess.results import Results # type: ignore
3
+ from ..models.solution import (
4
+ Solution,
5
+ ResultsRequest,
6
+ CompleteDataRequest,
7
+ SolutionDetail,
8
+ DataResult,
9
+ )
10
+ import os
11
+ import pandas as pd
12
+ from time import perf_counter
13
+ from fastapi import HTTPException, UploadFile
14
+ from zipfile import ZipFile
15
+ from typing import Optional, Any
16
+ from functools import cache
17
+
18
+
19
+ class SolutionRepository:
20
+ def get_list(self) -> list[Solution]:
21
+ ans = []
22
+ for folder in os.listdir(config.SOLUTION_FOLDER):
23
+ try:
24
+ ans.append(Solution.from_name(folder))
25
+ except (FileNotFoundError, NotADirectoryError):
26
+ continue
27
+ return ans
28
+
29
+ @cache
30
+ def get_detail(self, solution_name: str) -> SolutionDetail:
31
+ return SolutionDetail.from_name(solution_name)
32
+
33
+ @cache
34
+ def get_total(
35
+ self, solution: str, component: str, scenario: Optional[str] = None
36
+ ) -> DataResult:
37
+ solution_folder = os.path.join(config.SOLUTION_FOLDER, solution)
38
+ results = Results(solution_folder)
39
+
40
+ try:
41
+ unit: pd.Series[Any] | None = results.get_unit(
42
+ component, scenario_name=scenario
43
+ )
44
+ except:
45
+ unit = None
46
+
47
+ total: pd.DataFrame | pd.Series = results.get_total(
48
+ component, scenario_name=scenario
49
+ )
50
+
51
+ if type(total) is not pd.Series:
52
+ total = total.loc[~(total == 0).all(axis=1)]
53
+
54
+ if unit is not None:
55
+ unit_csv = unit.to_csv()
56
+ else:
57
+ unit_csv = None
58
+
59
+ return DataResult(data_csv=str(total.to_csv()), unit=unit_csv)
60
+
61
+ def get_energy_balance(
62
+ self,
63
+ solution: str,
64
+ node: str,
65
+ carrier: str,
66
+ scenario: Optional[str] = None,
67
+ year: Optional[int] = None,
68
+ ) -> dict[str, str]:
69
+ solution_folder = os.path.join(config.SOLUTION_FOLDER, solution)
70
+ results = Results(solution_folder)
71
+
72
+ if year is None:
73
+ year = 0
74
+ energy_balance: dict[str, pd.DataFrame] = results.get_energy_balance_dataframes(
75
+ node, carrier, year, scenario
76
+ )
77
+
78
+ ans = {key: val.drop_duplicates() for key, val in energy_balance.items()}
79
+
80
+ for key, series in ans.items():
81
+ if key == "demand":
82
+ continue
83
+
84
+ if type(series) is not pd.Series:
85
+ ans[key] = series.loc[~(series == 0).all(axis=1)]
86
+
87
+ ans = {key: val.to_csv() for key, val in ans.items()}
88
+ return ans
89
+
90
+ def get_dataframe(self, solution_name: str, df_request: ResultsRequest) -> str:
91
+ path = os.path.join(config.SOLUTION_FOLDER, solution_name)
92
+ argument_dictionary = {
93
+ key: val for key, val in df_request.dict().items() if val is not None
94
+ }
95
+
96
+ start = perf_counter()
97
+ results = Results(path)
98
+ print(f"Loading results took {perf_counter() - start}")
99
+
100
+ if "scenario" in argument_dictionary:
101
+ request_scenario = "scenario_" + argument_dictionary["scenario"]
102
+ if request_scenario not in results.results:
103
+ argument_dictionary["scenario"] = None
104
+ else:
105
+ argument_dictionary["scenario"] = request_scenario
106
+
107
+ start = perf_counter()
108
+ res: pd.DataFrame = results.get_summary_df(**argument_dictionary)
109
+ res = res.reset_index()
110
+ years = [i for i in res.columns if isinstance(i, int)]
111
+ others = [i for i in res.columns if not isinstance(i, int)]
112
+ res = pd.melt(res, id_vars=others, var_name="year", value_vars=years)
113
+
114
+ return res.to_csv()
115
+
116
+ async def upload_file(self, in_file: UploadFile) -> str:
117
+ file_path = os.path.join("./", str(in_file.filename))
118
+
119
+ async def upload() -> None:
120
+ pass
121
+ # async with aiofiles.open(file_path, "wb") as out_file:
122
+ # while content := await in_file.read(): # async read chunk
123
+ # await out_file.write(content) # async write chunk
124
+
125
+ await upload()
126
+
127
+ with ZipFile(file_path, "r") as zip:
128
+ contents: list[str] = zip.namelist()
129
+ print(contents)
130
+
131
+ return "Success"
132
+
133
+
134
+ solution_repository = SolutionRepository()
File without changes
@@ -0,0 +1,56 @@
1
+ from fastapi import APIRouter
2
+ from ..repositories.solution_repository import solution_repository
3
+ from ..models.solution import Solution, CompleteDataRequest, SolutionDetail, DataResult
4
+ from ..models.solution import ResultsRequest
5
+ from fastapi import UploadFile, status
6
+ from typing import Optional
7
+ from fastapi_cache.decorator import cache
8
+
9
+
10
+ router = APIRouter(prefix="/solutions", tags=["Solutions"])
11
+
12
+
13
+ @router.get("/list")
14
+ async def get_list() -> list[Solution]:
15
+ return solution_repository.get_list()
16
+
17
+
18
+ @router.get("/get_detail/{solution_name}")
19
+ async def get_detail(solution_name: str) -> SolutionDetail:
20
+ ans = solution_repository.get_detail(solution_name)
21
+ return ans
22
+
23
+
24
+ @router.get("/get_total/{solution_name}/{component_name}")
25
+ async def get_total(
26
+ solution_name: str, component_name: str, scenario: Optional[str] = None
27
+ ) -> DataResult:
28
+ ans = solution_repository.get_total(solution_name, component_name, scenario)
29
+ return ans
30
+
31
+
32
+ @router.get("/get_energy_balance/{solution_name}/{node_name}/{carrier_name}")
33
+ @cache(expire=30000)
34
+ async def get_energy_balance(
35
+ solution_name: str,
36
+ node_name: str,
37
+ carrier_name: str,
38
+ scenario: Optional[str] = None,
39
+ year: Optional[int] = 0,
40
+ ) -> dict[str, str]:
41
+ ans = solution_repository.get_energy_balance(
42
+ solution_name, node_name, carrier_name, scenario, year
43
+ )
44
+ return ans
45
+
46
+
47
+ @router.post("/upload", status_code=status.HTTP_201_CREATED)
48
+ async def upload(in_file: UploadFile) -> str:
49
+ """
50
+ Creates a dataset with files
51
+ :param in_file: zip file containing the files to be uploaded
52
+ :param title: title of the dataset
53
+ """
54
+ ans = await solution_repository.upload_file(in_file)
55
+
56
+ return ans
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.1
2
+ Name: zen_temple
3
+ Version: 0.0.1
4
+ Summary: The visualization plattform for ZEN Garden
5
+ Author-email: Vinzenz Muser <muserv@ethz.ch>
6
+ Project-URL: Homepage, https://github.com/ZEN-universe/ZEN-temple
7
+ Project-URL: Issues, https://github.com/ZEN-universe/ZEN-temple/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: uvicorn
15
+ Requires-Dist: fastapi
16
+ Requires-Dist: fastapi-cache2
17
+ Requires-Dist: python-dotenv
18
+ Requires-Dist: sqlmodel
19
+ Requires-Dist: python-multipart
20
+ Requires-Dist: zen-garden
21
+
22
+ ## ZEN Suite
23
+
24
+ Run with: `uvicorn src.main:app`
25
+ Develop with: `uvicorn src.main:app --reload --host 0.0.0.0`
26
+
27
+ If chnages to the models are made, alembic is responsible for the migrations. Use the following steps:
28
+
29
+ Create change-scripts: `alembic revision --autogenerate -m "<message>"`
30
+ Commit changes: `alembic upgrade head`
31
+
32
+ Build Docker-Container with `docker build -t example --build-arg ssh_prv_key="$(cat ~/.ssh/id_rsa)" --build-arg ssh_pub_key="$(cat ~/.ssh/id_rsa.pub)" .`
33
+
34
+ Watch out, this copies your private ssh-key to the container because the required repository (ZEN garden) is private and therefore you need git-authentification.
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.cfg
5
+ src/zen_temple/__init__.py
6
+ src/zen_temple/config.py
7
+ src/zen_temple/main.py
8
+ src/zen_temple.egg-info/PKG-INFO
9
+ src/zen_temple.egg-info/SOURCES.txt
10
+ src/zen_temple.egg-info/dependency_links.txt
11
+ src/zen_temple.egg-info/requires.txt
12
+ src/zen_temple.egg-info/top_level.txt
13
+ src/zen_temple/models/__init__.py
14
+ src/zen_temple/models/solution.py
15
+ src/zen_temple/repositories/__init__.py
16
+ src/zen_temple/repositories/solution_repository.py
17
+ src/zen_temple/routers/__init__.py
18
+ src/zen_temple/routers/solutions.py
@@ -0,0 +1,7 @@
1
+ uvicorn
2
+ fastapi
3
+ fastapi-cache2
4
+ python-dotenv
5
+ sqlmodel
6
+ python-multipart
7
+ zen-garden
@@ -0,0 +1 @@
1
+ zen_temple