iristool 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
iristool-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Pietro Di Leo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,407 @@
1
+ Metadata-Version: 2.4
2
+ Name: iristool
3
+ Version: 0.1.0
4
+ Summary: Simple pandas and SQL helpers for InterSystems IRIS
5
+ Author: Pietro Di Leo
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/pietrodileo/iristool
8
+ Project-URL: Issues, https://github.com/pietrodileo/iristool/issues
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: pandas<4,>=2.2
13
+ Requires-Dist: intersystems-irispython<6,>=5.2.1
14
+ Dynamic: license-file
15
+
16
+ **iristool**
17
+
18
+ Load pandas DataFrames into InterSystems IRIS and query them with Python.
19
+
20
+ ![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-3776ab?style=flat)![InterSystems IRIS](https://img.shields.io/badge/InterSystems-IRIS-00a99d?style=flat)![MIT License](https://img.shields.io/badge/license-MIT-2ea043?style=flat)
21
+
22
+ **iristool** is a small Python library for moving data between pandas and InterSystems IRIS. Create a SQL table from a DataFrame, run parameterized queries, and manage rows, tables, views, and indexes through one connection object.
23
+
24
+ Python runs in your own environment and connects to an existing IRIS server through the official `intersystems-irispython` DB API. Install the library on your laptop, in a notebook, or in a Python application; no Python packages need to be installed inside IRIS.
25
+
26
+ [Features](#features) · [Installation](#installation) · [Quick start](#quick-start) · [Examples](#examples) · [API](#api-reference) · [Testing](#development-and-testing) · [Publishing](#publishing-on-pypi)
27
+
28
+ ---
29
+
30
+
31
+
32
+ ## Features
33
+
34
+ - **DataFrame to SQL:** create a table, infer column types, and insert rows in batches.
35
+ - **SQL to DataFrame:** retrieve query results with optional bound parameters.
36
+ - **Data handling:** normalize column names and convert pandas missing values to SQL `NULL`.
37
+ - **Import control:** choose whether to fail, skip, or replace an existing table; supply explicit types, a primary key, constraints, and indexes.
38
+ - **SQL helpers:** insert and update rows, inspect metadata, and manage tables, columns, views, and indexes.
39
+ - **Small dependency list:** pandas and the official InterSystems Python SDK.
40
+
41
+ The library handles the IRIS part of a data workflow. Read a CSV with pandas, download data with another library, or construct a DataFrame yourself, then pass it to `df_to_table()`.
42
+
43
+ ## Installation
44
+
45
+
46
+
47
+ ### Requirements
48
+
49
+ - Python **3.10 or newer**, on a platform supported by the InterSystems SDK.
50
+ - A reachable **InterSystems IRIS 2022.1 or newer** server supporting the Python DB API.
51
+ - An IRIS account with SQL permissions for the operations you need.
52
+
53
+
54
+
55
+ ### With pip
56
+
57
+ Once the first release is published on PyPI:
58
+
59
+ ```bash
60
+ python -m pip install iristool
61
+ ```
62
+
63
+ > **First release status:** this repository is prepared for PyPI publication. Until the first upload succeeds, install from the source checkout instead.
64
+
65
+ From the folder containing `pyproject.toml`:
66
+
67
+ ```bash
68
+ python -m pip install .
69
+ ```
70
+
71
+ For development, use `python -m pip install -e .`. Both installation methods install the runtime dependencies automatically. Import the class with:
72
+
73
+ ```python
74
+ from iristool import IRIStool
75
+ ```
76
+
77
+
78
+
79
+ ## Quick start
80
+
81
+ Set your connection details. These example credentials are for the included local Docker development server:
82
+
83
+ ```bash
84
+ export IRIS_HOST="localhost"
85
+ export IRIS_PORT="9091"
86
+ export IRIS_NAMESPACE="USER"
87
+ export IRIS_USER="_SYSTEM"
88
+ export IRIS_PASSWORD="SYS"
89
+ ```
90
+
91
+ For another server, use its credentials and SuperServer port, usually `1972`.
92
+
93
+ ```python
94
+ import os
95
+ import pandas as pd
96
+ from iristool import IRIStool
97
+
98
+ connection = {
99
+ "host": os.getenv("IRIS_HOST", "localhost"),
100
+ "port": int(os.getenv("IRIS_PORT", "1972")),
101
+ "namespace": os.getenv("IRIS_NAMESPACE", "USER"),
102
+ "username": os.environ["IRIS_USER"],
103
+ "password": os.environ["IRIS_PASSWORD"],
104
+ }
105
+
106
+ people = pd.DataFrame({
107
+ "Name": ["Ada", "Grace"],
108
+ "Age": [36, 40],
109
+ })
110
+
111
+ with IRIStool(**connection) as db:
112
+ db.df_to_table(people, "People")
113
+ result = db.fetch("SELECT Name, Age FROM SQLUser.People ORDER BY Age")
114
+ print(result)
115
+ ```
116
+
117
+ This creates `SQLUser.People` and inserts two rows. The connection closes when the `with` block ends. A second run raises an error because the table exists; choose an existing-table policy below to make repeated imports explicit.
118
+
119
+ `USER` is the IRIS **namespace** selected when connecting. `SQLUser` is the default SQL **schema** used by the table helpers. Pass `table_schema` separately from the table name.
120
+
121
+ ## Examples
122
+
123
+ The following examples reuse the `connection` dictionary from the quick start.
124
+
125
+ ### Import a CSV
126
+
127
+ ```python
128
+ with IRIStool(**connection) as db:
129
+ db.df_to_table(pd.read_csv("people.csv"), "PeopleCSV", batch_size=1000)
130
+ ```
131
+
132
+ Pandas reads the file; iristool creates the table and inserts its contents. The entire DataFrame must fit in memory. `batch_size` limits the number of rows sent to IRIS per batch, rather than streaming the input file.
133
+
134
+ ### Control types and keys
135
+
136
+ ```python
137
+ sales = pd.DataFrame({
138
+ "Order Number": [1, 2],
139
+ "Customer": ["Ada", "Grace"],
140
+ "Total": [19.50, 42.00],
141
+ })
142
+
143
+ with IRIStool(**connection) as db:
144
+ db.df_to_table(
145
+ sales,
146
+ "Sales",
147
+ column_types={
148
+ "Order_Number": "INT",
149
+ "Customer": "VARCHAR(100)",
150
+ "Total": "DECIMAL(12,2)",
151
+ },
152
+ primary_key="Order_Number",
153
+ indices=[{"column": "Customer", "type": "index"}],
154
+ )
155
+ ```
156
+
157
+ When supplied, `column_types` must cover **every DataFrame column**. Its keys can use the original or normalized names. Use explicit SQL declarations when your schema needs fixed lengths, decimal precision, or other types that inference cannot decide for you.
158
+
159
+ ### Query with parameters
160
+
161
+ ```python
162
+ with IRIStool(**connection) as db:
163
+ adults = db.fetch(
164
+ "SELECT Name, Age FROM SQLUser.People WHERE Age >= ?",
165
+ parameters=[18],
166
+ )
167
+ print(adults)
168
+ ```
169
+
170
+ Use `?` placeholders for values. `fetch()` returns a pandas DataFrame; a query with no rows returns an empty DataFrame.
171
+
172
+ ### Insert and update rows
173
+
174
+ ```python
175
+ with IRIStool(**connection) as db:
176
+ db.insert_row("People", {"Name": "Linus", "Age": 28})
177
+ inserted = db.insert_many("People", [
178
+ {"Name": "Margaret", "Age": 32},
179
+ {"Name": "Barbara", "Age": 35},
180
+ ])
181
+ updated = db.update("People", {"Age": 37}, filters={"Name": "Ada"})
182
+ print(inserted, updated)
183
+ ```
184
+
185
+ `insert_many()` and `update()` return affected row counts. Each mutating helper commits its own successful operation and rolls back on failure; the connection context manager closes the connection and does not group multiple helper calls into one transaction.
186
+
187
+ ### Inspect a table
188
+
189
+ ```python
190
+ with IRIStool(**connection) as db:
191
+ print(db.table_exists("People"))
192
+ print(db.describe_table("People"))
193
+ print(db.show_namespace_tables(table_schema="SQLUser"))
194
+ ```
195
+
196
+
197
+
198
+ ## Import behavior
199
+
200
+
201
+
202
+ ### Existing tables
203
+
204
+
205
+ | Options passed to `df_to_table()` | Behavior when the table exists |
206
+ | ------------------------------------ | ---------------------------------------------------- |
207
+ | Default options | Raise an error |
208
+ | `exist_ok=True` | Skip the import and keep existing data |
209
+ | `exist_ok=True, drop_if_exists=True` | Drop the table, recreate it, and import the new data |
210
+
211
+
212
+ Replacement deletes the previous table and its data. Set `drop_related_views=True` only when you also want dependent views removed. To add rows to an existing table, use `insert_row()` or `insert_many()`; `df_to_table()` does not append.
213
+
214
+ ### Inferred SQL types
215
+
216
+
217
+ | DataFrame values or dtype | Inferred IRIS type |
218
+ | ---------------------------------------------- | ---------------------------------------------- |
219
+ | Boolean | `BIT` |
220
+ | Integers within the signed 32-bit range | `INT` |
221
+ | Larger integers within the signed 64-bit range | `BIGINT` |
222
+ | Integers outside the signed 64-bit range | `DECIMAL(20,0)` |
223
+ | Floating point | `DOUBLE` |
224
+ | Datetimes | `DATETIME`; midnight-only values become `DATE` |
225
+ | Datetimes all dated January 1, 1970 | `TIME`, unless the midnight rule applies |
226
+ | Python `date` / `time` values | `DATE` / `TIME` |
227
+ | Text up to 255 characters | `VARCHAR(255)` |
228
+ | Longer text | `CLOB` |
229
+ | All-null object column | `VARCHAR(255)` |
230
+
231
+
232
+ Nullable numeric and Boolean columns retain their dtype during inference. `None`, `pd.NA`, `NaN`, and `NaT` are inserted as SQL `NULL`. An empty DataFrame with named columns creates an empty table.
233
+
234
+ For object columns, inference recognizes dates/times or falls back to text length. Convert mixed values and structured objects before insertion. Normalize timezone-aware timestamps to the intended timezone and remove timezone information before importing.
235
+
236
+ ### Names and transactions
237
+
238
+ - DataFrame column names are trimmed; spaces and dots become underscores: `Total Sales` → `Total_Sales`.
239
+ - Identifiers must start with an ASCII letter and contain only ASCII letters, digits, or underscores. Column names that collide after normalization, including case differences, are rejected.
240
+ - Pass `"People"` and `table_schema="SQLUser"` separately, rather than `"SQLUser.People"` as a table name. Avoid SQL reserved words for table/schema names.
241
+ - All insertion batches from a DataFrame share one insertion transaction. If any row fails, the inserted rows are rolled back.
242
+ - Table creation and replacement are committed before insertion. A failed import can leave an empty new table; replacement is **not atomic**.
243
+ - SQL queries, type declarations, and constraints are SQL supplied by the caller. Row values use bound parameters.
244
+
245
+ `fetch()` uses pandas' normal dtype inference. A result containing large integers and nulls may become a floating-point column; select or cast appropriately when exact integer representation in the returned DataFrame matters.
246
+
247
+ ## API reference
248
+
249
+ Most table helpers default to `table_schema="SQLUser"`. See the method docstrings for complete signatures and options.
250
+
251
+
252
+ | Method | Purpose |
253
+ | -------------------------------------------------------- | --------------------------------------------------- |
254
+ | `df_to_table(df, table_name, ...)` | Create a table and import a DataFrame |
255
+ | `fetch(sql, parameters=None)` | Return query results as a DataFrame |
256
+ | `insert_row(table_name, values)` | Insert one dictionary of values |
257
+ | `insert_many(table_name, rows)` | Insert dictionaries in a batch |
258
+ | `update(table_name, new_values, filters)` | Update matching rows |
259
+ | `update_many(table_name, updates)` | Apply a list of `(new_values, filters)` pairs |
260
+ | `create_table(table_name, columns, ...)` | Create a table from SQL type declarations |
261
+ | `add_columns(table_name, new_columns)` | Add columns to an existing table |
262
+ | `drop_table(table_name, ...)` | Drop a table, or a view with `view_or_table="view"` |
263
+ | `table_exists(table_name)` | Check whether a table or view exists |
264
+ | `describe_table(table_name)` | Return column and index metadata |
265
+ | `create_index(index_name, table_name, column_name, ...)` | Create an index |
266
+ | `index_exists(table_name, index_name)` | Check index metadata |
267
+ | `create_view(view_name, sql, ...)` | Create a SQL view |
268
+ | `views_using_table(table_name)` | List dependent views |
269
+ | `show_namespace_tables(...)` | List tables, with optional name/schema filters |
270
+ | `show_namespace_schemas()` | List schemas containing base tables |
271
+ | `close()` | Close the connection explicitly |
272
+
273
+
274
+ ## Development and testing
275
+
276
+ Use a virtual environment with Python 3.10 or newer. These shell examples use macOS/Linux; on Windows, activate with `.venv\Scripts\Activate.ps1` and set environment variables using PowerShell.
277
+
278
+ ```bash
279
+ python3 -m venv .venv
280
+ source .venv/bin/activate
281
+ python -m pip install -e .
282
+ python -m unittest discover -s tests -v
283
+ ```
284
+
285
+ The regular suite runs unit tests and skips live database tests.
286
+
287
+ ### Run against Docker IRIS
288
+
289
+ ```bash
290
+ docker compose up -d --build
291
+ docker compose logs --tail 20
292
+ # Wait until IRIS reports that it has started.
293
+ IRIS_INTEGRATION=1 python -m unittest discover -s tests -v
294
+ docker compose stop
295
+ ```
296
+
297
+ The included development server uses:
298
+
299
+
300
+ | Setting | Value |
301
+ | ---------------------- | ------------------------------------------------------------ |
302
+ | SQL connection | `localhost:9091` |
303
+ | Management Portal | [localhost:9092](http://localhost:9092/csp/sys/UtilHome.csp) |
304
+ | Namespace | `USER` |
305
+ | Development account | `_SYSTEM` / `SYS` |
306
+ | Durable database files | `storage/` |
307
+
308
+
309
+ Live tests read the `IRIS_HOST`, `IRIS_PORT`, `IRIS_NAMESPACE`, `IRIS_USER`, and `IRIS_PASSWORD` variables, with the above defaults. They create uniquely named test tables/views and remove them after each test. The startup script unexpires development passwords.
310
+
311
+ Coverage includes mixed DataFrame types, nulls, Unicode and long text, large integers, batching, empty imports, replacement, row helpers, metadata, ordinary indexes/views, and rollback after constraint failures.
312
+
313
+ ## Troubleshooting
314
+
315
+
316
+ | Symptom | What to check |
317
+ | --------------------------------------- | ------------------------------------------------------------------------------------------------------ |
318
+ | `pip install iristool` finds no package | Before the first PyPI release, install the source checkout with `python -m pip install .` |
319
+ | Connection refused | Check that IRIS is running and the SuperServer port is reachable; this Compose setup maps it to `9091` |
320
+ | Authentication failure | Check the account, password, namespace access, and password expiration |
321
+ | Table already exists | Choose skip/replacement explicitly, or use row helpers to insert into it |
322
+ | Invalid identifier or colliding columns | Rename columns before importing; follow the naming rules above |
323
+ | Constraint failure in a batch | Correct the offending data; inserted rows roll back, but the created table remains |
324
+ | Timezone-aware datetime rejected | Convert to the desired timezone, then remove timezone information |
325
+
326
+
327
+ ## Publishing on PyPI
328
+
329
+ GitHub hosts the source; PyPI distributes the package used by `pip install iristool`. The package metadata is already in `pyproject.toml`, and this README becomes the PyPI project description.
330
+
331
+ ### 1. Set up your account
332
+
333
+ [Register on PyPI](https://pypi.org/account/register/), verify your email, and enable two-factor authentication. In [account settings](https://pypi.org/manage/account/#api-tokens), create an API token scoped to the entire account for the first upload. Afterward, replace it with a token scoped to `iristool`.
334
+
335
+ The first successful upload creates the project; there is no separate package registration form for this workflow. A missing public project page does not guarantee name availability. If PyPI rejects `iristool`, change `[project].name` and rebuild. See [PyPI's account and project help](https://pypi.org/help/).
336
+
337
+ ### 2. Prepare the release
338
+
339
+ Check the name, version, author, license, dependencies, and repository URL in `pyproject.toml`. Update the URL if this package will live in a new GitHub repository. Run the unit and live tests above.
340
+
341
+ From the repository root, with the virtual environment active:
342
+
343
+ ```bash
344
+ python -m pip install --upgrade build twine
345
+ python -m build --outdir dist/0.1.0
346
+ python -m twine check dist/0.1.0/*
347
+ ```
348
+
349
+ This produces a wheel and source archive for `0.1.0`. Build and validate again after changing the code, metadata, or README. See the [Python packaging tutorial](https://packaging.python.org/en/latest/tutorials/packaging-projects/).
350
+
351
+ ### 3. Check the built wheel
352
+
353
+ ```bash
354
+ python -m venv /tmp/iristool-release-check
355
+ /tmp/iristool-release-check/bin/python -m pip install dist/0.1.0/iristool-0.1.0-py3-none-any.whl
356
+ IRIS_INTEGRATION=1 /tmp/iristool-release-check/bin/python -I -m unittest discover -s tests -v
357
+ ```
358
+
359
+ Run this while the Docker IRIS server is running. `-I` isolates imports so the tests use the installed wheel.
360
+
361
+ ### 4. Upload
362
+
363
+ ```bash
364
+ python -m twine upload --username __token__ dist/0.1.0/*
365
+ ```
366
+
367
+ Paste the PyPI API token when Twine prompts for a password/token. Keep the token out of source files, Git, and shell command arguments. See [Twine's documentation](https://twine.readthedocs.io/en/stable/).
368
+
369
+ For an optional rehearsal, create a separate [TestPyPI account](https://test.pypi.org/account/register/) and token, then use `--repository testpypi` in the upload command. TestPyPI does not publish the package to production PyPI.
370
+
371
+ ### 5. Verify installation
372
+
373
+ After the upload succeeds, check [the PyPI project page](https://pypi.org/project/iristool/) and install from PyPI in a fresh environment:
374
+
375
+ ```bash
376
+ python -m venv /tmp/iristool-pypi-check
377
+ /tmp/iristool-pypi-check/bin/python -m pip install iristool==0.1.0
378
+ /tmp/iristool-pypi-check/bin/python -c "from iristool import IRIStool; print(IRIStool)"
379
+ ```
380
+
381
+ For later releases, increment `[project].version`, use a matching `dist/<version>` directory, and repeat the release steps. Uploaded filenames cannot be reused, including after deletion. After publishing, remove the first-release status note from this README. A matching GitHub tag/release helps users find the source for each version.
382
+
383
+ ## Project layout
384
+
385
+ ```text
386
+ .
387
+ ├── src/iristool/ Library and public import
388
+ ├── tests/ Unit and opt-in live IRIS tests
389
+ ├── pyproject.toml Package metadata and build configuration
390
+ ├── requirements.txt Runtime dependencies for pip
391
+ ├── Dockerfile Development IRIS image
392
+ ├── docker-compose.yml Local test server and port mappings
393
+ ├── docker-entrypoint.sh IRIS startup wrapper
394
+ ├── iris.script Development password initialization
395
+ ├── README.md
396
+ └── LICENSE
397
+ ```
398
+
399
+ The Python wheel includes the library, metadata, and license. Docker files and durable database data are not part of the installed library.
400
+
401
+ ## Credits and license
402
+
403
+ Created by **Pietro Di Leo**, based on the Python utilities from [iris_tool_and_data_manager](https://github.com/pietrodileo/iris_tool_and_data_manager).
404
+
405
+ Released under the **MIT License**. Copyright © 2025–2026 Pietro Di Leo.
406
+
407
+ See [LICENSE](LICENSE) for the full license text. A copy is included in both the Python wheel and source distribution.