muaradata 1.0.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.
- muaradata/__init__.py +25 -0
- muaradata/api.py +504 -0
- muaradata-1.0.0.dist-info/METADATA +333 -0
- muaradata-1.0.0.dist-info/RECORD +8 -0
- muaradata-1.0.0.dist-info/WHEEL +5 -0
- muaradata-1.0.0.dist-info/entry_points.txt +2 -0
- muaradata-1.0.0.dist-info/licenses/LICENSE +19 -0
- muaradata-1.0.0.dist-info/top_level.txt +1 -0
muaradata/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .api import (
|
|
2
|
+
fetch_data,
|
|
3
|
+
run_query,
|
|
4
|
+
exec_query,
|
|
5
|
+
run_exec,
|
|
6
|
+
insert_data,
|
|
7
|
+
generate_table,
|
|
8
|
+
copy_table,
|
|
9
|
+
# avail_data,
|
|
10
|
+
# test_connection,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"fetch_data",
|
|
15
|
+
"run_query",
|
|
16
|
+
"exec_query",
|
|
17
|
+
"run_exec",
|
|
18
|
+
"insert_data",
|
|
19
|
+
"generate_table",
|
|
20
|
+
"copy_table",
|
|
21
|
+
# "avail_data",
|
|
22
|
+
# "test_connection",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
__version__ = "1.0.0"
|
muaradata/api.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
warnings.filterwarnings("ignore")
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from muaradata.credentials.loader import load_credentials
|
|
9
|
+
from muaradata.credentials.loader import load_tunnels
|
|
10
|
+
from muaradata.core.registry import REGISTRY
|
|
11
|
+
from muaradata.core.insert_registry import INSERT_REGISTRY
|
|
12
|
+
from muaradata.core.normalize import normalize_df
|
|
13
|
+
from muaradata.core.table_registry import TABLE_REGISTRY
|
|
14
|
+
from muaradata.core.retry import with_retry, RetryConfig
|
|
15
|
+
|
|
16
|
+
retry_cfg_run = RetryConfig(retries=5, delay=5, backoff=2)
|
|
17
|
+
retry_cfg_exec = RetryConfig(retries=1, delay=2, backoff=2)
|
|
18
|
+
|
|
19
|
+
console = Console(log_path=False)
|
|
20
|
+
|
|
21
|
+
def get_client(aim, exec=False):
|
|
22
|
+
creds = load_credentials(aim)
|
|
23
|
+
driver_name = creds["driver"] # contoh: clickhouse / postgresql
|
|
24
|
+
use_tunnel = creds["use_tunnel"] # contoh: tunnel_1
|
|
25
|
+
tunnels = 'None'
|
|
26
|
+
if use_tunnel:
|
|
27
|
+
tunnels = load_tunnels(creds["name_tunnel"])
|
|
28
|
+
|
|
29
|
+
driver_cls = REGISTRY[driver_name]
|
|
30
|
+
client = driver_cls(creds, tunnels)
|
|
31
|
+
client.connect()
|
|
32
|
+
return client, driver_name
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@with_retry(retry_cfg_run)
|
|
36
|
+
def fetch_data(query, aim="iriis_ch"):
|
|
37
|
+
client, driver = get_client(aim)
|
|
38
|
+
with console.status("[bold green]Executing Query") as status:
|
|
39
|
+
df = client.query(query)
|
|
40
|
+
|
|
41
|
+
# console.log("Data berhasil diambil!")
|
|
42
|
+
return df
|
|
43
|
+
|
|
44
|
+
def run_query(query, aim):
|
|
45
|
+
warnings.filterwarnings("default", category=DeprecationWarning)
|
|
46
|
+
warnings.warn(
|
|
47
|
+
"USANG: Fungsi [run_query] akan dihapus secara permanen pada rilis versi masa depan. "
|
|
48
|
+
"Silakan gunakan fungsi [fetch_data] untuk menjaga keberlanjutan kode Anda.",
|
|
49
|
+
category=DeprecationWarning,
|
|
50
|
+
stacklevel=2
|
|
51
|
+
)
|
|
52
|
+
# print("⚠️ DEPRECATED / USANG: Fungsi [run_query] akan dihapus secara permanen pada rilis versi masa depan. Silakan gunakan fungsi [fetch_data] untuk menjaga keberlanjutan kode Anda.")
|
|
53
|
+
return fetch_data(query=query, aim=aim)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@with_retry(retry_cfg_exec)
|
|
57
|
+
def exec_query(query, aim="iriis_ch"):
|
|
58
|
+
client, driver = get_client(aim)
|
|
59
|
+
with console.status("Executing Query") as status:
|
|
60
|
+
client.execute(query)
|
|
61
|
+
console.log("Query successfully executed!")
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def run_exec(query, aim):
|
|
65
|
+
warnings.filterwarnings("default", category=DeprecationWarning)
|
|
66
|
+
warnings.warn(
|
|
67
|
+
"USANG: Fungsi [run_exec] akan dihapus secara permanen pada rilis versi masa depan. "
|
|
68
|
+
"Silakan gunakan fungsi [exec_query] untuk menjaga keberlanjutan kode Anda.",
|
|
69
|
+
category=DeprecationWarning,
|
|
70
|
+
stacklevel=2
|
|
71
|
+
)
|
|
72
|
+
# print("⚠️ DEPRECATED / USANG: Fungsi [run_exec] akan dihapus secara permanen pada rilis versi masa depan. Silakan gunakan fungsi [exec_query] untuk menjaga keberlanjutan kode Anda.")
|
|
73
|
+
return exec_query(query, aim)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def insert_data(result, aim="iriis_ch", nama_table=None, nama_table_ch=None, nama_table_pg=None, kolom=None, truncate=False, rename_kolom=None):
|
|
77
|
+
if kolom is None:
|
|
78
|
+
raise ValueError("Missing required parameter: kolom")
|
|
79
|
+
|
|
80
|
+
if rename_kolom:
|
|
81
|
+
result = result.rename(columns=rename_kolom)
|
|
82
|
+
|
|
83
|
+
if kolom=='auto':
|
|
84
|
+
string_cols = [col for col in result.select_dtypes(include=['object']).columns if not any(isinstance(val, list) for val in result[col].dropna())]
|
|
85
|
+
|
|
86
|
+
array_cols = [col for col in result.select_dtypes(include=['object']).columns if any(isinstance(val, list) for val in result[col].dropna())]
|
|
87
|
+
|
|
88
|
+
kolom = {
|
|
89
|
+
'all': result.columns.tolist(),
|
|
90
|
+
'float': result.select_dtypes(include=['float64', 'float32']).columns.tolist(),
|
|
91
|
+
'integer': result.select_dtypes(include=['int64', 'int32', 'int16', 'int8']).columns.tolist(),
|
|
92
|
+
'datetime': result.select_dtypes(include=['datetime']).columns.tolist(),
|
|
93
|
+
# 'string': result.select_dtypes(include=['object']).columns.tolist(),
|
|
94
|
+
'string': string_cols,
|
|
95
|
+
'array': array_cols,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
client, driver = get_client(aim=aim)
|
|
99
|
+
inserter_cls = INSERT_REGISTRY[driver]
|
|
100
|
+
inserter = inserter_cls(client)
|
|
101
|
+
df = normalize_df(result, kolom)
|
|
102
|
+
if nama_table is None:
|
|
103
|
+
nama_table = nama_table_ch if driver=='clickhouse' else nama_table_pg
|
|
104
|
+
inserter.insert(df, nama_table, kolom["all"], truncate)
|
|
105
|
+
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def generate_table(
|
|
110
|
+
df,
|
|
111
|
+
aim="iriis_ch",
|
|
112
|
+
nama_table=None,
|
|
113
|
+
drop_table=True,
|
|
114
|
+
ingest_data=True,
|
|
115
|
+
**kwargs,
|
|
116
|
+
):
|
|
117
|
+
arguments = [df, aim, nama_table]
|
|
118
|
+
|
|
119
|
+
# Cek apakah ada yang bernilai None atau kosong
|
|
120
|
+
if not all(arg is not None for arg in arguments):
|
|
121
|
+
raise ValueError("Semua argumen wajib diisi dan tidak boleh None!")
|
|
122
|
+
|
|
123
|
+
client, driver = get_client(aim, exec=True)
|
|
124
|
+
generator = TABLE_REGISTRY[driver](client)
|
|
125
|
+
generator.generate(df, nama_table, drop_table=drop_table, **kwargs)
|
|
126
|
+
|
|
127
|
+
DRIVER_TABLE_ARG = {
|
|
128
|
+
"clickhouse": "nama_table_ch",
|
|
129
|
+
"postgresql": "nama_table_pg",
|
|
130
|
+
"mysql": "nama_table",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
table_kwargs = {arg: None for arg in DRIVER_TABLE_ARG.values()}
|
|
134
|
+
table_kwargs[DRIVER_TABLE_ARG[driver]] = nama_table
|
|
135
|
+
|
|
136
|
+
if ingest_data:
|
|
137
|
+
insert_data(result=df, aim=aim, kolom='auto', **table_kwargs)
|
|
138
|
+
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def copy_table(
|
|
143
|
+
nama_table_source: str,
|
|
144
|
+
aim_source: str,
|
|
145
|
+
nama_table_destination: str,
|
|
146
|
+
aim_destination: str,
|
|
147
|
+
drop_table: bool = True,
|
|
148
|
+
with_data: bool = False,
|
|
149
|
+
sample_rows: int = 100,
|
|
150
|
+
**kwargs,
|
|
151
|
+
):
|
|
152
|
+
"""
|
|
153
|
+
Menyalin struktur tabel dari satu server ke server lain, lintas platform
|
|
154
|
+
dan lintas database (PostgreSQL ↔ ClickHouse atau driver apapun yang
|
|
155
|
+
terdaftar di REGISTRY).
|
|
156
|
+
|
|
157
|
+
Cara kerja:
|
|
158
|
+
1. Ambil sample baris dari tabel source untuk membaca struktur kolom
|
|
159
|
+
dan tipe data (bukan full data, kecuali with_data=True).
|
|
160
|
+
2. Resolve driver destination dari kredensial aim_destination.
|
|
161
|
+
3. Buat tabel di destination via generate_table() dengan kolom yang
|
|
162
|
+
sudah dinormalisasi. Ingest data hanya jika with_data=True.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
nama_table_source: Nama tabel di server source, termasuk schema
|
|
166
|
+
jika diperlukan. Contoh: "public.tx_ticket".
|
|
167
|
+
aim_source: Alias koneksi source yang terdaftar di credentials.
|
|
168
|
+
Contoh: "iriis_pg", "iriis_ch".
|
|
169
|
+
nama_table_destination: Nama tabel yang akan dibuat di server destination.
|
|
170
|
+
Contoh: "staging_area.tx_ticket".
|
|
171
|
+
aim_destination: Alias koneksi destination.
|
|
172
|
+
Contoh: "iriis_ch", "iriis_pg".
|
|
173
|
+
drop_table: Jika True, tabel destination di-drop & dibuat ulang
|
|
174
|
+
jika sudah ada. Default: True.
|
|
175
|
+
with_data: Jika True, data sample juga ikut dimasukkan ke
|
|
176
|
+
tabel destination setelah struktur dibuat.
|
|
177
|
+
Jika False, hanya struktur yang disalin. Default: False.
|
|
178
|
+
sample_rows: Jumlah baris yang diambil dari source untuk membaca
|
|
179
|
+
struktur. Nilai lebih besar membantu deteksi tipe kolom
|
|
180
|
+
yang lebih akurat (misal kolom dengan banyak NULL).
|
|
181
|
+
Default: 100.
|
|
182
|
+
**kwargs: Parameter tambahan yang diteruskan ke generate_table()
|
|
183
|
+
(misal engine, order_by untuk ClickHouse MergeTree).
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True jika proses selesai tanpa error.
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
ValueError: Jika driver destination tidak dikenali atau tidak didukung.
|
|
190
|
+
Exception: Meneruskan exception dari fetch_data / generate_table.
|
|
191
|
+
|
|
192
|
+
Contoh penggunaan:
|
|
193
|
+
# Salin struktur saja, dari PostgreSQL ke ClickHouse
|
|
194
|
+
copy_table("public.tx_ticket", "iriis_pg", "staging_area.tx_ticket", "iriis_ch")
|
|
195
|
+
|
|
196
|
+
# Salin struktur + data sample, dari ClickHouse ke PostgreSQL
|
|
197
|
+
copy_table(
|
|
198
|
+
"staging_area.tx_ticket", "iriis_ch",
|
|
199
|
+
"public.tx_ticket", "iriis_pg",
|
|
200
|
+
with_data=True,
|
|
201
|
+
sample_rows=500,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Salin antar ClickHouse dengan opsi engine khusus
|
|
205
|
+
copy_table(
|
|
206
|
+
"db_a.tx_ticket", "ch_server_a",
|
|
207
|
+
"db_b.tx_ticket", "ch_server_b",
|
|
208
|
+
engine="MergeTree()",
|
|
209
|
+
order_by="id",
|
|
210
|
+
)
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# ---------------------------------------------------------------------------
|
|
215
|
+
# Pemetaan tipe DB → dtype Pandas
|
|
216
|
+
# Dipakai oleh _fetch_typed_sample untuk cast kolom setelah fetch.
|
|
217
|
+
# Tambahkan entri baru di sini jika ada tipe DB yang belum terdaftar.
|
|
218
|
+
# ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
_CH_TO_PANDAS = {
|
|
221
|
+
# Integer
|
|
222
|
+
"int8": "Int8", "int16": "Int16", "int32": "Int32", "int64": "Int64",
|
|
223
|
+
"uint8": "UInt8", "uint16": "UInt16","uint32": "UInt32","uint64": "UInt64",
|
|
224
|
+
# Float
|
|
225
|
+
"float32": "float32", "float64": "float64",
|
|
226
|
+
# String / text
|
|
227
|
+
"string": "object", "fixedstring": "object",
|
|
228
|
+
# Datetime
|
|
229
|
+
"datetime": "datetime64[ns]", "datetime64": "datetime64[ns]",
|
|
230
|
+
"date": "datetime64[ns]", "date32": "datetime64[ns]",
|
|
231
|
+
# Boolean
|
|
232
|
+
"bool": "bool", "uint8": "bool",
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
_PG_TO_PANDAS = {
|
|
236
|
+
# Integer
|
|
237
|
+
"smallint": "Int16", "integer": "Int32", "int": "Int32",
|
|
238
|
+
"bigint": "Int64", "smallserial": "Int16","serial": "Int32",
|
|
239
|
+
"bigserial":"Int64",
|
|
240
|
+
# Float / numeric
|
|
241
|
+
"real": "float32", "double precision": "float64", "float": "float64",
|
|
242
|
+
"numeric": "float64", "decimal": "float64",
|
|
243
|
+
# String / text
|
|
244
|
+
"character varying": "object", "varchar": "object",
|
|
245
|
+
"character": "object", "char": "object", "text": "object", "name": "object",
|
|
246
|
+
"uuid": "object",
|
|
247
|
+
# Datetime
|
|
248
|
+
"timestamp without time zone": "datetime64[ns]",
|
|
249
|
+
"timestamp with time zone": "datetime64[ns]",
|
|
250
|
+
"timestamp": "datetime64[ns]",
|
|
251
|
+
"date": "datetime64[ns]",
|
|
252
|
+
"time": "object",
|
|
253
|
+
# Boolean
|
|
254
|
+
"boolean": "bool",
|
|
255
|
+
# JSON / array → object
|
|
256
|
+
"json": "object", "jsonb": "object", "array": "object",
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _parse_ch_type(raw_type: str) -> str:
|
|
261
|
+
"""
|
|
262
|
+
Ekstrak tipe dasar dari tipe ClickHouse yang bisa nested.
|
|
263
|
+
|
|
264
|
+
Contoh:
|
|
265
|
+
"Nullable(Int64)" → "int64"
|
|
266
|
+
"LowCardinality(String)" → "string"
|
|
267
|
+
"Array(String)" → "array"
|
|
268
|
+
"Int32" → "int32"
|
|
269
|
+
"""
|
|
270
|
+
t = raw_type.strip()
|
|
271
|
+
# Unwrap Nullable(...) dan LowCardinality(...)
|
|
272
|
+
for wrapper in ("Nullable", "LowCardinality"):
|
|
273
|
+
if t.startswith(wrapper + "(") and t.endswith(")"):
|
|
274
|
+
t = t[len(wrapper) + 1 : -1].strip()
|
|
275
|
+
# Array → kembalikan sebagai "array" (ditangani sebagai object)
|
|
276
|
+
if t.startswith("Array("):
|
|
277
|
+
return "array"
|
|
278
|
+
return t.lower()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _cast_df_by_schema(
|
|
282
|
+
df: "pd.DataFrame",
|
|
283
|
+
schema: "pd.DataFrame",
|
|
284
|
+
type_col: str = "_pandas_type",
|
|
285
|
+
) -> "pd.DataFrame":
|
|
286
|
+
"""
|
|
287
|
+
Cast kolom DataFrame berdasarkan dtype target yang sudah disiapkan di skema.
|
|
288
|
+
|
|
289
|
+
Fungsi ini agnostik terhadap driver — tidak perlu tahu apakah sumber datanya
|
|
290
|
+
ClickHouse atau PostgreSQL. Yang diperlukan hanya kolom ``name`` (nama kolom
|
|
291
|
+
di DataFrame) dan kolom ``type_col`` (dtype Pandas target sebagai string)
|
|
292
|
+
yang sudah disiapkan oleh pemanggil sebelumnya.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
df: DataFrame yang akan di-cast.
|
|
296
|
+
schema: DataFrame hasil DESCRIBE / information_schema yang sudah
|
|
297
|
+
memiliki kolom ``name`` dan kolom dtype target (``type_col``).
|
|
298
|
+
type_col: Nama kolom di ``schema`` yang berisi dtype Pandas target.
|
|
299
|
+
Default: ``"_pandas_type"``.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
DataFrame baru dengan tipe kolom yang sudah di-cast.
|
|
303
|
+
Kolom yang dtype target-nya None atau tidak dikenal dibiarkan apa adanya.
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
df = df.copy()
|
|
307
|
+
for _, row in schema.iterrows():
|
|
308
|
+
col_name = row["name"]
|
|
309
|
+
pandas_dtype = row.get(type_col)
|
|
310
|
+
|
|
311
|
+
if col_name not in df.columns or pandas_dtype is None:
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
if pandas_dtype == "datetime64[ns]":
|
|
316
|
+
df[col_name] = pd.to_datetime(df[col_name], errors="coerce")
|
|
317
|
+
elif pandas_dtype in ("bool", "boolean"):
|
|
318
|
+
df[col_name] = df[col_name].astype("boolean")
|
|
319
|
+
else:
|
|
320
|
+
df[col_name] = df[col_name].astype(pandas_dtype)
|
|
321
|
+
except (ValueError, TypeError):
|
|
322
|
+
# Tipe tidak bisa di-cast (misal ada nilai korup) — biarkan apa adanya
|
|
323
|
+
console.log(
|
|
324
|
+
f"[bold yellow]Peringatan:[/] Gagal cast kolom [{col_name}] "
|
|
325
|
+
f"ke [{pandas_dtype}], dibiarkan sebagai [{df[col_name].dtype}]."
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return df
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _fetch_typed_sample(nama_table: str, aim: str, sample_rows: int) -> "pd.DataFrame":
|
|
332
|
+
"""
|
|
333
|
+
Mengambil sample baris dari tabel source dengan tipe kolom yang akurat.
|
|
334
|
+
|
|
335
|
+
Masalah utama yang ditangani:
|
|
336
|
+
Kolom bertipe ``Nullable(Int64)`` atau ``Nullable(Float64)`` di ClickHouse,
|
|
337
|
+
serta tipe numerik di PostgreSQL, sering dikembalikan sebagai ``object``
|
|
338
|
+
oleh driver karena Python tidak punya tipe native untuk "int yang bisa None".
|
|
339
|
+
Pandas fallback ke ``object`` untuk menampung campuran nilai dan None,
|
|
340
|
+
sehingga ``generate_table`` tidak bisa menyimpulkan tipe kolom yang benar.
|
|
341
|
+
|
|
342
|
+
Strategi penanganan per driver:
|
|
343
|
+
- **ClickHouse**: gunakan ``DESCRIBE TABLE`` yang mengembalikan nama dan
|
|
344
|
+
tipe kolom secara eksplisit, lalu cast DataFrame berdasarkan peta
|
|
345
|
+
``_CH_TO_PANDAS``. ``DESCRIBE TABLE`` adalah perintah native ClickHouse.
|
|
346
|
+
- **PostgreSQL**: gunakan ``information_schema.columns`` yang merupakan
|
|
347
|
+
standar SQL-92 dan tersedia di semua DB relasional (PG, MySQL, MSSQL).
|
|
348
|
+
Cast berdasarkan peta ``_PG_TO_PANDAS``.
|
|
349
|
+
- **Driver lain / fallback**: jika driver tidak dikenal, kembalikan
|
|
350
|
+
DataFrame apa adanya dan tampilkan peringatan.
|
|
351
|
+
|
|
352
|
+
Untuk semua driver, query data menggunakan ``WHERE col IS NOT NULL AND ...``
|
|
353
|
+
agar baris dengan NULL tidak ikut dalam sample — memperkecil kemungkinan
|
|
354
|
+
driver salah inferensi tipe karena nilai kosong.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
nama_table: Nama tabel termasuk schema. Contoh: ``"public.tx_ticket"``,
|
|
358
|
+
``"iriis_datainfo.capability_mapping"``.
|
|
359
|
+
aim: Alias koneksi yang terdaftar di credentials.
|
|
360
|
+
sample_rows: Jumlah baris sample untuk inferensi tipe. Nilai lebih besar
|
|
361
|
+
lebih aman untuk tabel dengan banyak kolom sparse.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
DataFrame dengan dtype kolom yang paling akurat yang bisa diperoleh.
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
creds = load_credentials(aim)
|
|
368
|
+
driver = creds.get("driver", "").lower()
|
|
369
|
+
|
|
370
|
+
# --- Langkah 1: ambil skema kolom dari DB ---
|
|
371
|
+
if driver == "clickhouse":
|
|
372
|
+
# DESCRIBE TABLE mengembalikan: name, type, default_type, default_expression, ...
|
|
373
|
+
df_schema = fetch_data(f"DESCRIBE TABLE {nama_table}", aim=aim)
|
|
374
|
+
# Normalisasi nama kolom output DESCRIBE (bisa beda versi CH)
|
|
375
|
+
df_schema = df_schema.rename(columns=lambda c: c.lower())
|
|
376
|
+
df_schema["_pandas_type"] = df_schema["type"].apply(
|
|
377
|
+
lambda t: _CH_TO_PANDAS.get(_parse_ch_type(t))
|
|
378
|
+
)
|
|
379
|
+
type_source = "clickhouse DESCRIBE TABLE"
|
|
380
|
+
|
|
381
|
+
elif driver == "postgresql":
|
|
382
|
+
# information_schema.columns: standar SQL-92, tersedia di PG / MySQL / MSSQL
|
|
383
|
+
# Pisahkan schema dan table_name dari "schema.table" jika ada
|
|
384
|
+
if "." in nama_table:
|
|
385
|
+
schema_name, table_name = nama_table.split(".", 1)
|
|
386
|
+
else:
|
|
387
|
+
schema_name, table_name = "public", nama_table
|
|
388
|
+
|
|
389
|
+
df_schema = fetch_data(
|
|
390
|
+
f"SELECT column_name AS name, data_type AS type "
|
|
391
|
+
f"FROM information_schema.columns "
|
|
392
|
+
f"WHERE table_schema = '{schema_name}' "
|
|
393
|
+
f" AND table_name = '{table_name}' "
|
|
394
|
+
f"ORDER BY ordinal_position",
|
|
395
|
+
aim=aim,
|
|
396
|
+
)
|
|
397
|
+
df_schema["_pandas_type"] = df_schema["type"].str.lower().map(_PG_TO_PANDAS)
|
|
398
|
+
type_source = "information_schema.columns"
|
|
399
|
+
|
|
400
|
+
else:
|
|
401
|
+
# Driver tidak dikenal — skip cast, kembalikan data apa adanya
|
|
402
|
+
console.log(
|
|
403
|
+
f"[bold yellow]Peringatan:[/] Driver [{driver}] tidak didukung untuk "
|
|
404
|
+
"inferensi tipe otomatis. DataFrame mungkin bertipe object semua."
|
|
405
|
+
)
|
|
406
|
+
return fetch_data(f"SELECT * FROM {nama_table} LIMIT {sample_rows}", aim=aim)
|
|
407
|
+
|
|
408
|
+
if df_schema.empty:
|
|
409
|
+
console.log(
|
|
410
|
+
f"[bold yellow]Peringatan:[/] Tidak dapat membaca skema [{nama_table}] "
|
|
411
|
+
f"via {type_source}. Tabel mungkin tidak ada."
|
|
412
|
+
)
|
|
413
|
+
return pd.DataFrame()
|
|
414
|
+
print(df_schema)
|
|
415
|
+
exit()
|
|
416
|
+
# --- Langkah 2: fetch sample dengan filter IS NOT NULL ---
|
|
417
|
+
columns = df_schema["name"].tolist()
|
|
418
|
+
not_null_clause = " AND ".join(f"{col} IS NOT NULL" for col in columns)
|
|
419
|
+
query_filtered = (
|
|
420
|
+
f"SELECT * FROM {nama_table} "
|
|
421
|
+
f"WHERE {not_null_clause} "
|
|
422
|
+
f"LIMIT {sample_rows}"
|
|
423
|
+
)
|
|
424
|
+
df = fetch_data(query_filtered, aim=aim)
|
|
425
|
+
|
|
426
|
+
if df.empty:
|
|
427
|
+
# Fallback: tabel kosong atau semua baris ada NULL di setidaknya satu kolom
|
|
428
|
+
console.log(
|
|
429
|
+
f"[bold yellow]Peringatan:[/] Sample bersih kosong untuk [{nama_table}]. "
|
|
430
|
+
"Fallback ke query tanpa filter."
|
|
431
|
+
)
|
|
432
|
+
df = fetch_data(f"SELECT * FROM {nama_table} LIMIT {sample_rows}", aim=aim)
|
|
433
|
+
|
|
434
|
+
if df.empty:
|
|
435
|
+
return df
|
|
436
|
+
|
|
437
|
+
# --- Langkah 3: cast tipe kolom berdasarkan skema DB ---
|
|
438
|
+
# Kedua driver sudah menyiapkan kolom _pandas_type di df_schema,
|
|
439
|
+
# berisi dtype Pandas target. _cast_df_by_schema membaca kolom itu
|
|
440
|
+
# lalu melakukan cast per kolom secara aman.
|
|
441
|
+
df = _cast_df_by_schema(df, df_schema, type_col="_pandas_type")
|
|
442
|
+
|
|
443
|
+
return df
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# --- [1] Ambil sample dari source dengan inferensi tipe yang akurat ---
|
|
447
|
+
console.log(f"[bold cyan]Membaca struktur tabel:[/] {nama_table_source} dari [{aim_source}]")
|
|
448
|
+
df = _fetch_typed_sample(nama_table_source, aim_source, sample_rows)
|
|
449
|
+
|
|
450
|
+
# --- [2] Resolve driver destination ---
|
|
451
|
+
creds_dest = load_credentials(aim_destination)
|
|
452
|
+
driver_dest = creds_dest.get("driver", "").lower()
|
|
453
|
+
|
|
454
|
+
# Routing: semua driver yang dikenal dipetakan ke argumen generate_table()
|
|
455
|
+
DRIVER_TABLE_ARG = {
|
|
456
|
+
"clickhouse": "nama_table_ch",
|
|
457
|
+
"postgresql": "nama_table_pg",
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if driver_dest not in DRIVER_TABLE_ARG:
|
|
461
|
+
raise ValueError(
|
|
462
|
+
f"Driver destination '{driver_dest}' tidak dikenali. "
|
|
463
|
+
f"Driver yang didukung: {list(DRIVER_TABLE_ARG.keys())}. "
|
|
464
|
+
"Daftarkan driver baru di DRIVER_TABLE_ARG jika diperlukan."
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Bangun keyword arguments untuk generate_table() secara dinamis.
|
|
468
|
+
# Tabel destination diisi sesuai driver, sisanya None.
|
|
469
|
+
# aim_ch / aim_pg juga diisi dengan aim_destination yang sebenarnya
|
|
470
|
+
# agar generate_table tidak connect ke server default yang salah.
|
|
471
|
+
DRIVER_AIM_ARG = {
|
|
472
|
+
"clickhouse": "aim_ch",
|
|
473
|
+
"postgresql": "aim_pg",
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
table_kwargs = {arg: None for arg in DRIVER_TABLE_ARG.values()}
|
|
477
|
+
table_kwargs[DRIVER_TABLE_ARG[driver_dest]] = nama_table_destination
|
|
478
|
+
|
|
479
|
+
aim_kwargs = {arg: None for arg in DRIVER_AIM_ARG.values()}
|
|
480
|
+
aim_kwargs[DRIVER_AIM_ARG[driver_dest]] = aim_destination
|
|
481
|
+
|
|
482
|
+
# --- [3] Buat struktur tabel di destination ---
|
|
483
|
+
console.log(
|
|
484
|
+
f"[bold cyan]Membuat tabel:[/] {nama_table_destination} "
|
|
485
|
+
f"di [{aim_destination}] (driver: {driver_dest}, "
|
|
486
|
+
f"drop_table={drop_table}, with_data={with_data})"
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
generate_table(
|
|
490
|
+
df,
|
|
491
|
+
**table_kwargs,
|
|
492
|
+
**aim_kwargs,
|
|
493
|
+
drop_table=drop_table,
|
|
494
|
+
ingest_data=with_data,
|
|
495
|
+
**kwargs,
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
console.log(
|
|
499
|
+
f"[bold green]Selesai![/] Struktur tabel [{nama_table_source}] "
|
|
500
|
+
f"berhasil disalin ke [{nama_table_destination}]."
|
|
501
|
+
)
|
|
502
|
+
return True
|
|
503
|
+
|
|
504
|
+
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: muaradata
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Pustaka Python untuk koneksi multi-database dengan fitur auto-retry dan SSH tunneling.
|
|
5
|
+
Author: Redian Barqy M
|
|
6
|
+
Author-email: rbm.eki@gmail.com
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: pandas
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: psycopg2-binary
|
|
16
|
+
Requires-Dist: clickhouse-driver
|
|
17
|
+
Requires-Dist: clickhouse-connect
|
|
18
|
+
Requires-Dist: mysql-connector-python
|
|
19
|
+
Requires-Dist: sshtunnel
|
|
20
|
+
Requires-Dist: tqdm
|
|
21
|
+
Requires-Dist: rich
|
|
22
|
+
Requires-Dist: tabulate
|
|
23
|
+
Requires-Dist: cryptography
|
|
24
|
+
Requires-Dist: platformdirs
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: requires-dist
|
|
32
|
+
Dynamic: requires-python
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
# 📚 MUARA DATA
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
**MUARA DATA** adalah pustaka Python yang memudahkan Anda **terhubung ke berbagai jenis database (ex. ClickHouse dan PostgreSQL)**, menjalankan query dengan **retry otomatis**, serta **memasukkan data DataFrame ke database** dengan konversi tipe data otomatis.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ⚡️ Fitur Utama
|
|
45
|
+
|
|
46
|
+
🔀 **Multi-Database Support**
|
|
47
|
+
Mendukung koneksi ke **ClickHouse**, **PostgreSQL**, dan **MySQL** melalui satu antarmuka sederhana.
|
|
48
|
+
|
|
49
|
+
🔄 **Retry Mechanism Otomatis**
|
|
50
|
+
Menangani gangguan koneksi dengan retry berulang tanpa menghentikan proses utama.
|
|
51
|
+
|
|
52
|
+
📥 **Insert Data Otomatis**
|
|
53
|
+
Mendukung konversi tipe data (float, int, string, array, datetime) dan opsi truncate sebelum insert.
|
|
54
|
+
|
|
55
|
+
🛡️ **Secure SSH Tunneling**
|
|
56
|
+
Mendukung koneksi aman ke database di jaringan privat melalui **SSH Tunnel** (Bastion Host) dengan autentikasi kata sandi maupun SSH key.
|
|
57
|
+
|
|
58
|
+
⚙️ **Konfigurasi Fleksibel**
|
|
59
|
+
Semua koneksi dikelola melalui file terenkripsi, tanpa perlu hard-code credential di kode.
|
|
60
|
+
|
|
61
|
+
🔑 **Credential Manager**
|
|
62
|
+
Kelola kredensial database dengan aman dan cepat langsung melalui terminal menggunakan Credential Manager.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🧩 Instalasi
|
|
67
|
+
|
|
68
|
+
### 1. Install Muara Data
|
|
69
|
+
```bash
|
|
70
|
+
pip install muaradata
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Daftarkan Credential Database
|
|
74
|
+
Jalankan perintah ```muaradb``` melalui terminal atau command-line.
|
|
75
|
+
|
|
76
|
+
Saat pertama kali dijalankan, aplikasi akan otomatis membuat:
|
|
77
|
+
|
|
78
|
+
- File `users.enc` dengan akun admin default
|
|
79
|
+
- File `credentials.enc` dengan contoh credential
|
|
80
|
+
- File `tunnels.enc` dengan contoh tunnel
|
|
81
|
+
- File `.key` untuk masing-masing file terenkripsi
|
|
82
|
+
|
|
83
|
+
#### Login Awal
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
Username : admin
|
|
87
|
+
Password : Admin123!
|
|
88
|
+
```
|
|
89
|
+
> ⚠️ Sangat disarankan untuk segera mengganti password admin default
|
|
90
|
+
> setelah login pertama.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Quick Start
|
|
95
|
+
```python
|
|
96
|
+
from muaradata import fetch_data
|
|
97
|
+
|
|
98
|
+
df = fetch_data("SELECT 1", aim="iriis_ch")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🚀 Cara Penggunaan
|
|
104
|
+
|
|
105
|
+
### 🔹 Import Library
|
|
106
|
+
```python
|
|
107
|
+
from muaradata import fetch_data, exec_query, insert_data, generate_table
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 🛠️ Fungsionalitas Utama
|
|
113
|
+
|
|
114
|
+
### 1. Menjalankan Query (`fetch_data`)
|
|
115
|
+
Menjalankan perintah SQL dan mengembalikan hasil sebagai `pandas.DataFrame`.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
df = fetch_data(
|
|
119
|
+
query="SELECT * FROM sandbox.test_insert",
|
|
120
|
+
aim="iriis_ch",
|
|
121
|
+
retry_delay=10,
|
|
122
|
+
max_retries=20
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Parameter:**
|
|
127
|
+
| Nama | Tipe | Default | Deskripsi |
|
|
128
|
+
|------|------|----------|------------|
|
|
129
|
+
| `query` | `str` | — | Perintah SQL yang akan dijalankan |
|
|
130
|
+
| `aim` | `str` | — | Nama koneksi sesuai `credentials` |
|
|
131
|
+
| `engine` | — | `None` | Objek koneksi database (optional) |
|
|
132
|
+
| `retry_delay` | `int` | `10` | Waktu tunggu antar percobaan koneksi (detik) (optional) |
|
|
133
|
+
| `max_retries` | `int` | `20` | Jumlah maksimum percobaan koneksi ulang (optional) |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### 2. Menjalankan Query Eksekusi (`exec_query`)
|
|
138
|
+
Digunakan untuk query yang **tidak mengembalikan data**, seperti `INSERT`, `UPDATE`, atau `DELETE`.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
result = exec_query("DELETE FROM sandbox.test_insert WHERE id = 10", aim="iriis_pg")
|
|
142
|
+
print(result) # "Query Executed Successfully"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### 3. Menyimpan Data ke Database (`insert_data`)
|
|
148
|
+
Fungsi insert_data digunakan untuk menyisipkan data dari sebuah DataFrame ke dalam database seperti ClickHouse atau PostgreSQL. Fungsi ini mendukung konversi tipe data secara otomatis berdasarkan definisi kolom yang diberikan melalui parameter kolom.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
insert_data(
|
|
152
|
+
result=df,
|
|
153
|
+
aim='database_prod',
|
|
154
|
+
nama_table='site.test_insert',
|
|
155
|
+
kolom=kolom,
|
|
156
|
+
truncate=False
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Contoh struktur parameter kolom:
|
|
161
|
+
```python
|
|
162
|
+
kolom = {
|
|
163
|
+
'all': ['id', 'nama', 'alamat'], # Daftar semua kolom yang akan disisipkan
|
|
164
|
+
'float': [], # Kolom bertipe float
|
|
165
|
+
'integer': ['id'], # Kolom bertipe integer
|
|
166
|
+
'string': ['nama', 'alamat'], # Kolom bertipe string
|
|
167
|
+
'array': ['combination_band'], # Kolom bertipe array
|
|
168
|
+
'datetime': [] # Kolom bertipe datetime
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
> Jika tidak ada kolom untuk tipe tertentu, daftar dapat dikosongkan. Struktur ini memungkinkan konversi tipe data yang konsisten sebelum data dimasukkan ke dalam tabel database.
|
|
173
|
+
|
|
174
|
+
**Parameter:**
|
|
175
|
+
| Nama | Tipe | Deskripsi |
|
|
176
|
+
|------|------|------------|
|
|
177
|
+
| `result` | `DataFrame` | Data yang akan disimpan |
|
|
178
|
+
| `nama_table` | `str` | Nama tabel |
|
|
179
|
+
| `aim` | `str` | Nama koneksi database |
|
|
180
|
+
| `kolom` | `dict` | Struktur kolom dan tipe datanya, tulis `auto` akan menyesuaikan dengan struktur dataframe |
|
|
181
|
+
| `truncate` | `bool` | Jika `True`, tabel akan dikosongkan sebelum insert (optional) |
|
|
182
|
+
|
|
183
|
+
**💡 Tips:**
|
|
184
|
+
> - Parameter `nama_table` harus diisi dengan nama tabel lengkap beserta schema-nya (misalnya: `schema.nama_tabel`).
|
|
185
|
+
> - Jika kedua parameter diisi, maka proses akan dilakukan pada kedua database secara bersamaan **dengan syarat** struktur tabel pada kedua database **identik**.
|
|
186
|
+
> - Jika struktur tabel berbeda, maka pemanggilan fungsi harus dilakukan secara terpisah untuk masing-masing database.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
# Contoh pemanggilan fungsi secara terpisah
|
|
190
|
+
|
|
191
|
+
kolom_tabel1 = {
|
|
192
|
+
'all': [],
|
|
193
|
+
'float': [],
|
|
194
|
+
'integer': [],
|
|
195
|
+
'string': [],
|
|
196
|
+
'datetime': []
|
|
197
|
+
}
|
|
198
|
+
insert_data(
|
|
199
|
+
result=df,
|
|
200
|
+
aim='database_dev',
|
|
201
|
+
nama_table='sandbox.test_insert',
|
|
202
|
+
kolom=kolom_tabel,
|
|
203
|
+
truncate=False
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
kolom_tabel2 = {
|
|
207
|
+
'all': [],
|
|
208
|
+
'float': [],
|
|
209
|
+
'integer': [],
|
|
210
|
+
'string': [],
|
|
211
|
+
'datetime': []
|
|
212
|
+
}
|
|
213
|
+
insert_data(
|
|
214
|
+
result=df,
|
|
215
|
+
aim='database_prod',
|
|
216
|
+
nama_table='site.test_insert',
|
|
217
|
+
kolom=kolom_tabel2,
|
|
218
|
+
truncate=True
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### 4. Membuat Table (`generate_table`)
|
|
225
|
+
|
|
226
|
+
Fungsi `generate_table` digunakan untuk membuat perintah DDL (Data Definition Language) secara otomatis berdasarkan struktur dan tipe data yang terdapat dalam objek `pandas.DataFrame`. Perintah DDL yang dihasilkan akan disesuaikan dengan format tipe data yang sesuai untuk masing-masing database, dan kemudian dijalankan untuk membuat tabel secara langsung.
|
|
227
|
+
|
|
228
|
+
> Catatan: Pastikan struktur `DataFrame` telah sesuai dengan kebutuhan skema tabel sebelum menjalankan fungsi ini
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
**Parameter:**
|
|
232
|
+
| Nama | Tipe | Default | Deskripsi |
|
|
233
|
+
|------|------|----------|------------|
|
|
234
|
+
| `df` | `DataFrame` | - | Data yang akan dibuatkan tabel dan disimpan |
|
|
235
|
+
| `aim` | `str` | - | Nama koneksi database |
|
|
236
|
+
| `nama_table` | `str` | - | Nama tabel yang akan dibuat |
|
|
237
|
+
| `drop_table` | `str` | `True` | Menghapus table jika sudah ada didalam database |
|
|
238
|
+
| `ingest_data` | `bool` | `True` | Proses pengisian data ke dalam tabel setelah pembuatan |
|
|
239
|
+
| `**kwargs` | `str` | - | Parameter tambahan yang diteruskan ke generator.generate() untuk kedua driver (misal engine, order_by untuk ClickHouse MergeTree; schema, dtype untuk PostgreSQL). |
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Returns:
|
|
243
|
+
True jika proses selesai tanpa error.
|
|
244
|
+
```
|
|
245
|
+
Contoh Penggunaan:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
generate_table(
|
|
249
|
+
df,
|
|
250
|
+
aim='database_prod',
|
|
251
|
+
nama_table='default.sample_table',
|
|
252
|
+
ingest_data=True
|
|
253
|
+
)
|
|
254
|
+
```
|
|
255
|
+
### Informasi Tambahan
|
|
256
|
+
|
|
257
|
+
- **Parameter `ingest_data`**
|
|
258
|
+
Isi `ingest_data = False` jika tidak ingin langsung melakukan proses pengisian data ke dalam tabel setelah pembuatan.
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### 4. Mirroring Table (`copy_table`)
|
|
262
|
+
|
|
263
|
+
Menyalin struktur tabel dari satu server ke server lain, lintas platform dan lintas database (PostgreSQL ↔ ClickHouse atau driver apapun yang terdaftar di REGISTRY).
|
|
264
|
+
|
|
265
|
+
Cara kerja:
|
|
266
|
+
> 1. Ambil sample baris dari tabel source untuk membaca struktur kolom dan tipe data (bukan full data, kecuali with_data=True).
|
|
267
|
+
> 2. Resolve driver destination dari kredensial aim_destination.
|
|
268
|
+
> 3. Buat tabel di destination via generate_table() dengan kolom yang
|
|
269
|
+
sudah dinormalisasi. Ingest data hanya jika with_data=True.
|
|
270
|
+
```
|
|
271
|
+
Args:
|
|
272
|
+
nama_table_source: Nama tabel di server source, termasuk schema
|
|
273
|
+
jika diperlukan. Contoh: "public.tx_ticket".
|
|
274
|
+
aim_source: Alias koneksi source yang terdaftar di credentials.
|
|
275
|
+
Contoh: "iriis_pg", "iriis_ch".
|
|
276
|
+
nama_table_destination: Nama tabel yang akan dibuat di server destination.
|
|
277
|
+
Contoh: "staging_area.tx_ticket".
|
|
278
|
+
aim_destination: Alias koneksi destination.
|
|
279
|
+
Contoh: "iriis_ch", "iriis_pg".
|
|
280
|
+
drop_table: Jika True, tabel destination di-drop & dibuat ulang
|
|
281
|
+
jika sudah ada. Default: True.
|
|
282
|
+
with_data: Jika True, data sample juga ikut dimasukkan ke
|
|
283
|
+
tabel destination setelah struktur dibuat.
|
|
284
|
+
Jika False, hanya struktur yang disalin. Default: False.
|
|
285
|
+
sample_rows: Jumlah baris yang diambil dari source untuk membaca
|
|
286
|
+
struktur. Nilai lebih besar membantu deteksi tipe kolom
|
|
287
|
+
yang lebih akurat (misal kolom dengan banyak NULL).
|
|
288
|
+
Default: 100.
|
|
289
|
+
**kwargs: Parameter tambahan yang diteruskan ke generate_table()
|
|
290
|
+
(misal engine, order_by untuk ClickHouse MergeTree).
|
|
291
|
+
```
|
|
292
|
+
```
|
|
293
|
+
Returns:
|
|
294
|
+
True jika proses selesai tanpa error.
|
|
295
|
+
```
|
|
296
|
+
```
|
|
297
|
+
Raises:
|
|
298
|
+
ValueError: Jika driver destination tidak dikenali atau tidak didukung.
|
|
299
|
+
Exception: Meneruskan exception dari fetch_data / generate_table.
|
|
300
|
+
```
|
|
301
|
+
Contoh penggunaan:
|
|
302
|
+
```
|
|
303
|
+
from muaradata import copy_table
|
|
304
|
+
|
|
305
|
+
# Salin struktur saja, dari PostgreSQL ke ClickHouse
|
|
306
|
+
copy_table("public.tx_ticket", "db_source", "staging_area.tx_ticket", "db_staging")
|
|
307
|
+
|
|
308
|
+
# Salin struktur + data sample, dari ClickHouse ke PostgreSQL
|
|
309
|
+
copy_table(
|
|
310
|
+
"staging_area.tx_ticket", "db_source",
|
|
311
|
+
"public.tx_ticket", "db_staging",
|
|
312
|
+
with_data=True,
|
|
313
|
+
sample_rows=500,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Salin antar ClickHouse dengan opsi engine khusus
|
|
317
|
+
copy_table(
|
|
318
|
+
"db_a.tx_ticket", "ch_server_a",
|
|
319
|
+
"db_b.tx_ticket", "ch_server_b",
|
|
320
|
+
engine="MergeTree()",
|
|
321
|
+
order_by="id",
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 🧾 Lisensi & Informasi
|
|
327
|
+
|
|
328
|
+
**Author :** Redian Barqy Muhammad
|
|
329
|
+
**Email :** rbm.eki@gmail.com
|
|
330
|
+
**Copyright :** © 2025 MuaraData Project
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
MIT License
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
muaradata/__init__.py,sha256=OH-B3kLB_W5Ukc5KhUWQyp7yAI-JoP5pIl6Hn36AP-g,412
|
|
2
|
+
muaradata/api.py,sha256=wi4wgVoxFe64iWbuIF40p3eAGgbO9hQuPjhLF1shFY8,21376
|
|
3
|
+
muaradata-1.0.0.dist-info/licenses/LICENSE,sha256=BS7RPv1aXEm_1QjVqh-InKVvFSgJyCbkHNUrFZRWvzI,1077
|
|
4
|
+
muaradata-1.0.0.dist-info/METADATA,sha256=P2LBUkaIYHmZ3xqonA3fF42X9a9fN4ZCFmOms9PqCVA,11149
|
|
5
|
+
muaradata-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
muaradata-1.0.0.dist-info/entry_points.txt,sha256=3-LXZ7azI8FcJMrTfGFxkazZFAhbbWFL_K_J2IvlUzs,59
|
|
7
|
+
muaradata-1.0.0.dist-info/top_level.txt,sha256=oZbSjDdH1AFhMRa-WVafvOOMaoih07vj2ZS1Ztjt0F8,10
|
|
8
|
+
muaradata-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2024 REDIAN BARQY M
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
muaradata
|