mtsql 1.7.202312151026__py3-none-any.whl → 1.8.202401021406__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.
mt/sql/base.py CHANGED
@@ -11,6 +11,7 @@ from mt.base import deprecated_func
11
11
 
12
12
  __all__ = [
13
13
  "frame_sql",
14
+ "indices",
14
15
  "run_func",
15
16
  "conn_ctx",
16
17
  "engine_execute",
@@ -19,6 +20,8 @@ __all__ = [
19
20
  "exec_sql",
20
21
  "list_schemas",
21
22
  "list_tables",
23
+ "list_views",
24
+ "table_exists",
22
25
  ]
23
26
 
24
27
 
@@ -26,6 +29,12 @@ def frame_sql(frame_name, schema: tp.Optional[str] = None):
26
29
  return frame_name if schema is None else "{}.{}".format(schema, frame_name)
27
30
 
28
31
 
32
+ def indices(df):
33
+ """Returns the list of named indices of the dataframe, ignoring any unnamed index."""
34
+ a = list(df.index.names)
35
+ return a if a != [None] else []
36
+
37
+
29
38
  # ----- functions dealing with sql queries to overcome OperationalError -----
30
39
 
31
40
 
@@ -200,60 +209,6 @@ def read_sql(
200
209
  return df
201
210
 
202
211
 
203
- @deprecated_func(
204
- "1.0",
205
- suggested_func="mt.sql.base.read_sql",
206
- removed_version="2.0",
207
- docstring_prefix=" ",
208
- )
209
- def read_sql_query(
210
- sql,
211
- engine,
212
- index_col=None,
213
- set_index_after=False,
214
- nb_trials: int = 3,
215
- logger: tp.Optional[logg.IndentedLoggerAdapter] = None,
216
- **kwargs
217
- ):
218
- """Read an SQL query with a number of trials to overcome OperationalError.
219
-
220
- Parameters
221
- ----------
222
- sql : str
223
- SQL query to be executed
224
- engine : sqlalchemy.engine.Engine
225
- connection engine to the server
226
- index_col: string or list of strings, optional, default: None
227
- Column(s) to set as index(MultiIndex). See :func:`pandas.read_sql_query`.
228
- set_index_after: bool
229
- whether to set index specified by index_col via the pandas.read_sql_query() function or
230
- after the function has been invoked
231
- nb_trials: int
232
- number of query trials
233
- logger: mt.logg.IndentedLoggerAdapter, optional
234
- logger for debugging
235
- kwargs: dict
236
- other keyword arguments to be passed directly to :func:`pandas.read_sql_query`
237
-
238
- See Also
239
- --------
240
- pandas.read_sql_query
241
- """
242
-
243
- df = read_sql(
244
- sql,
245
- engine,
246
- index_col=index_col,
247
- nb_trials=nb_trials,
248
- exception_handling="raise",
249
- logger=logger,
250
- **kwargs
251
- )
252
- if index_col is None or not set_index_after:
253
- return df
254
- return df.set_index(index_col, drop=True)
255
-
256
-
257
212
  def read_sql_table(
258
213
  table_name,
259
214
  engine,
@@ -358,3 +313,46 @@ def list_tables(engine, schema: tp.Optional[str] = None):
358
313
  list of all table names
359
314
  """
360
315
  return sa.inspect(engine).get_table_names(schema=schema)
316
+
317
+
318
+ def list_views(engine, schema: tp.Optional[str] = None):
319
+ """Lists all views of a given schema.
320
+
321
+ Parameters
322
+ ----------
323
+ engine : sqlalchemy.engine.Engine
324
+ connection engine to the server
325
+ schema: str, optional
326
+ a valid schema name returned from :func:`list_schemas`. Default to sqlalchemy
327
+
328
+ Returns
329
+ -------
330
+ list
331
+ list of all view names
332
+ """
333
+ return sa.inspect(engine).get_view_names(schema=schema)
334
+
335
+
336
+ def table_exists(
337
+ table_name,
338
+ engine,
339
+ schema: tp.Optional[str] = None,
340
+ ):
341
+ """Checks if a table exists.
342
+
343
+ Parameters
344
+ ----------
345
+ table_name: str
346
+ name of table
347
+ engine: sqlalchemy.engine.Engine
348
+ an sqlalchemy connection engine created by function `create_engine()`
349
+ schema: str or None
350
+ a valid schema name returned from `list_schemas()`
351
+
352
+ Returns
353
+ -------
354
+ retval: bool
355
+ whether a table or a view exists with the given name
356
+ """
357
+
358
+ return sa.inspect(engine).has_table(table_name, schema=schema)
mt/sql/psql.py CHANGED
@@ -16,12 +16,10 @@ __all__ = [
16
16
  "pg_get_locked_transactions",
17
17
  "pg_cancel_backend",
18
18
  "pg_cancel_all_backends",
19
- "indices",
20
19
  "compliance_check",
21
20
  "as_column_name",
22
21
  "to_sql",
23
22
  "rename_schema",
24
- "list_views",
25
23
  "list_matviews",
26
24
  "list_foreign_tables",
27
25
  "list_frames",
@@ -137,12 +135,6 @@ def pg_cancel_all_backends(
137
135
  # ----- functions dealing with sql queries to overcome OperationalError -----
138
136
 
139
137
 
140
- def indices(df):
141
- """Returns the list of named indices of the dataframe, ignoring any unnamed index."""
142
- a = list(df.index.names)
143
- return a if a != [None] else []
144
-
145
-
146
138
  def compliance_check(df: pd.DataFrame):
147
139
  """Checks if a dataframe is compliant to PSQL.
148
140
 
@@ -207,7 +199,7 @@ def to_sql(
207
199
  logger: tp.Optional[logg.IndentedLoggerAdapter] = None,
208
200
  **kwargs,
209
201
  ):
210
- """Writes records stored in a DataFrame to an SQL database.
202
+ """Writes records stored in a DataFrame to a PostgreSQL database.
211
203
 
212
204
  With a number of trials to overcome OperationalError.
213
205
 
@@ -391,42 +383,6 @@ def rename_schema(
391
383
  )
392
384
 
393
385
 
394
- def list_views(
395
- engine,
396
- schema: tp.Optional[str] = None,
397
- nb_trials: int = 3,
398
- logger: tp.Optional[logg.IndentedLoggerAdapter] = None,
399
- ):
400
- """Lists all views of a given schema.
401
-
402
- Parameters
403
- ----------
404
- engine: sqlalchemy.engine.Engine
405
- an sqlalchemy connection engine created by function `create_engine()`
406
- schema: str or None
407
- a valid schema name returned from `list_schemas()`
408
- nb_trials: int
409
- number of query trials
410
- logger: mt.logg.IndentedLoggerAdapter, optional
411
- logger for debugging
412
-
413
- Returns
414
- -------
415
- out: list
416
- list of all view names
417
- """
418
- if schema is None:
419
- query_str = "select distinct viewname from pg_views;"
420
- else:
421
- query_str = (
422
- "select distinct viewname from pg_views where schemaname='{}';".format(
423
- schema
424
- )
425
- )
426
- df = read_sql(query_str, engine, nb_trials=nb_trials, logger=logger)
427
- return df["viewname"].tolist()
428
-
429
-
430
386
  def list_matviews(
431
387
  engine,
432
388
  schema: tp.Optional[str] = None,
@@ -521,7 +477,7 @@ def list_frames(
521
477
  data = []
522
478
  for item in list_tables(engine, schema=schema):
523
479
  data.append((item, "table"))
524
- for item in list_views(engine, schema=schema, nb_trials=nb_trials, logger=logger):
480
+ for item in list_views(engine, schema=schema):
525
481
  data.append((item, "view"))
526
482
  for item in list_matviews(
527
483
  engine, schema=schema, nb_trials=nb_trials, logger=logger
@@ -990,11 +946,9 @@ def frame_exists(
990
946
  retval: bool
991
947
  whether a table or a view exists with the given name
992
948
  """
993
- if frame_name in list_tables(engine, schema=schema):
949
+ if table_exists(frame_name, engine, schema=schema):
994
950
  return True
995
- if frame_name in list_views(
996
- engine, schema=schema, nb_trials=nb_trials, logger=logger
997
- ):
951
+ if frame_name in list_views(engine, schema=schema):
998
952
  return True
999
953
  return frame_name in list_matviews(
1000
954
  engine, schema=schema, nb_trials=nb_trials, logger=logger
@@ -1041,9 +995,7 @@ def drop_frame(
1041
995
  nb_trials=nb_trials,
1042
996
  logger=logger,
1043
997
  )
1044
- if frame_name in list_views(
1045
- engine, schema=schema, nb_trials=nb_trials, logger=logger
1046
- ):
998
+ if frame_name in list_views(engine, schema=schema):
1047
999
  return drop_view(
1048
1000
  frame_name,
1049
1001
  engine,
mt/sql/redshift.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from mt import tp, logg
4
4
 
5
5
  from .base import *
6
+ from .psql import compliance_check
6
7
 
7
8
 
8
9
  __api__ = [
@@ -418,3 +419,110 @@ def drop_column(
418
419
  schema, table_name, column_name
419
420
  )
420
421
  exec_sql(query_str, engine, nb_trials=nb_trials, logger=logger)
422
+
423
+
424
+ # ----- functions dealing with sql queries to overcome OperationalError -----
425
+
426
+
427
+ def to_sql(
428
+ df,
429
+ name,
430
+ engine,
431
+ schema: tp.Optional[str] = None,
432
+ if_exists="fail",
433
+ nb_trials: int = 3,
434
+ logger: tp.Optional[logg.IndentedLoggerAdapter] = None,
435
+ **kwargs,
436
+ ):
437
+ """Writes records stored in a DataFrame to a Redshift database.
438
+
439
+ With a number of trials to overcome OperationalError.
440
+
441
+ Parameters
442
+ ----------
443
+ df : pandas.DataFrame
444
+ dataframe to be sent to the server
445
+ name : str
446
+ name of the table to be written to
447
+ engine : sqlalchemy.engine.Engine
448
+ connection engine to the server
449
+ schema: string, optional
450
+ Specify the schema. If None, use default schema.
451
+ if_exists: str
452
+ what to do when the table exists. Passed as-is to :func:`pandas.DataFrame.to_sql`.
453
+ nb_trials: int
454
+ number of query trials
455
+ logger: mt.logg.IndentedLoggerAdapter, optional
456
+ logger for debugging
457
+ kwargs : dict
458
+ keyword arguments passed as-is to :func:`pandas.DataFrame.to_sql`
459
+
460
+ Raises
461
+ ------
462
+ sqlalchemy.exc.ProgrammingError if the local and remote frames do not have the same structure
463
+
464
+ Notes
465
+ -----
466
+ The function takes as input a PSQL-compliant dataframe (see `compliance_check()`). It ignores
467
+ any input `index` or `index_label` keyword. Instead, it considers 2 cases. If the dataframe has
468
+ an index or indices, then the tuple of all indices is turned into the primary key. If not,
469
+ there is no primary key and no index is uploaded.
470
+
471
+ See Also
472
+ --------
473
+ pandas.DataFrame.to_sql()
474
+
475
+ """
476
+
477
+ if kwargs:
478
+ if "index" in kwargs:
479
+ raise ValueError(
480
+ "The `mt.sql.psql.to_sql()` function does not accept `index` as a keyword."
481
+ )
482
+ if "index_label" in kwargs:
483
+ raise ValueError(
484
+ "This `mt.sql.psql.to_sql()` function does not accept `index_label` as a keyword."
485
+ )
486
+
487
+ compliance_check(df)
488
+ frame_sql_str = frame_sql(name, schema=schema)
489
+
490
+ # if the remote frame does not exist, force `if_exists` to 'replace'
491
+ if not table_exists(name, engine, schema=schema):
492
+ if_exists = "replace"
493
+ local_indices = indices(df)
494
+
495
+ if local_indices:
496
+ df = df.reset_index(drop=False)
497
+ retval = run_func(
498
+ df.to_sql,
499
+ name,
500
+ engine,
501
+ schema=schema,
502
+ if_exists=if_exists,
503
+ index=False,
504
+ index_label=None,
505
+ nb_trials=nb_trials,
506
+ logger=logger,
507
+ **kwargs,
508
+ )
509
+
510
+ query_str = (
511
+ f"ALTER TABLE {frame_sql_str} ADD PRIMARY KEY ({','.join(local_indices)});"
512
+ )
513
+ exec_sql(query_str, engine, nb_trials=nb_trials, logger=logger)
514
+ else:
515
+ retval = run_func(
516
+ df.to_sql,
517
+ name,
518
+ engine,
519
+ schema=schema,
520
+ if_exists=if_exists,
521
+ index=False,
522
+ index_label=None,
523
+ nb_trials=nb_trials,
524
+ logger=logger,
525
+ **kwargs,
526
+ )
527
+
528
+ return retval
mt/sql/version.py CHANGED
@@ -1,11 +1,11 @@
1
- VERSION_YEAR = 2023
2
- VERSION_MONTH = int('12')
3
- VERSION_DAY = int('15')
4
- VERSION_HOUR = int('10')
5
- VERSION_MINUTE = int('26')
1
+ VERSION_YEAR = 2024
2
+ VERSION_MONTH = int('01')
3
+ VERSION_DAY = int('02')
4
+ VERSION_HOUR = int('14')
5
+ VERSION_MINUTE = int('06')
6
6
  MAJOR_VERSION = 1
7
- MINOR_VERSION = 7
8
- PATCH_VERSION = 202312151026
9
- version_date = '2023/12/15 10:26'
7
+ MINOR_VERSION = 8
8
+ PATCH_VERSION = 202401021406
9
+ version_date = '2024/01/02 14:06'
10
10
  version = '{}.{}.{}'.format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
11
11
  __all__ = ['MAJOR_VERSION', 'MINOR_VERSION', 'PATCH_VERSION', 'version_date', 'version']
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mtsql
3
- Version: 1.7.202312151026
3
+ Version: 1.8.202401021406
4
4
  Summary: Extra Python modules to deal with the interaction between pandas dataframes and remote SQL servers, for Minh-Tri Pham
5
5
  Home-page: https://github.com/inteplus/mtsql
6
6
  Author: ['Minh-Tri Pham']
@@ -0,0 +1,12 @@
1
+ mt/sql/__init__.py,sha256=b7zO50apZxt9Hg2eOkJhRLrXgACR8eS5b-Rphdn5qNQ,44
2
+ mt/sql/base.py,sha256=FoK42rNDxVkqwtGiDJGvW1g02FJtSoqkMtbPlxBElA0,9780
3
+ mt/sql/mysql.py,sha256=n2ENDctdUqZuSaDAcrqZYtPtawq3Wx4dOPCRsCB5Q4w,4894
4
+ mt/sql/psql.py,sha256=JBdwPKwk1G6QzOJGRjUbIcxKosjPQ_PADG2VwDxCR4M,66036
5
+ mt/sql/redshift.py,sha256=cDDSPUqy6vIyKcKUNsQnZLESGLhgC8NYGyyIWhFTrNk,14789
6
+ mt/sql/sqlite.py,sha256=T2ak_hhNi_zRfpg_gp8JhNHn7D2kl4i-Ey6-9ANMtz0,8678
7
+ mt/sql/version.py,sha256=gnhJJ-02mxUSXuUdVmoieNHefSOLt5692teFJSVykOA,396
8
+ mtsql-1.8.202401021406.dist-info/LICENSE,sha256=PojkRlQzTT5Eg6Nj03XoIVEefN3u8iiIFf1p4rqe_t4,1070
9
+ mtsql-1.8.202401021406.dist-info/METADATA,sha256=vlwY4ZndshEQJY6Ux4D1N907fFbl7b_7K1erSDkPkuA,589
10
+ mtsql-1.8.202401021406.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
11
+ mtsql-1.8.202401021406.dist-info/top_level.txt,sha256=WcqGFu9cV7iMZg09iam8eNxUvGpLSKKF2Iubf6SJVOo,3
12
+ mtsql-1.8.202401021406.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- mt/sql/__init__.py,sha256=b7zO50apZxt9Hg2eOkJhRLrXgACR8eS5b-Rphdn5qNQ,44
2
- mt/sql/base.py,sha256=sFr7O_Odfsf2AHr9kq3DXGCAFInCKgHSgLJaen507_I,9994
3
- mt/sql/mysql.py,sha256=n2ENDctdUqZuSaDAcrqZYtPtawq3Wx4dOPCRsCB5Q4w,4894
4
- mt/sql/psql.py,sha256=dRN4wH1uQ-deGb2M-3PbdUfjHQ_1fbPXnR_94X1KMIU,67364
5
- mt/sql/redshift.py,sha256=ADi1I_p8S5ZmzbLCclhxiUou5gXZrLY9Hd9yTMoprB4,11630
6
- mt/sql/sqlite.py,sha256=T2ak_hhNi_zRfpg_gp8JhNHn7D2kl4i-Ey6-9ANMtz0,8678
7
- mt/sql/version.py,sha256=PeQLGKevhlxMrPdW08UhN8-u9JaxBvwOZY0yCmqMGmc,396
8
- mtsql-1.7.202312151026.dist-info/LICENSE,sha256=PojkRlQzTT5Eg6Nj03XoIVEefN3u8iiIFf1p4rqe_t4,1070
9
- mtsql-1.7.202312151026.dist-info/METADATA,sha256=zHt5Uh3O0YP5pkRj-xT1UCmVC2HfZqmGfQqcs5GrO3c,589
10
- mtsql-1.7.202312151026.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
11
- mtsql-1.7.202312151026.dist-info/top_level.txt,sha256=WcqGFu9cV7iMZg09iam8eNxUvGpLSKKF2Iubf6SJVOo,3
12
- mtsql-1.7.202312151026.dist-info/RECORD,,