cdiam-cli 0.1.0__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) [year] [fullname]
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,56 @@
1
+ Metadata-Version: 2.1
2
+ Name: cdiam-cli
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: tungtp99
6
+ Author-email: tungtp181199@gmail.com
7
+ Requires-Python: >=3.9,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: click (>=8.1.7,<9.0.0)
14
+ Requires-Dist: datetime (>=5.5,<6.0)
15
+ Requires-Dist: idna (==3.4)
16
+ Requires-Dist: numpy (>=1.24.0,<2.0.0)
17
+ Requires-Dist: pandas (>=2.1.0,<3.0.0)
18
+ Requires-Dist: pydantic (==1.10.2)
19
+ Requires-Dist: pymysql (==1.0.2)
20
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
21
+ Requires-Dist: sqlalchemy (==1.4.41)
22
+ Requires-Dist: sqlalchemy2-stubs (==0.0.2a27)
23
+ Requires-Dist: sqlmodel (==0.0.8)
24
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
25
+ Requires-Dist: typing-extensions (==4.3.0)
26
+ Description-Content-Type: text/markdown
27
+
28
+ # CDIAM CLI
29
+
30
+ This is a repository for CDIAM CLI. .
31
+
32
+ ## Getting Started
33
+
34
+ To get started with this project, follow these steps:
35
+
36
+ ### From source
37
+ 1. Clone the repository: `git clone github.com/C-DIAM/cdiam-cli.git`
38
+ 2. Install: `poetry install` install poetry with https://python-poetry.org/docs/
39
+
40
+ ### From pypip
41
+ Run `pip install cdiam-cli`
42
+
43
+ ### View CLI command
44
+ Run `cdiam_cli --help`
45
+
46
+ ## Features
47
+
48
+ - Save token: `python -m cdiam_cli save-token` must provide server endpoint E.g. https://c-diam.com/api and TOKEN get from CDIAM APP
49
+ - Call API: `python -m cdiam_cli call-api <PATH_TO_YAML_OR_JSON_PARAMS>` more detail about params schemas at `<SERVER_ENPOINT>/schemas/docs` E.g. https://c-diam.com/api/schemas/docs
50
+
51
+
52
+
53
+ ## License
54
+
55
+ This project is licensed under the [MIT License](LICENSE).
56
+
@@ -0,0 +1,28 @@
1
+ # CDIAM CLI
2
+
3
+ This is a repository for CDIAM CLI. .
4
+
5
+ ## Getting Started
6
+
7
+ To get started with this project, follow these steps:
8
+
9
+ ### From source
10
+ 1. Clone the repository: `git clone github.com/C-DIAM/cdiam-cli.git`
11
+ 2. Install: `poetry install` install poetry with https://python-poetry.org/docs/
12
+
13
+ ### From pypip
14
+ Run `pip install cdiam-cli`
15
+
16
+ ### View CLI command
17
+ Run `cdiam_cli --help`
18
+
19
+ ## Features
20
+
21
+ - Save token: `python -m cdiam_cli save-token` must provide server endpoint E.g. https://c-diam.com/api and TOKEN get from CDIAM APP
22
+ - Call API: `python -m cdiam_cli call-api <PATH_TO_YAML_OR_JSON_PARAMS>` more detail about params schemas at `<SERVER_ENPOINT>/schemas/docs` E.g. https://c-diam.com/api/schemas/docs
23
+
24
+
25
+
26
+ ## License
27
+
28
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,2 @@
1
+ from . import api
2
+ from . import helper
@@ -0,0 +1,5 @@
1
+ # python
2
+
3
+ from .main import main_group
4
+
5
+ main_group()
@@ -0,0 +1,4 @@
1
+ from .settings import save_token
2
+ from .download import download_data
3
+ from .api_action import call_api
4
+ from . import utils
@@ -0,0 +1,131 @@
1
+ import json
2
+ import click
3
+ from cdiam_cli.helper import yaml_helper, curl_helper
4
+ import subprocess
5
+ from typing import Union, Callable, Any, Dict, overload, Literal
6
+ from .settings import read_api_endpoint, read_api_token
7
+ from cdiam_cli.schemas import AnalysisResult
8
+ from cdiam_cli.schemas import ParamsRequestGetTaskStatus
9
+ from cdiam_cli.schemas import AnalysisResultRead
10
+ from time import sleep
11
+
12
+
13
+ def wait_task(task_id: str):
14
+ while True:
15
+ sleep(5)
16
+ print("Load status info")
17
+ res = run(
18
+ ParamsRequestGetTaskStatus(api="get_task_status", task_id=task_id).dict()
19
+ )
20
+ status = AnalysisResultRead(**res)
21
+
22
+ if status.task is None:
23
+ raise Exception("Task FAILURE")
24
+ if status.task.status == "FAILURE":
25
+ raise Exception("Task FAILURE")
26
+ if status.task.status == "SUCCESS":
27
+ break
28
+ return status
29
+
30
+
31
+ def parse_and_wait_task(process: Any):
32
+ analysis_result: AnalysisResult = AnalysisResult(**process)
33
+ assert analysis_result.task_id is not None
34
+ print(analysis_result)
35
+ status = wait_task(task_id=analysis_result.task_id)
36
+ print(status)
37
+ return status
38
+
39
+
40
+ @overload
41
+ def run(
42
+ params: Union[str, Dict[Any, Any]],
43
+ modify_params_callback: Union[Callable[[Any], Any], None] = None,
44
+ wait_as_analysis: Literal[False] = False,
45
+ ) -> Any: ...
46
+ @overload
47
+ def run(
48
+ params: Union[str, Dict[Any, Any]],
49
+ modify_params_callback: Union[Callable[[Any], Any], None] = None,
50
+ wait_as_analysis: Literal[True] = True,
51
+ ) -> AnalysisResult: ...
52
+ def run(
53
+ params: Union[str, Dict[Any, Any]],
54
+ modify_params_callback: Union[Callable[[Any], Any], None] = None,
55
+ wait_as_analysis: bool = False,
56
+ ):
57
+ """
58
+ Runs an API call with the provided parameters and returns the API response.
59
+
60
+ Args:
61
+ params (Union[str, Dict[Any, Any]]): The API parameters, either as a path to a JSON or YAML file, or as a dictionary.
62
+ More details about api schemas can be found in the documentation at https://c-diam.com/api/schemas/docs or if you are using different endpoint you can find it at https://<your-end-point>/api/schemas/docs.
63
+ modify_params_callback (Union[Callable[[Any], Any], None]): An optional callback function to modify the API parameters before the call.
64
+ wait_as_analysis (bool): If True, the function will wait for the API task to complete and return the analysis result.
65
+
66
+ Returns:
67
+ Any: The API response, or an AnalysisResult if wait_as_analysis is True.
68
+
69
+ Raises:
70
+ Exception: If the API call fails.
71
+ """
72
+
73
+ if isinstance(params, str):
74
+ if params.endswith(".json"):
75
+ with open(params) as f:
76
+ data = json.load(f)
77
+ else:
78
+ data = yaml_helper.read(params)
79
+ if modify_params_callback is not None:
80
+ data = modify_params_callback(data)
81
+ else:
82
+ data = params
83
+
84
+ if data["api"] in ["upload_matrix"]: # type: ignore
85
+ form_data = curl_helper.convert_json_to_form_data(data)
86
+ command = curl_helper.generate_curl_form_command(
87
+ form_data, endpoint=f"{read_api_endpoint()}/api_public/{data['api']}" # type: ignore
88
+ )
89
+ else:
90
+ command = curl_helper.generate_curl_json_command(
91
+ data, endpoint=f"{read_api_endpoint()}/api_public/{data['api']}" # type: ignore
92
+ )
93
+ command.append("-b")
94
+ command.append(f"cdiam_session_token={read_api_token()}")
95
+
96
+ process = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
97
+ process.check_returncode()
98
+ output = json.loads(process.stdout.decode())
99
+ if "detail" in output:
100
+ raise Exception(output)
101
+
102
+ if wait_as_analysis:
103
+ output = parse_and_wait_task(output)
104
+
105
+ return output
106
+
107
+
108
+ @click.command()
109
+ @click.argument("params", type=click.Path(exists=True), required=True)
110
+ def call_api(params: str):
111
+ """
112
+ Calls the API with the provided parameters and returns the API response.
113
+
114
+ Args:
115
+ params (str): The path to a JSON or YAML file containing the API parameters. More details about api schemas can be found in the documentation at https://c-diam.com/api/schemas/docs or if you are using different endpoint you can find it at https://<your-end-point>/api/schemas/docs.
116
+ Returns:
117
+ str: The API response.
118
+
119
+ Raises:
120
+ Exception: If the API call fails.
121
+ """
122
+
123
+ process = run(params)
124
+ return_code = process.returncode
125
+
126
+ if return_code == 0:
127
+ click.echo(process.stdout.decode())
128
+ return process.stdout.decode()
129
+ else:
130
+ click.echo(process.stdout.decode())
131
+ raise Exception(process.stderr.decode())
@@ -0,0 +1,28 @@
1
+ import click
2
+ import requests
3
+ import pandas as pd
4
+ from urllib.request import urlopen
5
+ from .settings import read_api_endpoint, read_api_token
6
+
7
+
8
+ @click.command()
9
+ @click.argument("object_id", type=str)
10
+ def download_data(object_id: str):
11
+ """This api download data of given objec_id"""
12
+ res = requests.get(
13
+ f"{read_api_endpoint()}/data/data/download-file/{object_id}",
14
+ cookies={"cdiam_session_token": read_api_token()},
15
+ )
16
+
17
+ if res.status_code != 200:
18
+ print(res.json())
19
+ else:
20
+ for url in res.json()["url"]:
21
+ response = urlopen(url)
22
+ file_name = response.headers.get_filename()
23
+ with requests.get(url, stream=True) as r:
24
+ r.raise_for_status()
25
+ with open(file_name, "wb") as f:
26
+ for chunk in r.iter_content(chunk_size=8192):
27
+ if chunk:
28
+ f.write(chunk)
@@ -0,0 +1,59 @@
1
+ import click
2
+ import os
3
+ from os.path import expanduser
4
+ import json
5
+
6
+
7
+ def get_path_settings():
8
+ home = expanduser("~")
9
+ return os.path.join(home, ".cdiam_cli")
10
+
11
+
12
+ def read_api_endpoint():
13
+ if not os.path.exists(get_path_settings()):
14
+ raise Exception("Run save-settings first")
15
+ with open(get_path_settings(), "r") as f:
16
+ settings = json.load(f)
17
+ if "endpoint" not in settings:
18
+ raise Exception(
19
+ "Cannot found endpoint setting. Please run save-settings first"
20
+ )
21
+ return settings["endpoint"]
22
+
23
+
24
+ def read_api_token():
25
+ if not os.path.exists(get_path_settings()):
26
+ raise Exception("Run save-settings first")
27
+ with open(get_path_settings(), "r") as f:
28
+ settings = json.load(f)
29
+ if "token" not in settings:
30
+ raise Exception(
31
+ "Cannot found token setting. Please run save-settings first"
32
+ )
33
+ return settings["token"]
34
+
35
+
36
+ class HiddenPassword(object):
37
+ def __init__(self, password=""):
38
+ self.password = password
39
+
40
+ def __str__(self):
41
+ return "*" * len(self.password)
42
+
43
+
44
+ @click.command()
45
+ def save_token():
46
+ """This api save your endpoint and your token in your machine"""
47
+ endpoint = click.prompt(
48
+ "Please enter the server endpoint E.g. https://c-diam.com/api", type=str
49
+ )
50
+ value = click.prompt("Please enter the token", type=HiddenPassword, hide_input=True)
51
+ with open(get_path_settings(), "w") as f:
52
+ json.dump(
53
+ {
54
+ "endpoint": endpoint,
55
+ "token": value.password,
56
+ },
57
+ f,
58
+ )
59
+ print(f"Save settings at {get_path_settings()}")
File without changes
@@ -0,0 +1,3 @@
1
+ from . import yaml_helper
2
+ from . import curl_helper
3
+ from . import json_helper
@@ -0,0 +1,42 @@
1
+ import json
2
+ from typing import Any, Dict
3
+
4
+
5
+ def _convert(data: Any, ret: Dict[str, str], key: str):
6
+ if isinstance(data, dict):
7
+ for k in data.keys():
8
+ _convert(data[k], ret, f"{key}[{k}]" if key != "" else k)
9
+ elif isinstance(data, list):
10
+ for index, k in enumerate(data):
11
+ _convert(data[index], ret, f"{key}[{index}]")
12
+ else:
13
+ if data is not None:
14
+ ret[key] = data
15
+
16
+
17
+ def generate_curl_form_command(form_data: Dict[str, str], endpoint: str):
18
+ command = ["curl", "-X", "POST", "-H", "Content-Type: multipart/form-data"]
19
+ for k, v in form_data.items():
20
+
21
+ command.append("-F")
22
+ if v is None or v.startswith("@"):
23
+ command.append(f'{k}={"null" if v is None else v}')
24
+ else:
25
+ command.append(f'{k}="{v}"')
26
+
27
+ command.append(endpoint)
28
+ return command
29
+
30
+
31
+ def generate_curl_json_command(json_data: Dict[str, str], endpoint: str):
32
+ command = ["curl", "-X", "POST", "-H", "Content-Type: application/json"]
33
+ command.append("-d")
34
+ command.append(json.dumps(json_data))
35
+ command.append(endpoint)
36
+ return command
37
+
38
+
39
+ def convert_json_to_form_data(json_object: Any):
40
+ ret = {}
41
+ _convert(json_object, ret, "")
42
+ return ret
@@ -0,0 +1,6 @@
1
+ import json
2
+
3
+ def read(path: str):
4
+ with open(path ,'r') as f:
5
+ return json.load(f)
6
+
@@ -0,0 +1,10 @@
1
+ import yaml
2
+
3
+ def read(path: str):
4
+ with open(path) as stream:
5
+ try:
6
+ return yaml.safe_load(stream)
7
+ except yaml.YAMLError as exc:
8
+ print(exc)
9
+ raise Exception(exc)
10
+
@@ -0,0 +1,15 @@
1
+ import click
2
+ from . import api
3
+
4
+
5
+ @click.group
6
+ def main_group():
7
+ pass
8
+
9
+
10
+ main_group.add_command(api.save_token)
11
+ main_group.add_command(api.call_api)
12
+
13
+
14
+ if __name__ == "__main__":
15
+ main_group()
@@ -0,0 +1 @@
1
+ from .schemas import *
@@ -0,0 +1,213 @@
1
+ from pydantic import BaseModel
2
+ from typing import Literal, Optional
3
+ from sqlmodel import (
4
+ Field,
5
+ SQLModel,
6
+ ForeignKeyConstraint,
7
+ Column,
8
+ DateTime,
9
+ func,
10
+ Relationship,
11
+ UniqueConstraint,
12
+ BigInteger,
13
+ )
14
+ import sqlalchemy as sa
15
+ from pydantic import validator, BaseModel
16
+ from datetime import datetime
17
+ from typing import Union, Callable, ClassVar, Optional, List, Dict, Any
18
+ import json
19
+
20
+
21
+ class ProjectSettings(BaseModel):
22
+ class Config:
23
+ orm_mode = True
24
+
25
+ cpdb: List[str] = []
26
+ enrichment: List[str] = []
27
+
28
+ @staticmethod
29
+ def encode_settings(settings: Union[Dict[str, Any], "ProjectSettings"]) -> str:
30
+ return ProjectSettings.parse_obj(settings).json()
31
+
32
+ @staticmethod
33
+ def decode_setting(settings: str) -> "ProjectSettings":
34
+ if settings is None:
35
+ settings = "{}"
36
+ return ProjectSettings.parse_obj(json.loads(settings))
37
+
38
+
39
+ class ProjectBase(SQLModel):
40
+ name: str = Field(max_length=128, nullable=False)
41
+
42
+
43
+ class Project(ProjectBase, table=True):
44
+ __tablename__: ClassVar[Union[str, Callable[..., str]]] = "project"
45
+ id: str = Field(
46
+ max_length=128, nullable=False, primary_key=True, index=True, unique=True
47
+ )
48
+ tombstone: Optional[bool] = Field(
49
+ default=False,
50
+ nullable=False,
51
+ description="Deleted marker of the project. True = deleted",
52
+ )
53
+ time_created: datetime = Field(
54
+ default=None,
55
+ sa_column=Column(DateTime(timezone=True), server_default=func.now()),
56
+ description="Time added",
57
+ )
58
+ is_public: bool = Field(
59
+ default=False,
60
+ nullable=False,
61
+ description="If the project is public",
62
+ )
63
+ delete_date: Optional[datetime] = Field(
64
+ default=None, sa_column=Column(sa.DateTime(timezone=True), nullable=True)
65
+ )
66
+ settings: Optional[str] = Field(
67
+ default=None,
68
+ max_length=10240,
69
+ nullable=True,
70
+ )
71
+ storage_used: int = Field(
72
+ sa_column=Column(BigInteger),
73
+ nullable=True,
74
+ description="Non-zero expression values",
75
+ )
76
+ created_by: str = Field(
77
+ max_length=128,
78
+ nullable=False,
79
+ index=True,
80
+ foreign_key="user.email",
81
+ )
82
+
83
+ @validator("settings")
84
+ def validate_settings(cls, v: str):
85
+ if v == "nan":
86
+ v = "{}"
87
+ if v is None:
88
+ v = "{}"
89
+ return ProjectSettings.encode_settings(ProjectSettings.decode_setting(v))
90
+
91
+ @validator("tombstone")
92
+ def validate_tombstone(cls, v):
93
+ if not v:
94
+ return False
95
+ return v
96
+
97
+
98
+ class ParamsRequestGetTaskStatus(BaseModel):
99
+ """
100
+ Represents the parameters for a request to get the status of an analysis task.
101
+
102
+ :param api: The API endpoint being called, which should be "get_task_status".
103
+ :param task_id: The unique identifier of the analysis task.
104
+ """
105
+
106
+ api: Literal["get_task_status"]
107
+ task_id: str
108
+
109
+
110
+ class TaskStatus(BaseModel):
111
+ status: str = "UNKNOWN"
112
+
113
+
114
+ class AnalysisResultBase(SQLModel):
115
+ result_id: str = Field(
116
+ max_length=128,
117
+ nullable=False,
118
+ primary_key=True,
119
+ description="ID of the analysis",
120
+ )
121
+ data_id: str = Field(max_length=128, nullable=False, description="ID of the data")
122
+ analysis: int = Field(
123
+ nullable=False,
124
+ foreign_key="analysis_catalog.id",
125
+ description="Reference to the analysis",
126
+ )
127
+ task_id: str = Field(
128
+ max_length=128,
129
+ nullable=False,
130
+ sa_column_kwargs={"server_default": ""},
131
+ description="ID of task in celery",
132
+ )
133
+ args: str = Field(
134
+ max_length=10240,
135
+ default="{}",
136
+ nullable=False,
137
+ description="Args of the analysis",
138
+ sa_column_kwargs={"server_default": "{}"},
139
+ )
140
+ project_id: str = Field(
141
+ max_length=128,
142
+ nullable=False,
143
+ description="ID of project this analysis belong to",
144
+ )
145
+ user_email: Optional[str] = Field(max_length=256, nullable=True)
146
+
147
+
148
+ class AnalysisCatalogBase(SQLModel):
149
+ name: str = Field(nullable=False, description="Name of the analysis")
150
+ description: Optional[str] = Field(
151
+ max_length=4096, description="Detailed information about this analysis"
152
+ )
153
+
154
+
155
+ class AnalysisCatalog(AnalysisCatalogBase, table=True):
156
+ """
157
+ This table stores available analyses type
158
+ that the App supports.
159
+ """
160
+
161
+ __tablename__: ClassVar[Union[str, Callable[..., str]]] = "analysis_catalog"
162
+ id: int = Field(primary_key=True)
163
+ time_created: datetime = Field(
164
+ sa_column=Column(DateTime(timezone=True), server_default=func.now()),
165
+ description="Time added",
166
+ )
167
+ time_modified: datetime = Field(
168
+ sa_column=Column(
169
+ DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
170
+ ),
171
+ description="Time modified",
172
+ )
173
+ """A list of all analysis results of this type"""
174
+ results: List["AnalysisResult"] = Relationship(back_populates="analysis_orm")
175
+
176
+ __table_args__ = (UniqueConstraint("name", name="_name_analysis_uc"),)
177
+
178
+
179
+ class AnalysisResultRead(AnalysisResultBase):
180
+ """
181
+ A response model of an anlysis result
182
+ """
183
+
184
+ time_created: datetime
185
+ time_modified: datetime
186
+ task: Optional[TaskStatus]
187
+ result_data_status: Optional[str]
188
+
189
+
190
+ class AnalysisResult(AnalysisResultBase, table=True):
191
+ __tablename__: ClassVar[Union[str, Callable[..., str]]] = "analysis_result"
192
+ __table_args__ = (
193
+ ForeignKeyConstraint(
194
+ ["project_id"], ["project.id"], onupdate="CASCADE", ondelete="CASCADE"
195
+ ),
196
+ )
197
+ """
198
+ This table stores all analyses that had been produced for a data
199
+ The analysis reference points to the description of the analysis.
200
+ The result_id reference points to the detail result of the analysis
201
+ """
202
+ time_created: datetime = Field(
203
+ sa_column=Column(DateTime(timezone=True), server_default=func.now()),
204
+ description="Time added",
205
+ )
206
+ time_modified: datetime = Field(
207
+ sa_column=Column(
208
+ DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
209
+ ),
210
+ description="Time modified",
211
+ )
212
+ """An object of the analysis type"""
213
+ analysis_orm: AnalysisCatalog = Relationship(back_populates="results")
@@ -0,0 +1,35 @@
1
+ [tool.poetry]
2
+ name = "cdiam-cli"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["tungtp99 <tungtp181199@gmail.com>"]
6
+ readme = "README.md"
7
+ packages = [{include = "cdiam_cli"}]
8
+
9
+ [scripts]
10
+ cdiam-cli = "cdiam_cli.main:main_group"
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.9"
14
+ click = "^8.1.7"
15
+ pyyaml = "^6.0.1"
16
+ tabulate = "^0.9.0"
17
+
18
+
19
+ datetime = "^5.5"
20
+ numpy = "^1.24.0"
21
+ pandas = "^2.1.0"
22
+ pydantic = "1.10.2"
23
+ sqlmodel = "0.0.8"
24
+ typing-extensions = "4.3.0"
25
+ sqlalchemy = "1.4.41"
26
+ sqlalchemy2-stubs = "0.0.2a27"
27
+ pymysql = "1.0.2"
28
+ idna = "3.4"
29
+ [build-system]
30
+ requires = ["poetry-core"]
31
+ build-backend = "poetry.core.masonry.api"
32
+
33
+
34
+ [tool.poetry.scripts]
35
+ cdiam-cli = 'cdiam_cli.main:main_group'