dynamic-tables 0.1.0__tar.gz → 0.2.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.
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynamic_tables
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A dynamic table creation and management library for PostgreSQL
5
- Home-page: https://github.com/scottrodeo/dynamic_tables
5
+ Home-page: https://github.com/scottrodeo/dynamic-tables-python
6
6
  Author: Scott Rodeo
7
7
  Author-email: signcactus@gmail.com
8
8
  License: UNKNOWN
9
- Project-URL: Author Website, https://patreon.com/scottrodeo
10
9
  Description: # Dynamic Tables (Python)
10
+
11
11
  A Python library for dynamically creating and managing PostgreSQL tables based on input data.
12
12
 
13
+ ---
14
+
13
15
  ## 🚀 Features
14
16
  - Automatically creates tables based on incoming data.
15
17
  - Supports dynamic naming conventions.
@@ -19,16 +21,19 @@ Description: # Dynamic Tables (Python)
19
21
  ---
20
22
 
21
23
  ## 📥 Installation
22
- ### **1️⃣ Install via GitHub (Recommended for Development)**
24
+
25
+ ### 1️⃣ Install via GitHub (Recommended for Development)
23
26
  Clone the repository and install in editable mode:
27
+
24
28
  ```bash
25
29
  git clone https://github.com/scottrodeo/dynamic-tables-python.git
26
30
  cd dynamic-tables-python
27
31
  pip install -e .
28
32
  ```
29
33
 
30
- ### **2️⃣ Install Directly via `pip`**
31
- The package is available on PyPI, you can install it with:
34
+ ### 2️⃣ Install Directly via `pip`
35
+ The package is available on PyPI, install it with:
36
+
32
37
  ```bash
33
38
  pip install dynamic-tables
34
39
  ```
@@ -36,22 +41,21 @@ Description: # Dynamic Tables (Python)
36
41
  ---
37
42
 
38
43
  ## 🏃‍♂️ Running the Example
39
- ### **1️⃣ Quick Run (Without Installation)**
40
- If you don't want to install the package, you can directly run the example script:
44
+
45
+ ### 1️⃣ Quick Run (Without Installation)
46
+ Run the example script directly:
47
+
41
48
  ```bash
42
49
  python3 examples/example.py
43
50
  ```
51
+
44
52
  💡 *This works because the script dynamically adjusts `sys.path`.*
45
53
 
46
- ### **2️⃣ Recommended (After Installation)**
47
- If you've installed the package (`pip install -e .`), simply run:
48
- ```bash
49
- python3 examples/example.py
50
- ```
51
54
 
52
55
  ---
53
56
 
54
57
  ## 📌 Example Usage
58
+
55
59
  Once installed, you can use `dynamic_tables` in your Python scripts:
56
60
 
57
61
  ```python
@@ -60,11 +64,12 @@ Description: # Dynamic Tables (Python)
60
64
  # Initialize the dynamic table manager
61
65
  tables = DynamicTables()
62
66
 
63
- # Example: Creating and inserting data dynamically
67
+ # Configure dynamic table properties
64
68
  tables.set_table_prefix("dtbl_")
65
69
  tables.set_columns("domain TEXT, category TEXT, lang TEXT")
66
70
  tables.set_dynamic_column("domain")
67
71
 
72
+ # Insert dynamic data
68
73
  tables.input("wikipedia.org", "cats", "en")
69
74
  tables.input("wikipedia.org", "dogs", "en")
70
75
 
@@ -75,6 +80,7 @@ Description: # Dynamic Tables (Python)
75
80
  ---
76
81
 
77
82
  ## 🛠️ Available Functions
83
+
78
84
  | Function | Description |
79
85
  |----------|-------------|
80
86
  | `set_columns("name TYPE, age TYPE")` | Define table schema |
@@ -83,20 +89,39 @@ Description: # Dynamic Tables (Python)
83
89
  | `input(value1, value2, ...)` | Insert a new row dynamically |
84
90
  | `show_tables()` | List all dynamically created tables |
85
91
  | `show_columns("table_name")` | Show column details for a specific table |
92
+ | `show_columns_all()` | Show column details for all tables |
86
93
  | `select_table("table_name")` | Retrieve all rows from a table |
87
94
  | `delete_tables()` | Drop all tables matching the prefix |
95
+ | `create_table("table_name", [("column1", "TYPE"), ("column2", "TYPE")])` | Create a table dynamically |
96
+ | `insert_data("table_name", {"column1": value1, "column2": value2})` | Insert data into a specific table |
97
+ | `get_tables()` | Retrieve a list of all tables in the database |
98
+ | `get_columns("table_name")` | Retrieve all columns for a given table |
99
+ | `get_table_rows("table_name")` | Retrieve all rows from a table |
100
+ | `connectHC("dbname", "user", "password", "host")` | Connect using hardcoded credentials |
101
+ | `connectJSON("config.json")` | Connect using credentials from a JSON config file |
102
+ | `connectENVS()` | Connect using environment variables |
103
+ | `connection_open("dbname", "user", "password", "host")` | Open a PostgreSQL database connection |
104
+ | `connection_close()` | Close the database connection |
105
+ | `status()` | Show the current configuration status |
106
+ | `show_db()` | Display the connected database name |
107
+ | `setup_logging(level, log_to_file)` | Configure logging settings |
108
+ | `change_log_level("LEVEL")` | Change the logging level at runtime |
109
+ | `show_version()` | Display the current version of the library |
88
110
 
89
111
  ---
90
112
 
91
113
  ## ⚡ Development
92
- ### **Running Tests**
93
- To run the test suite:
114
+
115
+ ### Running Tests
116
+ Run the test suite:
117
+
94
118
  ```bash
95
119
  pytest tests/
96
120
  ```
97
121
 
98
- ### **Linting**
122
+ ### Linting
99
123
  Ensure your code follows best practices:
124
+
100
125
  ```bash
101
126
  flake8 dynamic_tables/
102
127
  ```
@@ -104,7 +129,8 @@ Description: # Dynamic Tables (Python)
104
129
  ---
105
130
 
106
131
  ## 🤝 Contributing
107
- Contributions are welcome! If you'd like to improve `dynamic_tables`, follow these steps:
132
+
133
+ Contributions are welcome! To contribute:
108
134
  1. Fork the repository.
109
135
  2. Create a new branch (`git checkout -b feature-branch`).
110
136
  3. Commit your changes (`git commit -m "Added new feature"`).
@@ -114,18 +140,19 @@ Description: # Dynamic Tables (Python)
114
140
  ---
115
141
 
116
142
  ## 📄 License
143
+
117
144
  This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
118
145
 
119
146
  ---
120
147
 
121
148
  ## 🌎 Links
149
+
122
150
  - **GitHub Repository:** [Dynamic Tables (Python)](https://github.com/scottrodeo/dynamic-tables-python)
123
- - **Documentation:** *(To be added)*
124
151
  - **Issue Tracker:** [Report Issues](https://github.com/scottrodeo/dynamic-tables-python/issues)
125
152
 
126
153
  ---
127
154
 
128
- ### **🚀 Happy Coding!**
155
+ ### 🚀 Happy Coding!
129
156
 
130
157
 
131
158
  Platform: UNKNOWN
@@ -1,6 +1,9 @@
1
1
  # Dynamic Tables (Python)
2
+
2
3
  A Python library for dynamically creating and managing PostgreSQL tables based on input data.
3
4
 
5
+ ---
6
+
4
7
  ## 🚀 Features
5
8
  - Automatically creates tables based on incoming data.
6
9
  - Supports dynamic naming conventions.
@@ -10,16 +13,19 @@ A Python library for dynamically creating and managing PostgreSQL tables based o
10
13
  ---
11
14
 
12
15
  ## 📥 Installation
13
- ### **1️⃣ Install via GitHub (Recommended for Development)**
16
+
17
+ ### 1️⃣ Install via GitHub (Recommended for Development)
14
18
  Clone the repository and install in editable mode:
19
+
15
20
  ```bash
16
21
  git clone https://github.com/scottrodeo/dynamic-tables-python.git
17
22
  cd dynamic-tables-python
18
23
  pip install -e .
19
24
  ```
20
25
 
21
- ### **2️⃣ Install Directly via `pip`**
22
- The package is available on PyPI, you can install it with:
26
+ ### 2️⃣ Install Directly via `pip`
27
+ The package is available on PyPI, install it with:
28
+
23
29
  ```bash
24
30
  pip install dynamic-tables
25
31
  ```
@@ -27,22 +33,21 @@ pip install dynamic-tables
27
33
  ---
28
34
 
29
35
  ## 🏃‍♂️ Running the Example
30
- ### **1️⃣ Quick Run (Without Installation)**
31
- If you don't want to install the package, you can directly run the example script:
36
+
37
+ ### 1️⃣ Quick Run (Without Installation)
38
+ Run the example script directly:
39
+
32
40
  ```bash
33
41
  python3 examples/example.py
34
42
  ```
43
+
35
44
  💡 *This works because the script dynamically adjusts `sys.path`.*
36
45
 
37
- ### **2️⃣ Recommended (After Installation)**
38
- If you've installed the package (`pip install -e .`), simply run:
39
- ```bash
40
- python3 examples/example.py
41
- ```
42
46
 
43
47
  ---
44
48
 
45
49
  ## 📌 Example Usage
50
+
46
51
  Once installed, you can use `dynamic_tables` in your Python scripts:
47
52
 
48
53
  ```python
@@ -51,11 +56,12 @@ from dynamic_tables import DynamicTables
51
56
  # Initialize the dynamic table manager
52
57
  tables = DynamicTables()
53
58
 
54
- # Example: Creating and inserting data dynamically
59
+ # Configure dynamic table properties
55
60
  tables.set_table_prefix("dtbl_")
56
61
  tables.set_columns("domain TEXT, category TEXT, lang TEXT")
57
62
  tables.set_dynamic_column("domain")
58
63
 
64
+ # Insert dynamic data
59
65
  tables.input("wikipedia.org", "cats", "en")
60
66
  tables.input("wikipedia.org", "dogs", "en")
61
67
 
@@ -66,6 +72,7 @@ tables.show_tables()
66
72
  ---
67
73
 
68
74
  ## 🛠️ Available Functions
75
+
69
76
  | Function | Description |
70
77
  |----------|-------------|
71
78
  | `set_columns("name TYPE, age TYPE")` | Define table schema |
@@ -74,20 +81,39 @@ tables.show_tables()
74
81
  | `input(value1, value2, ...)` | Insert a new row dynamically |
75
82
  | `show_tables()` | List all dynamically created tables |
76
83
  | `show_columns("table_name")` | Show column details for a specific table |
84
+ | `show_columns_all()` | Show column details for all tables |
77
85
  | `select_table("table_name")` | Retrieve all rows from a table |
78
86
  | `delete_tables()` | Drop all tables matching the prefix |
87
+ | `create_table("table_name", [("column1", "TYPE"), ("column2", "TYPE")])` | Create a table dynamically |
88
+ | `insert_data("table_name", {"column1": value1, "column2": value2})` | Insert data into a specific table |
89
+ | `get_tables()` | Retrieve a list of all tables in the database |
90
+ | `get_columns("table_name")` | Retrieve all columns for a given table |
91
+ | `get_table_rows("table_name")` | Retrieve all rows from a table |
92
+ | `connectHC("dbname", "user", "password", "host")` | Connect using hardcoded credentials |
93
+ | `connectJSON("config.json")` | Connect using credentials from a JSON config file |
94
+ | `connectENVS()` | Connect using environment variables |
95
+ | `connection_open("dbname", "user", "password", "host")` | Open a PostgreSQL database connection |
96
+ | `connection_close()` | Close the database connection |
97
+ | `status()` | Show the current configuration status |
98
+ | `show_db()` | Display the connected database name |
99
+ | `setup_logging(level, log_to_file)` | Configure logging settings |
100
+ | `change_log_level("LEVEL")` | Change the logging level at runtime |
101
+ | `show_version()` | Display the current version of the library |
79
102
 
80
103
  ---
81
104
 
82
105
  ## ⚡ Development
83
- ### **Running Tests**
84
- To run the test suite:
106
+
107
+ ### Running Tests
108
+ Run the test suite:
109
+
85
110
  ```bash
86
111
  pytest tests/
87
112
  ```
88
113
 
89
- ### **Linting**
114
+ ### Linting
90
115
  Ensure your code follows best practices:
116
+
91
117
  ```bash
92
118
  flake8 dynamic_tables/
93
119
  ```
@@ -95,7 +121,8 @@ flake8 dynamic_tables/
95
121
  ---
96
122
 
97
123
  ## 🤝 Contributing
98
- Contributions are welcome! If you'd like to improve `dynamic_tables`, follow these steps:
124
+
125
+ Contributions are welcome! To contribute:
99
126
  1. Fork the repository.
100
127
  2. Create a new branch (`git checkout -b feature-branch`).
101
128
  3. Commit your changes (`git commit -m "Added new feature"`).
@@ -105,16 +132,17 @@ Contributions are welcome! If you'd like to improve `dynamic_tables`, follow the
105
132
  ---
106
133
 
107
134
  ## 📄 License
135
+
108
136
  This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
109
137
 
110
138
  ---
111
139
 
112
140
  ## 🌎 Links
141
+
113
142
  - **GitHub Repository:** [Dynamic Tables (Python)](https://github.com/scottrodeo/dynamic-tables-python)
114
- - **Documentation:** *(To be added)*
115
143
  - **Issue Tracker:** [Report Issues](https://github.com/scottrodeo/dynamic-tables-python/issues)
116
144
 
117
145
  ---
118
146
 
119
- ### **🚀 Happy Coding!**
147
+ ### 🚀 Happy Coding!
120
148
 
@@ -0,0 +1,336 @@
1
+ # this is dynamic_tables.py
2
+
3
+ import psycopg2, re, logging
4
+ from psycopg2 import sql
5
+
6
+ class DynamicTables:
7
+
8
+ def __init__(self):
9
+ """Initialize the class by calling self.initialize() to set up default values."""
10
+ self.initialize()
11
+
12
+ def initialize(self):
13
+ """Sets up default attributes for the table structure."""
14
+ self.column_list = []
15
+ self.column_dynamic = ''
16
+ self.table_prefix = 'dtbl_'
17
+ self.table_name_dynamic = ''
18
+ self.version = '0.1.2'
19
+ """Initialize with default logging level (CRITICAL) and no file logging."""
20
+ self.log_file_handler = None
21
+ self.setup_logging(level=logging.CRITICAL, log_to_file=False)
22
+
23
+ def setup_logging(self, level=logging.CRITICAL, log_to_file=False):
24
+ """
25
+ Configures logging level and optionally enables file logging.
26
+
27
+ Args:
28
+ level (int): Logging level (e.g., logging.DEBUG, logging.INFO).
29
+ log_to_file (bool): Enable or disable file logging.
30
+ """
31
+ logging.basicConfig(
32
+ level=level,
33
+ format="%(asctime)s [%(levelname)s] %(message)s",
34
+ handlers=[logging.StreamHandler()] # Console only by default
35
+ )
36
+
37
+ # Enable file logging if requested
38
+ if log_to_file and not self.log_file_handler:
39
+ self.log_file_handler = logging.FileHandler("dynamic_tables.log")
40
+ self.log_file_handler.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
41
+ logging.getLogger().addHandler(self.log_file_handler)
42
+
43
+ # Disable file logging if requested
44
+ elif not log_to_file and self.log_file_handler:
45
+ logging.getLogger().removeHandler(self.log_file_handler)
46
+ self.log_file_handler.close()
47
+ self.log_file_handler = None
48
+
49
+ logging.info(f"Logging level set to {logging.getLevelName(level)}")
50
+ if log_to_file:
51
+ logging.info("File logging enabled (dynamic_tables.log)")
52
+
53
+ def change_log_level(self, level_name):
54
+ """
55
+ Change the logging level at runtime.
56
+ Args:
57
+ level_name (str): "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
58
+ """
59
+ level = getattr(logging, level_name.upper(), logging.CRITICAL)
60
+ logging.getLogger().setLevel(level)
61
+ logging.info(f"Log level changed to {level_name.upper()}")
62
+
63
+ def set_columns(self, input_columns):
64
+ """Sets the columns for table creation based on user input."""
65
+ self.column_list = []
66
+ for column in input_columns.split(','):
67
+ column = column.strip()
68
+ parts = column.split(maxsplit=1)
69
+ if len(parts) == 2:
70
+ self.column_list.append((parts[0], parts[1]))
71
+ else:
72
+ logging.warning(f"Invalid column format: '{column}'")
73
+
74
+ def set_table_prefix(self, input_prefix):
75
+ """Defines a custom table name prefix."""
76
+ self.table_prefix = input_prefix
77
+
78
+ def set_dynamic_column(self, input_column):
79
+ """Sets the column that determines dynamic table names."""
80
+ self.column_dynamic = input_column
81
+
82
+ def connection_open(self, dbname, user, password, host):
83
+ """Opens a connection to the PostgreSQL database and initializes the cursor."""
84
+ self.conn = psycopg2.connect(
85
+ dbname=dbname,
86
+ user=user,
87
+ password=password,
88
+ host=host
89
+ )
90
+ self.cur = self.conn.cursor()
91
+
92
+ def connectHC(self, database, user, password, host):
93
+ """Helper function to open a connection using hardcoded credentials."""
94
+ self.connection_open(database, user, password, host)
95
+
96
+ def connectJSON(self, config_path="config.json"):
97
+ """Opens a database connection using credentials from a JSON config file."""
98
+ import json
99
+ with open(config_path) as config_file:
100
+ config = json.load(config_file)
101
+ database=config["database"]
102
+ user=config["user"]
103
+ password=config["password"]
104
+ host=config["host"]
105
+ self.connection_open(database, user, password, host)
106
+
107
+ def connectENVS(self):
108
+ """Opens a database connection using environment variables."""
109
+ import os
110
+ database=os.getenv("DTABLES_ENVS_PGSQL_DATABASE")
111
+ user=os.getenv("DTABLES_ENVS_PGSQL_USER")
112
+ password=os.getenv("DTABLES_ENVS_PGSQL_PASSWORD")
113
+ host=os.getenv("DTABLES_ENVS_PGSQL_HOST")
114
+ self.connection_open(database, user, password, host)
115
+
116
+ def connection_close(self):
117
+ """Closes the database cursor and connection."""
118
+ self.cur.close()
119
+ self.conn.close()
120
+
121
+ def close(self):
122
+ """Alias for connection_close() to improve readability."""
123
+ self.connection_close()
124
+
125
+ def get_tables(self):
126
+ """Returns a list of all table names in the public schema."""
127
+ try:
128
+ self.cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';")
129
+ tables = self.cur.fetchall()
130
+
131
+ if tables:
132
+ table_list = [table[0] for table in tables] # Convert to a simple list of table names
133
+ logging.info(f"Found {len(table_list)} tables.")
134
+ logging.debug(f"Tables: {table_list}")
135
+ return table_list
136
+ else:
137
+ logging.info("No tables found.")
138
+ return [] # Empty list instead of None for consistency
139
+
140
+ except Exception as e:
141
+ logging.error(f"Error retrieving tables: {e}", exc_info=True)
142
+ return None
143
+
144
+ def get_columns(self, table_name):
145
+ """Returns a list of all columns and their data types for a given table."""
146
+ try:
147
+ query = sql.SQL("""
148
+ SELECT column_name, data_type
149
+ FROM information_schema.columns
150
+ WHERE table_name = %s
151
+ ORDER BY ordinal_position;
152
+ """)
153
+ self.cur.execute(query, (table_name,))
154
+ columns = self.cur.fetchall()
155
+
156
+ if columns:
157
+ logging.info(f"Retrieved {len(columns)} columns from '{table_name}'.")
158
+ return columns
159
+ else:
160
+ logging.warning(f"No columns found for table '{table_name}' or the table does not exist.")
161
+ return []
162
+
163
+ except Exception as e:
164
+ logging.error(f"Error retrieving columns for '{table_name}': {e}", exc_info=True)
165
+ return None
166
+
167
+ def get_table_rows(self, table_name):
168
+ """Retrieves all rows from the specified table and returns them."""
169
+ try:
170
+ query = sql.SQL("SELECT * FROM {}").format(sql.Identifier(table_name))
171
+ self.cur.execute(query)
172
+ rows = self.cur.fetchall()
173
+
174
+ if rows:
175
+ logging.info(f"Retrieved {len(rows)} rows from '{table_name}'.")
176
+ logging.debug(f"Rows from '{table_name}': {rows}")
177
+ return rows # Returns data for other functions
178
+ else:
179
+ logging.info(f"Table '{table_name}' exists but has no data.")
180
+ return []
181
+
182
+ except Exception as e:
183
+ logging.error(f"Error retrieving rows from '{table_name}': {e}", exc_info=True)
184
+ return None
185
+
186
+ def show_db(self):
187
+ """Displays the name of the current database."""
188
+ self.cur.execute("SELECT current_database();")
189
+ db_name = self.cur.fetchone()[0]
190
+ logging.info(f"Connected to database: {db_name}")
191
+
192
+ def show_tables(self):
193
+ """Prints a list of all table names (uses get_tables)."""
194
+ tables = self.get_tables()
195
+
196
+ if tables:
197
+ print("Tables in the database:")
198
+ for table in tables:
199
+ print(f" - {table}")
200
+ else:
201
+ print("No tables found or an error occurred.")
202
+
203
+ def show_columns(self, table_name):
204
+ """Prints column details for human readability (uses get_columns)."""
205
+ columns = self.get_columns(table_name)
206
+
207
+ if columns:
208
+ print(f"Columns in '{table_name}':")
209
+ for name, dtype in columns:
210
+ print(f" - {name}: {dtype}")
211
+ else:
212
+ print(f"No columns found for '{table_name}' or the table does not exist.")
213
+
214
+ def show_columns_all(self):
215
+ """Lists column details for all tables."""
216
+ self.cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';")
217
+ for table in self.cur.fetchall():
218
+ self.show_columns(table[0])
219
+
220
+ def show_table(self, table_name):
221
+ """Displays all rows from the specified table (uses get_table_rows)."""
222
+ rows = self.get_table_rows(table_name)
223
+
224
+ if rows:
225
+ print(f"Rows in '{table_name}':")
226
+ for row in rows:
227
+ print(f" - {row}")
228
+ else:
229
+ print(f"Table '{table_name}' exists but has no data or an error occurred.")
230
+
231
+ def show_version(self):
232
+ """Show the current software version"""
233
+ print(self.version)
234
+
235
+ def select_table(self, table_name):
236
+ """Retrieves and prints all rows from the specified table."""
237
+ self.show_table(table_name)
238
+
239
+ def insert_data(self, table_name, data_dict):
240
+ """Inserts data into a dynamically created table."""
241
+ columns = data_dict.keys()
242
+ values = list(data_dict.values())
243
+
244
+ query = sql.SQL("""
245
+ INSERT INTO {} ({})
246
+ VALUES ({});
247
+ """).format(
248
+ sql.Identifier(table_name),
249
+ sql.SQL(', ').join(map(sql.Identifier, columns)),
250
+ sql.SQL(', ').join(sql.Placeholder() for _ in columns)
251
+ )
252
+
253
+ self.cur.execute(query, values)
254
+ self.conn.commit()
255
+
256
+ def create_table(self, table_name, columns):
257
+ """Creates a table dynamically if it does not already exist."""
258
+ column_definitions = sql.SQL(", ").join(
259
+ sql.SQL("{} {}").format(sql.Identifier(col_name), sql.SQL(col_type))
260
+ for col_name, col_type in columns
261
+ )
262
+
263
+ query = sql.SQL("""
264
+ CREATE TABLE IF NOT EXISTS {} (
265
+ id SERIAL PRIMARY KEY,
266
+ {}
267
+ );
268
+ """).format(sql.Identifier(table_name), column_definitions)
269
+
270
+ self.cur.execute(query)
271
+ self.conn.commit()
272
+
273
+ def delete_tables(self):
274
+ """Deletes all tables that match the current table prefix."""
275
+ try:
276
+ query = """
277
+ SELECT table_name FROM information_schema.tables
278
+ WHERE table_schema = 'public' AND table_name LIKE %s;
279
+ """
280
+ prefix_pattern = self.table_prefix + "%" # Dynamic prefix
281
+ logging.debug(f"Prefix pattern: {prefix_pattern}")
282
+
283
+ self.cur.execute(query, (prefix_pattern,))
284
+ tables = self.cur.fetchall()
285
+
286
+ if tables:
287
+ logging.info(f"Found {len(tables)} tables to delete.")
288
+ logging.debug(f"Tables to delete: {tables}")
289
+
290
+ for table in tables:
291
+ table_name = table[0]
292
+ logging.info(f"Dropping table: {table_name}")
293
+
294
+ drop_query = sql.SQL("DROP TABLE IF EXISTS {} CASCADE;").format(sql.Identifier(table_name))
295
+ self.cur.execute(drop_query)
296
+
297
+ self.conn.commit()
298
+ logging.info("Successfully deleted matching tables.")
299
+ else:
300
+ logging.info("No tables found matching the prefix.")
301
+
302
+ except Exception as e:
303
+ logging.error(f"Error deleting tables: {e}", exc_info=True)
304
+
305
+ def status(self):
306
+ """Logs the currently configured column settings."""
307
+ if self.column_list:
308
+ logging.info("Configured Columns:")
309
+ for column in self.column_list:
310
+ logging.info(f" - {column}")
311
+ else:
312
+ logging.info("No columns configured.")
313
+
314
+ logging.info(f"Dynamic Column: {self.column_dynamic if self.column_dynamic else 'None'}")
315
+
316
+ def format_table_name(self, input_column):
317
+ """Formats an input column name to create a valid table name."""
318
+ cleaned = re.sub(r'[^a-zA-Z0-9_]', '', input_column)
319
+ return self.table_prefix + cleaned
320
+
321
+ def input(self, *args):
322
+ """Handles data insertion by dynamically determining the table name and inserting values."""
323
+ column_dict = {}
324
+ self.table_name_dynamic = ''
325
+
326
+ for column_number, (column_name, _) in enumerate(self.column_list):
327
+ if column_name == self.column_dynamic:
328
+ self.table_name_dynamic = self.format_table_name(args[column_number])
329
+ column_dict[column_name] = args[column_number]
330
+ else:
331
+ column_dict[column_name] = args[column_number]
332
+
333
+ if self.table_name_dynamic:
334
+ self.create_table(self.table_name_dynamic, self.column_list)
335
+
336
+ self.insert_data(self.table_name_dynamic, column_dict)
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dynamic-tables
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A dynamic table creation and management library for PostgreSQL
5
- Home-page: https://github.com/scottrodeo/dynamic_tables
5
+ Home-page: https://github.com/scottrodeo/dynamic-tables-python
6
6
  Author: Scott Rodeo
7
7
  Author-email: signcactus@gmail.com
8
8
  License: UNKNOWN
9
- Project-URL: Author Website, https://patreon.com/scottrodeo
10
9
  Description: # Dynamic Tables (Python)
10
+
11
11
  A Python library for dynamically creating and managing PostgreSQL tables based on input data.
12
12
 
13
+ ---
14
+
13
15
  ## 🚀 Features
14
16
  - Automatically creates tables based on incoming data.
15
17
  - Supports dynamic naming conventions.
@@ -19,16 +21,19 @@ Description: # Dynamic Tables (Python)
19
21
  ---
20
22
 
21
23
  ## 📥 Installation
22
- ### **1️⃣ Install via GitHub (Recommended for Development)**
24
+
25
+ ### 1️⃣ Install via GitHub (Recommended for Development)
23
26
  Clone the repository and install in editable mode:
27
+
24
28
  ```bash
25
29
  git clone https://github.com/scottrodeo/dynamic-tables-python.git
26
30
  cd dynamic-tables-python
27
31
  pip install -e .
28
32
  ```
29
33
 
30
- ### **2️⃣ Install Directly via `pip`**
31
- The package is available on PyPI, you can install it with:
34
+ ### 2️⃣ Install Directly via `pip`
35
+ The package is available on PyPI, install it with:
36
+
32
37
  ```bash
33
38
  pip install dynamic-tables
34
39
  ```
@@ -36,22 +41,21 @@ Description: # Dynamic Tables (Python)
36
41
  ---
37
42
 
38
43
  ## 🏃‍♂️ Running the Example
39
- ### **1️⃣ Quick Run (Without Installation)**
40
- If you don't want to install the package, you can directly run the example script:
44
+
45
+ ### 1️⃣ Quick Run (Without Installation)
46
+ Run the example script directly:
47
+
41
48
  ```bash
42
49
  python3 examples/example.py
43
50
  ```
51
+
44
52
  💡 *This works because the script dynamically adjusts `sys.path`.*
45
53
 
46
- ### **2️⃣ Recommended (After Installation)**
47
- If you've installed the package (`pip install -e .`), simply run:
48
- ```bash
49
- python3 examples/example.py
50
- ```
51
54
 
52
55
  ---
53
56
 
54
57
  ## 📌 Example Usage
58
+
55
59
  Once installed, you can use `dynamic_tables` in your Python scripts:
56
60
 
57
61
  ```python
@@ -60,11 +64,12 @@ Description: # Dynamic Tables (Python)
60
64
  # Initialize the dynamic table manager
61
65
  tables = DynamicTables()
62
66
 
63
- # Example: Creating and inserting data dynamically
67
+ # Configure dynamic table properties
64
68
  tables.set_table_prefix("dtbl_")
65
69
  tables.set_columns("domain TEXT, category TEXT, lang TEXT")
66
70
  tables.set_dynamic_column("domain")
67
71
 
72
+ # Insert dynamic data
68
73
  tables.input("wikipedia.org", "cats", "en")
69
74
  tables.input("wikipedia.org", "dogs", "en")
70
75
 
@@ -75,6 +80,7 @@ Description: # Dynamic Tables (Python)
75
80
  ---
76
81
 
77
82
  ## 🛠️ Available Functions
83
+
78
84
  | Function | Description |
79
85
  |----------|-------------|
80
86
  | `set_columns("name TYPE, age TYPE")` | Define table schema |
@@ -83,20 +89,39 @@ Description: # Dynamic Tables (Python)
83
89
  | `input(value1, value2, ...)` | Insert a new row dynamically |
84
90
  | `show_tables()` | List all dynamically created tables |
85
91
  | `show_columns("table_name")` | Show column details for a specific table |
92
+ | `show_columns_all()` | Show column details for all tables |
86
93
  | `select_table("table_name")` | Retrieve all rows from a table |
87
94
  | `delete_tables()` | Drop all tables matching the prefix |
95
+ | `create_table("table_name", [("column1", "TYPE"), ("column2", "TYPE")])` | Create a table dynamically |
96
+ | `insert_data("table_name", {"column1": value1, "column2": value2})` | Insert data into a specific table |
97
+ | `get_tables()` | Retrieve a list of all tables in the database |
98
+ | `get_columns("table_name")` | Retrieve all columns for a given table |
99
+ | `get_table_rows("table_name")` | Retrieve all rows from a table |
100
+ | `connectHC("dbname", "user", "password", "host")` | Connect using hardcoded credentials |
101
+ | `connectJSON("config.json")` | Connect using credentials from a JSON config file |
102
+ | `connectENVS()` | Connect using environment variables |
103
+ | `connection_open("dbname", "user", "password", "host")` | Open a PostgreSQL database connection |
104
+ | `connection_close()` | Close the database connection |
105
+ | `status()` | Show the current configuration status |
106
+ | `show_db()` | Display the connected database name |
107
+ | `setup_logging(level, log_to_file)` | Configure logging settings |
108
+ | `change_log_level("LEVEL")` | Change the logging level at runtime |
109
+ | `show_version()` | Display the current version of the library |
88
110
 
89
111
  ---
90
112
 
91
113
  ## ⚡ Development
92
- ### **Running Tests**
93
- To run the test suite:
114
+
115
+ ### Running Tests
116
+ Run the test suite:
117
+
94
118
  ```bash
95
119
  pytest tests/
96
120
  ```
97
121
 
98
- ### **Linting**
122
+ ### Linting
99
123
  Ensure your code follows best practices:
124
+
100
125
  ```bash
101
126
  flake8 dynamic_tables/
102
127
  ```
@@ -104,7 +129,8 @@ Description: # Dynamic Tables (Python)
104
129
  ---
105
130
 
106
131
  ## 🤝 Contributing
107
- Contributions are welcome! If you'd like to improve `dynamic_tables`, follow these steps:
132
+
133
+ Contributions are welcome! To contribute:
108
134
  1. Fork the repository.
109
135
  2. Create a new branch (`git checkout -b feature-branch`).
110
136
  3. Commit your changes (`git commit -m "Added new feature"`).
@@ -114,18 +140,19 @@ Description: # Dynamic Tables (Python)
114
140
  ---
115
141
 
116
142
  ## 📄 License
143
+
117
144
  This project is licensed under the **MIT License**. See the [LICENSE](LICENSE) file for details.
118
145
 
119
146
  ---
120
147
 
121
148
  ## 🌎 Links
149
+
122
150
  - **GitHub Repository:** [Dynamic Tables (Python)](https://github.com/scottrodeo/dynamic-tables-python)
123
- - **Documentation:** *(To be added)*
124
151
  - **Issue Tracker:** [Report Issues](https://github.com/scottrodeo/dynamic-tables-python/issues)
125
152
 
126
153
  ---
127
154
 
128
- ### **🚀 Happy Coding!**
155
+ ### 🚀 Happy Coding!
129
156
 
130
157
 
131
158
  Platform: UNKNOWN
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="dynamic_tables",
5
- version="0.1.0",
5
+ version="0.2.0",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "psycopg2",
@@ -12,10 +12,7 @@ setup(
12
12
  description="A dynamic table creation and management library for PostgreSQL",
13
13
  long_description=open("README.md").read(),
14
14
  long_description_content_type="text/markdown",
15
- url="https://github.com/scottrodeo/dynamic_tables",
16
- project_urls={
17
- "Author Website": "https://patreon.com/scottrodeo",
18
- },
15
+ url="https://github.com/scottrodeo/dynamic-tables-python",
19
16
  classifiers=[
20
17
  "Programming Language :: Python :: 3",
21
18
  "License :: OSI Approved :: MIT License",
@@ -1,224 +0,0 @@
1
- # this is dynamic_tables.py
2
-
3
- import psycopg2, re
4
- from psycopg2 import sql
5
-
6
- class DynamicTables:
7
-
8
- def __init__(self):
9
- """Initialize the class by calling self.initialize() to set up default values."""
10
- self.initialize()
11
-
12
- def initialize(self):
13
- """Sets up default attributes for the table structure."""
14
- self.column_list = []
15
- self.column_dynamic = ''
16
- self.table_prefix = 'dtbl_'
17
- self.table_name_dynamic = ''
18
-
19
- def connection_open(self, dbname, user, password, host):
20
- """Opens a connection to the PostgreSQL database and initializes the cursor."""
21
- self.conn = psycopg2.connect(
22
- dbname=dbname,
23
- user=user,
24
- password=password,
25
- host=host
26
- )
27
- self.cur = self.conn.cursor()
28
-
29
- def connectHC(self, dbname, user, password, host):
30
- """Helper function to open a connection using hardcoded credentials."""
31
- self.connection_open(dbname, user, password, host)
32
-
33
- def connectJSON(self):
34
- """Opens a database connection using credentials from a JSON config file."""
35
- import json
36
- with open("config.json") as config_file:
37
- config = json.load(config_file)
38
- self.conn = psycopg2.connect(**config)
39
- self.cur = self.conn.cursor()
40
-
41
- def connectENVS(self):
42
- """Opens a database connection using environment variables."""
43
- import os
44
- self.conn = psycopg2.connect(
45
- host=os.getenv("DTABLES_ENVS_PGSQL_HOST"),
46
- user=os.getenv("DTABLES_ENVS_PGSQL_USER"),
47
- password=os.getenv("DTABLES_ENVS_PGSQL_PASSWORD"),
48
- database=os.getenv("DTABLES_ENVS_PGSQL_DATABASE")
49
- )
50
- self.cur = self.conn.cursor()
51
-
52
- def connection_close(self):
53
- """Closes the database cursor and connection."""
54
- self.cur.close()
55
- self.conn.close()
56
-
57
- def close(self):
58
- """Alias for connection_close() to improve readability."""
59
- self.connection_close()
60
-
61
- def format_table_name(self, input_column):
62
- """Formats an input column name to create a valid table name."""
63
- cleaned = re.sub(r'[^a-zA-Z0-9_]', '', input_column)
64
- return self.table_prefix + cleaned
65
-
66
- def input(self, *args):
67
- """Handles data insertion by dynamically determining the table name and inserting values."""
68
- column_dict = {}
69
- self.table_name_dynamic = ''
70
-
71
- for column_number, (column_name, _) in enumerate(self.column_list):
72
- if column_name == self.column_dynamic:
73
- self.table_name_dynamic = self.format_table_name(args[column_number])
74
- column_dict[column_name] = args[column_number]
75
- else:
76
- column_dict[column_name] = args[column_number]
77
-
78
- if self.table_name_dynamic:
79
- self.create_table(self.table_name_dynamic, self.column_list)
80
-
81
- self.insert_data(self.table_name_dynamic, column_dict)
82
-
83
- def insert_data(self, table_name, data_dict):
84
- """Inserts data into a dynamically created table."""
85
- columns = data_dict.keys()
86
- values = list(data_dict.values())
87
-
88
- query = sql.SQL("""
89
- INSERT INTO {} ({})
90
- VALUES ({});
91
- """).format(
92
- sql.Identifier(table_name),
93
- sql.SQL(', ').join(map(sql.Identifier, columns)),
94
- sql.SQL(', ').join(sql.Placeholder() for _ in columns)
95
- )
96
-
97
- self.cur.execute(query, values)
98
- self.conn.commit()
99
-
100
- def create_table(self, table_name, columns):
101
- """Creates a table dynamically if it does not already exist."""
102
- column_definitions = sql.SQL(", ").join(
103
- sql.SQL("{} {}").format(sql.Identifier(col_name), sql.SQL(col_type))
104
- for col_name, col_type in columns
105
- )
106
-
107
- query = sql.SQL("""
108
- CREATE TABLE IF NOT EXISTS {} (
109
- id SERIAL PRIMARY KEY,
110
- {}
111
- );
112
- """).format(sql.Identifier(table_name), column_definitions)
113
-
114
- self.cur.execute(query)
115
- self.conn.commit()
116
-
117
- def show_db(self):
118
- """Lists all available databases."""
119
- self.cur.execute("SELECT datname FROM pg_database WHERE datistemplate = false;")
120
- for row in self.cur.fetchall():
121
- print(row)
122
-
123
- def show_tables(self):
124
- """Fetch and return a list of all table names."""
125
- self.cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';")
126
- tables = self.cur.fetchall()
127
-
128
- if tables:
129
- table_list = [table[0] for table in tables] # Convert to a simple list of table names
130
- print("\n".join(table_list)) # Print for debugging
131
- return table_list
132
- else:
133
- print("No tables found.")
134
- return [] # Return an empty list instead of None
135
-
136
- def show_columns(self, table_name):
137
- """Displays all columns and their data types for a given table."""
138
- try:
139
- query = sql.SQL("""
140
- SELECT column_name, data_type
141
- FROM information_schema.columns
142
- WHERE table_name = %s
143
- ORDER BY ordinal_position;
144
- """)
145
- self.cur.execute(query, (table_name,))
146
- columns = self.cur.fetchall()
147
-
148
- if columns:
149
- print(f"Columns in table '{table_name}':")
150
- for column_name, data_type in columns:
151
- print(f" - {column_name}: {data_type}")
152
- else:
153
- print(f"No columns found for table '{table_name}' or the table does not exist.")
154
- except Exception as e:
155
- print(f"Error retrieving columns for '{table_name}': {e}")
156
-
157
- def show_columns_all(self):
158
- """Lists column details for all tables."""
159
- self.cur.execute("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';")
160
- for table in self.cur.fetchall():
161
- self.show_columns(table[0])
162
-
163
- def select_table(self, table_name):
164
- """Retrieves and prints all rows from the specified table."""
165
- query = sql.SQL("SELECT * FROM {}").format(sql.Identifier(table_name))
166
- self.cur.execute(query)
167
- rows = self.cur.fetchall()
168
-
169
- if rows:
170
- for row in rows:
171
- print(f"Row: {row}")
172
- else:
173
- print(f"Table '{table_name}' exists but has no data.")
174
-
175
- return rows
176
-
177
- def delete_tables(self):
178
- """Deletes all tables that match the current table prefix."""
179
- query = """
180
- SELECT table_name FROM information_schema.tables
181
- WHERE table_schema = 'public' AND table_name LIKE %s;
182
- """
183
- prefix_pattern = self.table_prefix + "%" # Dynamic prefix
184
- print("prefix_pattern:" + prefix_pattern) # Debugging output
185
- self.cur.execute(query, (prefix_pattern,))
186
-
187
- tables = self.cur.fetchall()
188
- print("DEBUG - Found tables to delete:", tables) # Debugging output
189
-
190
- for table in tables:
191
- table_name = table[0]
192
- print(f"Dropping table: {table_name}")
193
-
194
- drop_query = sql.SQL("DROP TABLE IF EXISTS {} CASCADE;").format(sql.Identifier(table_name))
195
- self.cur.execute(drop_query)
196
-
197
- self.conn.commit()
198
-
199
- def set_columns(self, input_columns):
200
- """Sets the columns for table creation based on user input."""
201
- self.column_list = []
202
- for column in input_columns.split(','):
203
- column = column.strip()
204
- parts = column.split(maxsplit=1)
205
- if len(parts) == 2:
206
- self.column_list.append((parts[0], parts[1]))
207
- else:
208
- print(f"Invalid column format: '{column}'")
209
-
210
- def set_table_prefix(self, input_prefix):
211
- """Defines a custom table name prefix."""
212
- self.table_prefix = input_prefix
213
-
214
- def set_dynamic_column(self, input_column):
215
- """Sets the column that determines dynamic table names."""
216
- self.column_dynamic = input_column
217
-
218
- def status(self):
219
- """Displays the currently configured column settings."""
220
- print('\nConfigured Columns:')
221
- for column in self.column_list:
222
- print(column)
223
- print('\nDynamic Column:\n', self.column_dynamic, '\n')
224
-
File without changes