monitor-app 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monitor_app/__init__.py +0 -0
- monitor_app/app.py +87 -0
- monitor_app/cli.py +180 -0
- monitor_app/config/config.py +95 -0
- monitor_app/csv/data.csv +5 -0
- monitor_app/csv/data_sub.csv +5 -0
- monitor_app/csv/data_sub_2.csv +5 -0
- monitor_app/csv/orders.csv +6 -0
- monitor_app/csv/products.csv +4 -0
- monitor_app/csv/users.csv +6 -0
- monitor_app/csv_to_db.py +85 -0
- monitor_app/instances/database.db +0 -0
- monitor_app/instances/test_database.db +0 -0
- monitor_app/static/css/bootstrap.min.css +6 -0
- monitor_app/static/favicon.ico +0 -0
- monitor_app/static/js/bootstrap.bundle.min.js +7 -0
- monitor_app/templates/base.html +45 -0
- monitor_app/templates/index.html +14 -0
- monitor_app/templates/table.html +40 -0
- monitor_app-0.1.0.dist-info/LICENSE +6 -0
- monitor_app-0.1.0.dist-info/METADATA +177 -0
- monitor_app-0.1.0.dist-info/RECORD +24 -0
- monitor_app-0.1.0.dist-info/WHEEL +4 -0
- monitor_app-0.1.0.dist-info/entry_points.txt +3 -0
monitor_app/__init__.py
ADDED
|
File without changes
|
monitor_app/app.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import os
|
|
3
|
+
import subprocess # ✅ `csv_to_db.py` を実行するために追加
|
|
4
|
+
from flask import Flask, render_template, abort
|
|
5
|
+
from flask_cors import CORS
|
|
6
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
7
|
+
from sqlalchemy import text
|
|
8
|
+
from monitor_app.config.config import (
|
|
9
|
+
SQLALCHEMY_DATABASE_URI,
|
|
10
|
+
SQLALCHEMY_TRACK_MODIFICATIONS,
|
|
11
|
+
ALLOWED_TABLES,
|
|
12
|
+
APP_TITLE,
|
|
13
|
+
HEADER_TEXT,
|
|
14
|
+
FOOTER_TEXT,
|
|
15
|
+
FAVICON_PATH, # ✅ Favicon を追加
|
|
16
|
+
TABLE_CELL_STYLES,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
app = Flask(__name__)
|
|
20
|
+
CORS(app)
|
|
21
|
+
|
|
22
|
+
# 設定を `config.py` から読み込む
|
|
23
|
+
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
|
|
24
|
+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = SQLALCHEMY_TRACK_MODIFICATIONS
|
|
25
|
+
|
|
26
|
+
db = SQLAlchemy(app)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.route("/")
|
|
30
|
+
def index():
|
|
31
|
+
"""トップページ"""
|
|
32
|
+
tables = list(ALLOWED_TABLES.keys()) # 📌 許可されたテーブルのみ表示
|
|
33
|
+
return render_template(
|
|
34
|
+
"index.html",
|
|
35
|
+
tables=tables,
|
|
36
|
+
app_title=APP_TITLE,
|
|
37
|
+
header_text=HEADER_TEXT,
|
|
38
|
+
footer_text=FOOTER_TEXT,
|
|
39
|
+
favicon_path=FAVICON_PATH, # ✅ Favicon を追加
|
|
40
|
+
title=APP_TITLE, # ✅ タイトルを設定
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.route("/table/<table_name>")
|
|
45
|
+
def show_table(table_name):
|
|
46
|
+
"""指定されたテーブルのデータを表示"""
|
|
47
|
+
if table_name not in ALLOWED_TABLES: # 📌 許可されていないテーブルは 404
|
|
48
|
+
abort(404)
|
|
49
|
+
|
|
50
|
+
table_info = ALLOWED_TABLES[table_name]
|
|
51
|
+
|
|
52
|
+
# 📌 `join` 設定があれば JOIN クエリを実行
|
|
53
|
+
query = (
|
|
54
|
+
text(table_info["join"])
|
|
55
|
+
if "join" in table_info
|
|
56
|
+
else text(f"SELECT * FROM {table_name}")
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
result = db.session.execute(query)
|
|
60
|
+
columns = result.keys()
|
|
61
|
+
data = [dict(zip(columns, row)) for row in result.fetchall()]
|
|
62
|
+
|
|
63
|
+
return render_template(
|
|
64
|
+
"table.html",
|
|
65
|
+
table_name=table_name,
|
|
66
|
+
columns=columns,
|
|
67
|
+
data=data,
|
|
68
|
+
cell_styles=TABLE_CELL_STYLES,
|
|
69
|
+
app_title=APP_TITLE,
|
|
70
|
+
header_text=HEADER_TEXT,
|
|
71
|
+
footer_text=FOOTER_TEXT,
|
|
72
|
+
favicon_path=FAVICON_PATH, # ✅ Favicon を追加
|
|
73
|
+
title=f"{table_name} - {APP_TITLE}", # ✅ タイトルを設定
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def run_server(host="0.0.0.0", port=9990, debug=False):
|
|
78
|
+
"""Flask Web アプリを起動"""
|
|
79
|
+
app.run(host=host, port=port, debug=debug)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
run_server()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
main()
|
monitor_app/cli.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import click
|
|
5
|
+
import toml
|
|
6
|
+
from monitor_app.app import run_server # `app.py` の `run_server()` を直接呼び出す
|
|
7
|
+
|
|
8
|
+
# TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "monitor_app")
|
|
9
|
+
PARENT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
10
|
+
MONITOR_APP_DIR = os.path.dirname(__file__)
|
|
11
|
+
CONFIG_DIR = os.path.join(MONITOR_APP_DIR, "config")
|
|
12
|
+
CSV_DIR = os.path.join(MONITOR_APP_DIR, "csv")
|
|
13
|
+
TEMPLATES_DIR = os.path.join(MONITOR_APP_DIR, "templates")
|
|
14
|
+
STATIC_DIR = os.path.join(MONITOR_APP_DIR, "static")
|
|
15
|
+
STATIC_CSS_DIR = os.path.join(STATIC_DIR, "css")
|
|
16
|
+
STATIC_JS_DIR = os.path.join(STATIC_DIR, "js")
|
|
17
|
+
PYPROJECT_TEMPLATE = os.path.join(MONITOR_APP_DIR, "pyproject.toml")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@click.group()
|
|
21
|
+
def cli():
|
|
22
|
+
"""Monitor App CLI ツール"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@click.command()
|
|
27
|
+
@click.argument("project_name")
|
|
28
|
+
def startproject(project_name):
|
|
29
|
+
"""新しい Monitor App プロジェクトを作成"""
|
|
30
|
+
project_path = os.path.abspath(project_name)
|
|
31
|
+
|
|
32
|
+
if os.path.exists(project_path):
|
|
33
|
+
click.echo(f"⚠️ 既に '{project_name}' が存在します!")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# 📂 プロジェクトフォルダ作成
|
|
37
|
+
os.makedirs(project_path)
|
|
38
|
+
|
|
39
|
+
# 📂 monitor_app アプリフォルダ作成
|
|
40
|
+
DEST_MONITOR_APP_DIR = os.path.join(project_path, "monitor_app")
|
|
41
|
+
DEST_PARENT_DIR = os.path.dirname(DEST_MONITOR_APP_DIR)
|
|
42
|
+
DEST_CONFIG_DIR = os.path.join(DEST_MONITOR_APP_DIR, "config")
|
|
43
|
+
DEST_CSV_DIR = os.path.join(DEST_MONITOR_APP_DIR, "csv")
|
|
44
|
+
DEST_INSTANCES_DIR = os.path.join(DEST_MONITOR_APP_DIR, "instances")
|
|
45
|
+
DEST_TEMPLATES_DIR = os.path.join(DEST_MONITOR_APP_DIR, "templates")
|
|
46
|
+
DEST_STATIC_DIR = os.path.join(DEST_MONITOR_APP_DIR, "static")
|
|
47
|
+
DEST_STATIC_CSS_DIR = os.path.join(DEST_STATIC_DIR, "css")
|
|
48
|
+
DEST_STATIC_JS_DIR = os.path.join(DEST_STATIC_DIR, "js")
|
|
49
|
+
os.makedirs(DEST_MONITOR_APP_DIR)
|
|
50
|
+
|
|
51
|
+
# 📂 CSV・インスタンスフォルダ作成
|
|
52
|
+
os.makedirs(DEST_CSV_DIR, exist_ok=True)
|
|
53
|
+
os.makedirs(DEST_CONFIG_DIR, exist_ok=True)
|
|
54
|
+
os.makedirs(DEST_INSTANCES_DIR, exist_ok=True)
|
|
55
|
+
os.makedirs(DEST_TEMPLATES_DIR, exist_ok=True)
|
|
56
|
+
os.makedirs(DEST_STATIC_JS_DIR, exist_ok=True)
|
|
57
|
+
os.makedirs(DEST_STATIC_CSS_DIR, exist_ok=True)
|
|
58
|
+
|
|
59
|
+
# 📄 `project_template` からファイルをコピー
|
|
60
|
+
monitor_app_files = ["app.py", "cli.py", "csv_to_db.py"]
|
|
61
|
+
for file in monitor_app_files:
|
|
62
|
+
src_path = os.path.join(MONITOR_APP_DIR, file)
|
|
63
|
+
dest_path = os.path.join(DEST_MONITOR_APP_DIR, file)
|
|
64
|
+
if os.path.exists(src_path):
|
|
65
|
+
shutil.copy(src_path, dest_path)
|
|
66
|
+
else:
|
|
67
|
+
click.echo(
|
|
68
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
config_files = ["config.py"]
|
|
72
|
+
for file in config_files:
|
|
73
|
+
src_path = os.path.join(CONFIG_DIR, file)
|
|
74
|
+
dest_path = os.path.join(DEST_CONFIG_DIR, file)
|
|
75
|
+
if os.path.exists(src_path):
|
|
76
|
+
shutil.copy(src_path, dest_path)
|
|
77
|
+
else:
|
|
78
|
+
click.echo(
|
|
79
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
templates_files = ["base.html", "index.html", "table.html"]
|
|
83
|
+
for file in templates_files:
|
|
84
|
+
src_path = os.path.join(TEMPLATES_DIR, file)
|
|
85
|
+
dest_path = os.path.join(DEST_TEMPLATES_DIR, file)
|
|
86
|
+
if os.path.exists(src_path):
|
|
87
|
+
shutil.copy(src_path, dest_path)
|
|
88
|
+
else:
|
|
89
|
+
click.echo(
|
|
90
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
css_files = ["bootstrap.min.css"]
|
|
94
|
+
for file in css_files:
|
|
95
|
+
src_path = os.path.join(STATIC_CSS_DIR, file)
|
|
96
|
+
dest_path = os.path.join(DEST_STATIC_CSS_DIR, file)
|
|
97
|
+
if os.path.exists(src_path):
|
|
98
|
+
shutil.copy(src_path, dest_path)
|
|
99
|
+
else:
|
|
100
|
+
click.echo(
|
|
101
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
js_files = ["bootstrap.bundle.min.js"]
|
|
105
|
+
for file in js_files:
|
|
106
|
+
src_path = os.path.join(STATIC_JS_DIR, file)
|
|
107
|
+
dest_path = os.path.join(DEST_STATIC_JS_DIR, file)
|
|
108
|
+
if os.path.exists(src_path):
|
|
109
|
+
shutil.copy(src_path, dest_path)
|
|
110
|
+
else:
|
|
111
|
+
click.echo(
|
|
112
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# 📄 Favicon コピー
|
|
116
|
+
favicon_src = os.path.join(MONITOR_APP_DIR, "static/favicon.ico")
|
|
117
|
+
favicon_dest = os.path.join(DEST_MONITOR_APP_DIR, "static/favicon.ico")
|
|
118
|
+
if os.path.exists(favicon_src):
|
|
119
|
+
shutil.copy(favicon_src, favicon_dest)
|
|
120
|
+
else:
|
|
121
|
+
click.echo(
|
|
122
|
+
"⚠️ favicon.ico が `project_template` に見つかりません。スキップします。"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
parent_files = ["pyproject.toml"]
|
|
126
|
+
for file in parent_files:
|
|
127
|
+
src_path = os.path.join(PARENT_DIR, file)
|
|
128
|
+
dest_path = os.path.join(DEST_PARENT_DIR, file)
|
|
129
|
+
if os.path.exists(src_path):
|
|
130
|
+
shutil.copy(src_path, dest_path)
|
|
131
|
+
else:
|
|
132
|
+
click.echo(
|
|
133
|
+
f"⚠️ {file} が `project_template` に見つかりません。スキップします。"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def run_command(command_list):
|
|
138
|
+
"""
|
|
139
|
+
📌 poetry がインストールされていれば `poetry run` を使用し、なければ `python` を使用
|
|
140
|
+
"""
|
|
141
|
+
if shutil.which("poetry"):
|
|
142
|
+
command_list.insert(0, "poetry")
|
|
143
|
+
command_list.insert(1, "run")
|
|
144
|
+
else:
|
|
145
|
+
command_list.insert(0, "python")
|
|
146
|
+
|
|
147
|
+
subprocess.run(command_list, check=True)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@click.command()
|
|
151
|
+
@click.option("--host", default="0.0.0.0", help="ホストアドレス")
|
|
152
|
+
@click.option("--port", default=9990, help="ポート番号")
|
|
153
|
+
@click.option("--csv", is_flag=True, help="CSV をデータベースに登録してから起動")
|
|
154
|
+
@click.option("--debug", is_flag=True, help="デバッグモードを有効化")
|
|
155
|
+
def runserver(host, port, csv, debug):
|
|
156
|
+
"""Flask Web アプリを起動"""
|
|
157
|
+
|
|
158
|
+
if csv:
|
|
159
|
+
click.echo("🔄 CSV をデータベースに登録中...")
|
|
160
|
+
run_command(["monitor_app/csv_to_db.py"])
|
|
161
|
+
click.echo("✅ CSV 登録完了!アプリを起動します...")
|
|
162
|
+
|
|
163
|
+
click.echo(f"🚀 Web アプリを {host}:{port} で起動")
|
|
164
|
+
run_server(host=host, port=port, debug=debug) # `run_server()` を直接呼び出す
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@click.command()
|
|
168
|
+
def import_csv():
|
|
169
|
+
"""CSV をデータベースにインポート"""
|
|
170
|
+
click.echo("📂 CSV をデータベースに登録中...")
|
|
171
|
+
run_command(["monitor_app/csv_to_db.py"])
|
|
172
|
+
click.echo("✅ CSV 登録完了!")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
cli.add_command(startproject)
|
|
176
|
+
cli.add_command(runserver)
|
|
177
|
+
cli.add_command(import_csv)
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
cli()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
# 📌 プロジェクトのルートディレクトリ
|
|
4
|
+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
5
|
+
|
|
6
|
+
# 📌 インスタンスディレクトリ(データベースの保存先)
|
|
7
|
+
INSTANCE_DIR = os.path.join(BASE_DIR, "instances")
|
|
8
|
+
os.makedirs(INSTANCE_DIR, exist_ok=True)
|
|
9
|
+
|
|
10
|
+
# 📌 使用するデータベースの種類(sqlite, mysql, postgresql)
|
|
11
|
+
DB_TYPE = "sqlite"
|
|
12
|
+
|
|
13
|
+
# 📌 SQLite のカスタムパス設定(None にするとデフォルトを使用)
|
|
14
|
+
CUSTOM_SQLITE_DB_PATH = None
|
|
15
|
+
|
|
16
|
+
# 📌 CSV ファイルの保存ディレクトリ
|
|
17
|
+
DEFAULT_CSV_DIR = os.path.join(BASE_DIR, "csv")
|
|
18
|
+
CUSTOM_CSV_DIR = None
|
|
19
|
+
|
|
20
|
+
CSV_DIR = os.path.abspath(CUSTOM_CSV_DIR) if CUSTOM_CSV_DIR else DEFAULT_CSV_DIR
|
|
21
|
+
os.makedirs(CSV_DIR, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
# 📌 各データベースの接続設定
|
|
24
|
+
if DB_TYPE == "sqlite":
|
|
25
|
+
if CUSTOM_SQLITE_DB_PATH:
|
|
26
|
+
DB_PATH = os.path.abspath(CUSTOM_SQLITE_DB_PATH)
|
|
27
|
+
else:
|
|
28
|
+
DB_PATH = os.path.join(INSTANCE_DIR, "database.db")
|
|
29
|
+
|
|
30
|
+
SQLALCHEMY_DATABASE_URI = f"sqlite:///{DB_PATH}"
|
|
31
|
+
|
|
32
|
+
elif DB_TYPE == "mysql":
|
|
33
|
+
MYSQL_USER = "root"
|
|
34
|
+
MYSQL_PASSWORD = "password"
|
|
35
|
+
MYSQL_HOST = "localhost"
|
|
36
|
+
MYSQL_PORT = "3306"
|
|
37
|
+
MYSQL_DB = "monitor_app"
|
|
38
|
+
|
|
39
|
+
SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}"
|
|
40
|
+
|
|
41
|
+
elif DB_TYPE == "postgresql":
|
|
42
|
+
POSTGRES_USER = "postgres"
|
|
43
|
+
POSTGRES_PASSWORD = "password"
|
|
44
|
+
POSTGRES_HOST = "localhost"
|
|
45
|
+
POSTGRES_PORT = "5432"
|
|
46
|
+
POSTGRES_DB = "monitor_app"
|
|
47
|
+
|
|
48
|
+
SQLALCHEMY_DATABASE_URI = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(f"Unsupported DB_TYPE: {DB_TYPE}")
|
|
52
|
+
|
|
53
|
+
# 📌 SQLAlchemy 設定
|
|
54
|
+
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
55
|
+
|
|
56
|
+
# 📌 許可されたテーブルのみを表示
|
|
57
|
+
ALLOWED_TABLES = {
|
|
58
|
+
"users": {"columns": ["id", "name", "email"], "primary_key": "id"},
|
|
59
|
+
"products": {"columns": ["id", "name", "price"], "primary_key": "id"},
|
|
60
|
+
"orders": {
|
|
61
|
+
"columns": ["id", "user_id", "product_id", "amount"],
|
|
62
|
+
"primary_key": "id",
|
|
63
|
+
"foreign_keys": {"user_id": "users.id", "product_id": "products.id"},
|
|
64
|
+
"join": """
|
|
65
|
+
SELECT orders.id, users.name AS user_name, products.name AS product_name, orders.amount
|
|
66
|
+
FROM orders
|
|
67
|
+
JOIN users ON orders.user_id = users.id
|
|
68
|
+
JOIN products ON orders.product_id = products.id
|
|
69
|
+
""",
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# 📌 ヘッダーとフッターの設定
|
|
74
|
+
APP_TITLE = "Monitor App"
|
|
75
|
+
HEADER_TEXT = "📊 Monitor Dashboard"
|
|
76
|
+
FOOTER_TEXT = "© 2025 Monitor App - Powered by Flask & Bootstrap"
|
|
77
|
+
FAVICON_PATH = "favicon.ico"
|
|
78
|
+
|
|
79
|
+
# 📌 **テーブルセルのスタイル**
|
|
80
|
+
TABLE_CELL_STYLES = {
|
|
81
|
+
"orders": {
|
|
82
|
+
"amount": {
|
|
83
|
+
"greater_than": {"value": 10, "class": "bg-danger text-white"},
|
|
84
|
+
"less_than": {"value": 5, "class": "bg-warning text-dark"},
|
|
85
|
+
"equal_to": {"value": 7, "class": "bg-success text-white"},
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"products": {
|
|
89
|
+
"price": {
|
|
90
|
+
"greater_than": {"value": 1000, "class": "bg-primary text-white"},
|
|
91
|
+
"less_than": {"value": 500, "class": "bg-info text-dark"},
|
|
92
|
+
"equal_to": {"value": 750, "class": "bg-secondary text-white"},
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}
|
monitor_app/csv/data.csv
ADDED
monitor_app/csv_to_db.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from sqlalchemy import (
|
|
4
|
+
create_engine,
|
|
5
|
+
MetaData,
|
|
6
|
+
Table,
|
|
7
|
+
Column,
|
|
8
|
+
Integer,
|
|
9
|
+
String,
|
|
10
|
+
Float,
|
|
11
|
+
ForeignKey,
|
|
12
|
+
)
|
|
13
|
+
from monitor_app.config.config import (
|
|
14
|
+
SQLALCHEMY_DATABASE_URI,
|
|
15
|
+
ALLOWED_TABLES,
|
|
16
|
+
CSV_DIR, # ✅ `CSV_DIR` を `config.py` から取得
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# データベースエンジンの作成
|
|
20
|
+
engine = create_engine(SQLALCHEMY_DATABASE_URI)
|
|
21
|
+
metadata = MetaData()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_tables():
|
|
25
|
+
"""ALLOWED_TABLES に基づいてテーブルを作成"""
|
|
26
|
+
for table_name, table_info in ALLOWED_TABLES.items():
|
|
27
|
+
columns = []
|
|
28
|
+
|
|
29
|
+
# プライマリーキーの追加
|
|
30
|
+
primary_key = table_info.get("primary_key")
|
|
31
|
+
|
|
32
|
+
# カラムの型を判定
|
|
33
|
+
for col in table_info["columns"]:
|
|
34
|
+
col_type = String(255) # デフォルトは String
|
|
35
|
+
|
|
36
|
+
if col == primary_key: # プライマリーキー
|
|
37
|
+
col_type = Integer
|
|
38
|
+
elif col.endswith("_id"): # ID系のカラムは Integer 型
|
|
39
|
+
col_type = Integer
|
|
40
|
+
elif "price" in col or "amount" in col: # 金額や数量系のカラムは Float 型
|
|
41
|
+
col_type = Float
|
|
42
|
+
|
|
43
|
+
col_def = Column(col, col_type, primary_key=(col == primary_key))
|
|
44
|
+
columns.append(col_def)
|
|
45
|
+
|
|
46
|
+
# 外部キーの設定
|
|
47
|
+
if "foreign_keys" in table_info:
|
|
48
|
+
for col, ref in table_info["foreign_keys"].items():
|
|
49
|
+
columns.append(Column(col, Integer, ForeignKey(ref)))
|
|
50
|
+
|
|
51
|
+
# テーブルを作成
|
|
52
|
+
Table(table_name, metadata, *columns, extend_existing=True)
|
|
53
|
+
|
|
54
|
+
metadata.create_all(engine) # データベースに適用
|
|
55
|
+
print("✅ テーブルを作成しました")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def import_csv_to_db():
|
|
59
|
+
"""CSV をデータベースにインポート"""
|
|
60
|
+
for file in os.listdir(CSV_DIR):
|
|
61
|
+
if file.endswith(".csv"):
|
|
62
|
+
table_name = os.path.splitext(file)[0]
|
|
63
|
+
if (
|
|
64
|
+
table_name in ALLOWED_TABLES
|
|
65
|
+
): # 📌 `ALLOWED_TABLES` に含まれるもののみ処理
|
|
66
|
+
file_path = os.path.join(CSV_DIR, file)
|
|
67
|
+
df = pd.read_csv(file_path)
|
|
68
|
+
|
|
69
|
+
# データ型を SQLAlchemy のカラム定義と合わせる
|
|
70
|
+
for col in df.columns:
|
|
71
|
+
if col in ALLOWED_TABLES[table_name]["columns"]:
|
|
72
|
+
if col.endswith("_id") or col == ALLOWED_TABLES[table_name].get(
|
|
73
|
+
"primary_key"
|
|
74
|
+
):
|
|
75
|
+
df[col] = df[col].astype("Int64") # Nullを考慮した整数型
|
|
76
|
+
elif "price" in col or "amount" in col:
|
|
77
|
+
df[col] = df[col].astype(float)
|
|
78
|
+
|
|
79
|
+
df.to_sql(table_name, con=engine, if_exists="replace", index=False)
|
|
80
|
+
print(f"✅ {table_name} に {len(df)} 件のデータを挿入しました")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
create_tables()
|
|
85
|
+
import_csv_to_db()
|
|
Binary file
|
|
File without changes
|