ping-dataexport 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.
Files changed (54) hide show
  1. ping_dataexport-0.2.0/PKG-INFO +395 -0
  2. ping_dataexport-0.2.0/README.md +366 -0
  3. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/pyproject.toml +1 -1
  4. ping_dataexport-0.2.0/src/ping_dataexport/__config__.py +39 -0
  5. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/__init__.py +1 -1
  6. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/api.py +7 -3
  7. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/config.py +2 -1
  8. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/export/engine.py +7 -2
  9. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/job/spec.py +2 -1
  10. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/paths.py +4 -2
  11. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/groupby.py +4 -3
  12. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/monthbydate.py +2 -1
  13. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/watermark.py +5 -4
  14. ping_dataexport-0.2.0/src/ping_dataexport.egg-info/PKG-INFO +395 -0
  15. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport.egg-info/SOURCES.txt +1 -0
  16. ping_dataexport-0.1.0/PKG-INFO +0 -228
  17. ping_dataexport-0.1.0/README.md +0 -199
  18. ping_dataexport-0.1.0/src/ping_dataexport.egg-info/PKG-INFO +0 -228
  19. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/LICENSE +0 -0
  20. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/MANIFEST.in +0 -0
  21. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/config.sample.ini +0 -0
  22. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/job.sample.ini +0 -0
  23. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/setup.cfg +0 -0
  24. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/__main__.py +0 -0
  25. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/applogger.py +0 -0
  26. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/cli.py +0 -0
  27. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/__init__.py +0 -0
  28. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/connector.py +0 -0
  29. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/oracle_connector.py +0 -0
  30. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/pyodbc_connector.py +0 -0
  31. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/sqlite_connector.py +0 -0
  32. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/db/type_map.py +0 -0
  33. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/errors.py +0 -0
  34. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/export/__init__.py +0 -0
  35. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/export/options.py +0 -0
  36. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/export/runner.py +0 -0
  37. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/export/util.py +0 -0
  38. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/job/__init__.py +0 -0
  39. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/job/executor.py +0 -0
  40. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/job/shared_context.py +0 -0
  41. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/job/validator.py +0 -0
  42. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/__init__.py +0 -0
  43. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/base.py +0 -0
  44. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/datelist.py +0 -0
  45. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/planner/single.py +0 -0
  46. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/writer/__init__.py +0 -0
  47. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/writer/arrow_map.py +0 -0
  48. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/writer/base.py +0 -0
  49. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/writer/delimited.py +0 -0
  50. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport/writer/parquet.py +0 -0
  51. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport.egg-info/dependency_links.txt +0 -0
  52. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport.egg-info/entry_points.txt +0 -0
  53. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport.egg-info/requires.txt +0 -0
  54. {ping_dataexport-0.1.0 → ping_dataexport-0.2.0}/src/ping_dataexport.egg-info/top_level.txt +0 -0
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.4
2
+ Name: ping-dataexport
3
+ Version: 0.2.0
4
+ Summary: Universal database data export to CSV/TXT/Parquet — streaming, typed, no pandas
5
+ Author: vorapol
6
+ License-Expression: Apache-2.0
7
+ Keywords: export,database,csv,parquet,etl,data-platform,airflow
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Database
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pyarrow>=18
19
+ Provides-Extra: odbc
20
+ Requires-Dist: pyodbc>=5; extra == "odbc"
21
+ Provides-Extra: oracle
22
+ Requires-Dist: oracledb>=2; extra == "oracle"
23
+ Provides-Extra: drivers
24
+ Requires-Dist: pyodbc>=5; extra == "drivers"
25
+ Requires-Dist: oracledb>=2; extra == "drivers"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # ping-dataexport
31
+
32
+ ## PING (ping-godhand)'s Universal Data Exporter
33
+
34
+ ## คำอธิบาย (ภาษาไทย)
35
+
36
+ โปรแกรมช่วย Export ข้อมูลอย่างง่าย: เชื่อมต่อฐานข้อมูล รันคำสั่ง SQL
37
+ แล้ว Export ผลลัพธ์ออกเป็นไฟล์ **CSV / TXT / Parquet** สร้างมาเพื่องาน Data Platform
38
+ โดยเฉพาะ — ส่งออกข้อมูลจากฐานข้อมูล ให้เป็นไฟล์ที่ Data Lake
39
+ หรือ Data Warehouse นำไปใช้ต่อได้ทันที
40
+
41
+ - **ความถูกต้องของชนิดข้อมูลมาก่อนเสมอ** — ข้อมูลจาก cursor → writer
42
+ ทีละ Row ด้วย Data Type ดั้งเดิมเสมอเช่น: `DECIMAL(p,s)` ลงใน Parquet เป็น
43
+ `decimal128(p,s)`, คอลัมน์ int ที่มี NULL ยังคงเป็น int, วันที่ยังคงเป็นวันที่
44
+ และไม่ใช้ pandas เด็ดขาด
45
+ - **ฐานข้อมูลที่รองรับ** — SQLite (Library มาตรฐาน ไม่ต้องติดตั้งอะไรเพิ่ม),
46
+ SQL Server (MS ODBC / FreeTDS), Oracle (โหมด thin แบบเ Native หรือ ODBC)
47
+ และแหล่งข้อมูล ODBC ใดก็ได้
48
+ - **ใช้งานได้สองทาง** — ผ่าน CLI `ping-dataexport` หรือผ่าน Library API (`ping_dataexport.api`)
49
+ ที่ออกแบบมาสำหรับ Airflow
50
+
51
+ ## Description (English)
52
+
53
+ Universal database data export: connect to a database, run a SQL query, stream
54
+ the result to **CSV / TXT / Parquet**. Built for data-platform work — exporting
55
+ legacy databases into files a data lake or warehouse can ingest.
56
+
57
+ - **Type fidelity first** — rows stream cursor → writer with native driver
58
+ types. `DECIMAL(p,s)` lands in Parquet as `decimal128(p,s)`, ints with NULLs
59
+ stay ints, dates stay dates. No pandas, ever.
60
+ - **Databases** — SQLite (stdlib, zero setup), SQL Server (MS ODBC / FreeTDS),
61
+ Oracle (native thin mode or ODBC), any ODBC source.
62
+ - **Two front doors** — a CLI and a library API (`ping_dataexport.api`) built
63
+ for Airflow: exceptions instead of exit codes, results as objects.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ pip install ping-dataexport # base (pyarrow) — sqlite works out of the box
69
+ pip install ping-dataexport[odbc] # + pyodbc (SQL Server, FreeTDS, ODBC)
70
+ pip install ping-dataexport[oracle] # + oracledb (Oracle native)
71
+ pip install ping-dataexport[drivers] # + both
72
+ ```
73
+
74
+ ## 1. Create a config.ini
75
+
76
+ One `[Main]` section for defaults, one section per database source:
77
+
78
+ ```ini
79
+ [Main]
80
+ ExportFolder=./output ; relative output/log paths resolve under this
81
+ FetchSize=10000
82
+ ForceMakeDir=Yes
83
+ RowIDColumn=row_id ; optional audit column names
84
+ DataDateColumn=data_date ; optional audit data extract date
85
+
86
+ [SQLITE01]
87
+ Type=sqlite
88
+ Database=./demo.sqlite
89
+
90
+ [DB01]
91
+ Type=mssql-odbc
92
+ Host=host
93
+ Port=1433
94
+ Database=DB
95
+ Username=user
96
+ Password=secret
97
+
98
+ [ORA01]
99
+ Type=oracle
100
+ Host=orahost
101
+ Port=1521
102
+ Service=ORCLPDB
103
+ Username=scott
104
+ Password=tiger
105
+ ```
106
+
107
+ A full sample ships as `config.sample.ini` in the source distribution.
108
+
109
+ ## 2. Config keys — type / possible values
110
+
111
+ `[Main]` (all optional; a missing `[Main]` yields the defaults):
112
+
113
+ | Key | Type | Possible values / default |
114
+ |-----|------|---------------------------|
115
+ | `ExportFolder` | path | base folder for relative output/log paths; default: current directory |
116
+ | `TempFolder` | path | reserved for a future release |
117
+ | `FetchSize` | int > 0 | rows per batch; default `50000` |
118
+ | `PreviewRow` | int > 0 | rows shown in console/test preview; default `10` |
119
+ | `ForceMakeDir` | bool | `Yes`/`No`, `True`/`False`, `1`/`0`, `on`/`off`; default `Yes` |
120
+ | `RowIDColumn` | string | audit column name (running row number); absent = no column |
121
+ | `DataDateColumn` | string | audit column name (data extract date); absent = no column |
122
+ | `LogFile` | path | default `{ExportFolder}/logs/log_{jobname}.txt` |
123
+
124
+ DB section (one per source; section name is what `-s` refers to):
125
+
126
+ | Key | Type | Possible values / notes |
127
+ |-----|------|--------------------------|
128
+ | `Type` | enum | `sqlite` \| `mssql-odbc` \| `freetds` \| `oracle` \| `oracle-odbc` \| `odbc`; omitted with `ConnectionString`/`DSN` present ⇒ `odbc` |
129
+ | `Name` | string | display name in logs; default: the section name |
130
+ | `ConnectionString` | string | raw ODBC connection string (alternative to Host/Port/Database) |
131
+ | `DSN` | string | ODBC data source name |
132
+ | `Host` / `Port` | string / int | server address |
133
+ | `Database` | string | database name; for `sqlite`: the file path |
134
+ | `Service` / `SID` | string | Oracle service name or SID |
135
+ | `Username` | string | login user |
136
+ | `Password` | string | plain-text password |
137
+ | `PasswordBase64` | string | base64-encoded password (obfuscation, not encryption); when both are set, `Password` wins |
138
+ | `Encoding` | string | e.g. `utf-8`, `tis-620` |
139
+ | `Driver` | string | ODBC driver name override, e.g. `ODBC Driver 18 for SQL Server` |
140
+
141
+ ```ini
142
+ PasswordBase64=c2VjcmV0 ; base64("secret")
143
+ ```
144
+
145
+ ## 3. Run an export (CLI)
146
+
147
+ `ping-dataexport` and `python -m ping_dataexport` are equivalent. The config
148
+ file defaults to `./config.ini` (`-cf` to point elsewhere).
149
+
150
+ ```bash
151
+ # preview to the console (no -o/-od): prints the first rows
152
+ ping-dataexport -s SQLITE01 -q "SELECT * FROM sales"
153
+
154
+ # single file — format inferred from the extension (.csv/.txt/.parquet)
155
+ ping-dataexport -s SQLITE01 -q "SELECT * FROM sales" -o sales.parquet
156
+ ping-dataexport -s SQLITE01 -q "SELECT * FROM sales" -o sales.csv -gz # gzip
157
+
158
+ # validate + preview only, write nothing
159
+ ping-dataexport -s SQLITE01 -q "SELECT * FROM sales" -o sales.csv -t
160
+ ```
161
+
162
+ All options:
163
+
164
+ | Option | Full name | Meaning |
165
+ |--------|-----------|---------|
166
+ | `-s` | `--source` | DB config section name (e.g. `DB01`) |
167
+ | `-q` | `--query` | SQL query |
168
+ | `-o` | `--output` | single output file (format from extension, or `-f`) |
169
+ | `-od` | `--outputdir` | output directory (required for `-m` modes) |
170
+ | `-bn` | `--basename` | base filename for mode exports |
171
+ | `-f` | `--format` | `csv`, `txt`, `parquet` |
172
+ | `-gz` | `--gzip` | gzip the output (CSV/TXT only) |
173
+ | `-sp` | `--sep` | field separator (default `,`; `\t` maps to tab) |
174
+ | `-quo` | `--quote` | quote character (default `"`) |
175
+ | `-stnl` | `--stripnewline` | newline handling in text columns: `space` \| `blank` \| `escape` \| `doubleescape` |
176
+ | `-stmc` | `--stripmetachar` | strip invisible/control characters from text columns |
177
+ | `-fs` | `--fetchsize` | fetch size (rows per batch) |
178
+ | `-m` | `--mode` | export mode: `groupby` \| `date` \| `relativedate` \| `monthbydate` \| `splitrow` \| `watermark` — see section 4 |
179
+ | `-col` | `--column` | column the mode partitions on |
180
+ | `-row` | `--row` | rows per file (`-m splitrow`) |
181
+ | `-df` | `--datefrom` | start / anchor date `yyyy-MM-dd` |
182
+ | `-dt` | `--dateto` | end date `yyyy-MM-dd` (`-m date`) |
183
+ | `-dr` | `--daysrelative` | days back from `-df` (`-m relativedate`) |
184
+ | `-mr` | `--monthsrelative` | months back from `-df` (`-m monthbydate`) |
185
+ | `-rm` | `--removeexisting` | `Yes`: clear the output directory before exporting |
186
+ | `-j` | `--job` | run one job.ini section — see section 5 |
187
+ | `-jf` | `--jobfile` | job file path (default `./job.ini`) |
188
+ | `-pl` | `--parallel` | run ALL job sections in parallel with N workers (default: CPU count) |
189
+ | `-n` | `--name` | job name shown in logs and the default log filename |
190
+ | `-cf` | `--configfile` | config file path (default `./config.ini`) |
191
+ | `-lf` | `--logfile` | log file path |
192
+ | `-t` | `--test` | test mode: validate + preview, no files |
193
+
194
+ ## 4. Export modes (partitioned outputs)
195
+
196
+ Modes split one query into multiple files. All need `-od` (output directory)
197
+ + `-bn` (base filename); partition values are always bound SQL parameters.
198
+
199
+ **`groupby` — one file per distinct value of a column:**
200
+
201
+ ```bash
202
+ ping-dataexport -s DB01 -q "SELECT * FROM sales" -m groupby -col region -od by_region -bn sales
203
+ ```
204
+ ```
205
+ by_region/
206
+ ├── sales_@@NULL@@.csv <- rows where region IS NULL
207
+ ├── sales_MY.csv
208
+ ├── sales_SG.csv
209
+ ├── sales_TH.csv
210
+ └── sales_VN.csv
211
+ ```
212
+
213
+ **`date` — one file per day in a date range (inclusive):**
214
+
215
+ ```bash
216
+ ping-dataexport -s DB01 -q "SELECT * FROM sales" -m date -col txn_date \
217
+ -df 2026-01-01 -dt 2026-01-05 -od daily -bn sales
218
+ ```
219
+ ```
220
+ daily/
221
+ ├── sales_2026-01-01.csv
222
+ ├── sales_2026-01-02.csv
223
+ ├── sales_2026-01-03.csv
224
+ ├── sales_2026-01-04.csv
225
+ └── sales_2026-01-05.csv
226
+ ```
227
+
228
+ **`relativedate` — N days back from an anchor date, anchor included:**
229
+
230
+ ```bash
231
+ ping-dataexport -s DB01 -q "SELECT * FROM sales" -m relativedate -col txn_date \
232
+ -df 2026-01-10 -dr 3 -od rel -bn sales
233
+ ```
234
+ ```
235
+ rel/
236
+ ├── sales_2026-01-07.csv
237
+ ├── sales_2026-01-08.csv
238
+ ├── sales_2026-01-09.csv
239
+ └── sales_2026-01-10.csv
240
+ ```
241
+
242
+ **`monthbydate` — N months back plus the anchor month (the anchor month stops
243
+ at the anchor date):**
244
+
245
+ ```bash
246
+ ping-dataexport -s DB01 -q "SELECT * FROM sales" -m monthbydate -col txn_date \
247
+ -df 2026-03-15 -mr 3 -od monthly -bn sales
248
+ ```
249
+ ```
250
+ monthly/
251
+ ├── sales_202512.csv
252
+ ├── sales_202601.csv
253
+ ├── sales_202602.csv
254
+ └── sales_202603.csv <- 2026-03-01 .. 2026-03-15 only
255
+ ```
256
+
257
+ **`splitrow` — rotate to a new file every N rows:**
258
+
259
+ ```bash
260
+ ping-dataexport -s DB01 -q "SELECT * FROM sales ORDER BY id" -m splitrow -row 100000 -od split -bn part
261
+ ```
262
+ ```
263
+ split/
264
+ ├── part_1.csv <- 100,000 rows each
265
+ ├── part_2.csv
266
+ └── part_3.csv <- remainder
267
+ ```
268
+
269
+ **`watermark` — incremental: only rows newer than the last run:**
270
+
271
+ ```bash
272
+ ping-dataexport -s DB01 -q "SELECT * FROM sales" -m watermark -col txn_date -od incr -bn sales
273
+ ```
274
+ ```
275
+ incr/
276
+ ├── sales_20260831_120000.csv <- filename = run timestamp
277
+ └── __watermark__.ini <- state file: highest txn_date exported so far
278
+ ```
279
+
280
+ Run the watermark job again: no new rows → no new file; new rows → one more
281
+ timestamped file. Delete `__watermark__.ini` to start over from a full export.
282
+
283
+ Every run also prints structured log lines to the console (and to the log
284
+ file — see *Output and logs* below):
285
+
286
+ ```
287
+ 2026-08-31 11:24:41 | JOB_20260831_112441 | INFO | CONFIG | JOB | source=DB01 mode=groupby
288
+ 2026-08-31 11:24:41 | JOB_20260831_112441 | INFO | EXPORT | PARTITION | region = 'TH'
289
+ 2026-08-31 11:24:41 | JOB_20260831_112441 | INFO | EXPORT | FILE | ./output/by_region/sales_TH.csv
290
+ 2026-08-31 11:24:41 | JOB_20260831_112441 | INFO | EXPORT | PROGRESS | 200 rows (finished)
291
+ 2026-08-31 11:24:41 | JOB_20260831_112441 | INFO | RESULT | COMPLETED | 0.2 seconds
292
+ ```
293
+
294
+ ## 5. Job files (job.ini)
295
+
296
+ Put recurring exports in a job file so you never retype long commands. Each
297
+ `[Section]` is **one job**: run one with `-j SectionName`, or all of them at
298
+ once with `-pl`. Every key is simply a CLI option written as a config key:
299
+
300
+ ```ini
301
+ [DailySales]
302
+ Source=DB01
303
+ Query=SELECT id, txn_date, amount FROM sales
304
+ WHERE region = 'TH'
305
+ Mode=date
306
+ Column=txn_date
307
+ DateFrom=2026-01-01
308
+ DateTo=2026-01-31
309
+ OutputDir=out/daily_sales
310
+ BaseName=sales
311
+ Format=csv
312
+
313
+ [FullDump]
314
+ Source=DB01
315
+ Query=SELECT * FROM sales
316
+ Output=out/full_dump.parquet
317
+ ```
318
+
319
+ ```bash
320
+ ping-dataexport -j DailySales # one section (job.ini by default, -jf elsewhere)
321
+ ping-dataexport -j DailySales -od other_dir # CLI options override the section
322
+ ping-dataexport -pl 4 # ALL sections in parallel, 4 workers
323
+ ```
324
+
325
+ Every job.ini key and the CLI option it mirrors (keys are case-insensitive):
326
+
327
+ | Job key | CLI option | Meaning |
328
+ |---------|------------|---------|
329
+ | `Source` | `-s` / `--source` | which config.ini DB section to export from |
330
+ | `Query` | `-q` / `--query` | the SQL to run; indent continuation lines for multi-line queries |
331
+ | `Output` | `-o` / `--output` | single output file path |
332
+ | `OutputDir` | `-od` / `--outputdir` | output directory (needed when `Mode=` is set) |
333
+ | `BaseName` | `-bn` / `--basename` | base filename for mode exports |
334
+ | `Format` | `-f` / `--format` | `csv` / `txt` / `parquet` (else inferred from `Output` extension) |
335
+ | `Gzip` | `-gz` / `--gzip` | `Yes`/`No` — gzip the output (CSV/TXT only) |
336
+ | `Sep` | `-sp` / `--sep` | field separator (default `,`; `\t` = tab) |
337
+ | `Quote` | `-quo` / `--quote` | quote character (default `"`) |
338
+ | `StripNewLine` | `-stnl` / `--stripnewline` | `space` \| `blank` \| `escape` \| `doubleescape` |
339
+ | `StripMetaChar` | `-stmc` / `--stripmetachar` | `Yes`/`No` — strip invisible/control characters |
340
+ | `FetchSize` | `-fs` / `--fetchsize` | rows per batch |
341
+ | `Mode` | `-m` / `--mode` | `groupby` / `date` / `relativedate` / `monthbydate` / `splitrow` / `watermark` |
342
+ | `Column` | `-col` / `--column` | the column the mode partitions on |
343
+ | `Row` | `-row` / `--row` | rows per file (`Mode=splitrow`) |
344
+ | `DateFrom` | `-df` / `--datefrom` | start / anchor date, `yyyy-MM-dd` |
345
+ | `DateTo` | `-dt` / `--dateto` | end date, `yyyy-MM-dd` (`Mode=date`) |
346
+ | `DaysRelative` | `-dr` / `--daysrelative` | days back from `DateFrom` (`Mode=relativedate`) |
347
+ | `MonthsRelative` | `-mr` / `--monthsrelative` | months back from `DateFrom` (`Mode=monthbydate`) |
348
+ | `RemoveExisting` | `-rm` / `--removeexisting` | `Yes`: clear `OutputDir` before exporting |
349
+ | `Name` | `-n` / `--name` | job name shown in logs; default: the section name |
350
+
351
+ A full sample ships as `job.sample.ini` in the source distribution.
352
+
353
+ ## 6. Use as a library (Python / Airflow)
354
+
355
+ ```python
356
+ from ping_dataexport.api import run_export, run_job, run_jobfile
357
+
358
+ # ad-hoc — any CLI option works as a keyword argument
359
+ result = run_export("DB01", "SELECT * FROM sales",
360
+ configfile="config.ini", output="sales.parquet")
361
+ print(result.status, result.rows, result.files)
362
+
363
+ # one job.ini section / every section in parallel
364
+ run_job("DailySales", jobfile="job.ini", configfile="config.ini")
365
+ results = run_jobfile(jobfile="job.ini", configfile="config.ini", workers=4)
366
+ ```
367
+
368
+ Failures raise exceptions (`ConfigError`, `JobValidationError`, `ExportError`)
369
+ — never `sys.exit` — so an Airflow task fails cleanly. `run_jobfile` is the
370
+ exception: one job's failure never stops the others; check each
371
+ `ExportResult.status`.
372
+
373
+ The package also works with **no pip install at all**: copy the
374
+ `ping_dataexport/` folder next to your code and import it — relative imports
375
+ only, no metadata lookups.
376
+
377
+ ## Output and logs
378
+
379
+ - Relative output paths resolve under `ExportFolder`; `ForceMakeDir=Yes`
380
+ creates missing directories.
381
+ - Every run logs structured lines to stdout and to a log file:
382
+ `-lf path` > `LogFile=` in config.ini > default
383
+ `{ExportFolder}/logs/log_{jobname}.txt`.
384
+ - CLI exit codes: `0` success, `1` failure (in `-pl` parallel mode: `1` if any
385
+ job failed).
386
+
387
+ ## ODBC prerequisites (ODBC sources only)
388
+
389
+ The Python drivers install via pip, but ODBC drivers are OS-level installs:
390
+ Microsoft ODBC Driver 17/18 for SQL Server, FreeTDS (`apt install tdsodbc`), or
391
+ your Oracle ODBC driver. `Type=sqlite` and `Type=oracle` (thin mode) need none.
392
+
393
+ ## License
394
+
395
+ Apache-2.0.