BODtoJSON 0.0.7__tar.gz → 1.5.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.
- bodtojson-1.5.0/LICENSE +21 -0
- bodtojson-1.5.0/PKG-INFO +144 -0
- bodtojson-1.5.0/README.md +128 -0
- bodtojson-1.5.0/pyproject.toml +25 -0
- bodtojson-1.5.0/setup.cfg +4 -0
- bodtojson-1.5.0/src/BODtoJSON/__init__.py +10 -0
- bodtojson-1.5.0/src/BODtoJSON/api.py +110 -0
- bodtojson-1.5.0/src/BODtoJSON/mapper.py +108 -0
- bodtojson-1.5.0/src/BODtoJSON.egg-info/PKG-INFO +144 -0
- bodtojson-1.5.0/src/BODtoJSON.egg-info/SOURCES.txt +13 -0
- bodtojson-1.5.0/tests/test_api.py +58 -0
- bodtojson-1.5.0/tests/test_mapper.py +57 -0
- BODtoJSON-0.0.7/BODtoJSON/BODtoJSON.py +0 -106
- BODtoJSON-0.0.7/BODtoJSON/__init_.py +0 -1
- BODtoJSON-0.0.7/BODtoJSON.egg-info/PKG-INFO +0 -38
- BODtoJSON-0.0.7/BODtoJSON.egg-info/SOURCES.txt +0 -10
- BODtoJSON-0.0.7/PKG-INFO +0 -38
- BODtoJSON-0.0.7/README.md +0 -24
- BODtoJSON-0.0.7/setup.cfg +0 -7
- BODtoJSON-0.0.7/setup.py +0 -25
- {BODtoJSON-0.0.7 → bodtojson-1.5.0/src}/BODtoJSON.egg-info/dependency_links.txt +0 -0
- {BODtoJSON-0.0.7 → bodtojson-1.5.0/src}/BODtoJSON.egg-info/requires.txt +0 -0
- {BODtoJSON-0.0.7 → bodtojson-1.5.0/src}/BODtoJSON.egg-info/top_level.txt +0 -0
bodtojson-1.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Niraj Kakodkar
|
|
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.
|
bodtojson-1.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: BODtoJSON
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Production-grade Infor OAGIS BOD to Flattened JSON converter.
|
|
5
|
+
Author: Niraj Kakodkar
|
|
6
|
+
Project-URL: Homepage, https://github.com/thekakodkar/BODtoJSON
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
|
+
Classifier: Intended Audience :: Information Technology
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: xmltodict
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# BODtoJSON v1.5.0
|
|
18
|
+
|
|
19
|
+
**BODtoJSON** is an enterprise-grade Python library designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON structures.
|
|
20
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
21
|
+
|
|
22
|
+
v1.5.0 establishes a dual-pipeline standard, introducing strict single-line **Newline Delimited JSON (NDJSON)** generation alongside standard JSON, optimized directly for massive stream ingestions into Data Lakehouses and Vector Databases.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🚀 What’s New in v1.5.0 (vs. v1.0.0)
|
|
27
|
+
|
|
28
|
+
The transition to v1.5.0 introduces explicit processing pipelines and an enterprise API tier while safeguarding legacy system investments:
|
|
29
|
+
|
|
30
|
+
* **Explicit Format Architecture:** Replaced parameter-driven routing with dedicated top-level API methods—`to_json` and `to_ndjson`—improving interface predictability and self-documentation.
|
|
31
|
+
* **Strict NDJSON Linearization:** The native engine now features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Repeating record blocks (arrays) are unpacked and emitted as individual, single-line text records.
|
|
32
|
+
* **100% Backward Compatibility:** The original `convert()` function remains functional at the package root. It automatically routes to `to_json` while issuing non-breaking `DeprecationWarning` logs to guide future code modernization.
|
|
33
|
+
* **Enterprise REST API Tier (`api.py`):** Added a lightweight, high-performance FastAPI implementation featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
34
|
+
* **Pytest Migration:** The automated quality assurance framework has been upgraded to **`pytest`**, featuring regression test parameters that actively validate deprecation warning assertions.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 📈 Why it is Better & Improved
|
|
39
|
+
|
|
40
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
41
|
+
| :--- | :--- | :--- |
|
|
42
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
43
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
44
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
45
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
46
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🛠️ Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install BODtoJSON
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 📂 Project Structure
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
BODtoJSON/
|
|
60
|
+
├── src/
|
|
61
|
+
│ └── BODtoJSON/
|
|
62
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
63
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
64
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
65
|
+
├── tests/
|
|
66
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
67
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
68
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
69
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
70
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
71
|
+
├── scripts/
|
|
72
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
73
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
74
|
+
└── README.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🧪 Validation & Testing
|
|
80
|
+
|
|
81
|
+
### 1. Automated Testing (QA)
|
|
82
|
+
To run the standardized test suite and verify engine logic:
|
|
83
|
+
```powershell
|
|
84
|
+
python -m pytest -v -p no:cacheprovider
|
|
85
|
+
```
|
|
86
|
+
### 2. Manual Payload Inspection (UAT)
|
|
87
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
88
|
+
```powershell
|
|
89
|
+
python scripts/inspect_payload.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
93
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
## 💻 Code Example
|
|
97
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
98
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from BODtoJSON import to_ndjson
|
|
102
|
+
|
|
103
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
104
|
+
|
|
105
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
106
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
110
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
111
|
+
```python
|
|
112
|
+
from BODtoJSON import to_json
|
|
113
|
+
|
|
114
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
115
|
+
|
|
116
|
+
# Returns standard, single flat JSON string
|
|
117
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
121
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
122
|
+
```python
|
|
123
|
+
from BODtoJSON import convert
|
|
124
|
+
|
|
125
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
126
|
+
|
|
127
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
128
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
129
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
130
|
+
```
|
|
131
|
+
4. Running the Enterprise API Tier
|
|
132
|
+
Launch your processing microservice engine locally:
|
|
133
|
+
```bash
|
|
134
|
+
uvicorn BODtoJSON.api:app --reload
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Navigate your browser to http://127.0.0.1:8000/docs to interact with the visual Swagger UI. Use /convert/json or /convert/ndjson to execute on-demand pipeline testing.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🤝 Commercial Value
|
|
142
|
+
By providing native, low-latency transformations for both document-level applications and streaming data pipelines, **BODtoJSON** v1.5.0 reduces cloud compute and token serialization costs by up to 90%. It acts as an optimized, zero-friction interface between legacy ERP architectures (Infor LN/M3) and modern enterprise data lakes or generative AI strategies.
|
|
143
|
+
**Author:** Niraj Kakodkar
|
|
144
|
+
**License:** MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# BODtoJSON v1.5.0
|
|
2
|
+
|
|
3
|
+
**BODtoJSON** is an enterprise-grade Python library designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON structures.
|
|
4
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
5
|
+
|
|
6
|
+
v1.5.0 establishes a dual-pipeline standard, introducing strict single-line **Newline Delimited JSON (NDJSON)** generation alongside standard JSON, optimized directly for massive stream ingestions into Data Lakehouses and Vector Databases.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 What’s New in v1.5.0 (vs. v1.0.0)
|
|
11
|
+
|
|
12
|
+
The transition to v1.5.0 introduces explicit processing pipelines and an enterprise API tier while safeguarding legacy system investments:
|
|
13
|
+
|
|
14
|
+
* **Explicit Format Architecture:** Replaced parameter-driven routing with dedicated top-level API methods—`to_json` and `to_ndjson`—improving interface predictability and self-documentation.
|
|
15
|
+
* **Strict NDJSON Linearization:** The native engine now features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Repeating record blocks (arrays) are unpacked and emitted as individual, single-line text records.
|
|
16
|
+
* **100% Backward Compatibility:** The original `convert()` function remains functional at the package root. It automatically routes to `to_json` while issuing non-breaking `DeprecationWarning` logs to guide future code modernization.
|
|
17
|
+
* **Enterprise REST API Tier (`api.py`):** Added a lightweight, high-performance FastAPI implementation featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
18
|
+
* **Pytest Migration:** The automated quality assurance framework has been upgraded to **`pytest`**, featuring regression test parameters that actively validate deprecation warning assertions.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 📈 Why it is Better & Improved
|
|
23
|
+
|
|
24
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
25
|
+
| :--- | :--- | :--- |
|
|
26
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
27
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
28
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
29
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
30
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🛠️ Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install BODtoJSON
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 📂 Project Structure
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
BODtoJSON/
|
|
44
|
+
├── src/
|
|
45
|
+
│ └── BODtoJSON/
|
|
46
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
47
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
48
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
49
|
+
├── tests/
|
|
50
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
51
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
52
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
53
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
54
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
55
|
+
├── scripts/
|
|
56
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
57
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
58
|
+
└── README.md
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 🧪 Validation & Testing
|
|
64
|
+
|
|
65
|
+
### 1. Automated Testing (QA)
|
|
66
|
+
To run the standardized test suite and verify engine logic:
|
|
67
|
+
```powershell
|
|
68
|
+
python -m pytest -v -p no:cacheprovider
|
|
69
|
+
```
|
|
70
|
+
### 2. Manual Payload Inspection (UAT)
|
|
71
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
72
|
+
```powershell
|
|
73
|
+
python scripts/inspect_payload.py
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
77
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
## 💻 Code Example
|
|
81
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
82
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from BODtoJSON import to_ndjson
|
|
86
|
+
|
|
87
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
88
|
+
|
|
89
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
90
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
94
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
95
|
+
```python
|
|
96
|
+
from BODtoJSON import to_json
|
|
97
|
+
|
|
98
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
99
|
+
|
|
100
|
+
# Returns standard, single flat JSON string
|
|
101
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
105
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
106
|
+
```python
|
|
107
|
+
from BODtoJSON import convert
|
|
108
|
+
|
|
109
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
110
|
+
|
|
111
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
112
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
113
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
114
|
+
```
|
|
115
|
+
4. Running the Enterprise API Tier
|
|
116
|
+
Launch your processing microservice engine locally:
|
|
117
|
+
```bash
|
|
118
|
+
uvicorn BODtoJSON.api:app --reload
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Navigate your browser to http://127.0.0.1:8000/docs to interact with the visual Swagger UI. Use /convert/json or /convert/ndjson to execute on-demand pipeline testing.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🤝 Commercial Value
|
|
126
|
+
By providing native, low-latency transformations for both document-level applications and streaming data pipelines, **BODtoJSON** v1.5.0 reduces cloud compute and token serialization costs by up to 90%. It acts as an optimized, zero-friction interface between legacy ERP architectures (Infor LN/M3) and modern enterprise data lakes or generative AI strategies.
|
|
127
|
+
**Author:** Niraj Kakodkar
|
|
128
|
+
**License:** MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "BODtoJSON"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Niraj Kakodkar" },
|
|
10
|
+
]
|
|
11
|
+
description = "Production-grade Infor OAGIS BOD to Flattened JSON converter."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"xmltodict",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 5 - Production/Stable",
|
|
19
|
+
"Intended Audience :: Information Technology",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
"Homepage" = "https://github.com/thekakodkar/BODtoJSON"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BODtoJSON: A high-performance OAGIS BOD to Flattened JSON/NDJSON converter.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.1.0"
|
|
6
|
+
|
|
7
|
+
# Expose both legacy and modern functionality at the top level
|
|
8
|
+
from .mapper import to_json, to_ndjson, convert, BODConverter
|
|
9
|
+
|
|
10
|
+
__all__ = ["to_json", "to_ndjson", "convert", "BODConverter"]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from fastapi import FastAPI, Body, Response
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from BODtoJSON import to_json, to_ndjson, convert
|
|
4
|
+
|
|
5
|
+
app = FastAPI(
|
|
6
|
+
title="BODtoJSON API",
|
|
7
|
+
version="1.1.0",
|
|
8
|
+
description="Enterprise OAGIS Integration Data Fabric Engine"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# -------------------------------------------------------------------
|
|
12
|
+
# PYDANTIC SCHEMAS (For Structured JSON Input Endpoints)
|
|
13
|
+
# -------------------------------------------------------------------
|
|
14
|
+
class BODRequest(BaseModel):
|
|
15
|
+
xml: str
|
|
16
|
+
verb: str = "Sync"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# -------------------------------------------------------------------
|
|
20
|
+
# METRIC/HEALTH ENDPOINTS
|
|
21
|
+
# -------------------------------------------------------------------
|
|
22
|
+
@app.get("/")
|
|
23
|
+
async def root():
|
|
24
|
+
return {
|
|
25
|
+
"service": "BODtoJSON",
|
|
26
|
+
"status": "running",
|
|
27
|
+
"version": "1.1.0"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.get("/health")
|
|
32
|
+
async def health():
|
|
33
|
+
return {"status": "healthy"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# -------------------------------------------------------------------
|
|
37
|
+
# MODERN ENDPOINTS (Optimized text/plain input for Swagger UI)
|
|
38
|
+
# -------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
@app.post("/convert/ndjson")
|
|
41
|
+
async def convert_to_ndjson_stream(
|
|
42
|
+
xml_data: str = Body(..., media_type="text/plain", description="Paste raw OAGIS XML BOD here"),
|
|
43
|
+
verb: str = "Sync"
|
|
44
|
+
):
|
|
45
|
+
"""Transforms raw XML payloads into strict single-line NDJSON format."""
|
|
46
|
+
try:
|
|
47
|
+
# High performance raw text return - zero double-serialization overhead
|
|
48
|
+
result_str = to_ndjson(xml_data, verb)
|
|
49
|
+
return Response(content=result_str, media_type="application/x-ndjson")
|
|
50
|
+
except Exception as e:
|
|
51
|
+
return Response(
|
|
52
|
+
content=f'{{"status": "error", "message": "{str(e)}"}}',
|
|
53
|
+
status_code=500,
|
|
54
|
+
media_type="application/json"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@app.post("/convert/json")
|
|
59
|
+
async def convert_to_standard_json(
|
|
60
|
+
xml_data: str = Body(..., media_type="text/plain", description="Paste raw OAGIS XML BOD here"),
|
|
61
|
+
verb: str = "Sync"
|
|
62
|
+
):
|
|
63
|
+
"""Transforms raw XML payloads into standard flattened JSON strings."""
|
|
64
|
+
try:
|
|
65
|
+
result_str = to_json(xml_data, verb)
|
|
66
|
+
return Response(content=result_str, media_type="application/json")
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return Response(
|
|
69
|
+
content=f'{{"status": "error", "message": "{str(e)}"}}',
|
|
70
|
+
status_code=500,
|
|
71
|
+
media_type="application/json"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# -------------------------------------------------------------------
|
|
76
|
+
# LEGACY ENDPOINTS (Preserved exactly to prevent production breaks)
|
|
77
|
+
# -------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
@app.post("/convert")
|
|
80
|
+
async def legacy_convert_raw_xml(
|
|
81
|
+
xml_data: str = Body(..., media_type="text/plain", description="Paste raw OAGIS XML BOD here"),
|
|
82
|
+
verb: str = "Sync"
|
|
83
|
+
):
|
|
84
|
+
"""Legacy endpoint mapping directly to the backward-compatible convert function."""
|
|
85
|
+
try:
|
|
86
|
+
result_str = convert(xml_data, verb)
|
|
87
|
+
return Response(content=result_str, media_type="application/json")
|
|
88
|
+
except Exception as e:
|
|
89
|
+
return Response(
|
|
90
|
+
content=f'{{"status": "error", "message": "{str(e)}"}}',
|
|
91
|
+
status_code=500,
|
|
92
|
+
media_type="application/json"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.post("/convert/json/wrapper")
|
|
97
|
+
async def legacy_convert_json_wrapper(req: BODRequest):
|
|
98
|
+
"""
|
|
99
|
+
Legacy structured JSON request endpoint.
|
|
100
|
+
Expects JSON input: {"xml": "<PurchaseOrder>...</PurchaseOrder>", "verb": "Sync"}
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
result_str = convert(req.xml, req.verb)
|
|
104
|
+
return Response(content=result_str, media_type="application/json")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return Response(
|
|
107
|
+
content=f'{{"status": "error", "message": "{str(e)}"}}',
|
|
108
|
+
status_code=500,
|
|
109
|
+
media_type="application/json"
|
|
110
|
+
)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import xml.etree.ElementTree as ET
|
|
2
|
+
import xmltodict
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Dict, Any
|
|
7
|
+
|
|
8
|
+
class BODConverter:
|
|
9
|
+
def __init__(self, pnode: str = 'DataArea', separator: str = '_'):
|
|
10
|
+
self.pnode = pnode
|
|
11
|
+
self.separator = separator
|
|
12
|
+
|
|
13
|
+
def _prepare_raw_dict(self, bod_xml: str, verb: str) -> Dict[str, Any]:
|
|
14
|
+
"""Internal helper to clean XML, strip verbs, and extract the raw dictionary."""
|
|
15
|
+
clean_xml = bod_xml.strip().encode('utf-8').decode('utf-8-sig')
|
|
16
|
+
sanitized_xml = re.sub(r'\sxmlns(?::\w+)?="[^"]+"', '', clean_xml, count=1)
|
|
17
|
+
|
|
18
|
+
root = ET.fromstring(sanitized_xml)
|
|
19
|
+
data_area = root.find(f".//{self.pnode}")
|
|
20
|
+
|
|
21
|
+
if data_area is None:
|
|
22
|
+
raise ValueError(f"Target node '{self.pnode}' not found.")
|
|
23
|
+
|
|
24
|
+
verb_node = data_area.find(verb)
|
|
25
|
+
if verb_node is not None:
|
|
26
|
+
data_area.remove(verb_node)
|
|
27
|
+
|
|
28
|
+
noun_node = list(data_area)[0]
|
|
29
|
+
xml_fragment = ET.tostring(noun_node, encoding='utf-8').decode('utf-8')
|
|
30
|
+
|
|
31
|
+
return xmltodict.parse(xml_fragment)
|
|
32
|
+
|
|
33
|
+
def _flatify(self, obj: Any, prefix: str = "") -> Dict[str, Any]:
|
|
34
|
+
"""Recursively flattens deeply nested structures into single-level key-values."""
|
|
35
|
+
flat_dict = {}
|
|
36
|
+
if isinstance(obj, dict):
|
|
37
|
+
for k, v in obj.items():
|
|
38
|
+
new_key = f"{prefix}{self.separator}{k}" if prefix else k
|
|
39
|
+
flat_dict.update(self._flatify(v, new_key))
|
|
40
|
+
elif isinstance(obj, (list, tuple)):
|
|
41
|
+
for i, v in enumerate(obj):
|
|
42
|
+
new_key = f"{prefix}{self.separator}{i}"
|
|
43
|
+
flat_dict.update(self._flatify(v, new_key))
|
|
44
|
+
else:
|
|
45
|
+
flat_dict[prefix] = obj
|
|
46
|
+
return flat_dict
|
|
47
|
+
|
|
48
|
+
def to_json(self, bod_xml: str, verb: str) -> str:
|
|
49
|
+
"""Converts OAGIS XML to a single, standard flat JSON string."""
|
|
50
|
+
try:
|
|
51
|
+
raw_dict = self._prepare_raw_dict(bod_xml, verb)
|
|
52
|
+
flattened = self._flatify(raw_dict)
|
|
53
|
+
return json.dumps(flattened)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return json.dumps({"status": "error", "message": str(e)})
|
|
56
|
+
|
|
57
|
+
def to_ndjson(self, bod_xml: str, verb: str) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Converts OAGIS XML into strict NDJSON format.
|
|
60
|
+
Flattens and compresses individual objects onto single lines terminated by a newline.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
raw_dict = self._prepare_raw_dict(bod_xml, verb)
|
|
64
|
+
noun_key = list(raw_dict.keys())[0]
|
|
65
|
+
noun_content = raw_dict[noun_key]
|
|
66
|
+
|
|
67
|
+
if isinstance(noun_content, list):
|
|
68
|
+
lines = []
|
|
69
|
+
for record in noun_content:
|
|
70
|
+
flat_record = self._flatify({noun_key: record})
|
|
71
|
+
compact_line = json.dumps(flat_record, separators=(',', ':')).strip()
|
|
72
|
+
lines.append(compact_line)
|
|
73
|
+
return "\n".join(lines) + "\n"
|
|
74
|
+
|
|
75
|
+
flat_record = self._flatify(raw_dict)
|
|
76
|
+
compact_line = json.dumps(flat_record, separators=(',', ':')).strip()
|
|
77
|
+
return compact_line + "\n"
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
return json.dumps({"status": "error", "message": str(e)}) + "\n"
|
|
81
|
+
|
|
82
|
+
def convert(self, bod_xml: str, verb: str) -> str:
|
|
83
|
+
"""Legacy class method kept for 100% backward compatibility."""
|
|
84
|
+
return self.to_json(bod_xml, verb)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# -------------------------------------------------------------------
|
|
88
|
+
# Top-Level Facade Functions (Global Access Shorthand)
|
|
89
|
+
# -------------------------------------------------------------------
|
|
90
|
+
def to_json(bod_xml: str, verb: str) -> str:
|
|
91
|
+
"""Primary API for Standard JSON conversion"""
|
|
92
|
+
return BODConverter().to_json(bod_xml, verb)
|
|
93
|
+
|
|
94
|
+
def to_ndjson(bod_xml: str, verb: str) -> str:
|
|
95
|
+
"""Primary API for Newline Delimited JSON (NDJSON) conversion"""
|
|
96
|
+
return BODConverter().to_ndjson(bod_xml, verb)
|
|
97
|
+
|
|
98
|
+
def convert(bod_xml: str, verb: str) -> str:
|
|
99
|
+
"""
|
|
100
|
+
LEGACY FUNCTION: Maintained to prevent breaking production changes.
|
|
101
|
+
Maps directly to to_json() behind the scenes.
|
|
102
|
+
"""
|
|
103
|
+
warnings.warn(
|
|
104
|
+
"The 'convert' function is deprecated and will be removed in v2.0.0. Use 'to_json' or 'to_ndjson' instead.",
|
|
105
|
+
DeprecationWarning,
|
|
106
|
+
stacklevel=2
|
|
107
|
+
)
|
|
108
|
+
return to_json(bod_xml, verb)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: BODtoJSON
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Production-grade Infor OAGIS BOD to Flattened JSON converter.
|
|
5
|
+
Author: Niraj Kakodkar
|
|
6
|
+
Project-URL: Homepage, https://github.com/thekakodkar/BODtoJSON
|
|
7
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
8
|
+
Classifier: Intended Audience :: Information Technology
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: xmltodict
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# BODtoJSON v1.5.0
|
|
18
|
+
|
|
19
|
+
**BODtoJSON** is an enterprise-grade Python library designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON structures.
|
|
20
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
21
|
+
|
|
22
|
+
v1.5.0 establishes a dual-pipeline standard, introducing strict single-line **Newline Delimited JSON (NDJSON)** generation alongside standard JSON, optimized directly for massive stream ingestions into Data Lakehouses and Vector Databases.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🚀 What’s New in v1.5.0 (vs. v1.0.0)
|
|
27
|
+
|
|
28
|
+
The transition to v1.5.0 introduces explicit processing pipelines and an enterprise API tier while safeguarding legacy system investments:
|
|
29
|
+
|
|
30
|
+
* **Explicit Format Architecture:** Replaced parameter-driven routing with dedicated top-level API methods—`to_json` and `to_ndjson`—improving interface predictability and self-documentation.
|
|
31
|
+
* **Strict NDJSON Linearization:** The native engine now features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Repeating record blocks (arrays) are unpacked and emitted as individual, single-line text records.
|
|
32
|
+
* **100% Backward Compatibility:** The original `convert()` function remains functional at the package root. It automatically routes to `to_json` while issuing non-breaking `DeprecationWarning` logs to guide future code modernization.
|
|
33
|
+
* **Enterprise REST API Tier (`api.py`):** Added a lightweight, high-performance FastAPI implementation featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
34
|
+
* **Pytest Migration:** The automated quality assurance framework has been upgraded to **`pytest`**, featuring regression test parameters that actively validate deprecation warning assertions.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 📈 Why it is Better & Improved
|
|
39
|
+
|
|
40
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
41
|
+
| :--- | :--- | :--- |
|
|
42
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
43
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
44
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
45
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
46
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🛠️ Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install BODtoJSON
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 📂 Project Structure
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
BODtoJSON/
|
|
60
|
+
├── src/
|
|
61
|
+
│ └── BODtoJSON/
|
|
62
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
63
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
64
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
65
|
+
├── tests/
|
|
66
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
67
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
68
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
69
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
70
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
71
|
+
├── scripts/
|
|
72
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
73
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
74
|
+
└── README.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 🧪 Validation & Testing
|
|
80
|
+
|
|
81
|
+
### 1. Automated Testing (QA)
|
|
82
|
+
To run the standardized test suite and verify engine logic:
|
|
83
|
+
```powershell
|
|
84
|
+
python -m pytest -v -p no:cacheprovider
|
|
85
|
+
```
|
|
86
|
+
### 2. Manual Payload Inspection (UAT)
|
|
87
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
88
|
+
```powershell
|
|
89
|
+
python scripts/inspect_payload.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
93
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
## 💻 Code Example
|
|
97
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
98
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from BODtoJSON import to_ndjson
|
|
102
|
+
|
|
103
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
104
|
+
|
|
105
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
106
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
110
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
111
|
+
```python
|
|
112
|
+
from BODtoJSON import to_json
|
|
113
|
+
|
|
114
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
115
|
+
|
|
116
|
+
# Returns standard, single flat JSON string
|
|
117
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
121
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
122
|
+
```python
|
|
123
|
+
from BODtoJSON import convert
|
|
124
|
+
|
|
125
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
126
|
+
|
|
127
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
128
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
129
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
130
|
+
```
|
|
131
|
+
4. Running the Enterprise API Tier
|
|
132
|
+
Launch your processing microservice engine locally:
|
|
133
|
+
```bash
|
|
134
|
+
uvicorn BODtoJSON.api:app --reload
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Navigate your browser to http://127.0.0.1:8000/docs to interact with the visual Swagger UI. Use /convert/json or /convert/ndjson to execute on-demand pipeline testing.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🤝 Commercial Value
|
|
142
|
+
By providing native, low-latency transformations for both document-level applications and streaming data pipelines, **BODtoJSON** v1.5.0 reduces cloud compute and token serialization costs by up to 90%. It acts as an optimized, zero-friction interface between legacy ERP architectures (Infor LN/M3) and modern enterprise data lakes or generative AI strategies.
|
|
143
|
+
**Author:** Niraj Kakodkar
|
|
144
|
+
**License:** MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/BODtoJSON/__init__.py
|
|
5
|
+
src/BODtoJSON/api.py
|
|
6
|
+
src/BODtoJSON/mapper.py
|
|
7
|
+
src/BODtoJSON.egg-info/PKG-INFO
|
|
8
|
+
src/BODtoJSON.egg-info/SOURCES.txt
|
|
9
|
+
src/BODtoJSON.egg-info/dependency_links.txt
|
|
10
|
+
src/BODtoJSON.egg-info/requires.txt
|
|
11
|
+
src/BODtoJSON.egg-info/top_level.txt
|
|
12
|
+
tests/test_api.py
|
|
13
|
+
tests/test_mapper.py
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from fastapi.testclient import TestClient
|
|
3
|
+
from BODtoJSON.api import app
|
|
4
|
+
|
|
5
|
+
client = TestClient(app)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_api_convert_to_json(xml_loader):
|
|
9
|
+
"""Validates the modern structured JSON streaming path."""
|
|
10
|
+
xml_data = xml_loader.load("sync_PO_PSU000049.xml")
|
|
11
|
+
|
|
12
|
+
# Pass the XML string directly as the raw request content
|
|
13
|
+
response = client.post(
|
|
14
|
+
"/convert/json?verb=Sync",
|
|
15
|
+
content=xml_data,
|
|
16
|
+
headers={"Content-Type": "text/plain"}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
assert response.status_code == 200
|
|
20
|
+
assert response.headers["content-type"] == "application/json"
|
|
21
|
+
|
|
22
|
+
# Verify the flattened content was returned cleanly
|
|
23
|
+
data = response.json()
|
|
24
|
+
assert "PurchaseOrder_PurchaseOrderHeader_DisplayID" in data
|
|
25
|
+
assert data["PurchaseOrder_PurchaseOrderHeader_DisplayID"] == "PSU000049"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_api_convert_to_ndjson(xml_loader):
|
|
29
|
+
"""Validates the modern linearized NDJSON streaming path."""
|
|
30
|
+
xml_data = xml_loader.load("sync_PO_PSU000049.xml")
|
|
31
|
+
|
|
32
|
+
response = client.post(
|
|
33
|
+
"/convert/ndjson?verb=Sync",
|
|
34
|
+
content=xml_data,
|
|
35
|
+
headers={"Content-Type": "text/plain"}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
assert response.status_code == 200
|
|
39
|
+
assert response.headers["content-type"] == "application/x-ndjson"
|
|
40
|
+
|
|
41
|
+
# NDJSON response returns raw text rows. Parse the first row to validate.
|
|
42
|
+
raw_text = response.text
|
|
43
|
+
assert raw_text.endswith("\n") # Ensure strict NDJSON spacing compliance
|
|
44
|
+
|
|
45
|
+
first_record = json.loads(raw_text.split("\n")[0])
|
|
46
|
+
assert first_record["PurchaseOrder_PurchaseOrderHeader_DisplayID"] == "PSU000049"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_legacy_api_convert_fallback(xml_loader):
|
|
50
|
+
"""Validates that old integrations hitting /convert still work flawlessly."""
|
|
51
|
+
xml_data = xml_loader.load("sync_PO_PSU000049.xml")
|
|
52
|
+
|
|
53
|
+
response = client.post(
|
|
54
|
+
"/convert?verb=Sync",
|
|
55
|
+
content=xml_data,
|
|
56
|
+
headers={"Content-Type": "text/plain"}
|
|
57
|
+
)
|
|
58
|
+
assert response.status_code == 200
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pytest
|
|
3
|
+
from BODtoJSON import to_json, to_ndjson, convert
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.parametrize(
|
|
7
|
+
"file_name, expected_id",
|
|
8
|
+
[
|
|
9
|
+
("sync_PO_PSU000049.xml", "PSU000049"),
|
|
10
|
+
("sync_PO_PSU000050.xml", "PSU000050"),
|
|
11
|
+
]
|
|
12
|
+
)
|
|
13
|
+
def test_to_json_flattening(xml_loader, file_name, expected_id):
|
|
14
|
+
"""Verifies standard structured JSON formatting maps correctly."""
|
|
15
|
+
xml = xml_loader.load(file_name)
|
|
16
|
+
|
|
17
|
+
result_json = to_json(xml, "Sync")
|
|
18
|
+
data = json.loads(result_json)
|
|
19
|
+
|
|
20
|
+
key = "PurchaseOrder_PurchaseOrderHeader_DocumentID_ID_#text"
|
|
21
|
+
assert data.get(key) == expected_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize(
|
|
25
|
+
"file_name, expected_id",
|
|
26
|
+
[
|
|
27
|
+
("sync_PO_PSU000049.xml", "PSU000049"),
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
def test_to_ndjson_linearization(xml_loader, file_name, expected_id):
|
|
31
|
+
"""Verifies strict compression and single-line execution constraints for NDJSON."""
|
|
32
|
+
xml = xml_loader.load(file_name)
|
|
33
|
+
|
|
34
|
+
result_ndjson = to_ndjson(xml, "Sync")
|
|
35
|
+
|
|
36
|
+
# Operational Checks: No multi-line breaks allowed inside a single record
|
|
37
|
+
assert result_ndjson.count("\n") == 1
|
|
38
|
+
assert " " not in result_ndjson.split(":")[0] # Verify dense spaces mapping
|
|
39
|
+
|
|
40
|
+
data = json.loads(result_ndjson.strip())
|
|
41
|
+
key = "PurchaseOrder_PurchaseOrderHeader_DocumentID_ID_#text"
|
|
42
|
+
assert data.get(key) == expected_id
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_legacy_convert_deprecation_warning(xml_loader):
|
|
46
|
+
"""
|
|
47
|
+
Regression Testing: Assures that calling the old 'convert' function
|
|
48
|
+
still works but fires a deprecation alert to keep developers modern.
|
|
49
|
+
"""
|
|
50
|
+
xml = xml_loader.load("sync_PO_PSU000049.xml")
|
|
51
|
+
|
|
52
|
+
# This context manager passes only if a DeprecationWarning is successfully caught
|
|
53
|
+
with pytest.deprecated_call():
|
|
54
|
+
result = convert(xml, "Sync")
|
|
55
|
+
|
|
56
|
+
data = json.loads(result)
|
|
57
|
+
assert "PurchaseOrder_PurchaseOrderHeader_DisplayID" in data
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2020 Niraj Kakodkar
|
|
2
|
-
#
|
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
-
# furnished to do so, subject to the following conditions:
|
|
9
|
-
#
|
|
10
|
-
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
-
# copies or substantial portions of the Software.
|
|
12
|
-
#
|
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
-
# SOFTWARE.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
import xmltodict, json
|
|
23
|
-
import xml.etree.ElementTree as ET
|
|
24
|
-
import re
|
|
25
|
-
pnode = 'DataArea'
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def convert(bod, verb):
|
|
29
|
-
"""
|
|
30
|
-
converts the input BOX XML to Flat JASON
|
|
31
|
-
:param bod:
|
|
32
|
-
:return: Flat JSON
|
|
33
|
-
"""
|
|
34
|
-
# Remove Namespaces from XML
|
|
35
|
-
input = re.sub(' xmlns="[^"]+"', '', bod, count=1)
|
|
36
|
-
|
|
37
|
-
# Create an Element Tree
|
|
38
|
-
root = ET.fromstring(input)
|
|
39
|
-
|
|
40
|
-
# Extract the Data Area
|
|
41
|
-
# Remove unnecessary Nodes
|
|
42
|
-
for parentnode in root.findall(pnode):
|
|
43
|
-
for node in parentnode.findall(verb):
|
|
44
|
-
parentnode.remove(node)
|
|
45
|
-
root = ET.tostring(parentnode).decode('utf-8')
|
|
46
|
-
|
|
47
|
-
jsonout = xmltodict.parse(root)
|
|
48
|
-
jsonout = flatify(jsonout)
|
|
49
|
-
jsonout = json.dumps(jsonout)
|
|
50
|
-
return (jsonout)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def _genkey(prev_key, separator, new_key):
|
|
54
|
-
"""
|
|
55
|
-
Returns the new_key in case no prev key exists, concatenates
|
|
56
|
-
prev key, separator, and new_key otherwise
|
|
57
|
-
:param prev_key:
|
|
58
|
-
:param separator:
|
|
59
|
-
:param new_key:
|
|
60
|
-
:return: a string if prev_key exists and simply passes through the
|
|
61
|
-
new_key otherwise
|
|
62
|
-
"""
|
|
63
|
-
if prev_key:
|
|
64
|
-
return u"{}{}{}".format(prev_key, separator, new_key)
|
|
65
|
-
else:
|
|
66
|
-
return new_key
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def flatify(idict, separator="_", root_keys_to_ignore=set()):
|
|
70
|
-
"""
|
|
71
|
-
Creates flat dictionary from the nested dictionary
|
|
72
|
-
|
|
73
|
-
:param idict: Nested dictionary we want to make flat
|
|
74
|
-
:param separator: string to separate dictionary keys by
|
|
75
|
-
:param root_keys_to_ignore: set of root keys to ignore from flattening
|
|
76
|
-
:return: flat dictionary
|
|
77
|
-
"""
|
|
78
|
-
assert isinstance(idict, dict), "flattify requires a dictionary input"
|
|
79
|
-
assert isinstance(separator, str), "separator must be string"
|
|
80
|
-
|
|
81
|
-
flat_dict = dict()
|
|
82
|
-
|
|
83
|
-
def _flatify(object_, key):
|
|
84
|
-
"""
|
|
85
|
-
:param object_: object to flatten
|
|
86
|
-
:param key: carries the concatenated key for the object_
|
|
87
|
-
:return: None
|
|
88
|
-
"""
|
|
89
|
-
# Empty object can't be iterated, take as is
|
|
90
|
-
if not object_:
|
|
91
|
-
flat_dict[key] = object_
|
|
92
|
-
# These object types support iteration
|
|
93
|
-
elif isinstance(object_, dict):
|
|
94
|
-
for object_key in object_:
|
|
95
|
-
if not (not key and object_key in root_keys_to_ignore):
|
|
96
|
-
_flatify(object_[object_key], _genkey(key,
|
|
97
|
-
separator,
|
|
98
|
-
object_key))
|
|
99
|
-
elif isinstance(object_, (list, set, tuple)):
|
|
100
|
-
for index, item in enumerate(object_):
|
|
101
|
-
_flatify(item, _genkey(key, separator, index))
|
|
102
|
-
else:
|
|
103
|
-
flat_dict[key] = object_
|
|
104
|
-
|
|
105
|
-
_flatify(idict, None)
|
|
106
|
-
return flat_dict
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from BODtoJSON.BODtoJSON import convert
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: BODtoJSON
|
|
3
|
-
Version: 0.0.7
|
|
4
|
-
Summary: Converts Infor QAGIS BODs to flattened JSON
|
|
5
|
-
Home-page: https://github.com/thekakodkar/Mapper
|
|
6
|
-
Author: Niraj Kakodkar
|
|
7
|
-
Author-email: niraj.kakodkar@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: # Mapper
|
|
10
|
-
|
|
11
|
-
Mapper is a python library to convert the Infor OAGIS BODs to Flattened JSON.
|
|
12
|
-
The library simplifies the converted JSON by keeping only the content within DataArea.
|
|
13
|
-
The library will remove the Verb node from DataArea and keeps only the Noun node.
|
|
14
|
-
|
|
15
|
-
#### Usage
|
|
16
|
-
|
|
17
|
-
##### Getting it
|
|
18
|
-
To download BODtoJSON, either fork this github repo or simply use Pypi via pip.
|
|
19
|
-
```python
|
|
20
|
-
$ pip install BODtoJSON==0.0.7
|
|
21
|
-
```
|
|
22
|
-
##### Using it:
|
|
23
|
-
```python
|
|
24
|
-
from BODtoJSON.BODtoJSON import convert
|
|
25
|
-
output_var = convert(bodxml, verb)
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
#### MIT License
|
|
29
|
-
Read License.txt
|
|
30
|
-
|
|
31
|
-
#### GITHUB
|
|
32
|
-
Fork me on github https://github.com/thekakodkar/Mapper
|
|
33
|
-
Platform: UNKNOWN
|
|
34
|
-
Classifier: Programming Language :: Python :: 3
|
|
35
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
-
Classifier: Operating System :: OS Independent
|
|
37
|
-
Requires-Python: >=3.0
|
|
38
|
-
Description-Content-Type: text/markdown
|
BODtoJSON-0.0.7/PKG-INFO
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: BODtoJSON
|
|
3
|
-
Version: 0.0.7
|
|
4
|
-
Summary: Converts Infor QAGIS BODs to flattened JSON
|
|
5
|
-
Home-page: https://github.com/thekakodkar/Mapper
|
|
6
|
-
Author: Niraj Kakodkar
|
|
7
|
-
Author-email: niraj.kakodkar@gmail.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: # Mapper
|
|
10
|
-
|
|
11
|
-
Mapper is a python library to convert the Infor OAGIS BODs to Flattened JSON.
|
|
12
|
-
The library simplifies the converted JSON by keeping only the content within DataArea.
|
|
13
|
-
The library will remove the Verb node from DataArea and keeps only the Noun node.
|
|
14
|
-
|
|
15
|
-
#### Usage
|
|
16
|
-
|
|
17
|
-
##### Getting it
|
|
18
|
-
To download BODtoJSON, either fork this github repo or simply use Pypi via pip.
|
|
19
|
-
```python
|
|
20
|
-
$ pip install BODtoJSON==0.0.7
|
|
21
|
-
```
|
|
22
|
-
##### Using it:
|
|
23
|
-
```python
|
|
24
|
-
from BODtoJSON.BODtoJSON import convert
|
|
25
|
-
output_var = convert(bodxml, verb)
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
#### MIT License
|
|
29
|
-
Read License.txt
|
|
30
|
-
|
|
31
|
-
#### GITHUB
|
|
32
|
-
Fork me on github https://github.com/thekakodkar/Mapper
|
|
33
|
-
Platform: UNKNOWN
|
|
34
|
-
Classifier: Programming Language :: Python :: 3
|
|
35
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
-
Classifier: Operating System :: OS Independent
|
|
37
|
-
Requires-Python: >=3.0
|
|
38
|
-
Description-Content-Type: text/markdown
|
BODtoJSON-0.0.7/README.md
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Mapper
|
|
2
|
-
|
|
3
|
-
Mapper is a python library to convert the Infor OAGIS BODs to Flattened JSON.
|
|
4
|
-
The library simplifies the converted JSON by keeping only the content within DataArea.
|
|
5
|
-
The library will remove the Verb node from DataArea and keeps only the Noun node.
|
|
6
|
-
|
|
7
|
-
#### Usage
|
|
8
|
-
|
|
9
|
-
##### Getting it
|
|
10
|
-
To download BODtoJSON, either fork this github repo or simply use Pypi via pip.
|
|
11
|
-
```python
|
|
12
|
-
$ pip install BODtoJSON==0.0.7
|
|
13
|
-
```
|
|
14
|
-
##### Using it:
|
|
15
|
-
```python
|
|
16
|
-
from BODtoJSON.BODtoJSON import convert
|
|
17
|
-
output_var = convert(bodxml, verb)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
#### MIT License
|
|
21
|
-
Read License.txt
|
|
22
|
-
|
|
23
|
-
#### GITHUB
|
|
24
|
-
Fork me on github https://github.com/thekakodkar/Mapper
|
BODtoJSON-0.0.7/setup.cfg
DELETED
BODtoJSON-0.0.7/setup.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import setuptools
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
with open("README.md", "r") as fh:
|
|
5
|
-
long_description = fh.read()
|
|
6
|
-
|
|
7
|
-
setup(
|
|
8
|
-
name="BODtoJSON",
|
|
9
|
-
version="0.0.7",
|
|
10
|
-
author="Niraj Kakodkar",
|
|
11
|
-
author_email="niraj.kakodkar@gmail.com",
|
|
12
|
-
license='MIT',
|
|
13
|
-
description="Converts Infor QAGIS BODs to flattened JSON",
|
|
14
|
-
long_description=long_description,
|
|
15
|
-
long_description_content_type="text/markdown",
|
|
16
|
-
url="https://github.com/thekakodkar/Mapper",
|
|
17
|
-
packages=['BODtoJSON'],
|
|
18
|
-
classifiers=[
|
|
19
|
-
"Programming Language :: Python :: 3",
|
|
20
|
-
"License :: OSI Approved :: MIT License",
|
|
21
|
-
"Operating System :: OS Independent",
|
|
22
|
-
],
|
|
23
|
-
install_requires=['xmltodict'],
|
|
24
|
-
python_requires='>=3.0',
|
|
25
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|