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 +21 -0
- iristool-0.1.0/PKG-INFO +407 -0
- iristool-0.1.0/README.md +392 -0
- iristool-0.1.0/pyproject.toml +21 -0
- iristool-0.1.0/setup.cfg +4 -0
- iristool-0.1.0/src/iristool/__init__.py +5 -0
- iristool-0.1.0/src/iristool/core.py +1082 -0
- iristool-0.1.0/src/iristool.egg-info/PKG-INFO +407 -0
- iristool-0.1.0/src/iristool.egg-info/SOURCES.txt +12 -0
- iristool-0.1.0/src/iristool.egg-info/dependency_links.txt +1 -0
- iristool-0.1.0/src/iristool.egg-info/requires.txt +2 -0
- iristool-0.1.0/src/iristool.egg-info/top_level.txt +1 -0
- iristool-0.1.0/tests/test_iris_integration.py +125 -0
- iristool-0.1.0/tests/test_iristool.py +155 -0
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.
|
iristool-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
+

|
|
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.
|