BODtoJSON 1.0.0__tar.gz → 2.0.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-2.0.0/PKG-INFO +143 -0
- bodtojson-2.0.0/README.md +127 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/pyproject.toml +2 -2
- bodtojson-2.0.0/src/BODtoJSON/__init__.py +9 -0
- bodtojson-2.0.0/src/BODtoJSON/api.py +110 -0
- bodtojson-2.0.0/src/BODtoJSON/mapper.py +204 -0
- bodtojson-2.0.0/src/BODtoJSON.egg-info/PKG-INFO +143 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/src/BODtoJSON.egg-info/SOURCES.txt +2 -0
- bodtojson-2.0.0/tests/test_api.py +58 -0
- bodtojson-2.0.0/tests/test_mapper.py +57 -0
- bodtojson-1.0.0/PKG-INFO +0 -113
- bodtojson-1.0.0/README.md +0 -97
- bodtojson-1.0.0/src/BODtoJSON/__init__.py +0 -10
- bodtojson-1.0.0/src/BODtoJSON/mapper.py +0 -56
- bodtojson-1.0.0/src/BODtoJSON.egg-info/PKG-INFO +0 -113
- bodtojson-1.0.0/tests/test_mapper.py +0 -28
- {bodtojson-1.0.0 → bodtojson-2.0.0}/LICENSE +0 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/setup.cfg +0 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/src/BODtoJSON.egg-info/dependency_links.txt +0 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/src/BODtoJSON.egg-info/requires.txt +0 -0
- {bodtojson-1.0.0 → bodtojson-2.0.0}/src/BODtoJSON.egg-info/top_level.txt +0 -0
bodtojson-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: BODtoJSON
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Zero-dependency OAGIS BOD to Flattened JSON and NDJSON 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 v2.0.0
|
|
18
|
+
|
|
19
|
+
**BODtoJSON** is a zero-dependency, enterprise-grade Python data liquidity engine designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON and NDJSON structures.
|
|
20
|
+
|
|
21
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
22
|
+
|
|
23
|
+
v2.0.0 marks a major architectural milestone: eliminating all external core dependencies, introducing native single-pass ElementTree traversal, and establishing a dual-pipeline streaming standard for Data Lakehouses and Vector Databases.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 What’s New in v2.0.0 (vs. v1.0.0)
|
|
28
|
+
|
|
29
|
+
* **Zero-Dependency Core Engine:** Removed `xmltodict` in favor of a single-pass recursive traversal built purely on Python’s native `xml.etree.ElementTree`. This eliminates external supply-chain risks, shrinks deployment footprints for air-gapped or serverless environments, and halves intermediate memory allocations.
|
|
30
|
+
* **Explicit Dual-Pipeline 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:** Features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Multi-noun batch payloads inside `DataArea` are automatically unpacked and emitted as individual, single-line text records.
|
|
32
|
+
* **Enterprise REST API Tier (`api.py`):** Added an optional, high-performance FastAPI microservice layer featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
33
|
+
* **Pytest Suite:** Standardized automated quality assurance on **`pytest`**, including regression assertions for deprecation warnings.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📈 Why it is Better & Improved
|
|
38
|
+
|
|
39
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
40
|
+
| :--- | :--- | :--- |
|
|
41
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
42
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
43
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
44
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
45
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🛠️ Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install BODtoJSON
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 📂 Project Structure
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
BODtoJSON/
|
|
59
|
+
├── src/
|
|
60
|
+
│ └── BODtoJSON/
|
|
61
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
62
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
63
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
64
|
+
├── tests/
|
|
65
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
66
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
67
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
68
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
69
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
70
|
+
├── scripts/
|
|
71
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
72
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
73
|
+
└── README.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🧪 Validation & Testing
|
|
79
|
+
|
|
80
|
+
### 1. Automated Testing (QA)
|
|
81
|
+
To run the standardized test suite and verify engine logic:
|
|
82
|
+
```powershell
|
|
83
|
+
python -m pytest -v -p no:cacheprovider
|
|
84
|
+
```
|
|
85
|
+
### 2. Manual Payload Inspection (UAT)
|
|
86
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
87
|
+
```powershell
|
|
88
|
+
python scripts/inspect_payload.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
92
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
## 💻 Code Example
|
|
96
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
97
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from BODtoJSON import to_ndjson
|
|
101
|
+
|
|
102
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
103
|
+
|
|
104
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
105
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
109
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
110
|
+
```python
|
|
111
|
+
from BODtoJSON import to_json
|
|
112
|
+
|
|
113
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
114
|
+
|
|
115
|
+
# Returns standard, single flat JSON string
|
|
116
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
120
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
121
|
+
```python
|
|
122
|
+
from BODtoJSON import convert
|
|
123
|
+
|
|
124
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
125
|
+
|
|
126
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
127
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
128
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
129
|
+
```
|
|
130
|
+
4. Running the Enterprise API Tier
|
|
131
|
+
Launch your processing microservice engine locally:
|
|
132
|
+
```bash
|
|
133
|
+
uvicorn BODtoJSON.api:app --reload
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🤝 Commercial Value
|
|
141
|
+
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.
|
|
142
|
+
**Author:** Niraj Kakodkar
|
|
143
|
+
**License:** MIT
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# BODtoJSON v2.0.0
|
|
2
|
+
|
|
3
|
+
**BODtoJSON** is a zero-dependency, enterprise-grade Python data liquidity engine designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON and NDJSON structures.
|
|
4
|
+
|
|
5
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
6
|
+
|
|
7
|
+
v2.0.0 marks a major architectural milestone: eliminating all external core dependencies, introducing native single-pass ElementTree traversal, and establishing a dual-pipeline streaming standard for Data Lakehouses and Vector Databases.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🚀 What’s New in v2.0.0 (vs. v1.0.0)
|
|
12
|
+
|
|
13
|
+
* **Zero-Dependency Core Engine:** Removed `xmltodict` in favor of a single-pass recursive traversal built purely on Python’s native `xml.etree.ElementTree`. This eliminates external supply-chain risks, shrinks deployment footprints for air-gapped or serverless environments, and halves intermediate memory allocations.
|
|
14
|
+
* **Explicit Dual-Pipeline 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:** Features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Multi-noun batch payloads inside `DataArea` are automatically unpacked and emitted as individual, single-line text records.
|
|
16
|
+
* **Enterprise REST API Tier (`api.py`):** Added an optional, high-performance FastAPI microservice layer featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
17
|
+
* **Pytest Suite:** Standardized automated quality assurance on **`pytest`**, including regression assertions for deprecation warnings.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📈 Why it is Better & Improved
|
|
22
|
+
|
|
23
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
24
|
+
| :--- | :--- | :--- |
|
|
25
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
26
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
27
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
28
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
29
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 🛠️ Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install BODtoJSON
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 📂 Project Structure
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
BODtoJSON/
|
|
43
|
+
├── src/
|
|
44
|
+
│ └── BODtoJSON/
|
|
45
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
46
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
47
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
48
|
+
├── tests/
|
|
49
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
50
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
51
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
52
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
53
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
54
|
+
├── scripts/
|
|
55
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
56
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
57
|
+
└── README.md
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 🧪 Validation & Testing
|
|
63
|
+
|
|
64
|
+
### 1. Automated Testing (QA)
|
|
65
|
+
To run the standardized test suite and verify engine logic:
|
|
66
|
+
```powershell
|
|
67
|
+
python -m pytest -v -p no:cacheprovider
|
|
68
|
+
```
|
|
69
|
+
### 2. Manual Payload Inspection (UAT)
|
|
70
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
71
|
+
```powershell
|
|
72
|
+
python scripts/inspect_payload.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
76
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
## 💻 Code Example
|
|
80
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
81
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from BODtoJSON import to_ndjson
|
|
85
|
+
|
|
86
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
87
|
+
|
|
88
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
89
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
93
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
94
|
+
```python
|
|
95
|
+
from BODtoJSON import to_json
|
|
96
|
+
|
|
97
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
98
|
+
|
|
99
|
+
# Returns standard, single flat JSON string
|
|
100
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
104
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
105
|
+
```python
|
|
106
|
+
from BODtoJSON import convert
|
|
107
|
+
|
|
108
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
109
|
+
|
|
110
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
111
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
112
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
113
|
+
```
|
|
114
|
+
4. Running the Enterprise API Tier
|
|
115
|
+
Launch your processing microservice engine locally:
|
|
116
|
+
```bash
|
|
117
|
+
uvicorn BODtoJSON.api:app --reload
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🤝 Commercial Value
|
|
125
|
+
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.
|
|
126
|
+
**Author:** Niraj Kakodkar
|
|
127
|
+
**License:** MIT
|
|
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "BODtoJSON"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Niraj Kakodkar" },
|
|
10
10
|
]
|
|
11
|
-
description = "
|
|
11
|
+
description = "Zero-dependency OAGIS BOD to Flattened JSON and NDJSON converter"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.8"
|
|
14
14
|
dependencies = [
|
|
@@ -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="2.0.0",
|
|
8
|
+
description="Zero-Dependency 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": "2.0.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,204 @@
|
|
|
1
|
+
import xml.etree.ElementTree as ET
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import warnings
|
|
5
|
+
from typing import Dict, Any, List, Tuple
|
|
6
|
+
|
|
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_noun_element(self, bod_xml: str, verb: str) -> ET.Element:
|
|
14
|
+
"""Cleans XML, strips the administrative Verb, and extracts the primary Noun element."""
|
|
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
|
+
children = list(data_area)
|
|
29
|
+
if not children:
|
|
30
|
+
raise ValueError(f"No Noun element found inside '{self.pnode}'.")
|
|
31
|
+
|
|
32
|
+
return children[0]
|
|
33
|
+
|
|
34
|
+
def _strip_ns(self, tag: str) -> str:
|
|
35
|
+
"""Strips any residual {namespace} URI prefixes from ElementTree tags."""
|
|
36
|
+
return tag.split('}', 1)[-1] if '}' in tag else tag
|
|
37
|
+
|
|
38
|
+
def _flatten_element(self, elem: ET.Element, prefix: str = "") -> Dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Recursively traverses an ElementTree node directly into a flattened dictionary,
|
|
41
|
+
preserving exact xmltodict attribute (@) and text (#text) key parity.
|
|
42
|
+
"""
|
|
43
|
+
flat_dict: Dict[str, Any] = {}
|
|
44
|
+
tag_name = self._strip_ns(elem.tag)
|
|
45
|
+
current_prefix = f"{prefix}{self.separator}{tag_name}" if prefix else tag_name
|
|
46
|
+
|
|
47
|
+
# 1. Extract XML Attributes (e.g., _@accountingEntity)
|
|
48
|
+
for attr_k, attr_v in elem.attrib.items():
|
|
49
|
+
clean_attr = self._strip_ns(attr_k)
|
|
50
|
+
flat_dict[f"{current_prefix}{self.separator}@{clean_attr}"] = attr_v
|
|
51
|
+
|
|
52
|
+
children = list(elem)
|
|
53
|
+
text_val = elem.text.strip() if elem.text and elem.text.strip() else None
|
|
54
|
+
|
|
55
|
+
# 2. Leaf Node Handling
|
|
56
|
+
if not children:
|
|
57
|
+
if elem.attrib:
|
|
58
|
+
# If node has both attributes and text, assign text to _#text
|
|
59
|
+
if text_val is not None:
|
|
60
|
+
flat_dict[f"{current_prefix}{self.separator}#text"] = text_val
|
|
61
|
+
else:
|
|
62
|
+
# Pure text leaf node (maps to None if empty, matching xmltodict parity)
|
|
63
|
+
flat_dict[current_prefix] = text_val
|
|
64
|
+
return flat_dict
|
|
65
|
+
|
|
66
|
+
# 3. Mixed Content Handling (Parent node has children AND direct text)
|
|
67
|
+
if text_val is not None:
|
|
68
|
+
flat_dict[f"{current_prefix}{self.separator}#text"] = text_val
|
|
69
|
+
|
|
70
|
+
# 4. Group Child Elements by Tag Name to Detect Repeating Arrays
|
|
71
|
+
grouped_children: Dict[str, List[ET.Element]] = {}
|
|
72
|
+
for child in children:
|
|
73
|
+
c_tag = self._strip_ns(child.tag)
|
|
74
|
+
grouped_children.setdefault(c_tag, []).append(child)
|
|
75
|
+
|
|
76
|
+
for c_tag, c_list in grouped_children.items():
|
|
77
|
+
if len(c_list) == 1:
|
|
78
|
+
flat_dict.update(self._flatten_element(c_list[0], current_prefix))
|
|
79
|
+
else:
|
|
80
|
+
# Repeating sibling tags get indexed (e.g., _PurchaseOrderLine_0_...)
|
|
81
|
+
for idx, child_elem in enumerate(c_list):
|
|
82
|
+
indexed_prefix = f"{current_prefix}{self.separator}{c_tag}{self.separator}{idx}"
|
|
83
|
+
# Process attributes and children of the indexed element directly
|
|
84
|
+
flat_dict.update(self._flatten_indexed_child(child_elem, indexed_prefix))
|
|
85
|
+
|
|
86
|
+
return flat_dict
|
|
87
|
+
|
|
88
|
+
def _flatten_indexed_child(self, elem: ET.Element, indexed_prefix: str) -> Dict[str, Any]:
|
|
89
|
+
"""Helper to flatten elements inside a repeating sibling list without duplicating the tag name."""
|
|
90
|
+
flat_dict: Dict[str, Any] = {}
|
|
91
|
+
|
|
92
|
+
for attr_k, attr_v in elem.attrib.items():
|
|
93
|
+
clean_attr = self._strip_ns(attr_k)
|
|
94
|
+
flat_dict[f"{indexed_prefix}{self.separator}@{clean_attr}"] = attr_v
|
|
95
|
+
|
|
96
|
+
children = list(elem)
|
|
97
|
+
text_val = elem.text.strip() if elem.text and elem.text.strip() else None
|
|
98
|
+
|
|
99
|
+
if not children:
|
|
100
|
+
if elem.attrib:
|
|
101
|
+
if text_val is not None:
|
|
102
|
+
flat_dict[f"{indexed_prefix}{self.separator}#text"] = text_val
|
|
103
|
+
else:
|
|
104
|
+
flat_dict[indexed_prefix] = text_val
|
|
105
|
+
return flat_dict
|
|
106
|
+
|
|
107
|
+
if text_val is not None:
|
|
108
|
+
flat_dict[f"{indexed_prefix}{self.separator}#text"] = text_val
|
|
109
|
+
|
|
110
|
+
grouped_children: Dict[str, List[ET.Element]] = {}
|
|
111
|
+
for child in children:
|
|
112
|
+
c_tag = self._strip_ns(child.tag)
|
|
113
|
+
grouped_children.setdefault(c_tag, []).append(child)
|
|
114
|
+
|
|
115
|
+
for c_tag, c_list in grouped_children.items():
|
|
116
|
+
if len(c_list) == 1:
|
|
117
|
+
flat_dict.update(self._flatten_element(c_list[0], indexed_prefix))
|
|
118
|
+
else:
|
|
119
|
+
for idx, child_elem in enumerate(c_list):
|
|
120
|
+
sub_prefix = f"{indexed_prefix}{self.separator}{c_tag}{self.separator}{idx}"
|
|
121
|
+
flat_dict.update(self._flatten_indexed_child(child_elem, sub_prefix))
|
|
122
|
+
|
|
123
|
+
return flat_dict
|
|
124
|
+
|
|
125
|
+
def to_json(self, bod_xml: str, verb: str) -> str:
|
|
126
|
+
"""Converts OAGIS XML to a single, standard flat JSON string."""
|
|
127
|
+
try:
|
|
128
|
+
noun_elem = self._prepare_noun_element(bod_xml, verb)
|
|
129
|
+
flattened = self._flatten_element(noun_elem)
|
|
130
|
+
return json.dumps(flattened)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
return json.dumps({"status": "error", "message": str(e)})
|
|
133
|
+
|
|
134
|
+
def to_ndjson(self, bod_xml: str, verb: str) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Converts OAGIS XML into strict NDJSON format.
|
|
137
|
+
Unpacks repeating sibling elements under DataArea into single-line records.
|
|
138
|
+
"""
|
|
139
|
+
try:
|
|
140
|
+
clean_xml = bod_xml.strip().encode('utf-8').decode('utf-8-sig')
|
|
141
|
+
sanitized_xml = re.sub(r'\sxmlns(?::\w+)?="[^"]+"', '', clean_xml, count=1)
|
|
142
|
+
|
|
143
|
+
root = ET.fromstring(sanitized_xml)
|
|
144
|
+
data_area = root.find(f".//{self.pnode}")
|
|
145
|
+
|
|
146
|
+
if data_area is None:
|
|
147
|
+
raise ValueError(f"Target node '{self.pnode}' not found.")
|
|
148
|
+
|
|
149
|
+
verb_node = data_area.find(verb)
|
|
150
|
+
if verb_node is not None:
|
|
151
|
+
data_area.remove(verb_node)
|
|
152
|
+
|
|
153
|
+
noun_nodes = list(data_area)
|
|
154
|
+
if not noun_nodes:
|
|
155
|
+
raise ValueError(f"No Noun element found inside '{self.pnode}'.")
|
|
156
|
+
|
|
157
|
+
lines = []
|
|
158
|
+
for noun_elem in noun_nodes:
|
|
159
|
+
flat_record = self._flatten_element(noun_elem)
|
|
160
|
+
compact_line = json.dumps(flat_record, separators=(',', ':')).strip()
|
|
161
|
+
lines.append(compact_line)
|
|
162
|
+
|
|
163
|
+
return "\n".join(lines) + "\n"
|
|
164
|
+
|
|
165
|
+
except Exception as e:
|
|
166
|
+
return json.dumps({"status": "error", "message": str(e)}) + "\n"
|
|
167
|
+
|
|
168
|
+
def convert(bod_xml: str, verb: str) -> str:
|
|
169
|
+
"""
|
|
170
|
+
LEGACY FUNCTION: Maintained to prevent breaking production changes.
|
|
171
|
+
Maps directly to to_json() behind the scenes.
|
|
172
|
+
"""
|
|
173
|
+
warnings.warn(
|
|
174
|
+
"The 'convert' function is deprecated and will be removed in v3.0.0. Use 'to_json' or 'to_ndjson' instead.",
|
|
175
|
+
DeprecationWarning,
|
|
176
|
+
stacklevel=2
|
|
177
|
+
)
|
|
178
|
+
return to_json(bod_xml, verb)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# -------------------------------------------------------------------
|
|
182
|
+
# Top-Level Facade Functions (Global Access Shorthand)
|
|
183
|
+
# -------------------------------------------------------------------
|
|
184
|
+
def to_json(bod_xml: str, verb: str) -> str:
|
|
185
|
+
"""Primary API for Standard JSON conversion"""
|
|
186
|
+
return BODConverter().to_json(bod_xml, verb)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def to_ndjson(bod_xml: str, verb: str) -> str:
|
|
190
|
+
"""Primary API for Newline Delimited JSON (NDJSON) conversion"""
|
|
191
|
+
return BODConverter().to_ndjson(bod_xml, verb)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def convert(bod_xml: str, verb: str) -> str:
|
|
195
|
+
"""
|
|
196
|
+
LEGACY FUNCTION: Maintained to prevent breaking production changes.
|
|
197
|
+
Maps directly to to_json() behind the scenes.
|
|
198
|
+
"""
|
|
199
|
+
warnings.warn(
|
|
200
|
+
"The 'convert' function is deprecated and will be removed in v2.0.0. Use 'to_json' or 'to_ndjson' instead.",
|
|
201
|
+
DeprecationWarning,
|
|
202
|
+
stacklevel=2
|
|
203
|
+
)
|
|
204
|
+
return to_json(bod_xml, verb)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: BODtoJSON
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Zero-dependency OAGIS BOD to Flattened JSON and NDJSON 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 v2.0.0
|
|
18
|
+
|
|
19
|
+
**BODtoJSON** is a zero-dependency, enterprise-grade Python data liquidity engine designed to transform complex Infor OAGIS (XML) Business Object Documents into modern, AI-ready flattened JSON and NDJSON structures.
|
|
20
|
+
|
|
21
|
+
<center><img width="769" height="336" alt="BOD2JSon" src="https://github.com/user-attachments/assets/0b089179-f137-4d56-b717-85cffb20d622" /></center>
|
|
22
|
+
|
|
23
|
+
v2.0.0 marks a major architectural milestone: eliminating all external core dependencies, introducing native single-pass ElementTree traversal, and establishing a dual-pipeline streaming standard for Data Lakehouses and Vector Databases.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 What’s New in v2.0.0 (vs. v1.0.0)
|
|
28
|
+
|
|
29
|
+
* **Zero-Dependency Core Engine:** Removed `xmltodict` in favor of a single-pass recursive traversal built purely on Python’s native `xml.etree.ElementTree`. This eliminates external supply-chain risks, shrinks deployment footprints for air-gapped or serverless environments, and halves intermediate memory allocations.
|
|
30
|
+
* **Explicit Dual-Pipeline 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:** Features compact serialization via `separators=(',', ':')` and forced line termination (`\n`). Multi-noun batch payloads inside `DataArea` are automatically unpacked and emitted as individual, single-line text records.
|
|
32
|
+
* **Enterprise REST API Tier (`api.py`):** Added an optional, high-performance FastAPI microservice layer featuring zero-overhead streaming using raw `Response` classes, eliminating middleware double-serialization bottlenecks.
|
|
33
|
+
* **Pytest Suite:** Standardized automated quality assurance on **`pytest`**, including regression assertions for deprecation warnings.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📈 Why it is Better & Improved
|
|
38
|
+
|
|
39
|
+
| Feature | Production (v1.0.0) | **Streaming Engine (v1.5.0)** |
|
|
40
|
+
| :--- | :--- | :--- |
|
|
41
|
+
| **Data Formats** | Standard Structured JSON string | **Dual-Engine: Standard JSON & Strict Single-Line NDJSON** |
|
|
42
|
+
| **API Interface** | Single `convert()` entry point | **Explicit `to_json()` and `to_ndjson()` methods** |
|
|
43
|
+
| **Ingestion Target** | Application memory / NoSQL DBs | **Data Lakehouses (Snowflake/Databricks) & Vector DBs (RAG)** |
|
|
44
|
+
| **System Delivery** | Library imports only | **Library + High-Performance FastAPI Ingestion Tier** |
|
|
45
|
+
| **Regression Safety**| Basic structural testing | **Deprecation warning validation via Pytest** |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 🛠️ Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install BODtoJSON
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 📂 Project Structure
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
BODtoJSON/
|
|
59
|
+
├── src/
|
|
60
|
+
│ └── BODtoJSON/
|
|
61
|
+
│ ├── __init__.py # Universal API exposure & versioning
|
|
62
|
+
│ ├── mapper.py # Dual-Pipeline Transformation Engine
|
|
63
|
+
│ └── api.py # FastAPI Microservice Ingestion Layer
|
|
64
|
+
├── tests/
|
|
65
|
+
│ ├── data/ # Raw OAGIS XML Test Datasets (.xml)
|
|
66
|
+
│ ├── config.py # Global Test Environments Data Configuration
|
|
67
|
+
│ ├── conftest.py # Pytest Dataset Loader Fixtures
|
|
68
|
+
│ ├── test_mapper.py # Core Engine Unit Tests & Deprecation Validation
|
|
69
|
+
│ └── test_api.py # API Performance Endpoint Verification
|
|
70
|
+
├── scripts/
|
|
71
|
+
│ └── inspect_payload.py # Multi-Profile Business Validation Utility
|
|
72
|
+
├── pyproject.toml # Modern Build and Pytest Configurations
|
|
73
|
+
└── README.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 🧪 Validation & Testing
|
|
79
|
+
|
|
80
|
+
### 1. Automated Testing (QA)
|
|
81
|
+
To run the standardized test suite and verify engine logic:
|
|
82
|
+
```powershell
|
|
83
|
+
python -m pytest -v -p no:cacheprovider
|
|
84
|
+
```
|
|
85
|
+
### 2. Manual Payload Inspection (UAT)
|
|
86
|
+
To verify the commercial value of the output and inspect the flattened JSON:
|
|
87
|
+
```powershell
|
|
88
|
+
python scripts/inspect_payload.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This script generates an `output_preview.json` in the root directory for side-by-side audit with the original XML.
|
|
92
|
+
Note: Toggle the mode parameter within inspect_payload.py between "ndjson", "json", and "legacy" to evaluate various system outputs directly in output_preview.json.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
## 💻 Code Example
|
|
96
|
+
1. Modern Pipeline: Newline Delimited JSON (NDJSON)
|
|
97
|
+
Optimized for Data Lakehouses (Snowflake/Databricks) and Vector Database ingestion.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from BODtoJSON import to_ndjson
|
|
101
|
+
|
|
102
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
103
|
+
|
|
104
|
+
# Returns a dense, single-line string with zero internal spaces, terminated by a clean \n
|
|
105
|
+
linearized_ndjson = to_ndjson(xml_input, verb="Sync")
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
2. Modern Pipeline: Standard Flattened JSON
|
|
109
|
+
Optimized for application-level consumption and quick document-store indexing.
|
|
110
|
+
```python
|
|
111
|
+
from BODtoJSON import to_json
|
|
112
|
+
|
|
113
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
114
|
+
|
|
115
|
+
# Returns standard, single flat JSON string
|
|
116
|
+
flattened_json = to_json(xml_input, verb="Sync")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
3. Legacy Pipeline: Backward-Compatible Conversion
|
|
120
|
+
Maintained to ensure active production systems do not break during upgrades.
|
|
121
|
+
```python
|
|
122
|
+
from BODtoJSON import convert
|
|
123
|
+
|
|
124
|
+
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
125
|
+
|
|
126
|
+
# WARNING: This method is deprecated and will be removed in v2.0.0.
|
|
127
|
+
# It internally routes to to_json() but fires a DeprecationWarning log.
|
|
128
|
+
legacy_json = convert(xml_input, verb="Sync")
|
|
129
|
+
```
|
|
130
|
+
4. Running the Enterprise API Tier
|
|
131
|
+
Launch your processing microservice engine locally:
|
|
132
|
+
```bash
|
|
133
|
+
uvicorn BODtoJSON.api:app --reload
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
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.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 🤝 Commercial Value
|
|
141
|
+
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.
|
|
142
|
+
**Author:** Niraj Kakodkar
|
|
143
|
+
**License:** MIT
|
|
@@ -2,10 +2,12 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/BODtoJSON/__init__.py
|
|
5
|
+
src/BODtoJSON/api.py
|
|
5
6
|
src/BODtoJSON/mapper.py
|
|
6
7
|
src/BODtoJSON.egg-info/PKG-INFO
|
|
7
8
|
src/BODtoJSON.egg-info/SOURCES.txt
|
|
8
9
|
src/BODtoJSON.egg-info/dependency_links.txt
|
|
9
10
|
src/BODtoJSON.egg-info/requires.txt
|
|
10
11
|
src/BODtoJSON.egg-info/top_level.txt
|
|
12
|
+
tests/test_api.py
|
|
11
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
|
bodtojson-1.0.0/PKG-INFO
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: BODtoJSON
|
|
3
|
-
Version: 1.0.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.0.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
|
-
|
|
21
|
-
v1.0.0 represents a significant leap from the 0.0.x series, moving from a utility script to a stable, commercialized data asset.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 🚀 What’s New in v1.0.0 (vs. v0.0.7)
|
|
26
|
-
|
|
27
|
-
The transition from v0.0.7 to v1.0.0 is more than a version bump; it is a complete architectural refactor:
|
|
28
|
-
|
|
29
|
-
* **`src/` Layout Adoption:** Migrated from a flat directory to the professional `src/` layout.
|
|
30
|
-
* **Strategic Verb Stripping:** Unlike v0.0.7, which often included administrative noise, v1.0.0 intelligently isolates the **Noun** (e.g., `PurchaseOrder`) and removes the **Verb** (e.g., `Sync`). This creates a cleaner dataset for LLM ingestion.
|
|
31
|
-
* **Defensive XML Engine:** * **Namespace Neutralization:** v1.0.0 uses dynamic regex to strip primary namespaces automatically.
|
|
32
|
-
* **BOM Handling:** Added support for UTF-8 Byte Order Marks to prevent "Invalid Token" errors common in ERP exports.
|
|
33
|
-
* **Consolidated API:** The interface has been simplified. You no longer need to navigate deep submodules; simply use `from BODtoJSON import convert`.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 📈 Why it is Better & Improved
|
|
38
|
-
|
|
39
|
-
| Feature | Legacy (v0.0.7) | **Production (v1.0.0)** |
|
|
40
|
-
| :--- | :--- | :--- |
|
|
41
|
-
| **Data Integrity** | Basic key-value mapping | **Preserves Attributes (`_@`) and Values (`_#text`)** |
|
|
42
|
-
| **AI Readiness** | Nested dictionaries (hard to index) | **Fully Flattened (Perfect for Vector DBs)** |
|
|
43
|
-
| **Stability** | Prone to XML namespace errors | **Resilient to OAGIS schema variations** |
|
|
44
|
-
| **Deployment** | Manual script copying | **Standardized Wheel/Source distribution** |
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 🛠️ Installation
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
pip install BODtoJSON
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 📂 Project Structure
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
BODtoJSON/
|
|
60
|
-
├── src/
|
|
61
|
-
│ └── BODtoJSON/
|
|
62
|
-
│ ├── __init__.py # Version & API exposure
|
|
63
|
-
│ └── mapper.py # Core Transformation Engine
|
|
64
|
-
├── tests/
|
|
65
|
-
│ ├── config.py # OAGIS XML Test Samples
|
|
66
|
-
│ └── test_mapper.py # Automated Unit Tests (unittest)
|
|
67
|
-
├── scripts/
|
|
68
|
-
│ └── inspect_payload.py # Manual Business Validation Utility
|
|
69
|
-
├── pyproject.toml # Modern Build Configuration
|
|
70
|
-
└── README.md
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 🧪 Validation & Testing
|
|
76
|
-
|
|
77
|
-
### 1. Automated Testing (QA)
|
|
78
|
-
To run the standardized test suite and verify engine logic:
|
|
79
|
-
```powershell
|
|
80
|
-
python -m unittest discover tests
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 2. Manual Payload Inspection (UAT)
|
|
84
|
-
To verify the commercial value of the output and inspect the flattened JSON:
|
|
85
|
-
```powershell
|
|
86
|
-
python scripts/inspect_payload.py
|
|
87
|
-
```
|
|
88
|
-
This script generates an `inspection_result.json` in the root directory for side-by-side audit with the original XML.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 💻 Code Example
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
from BODtoJSON import convert
|
|
96
|
-
|
|
97
|
-
# Sample OAGIS XML
|
|
98
|
-
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
99
|
-
|
|
100
|
-
# High-fidelity conversion
|
|
101
|
-
# Strips 'Sync' verb, flattens 'PurchaseOrder' noun
|
|
102
|
-
flattened_json = convert(xml_input, verb="Sync")
|
|
103
|
-
|
|
104
|
-
print(flattened_json)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## 🤝 Commercial Value
|
|
110
|
-
By normalizing complex ERP structures into flat, labeled JSON, **BODtoJSON** reduces the engineering overhead of AI data preparation by up to 90%. It is built to be a stable foundation for forward-thinking AI strategies and automated business intelligence.
|
|
111
|
-
|
|
112
|
-
**Author:** Niraj Kakodkar
|
|
113
|
-
**License:** MIT
|
bodtojson-1.0.0/README.md
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
# BODtoJSON v1.0.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
|
-
|
|
5
|
-
v1.0.0 represents a significant leap from the 0.0.x series, moving from a utility script to a stable, commercialized data asset.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 🚀 What’s New in v1.0.0 (vs. v0.0.7)
|
|
10
|
-
|
|
11
|
-
The transition from v0.0.7 to v1.0.0 is more than a version bump; it is a complete architectural refactor:
|
|
12
|
-
|
|
13
|
-
* **`src/` Layout Adoption:** Migrated from a flat directory to the professional `src/` layout.
|
|
14
|
-
* **Strategic Verb Stripping:** Unlike v0.0.7, which often included administrative noise, v1.0.0 intelligently isolates the **Noun** (e.g., `PurchaseOrder`) and removes the **Verb** (e.g., `Sync`). This creates a cleaner dataset for LLM ingestion.
|
|
15
|
-
* **Defensive XML Engine:** * **Namespace Neutralization:** v1.0.0 uses dynamic regex to strip primary namespaces automatically.
|
|
16
|
-
* **BOM Handling:** Added support for UTF-8 Byte Order Marks to prevent "Invalid Token" errors common in ERP exports.
|
|
17
|
-
* **Consolidated API:** The interface has been simplified. You no longer need to navigate deep submodules; simply use `from BODtoJSON import convert`.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 📈 Why it is Better & Improved
|
|
22
|
-
|
|
23
|
-
| Feature | Legacy (v0.0.7) | **Production (v1.0.0)** |
|
|
24
|
-
| :--- | :--- | :--- |
|
|
25
|
-
| **Data Integrity** | Basic key-value mapping | **Preserves Attributes (`_@`) and Values (`_#text`)** |
|
|
26
|
-
| **AI Readiness** | Nested dictionaries (hard to index) | **Fully Flattened (Perfect for Vector DBs)** |
|
|
27
|
-
| **Stability** | Prone to XML namespace errors | **Resilient to OAGIS schema variations** |
|
|
28
|
-
| **Deployment** | Manual script copying | **Standardized Wheel/Source distribution** |
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 🛠️ Installation
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
pip install BODtoJSON
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
---
|
|
39
|
-
|
|
40
|
-
## 📂 Project Structure
|
|
41
|
-
|
|
42
|
-
```text
|
|
43
|
-
BODtoJSON/
|
|
44
|
-
├── src/
|
|
45
|
-
│ └── BODtoJSON/
|
|
46
|
-
│ ├── __init__.py # Version & API exposure
|
|
47
|
-
│ └── mapper.py # Core Transformation Engine
|
|
48
|
-
├── tests/
|
|
49
|
-
│ ├── config.py # OAGIS XML Test Samples
|
|
50
|
-
│ └── test_mapper.py # Automated Unit Tests (unittest)
|
|
51
|
-
├── scripts/
|
|
52
|
-
│ └── inspect_payload.py # Manual Business Validation Utility
|
|
53
|
-
├── pyproject.toml # Modern Build Configuration
|
|
54
|
-
└── README.md
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## 🧪 Validation & Testing
|
|
60
|
-
|
|
61
|
-
### 1. Automated Testing (QA)
|
|
62
|
-
To run the standardized test suite and verify engine logic:
|
|
63
|
-
```powershell
|
|
64
|
-
python -m unittest discover tests
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### 2. Manual Payload Inspection (UAT)
|
|
68
|
-
To verify the commercial value of the output and inspect the flattened JSON:
|
|
69
|
-
```powershell
|
|
70
|
-
python scripts/inspect_payload.py
|
|
71
|
-
```
|
|
72
|
-
This script generates an `inspection_result.json` in the root directory for side-by-side audit with the original XML.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## 💻 Code Example
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
from BODtoJSON import convert
|
|
80
|
-
|
|
81
|
-
# Sample OAGIS XML
|
|
82
|
-
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
83
|
-
|
|
84
|
-
# High-fidelity conversion
|
|
85
|
-
# Strips 'Sync' verb, flattens 'PurchaseOrder' noun
|
|
86
|
-
flattened_json = convert(xml_input, verb="Sync")
|
|
87
|
-
|
|
88
|
-
print(flattened_json)
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## 🤝 Commercial Value
|
|
94
|
-
By normalizing complex ERP structures into flat, labeled JSON, **BODtoJSON** reduces the engineering overhead of AI data preparation by up to 90%. It is built to be a stable foundation for forward-thinking AI strategies and automated business intelligence.
|
|
95
|
-
|
|
96
|
-
**Author:** Niraj Kakodkar
|
|
97
|
-
**License:** MIT
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import xml.etree.ElementTree as ET
|
|
2
|
-
import xmltodict
|
|
3
|
-
import json
|
|
4
|
-
import re
|
|
5
|
-
from typing import Dict, Any
|
|
6
|
-
|
|
7
|
-
class BODConverter:
|
|
8
|
-
def __init__(self, pnode: str = 'DataArea', separator: str = '_'):
|
|
9
|
-
self.pnode = pnode
|
|
10
|
-
self.separator = separator
|
|
11
|
-
|
|
12
|
-
def convert(self, bod_xml: str, verb: str) -> str:
|
|
13
|
-
try:
|
|
14
|
-
# Defensive cleaning to handle BOM and namespaces
|
|
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
|
-
# Remove administrative Verb (Sync/Process)
|
|
25
|
-
verb_node = data_area.find(verb)
|
|
26
|
-
if verb_node is not None:
|
|
27
|
-
data_area.remove(verb_node)
|
|
28
|
-
|
|
29
|
-
# Extract the Noun (the primary child of DataArea)
|
|
30
|
-
# This prevents wrapping the JSON in a redundant 'DataArea' key
|
|
31
|
-
noun_node = list(data_area)[0]
|
|
32
|
-
xml_fragment = ET.tostring(noun_node, encoding='utf-8').decode('utf-8')
|
|
33
|
-
|
|
34
|
-
raw_dict = xmltodict.parse(xml_fragment)
|
|
35
|
-
return json.dumps(self._flatify(raw_dict))
|
|
36
|
-
|
|
37
|
-
except Exception as e:
|
|
38
|
-
return json.dumps({"status": "error", "message": str(e)})
|
|
39
|
-
|
|
40
|
-
def _flatify(self, obj: Any, prefix: str = "") -> Dict[str, Any]:
|
|
41
|
-
flat_dict = {}
|
|
42
|
-
if isinstance(obj, dict):
|
|
43
|
-
for k, v in obj.items():
|
|
44
|
-
new_key = f"{prefix}{self.separator}{k}" if prefix else k
|
|
45
|
-
flat_dict.update(self._flatify(v, new_key))
|
|
46
|
-
elif isinstance(obj, (list, tuple)):
|
|
47
|
-
for i, v in enumerate(obj):
|
|
48
|
-
new_key = f"{prefix}{self.separator}{i}"
|
|
49
|
-
flat_dict.update(self._flatify(v, new_key))
|
|
50
|
-
else:
|
|
51
|
-
flat_dict[prefix] = obj
|
|
52
|
-
return flat_dict
|
|
53
|
-
|
|
54
|
-
def convert(bod_xml: str, verb: str) -> str:
|
|
55
|
-
"""Primary API for BODtoJSON v1.0.0"""
|
|
56
|
-
return BODConverter().convert(bod_xml, verb)
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: BODtoJSON
|
|
3
|
-
Version: 1.0.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.0.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
|
-
|
|
21
|
-
v1.0.0 represents a significant leap from the 0.0.x series, moving from a utility script to a stable, commercialized data asset.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 🚀 What’s New in v1.0.0 (vs. v0.0.7)
|
|
26
|
-
|
|
27
|
-
The transition from v0.0.7 to v1.0.0 is more than a version bump; it is a complete architectural refactor:
|
|
28
|
-
|
|
29
|
-
* **`src/` Layout Adoption:** Migrated from a flat directory to the professional `src/` layout.
|
|
30
|
-
* **Strategic Verb Stripping:** Unlike v0.0.7, which often included administrative noise, v1.0.0 intelligently isolates the **Noun** (e.g., `PurchaseOrder`) and removes the **Verb** (e.g., `Sync`). This creates a cleaner dataset for LLM ingestion.
|
|
31
|
-
* **Defensive XML Engine:** * **Namespace Neutralization:** v1.0.0 uses dynamic regex to strip primary namespaces automatically.
|
|
32
|
-
* **BOM Handling:** Added support for UTF-8 Byte Order Marks to prevent "Invalid Token" errors common in ERP exports.
|
|
33
|
-
* **Consolidated API:** The interface has been simplified. You no longer need to navigate deep submodules; simply use `from BODtoJSON import convert`.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 📈 Why it is Better & Improved
|
|
38
|
-
|
|
39
|
-
| Feature | Legacy (v0.0.7) | **Production (v1.0.0)** |
|
|
40
|
-
| :--- | :--- | :--- |
|
|
41
|
-
| **Data Integrity** | Basic key-value mapping | **Preserves Attributes (`_@`) and Values (`_#text`)** |
|
|
42
|
-
| **AI Readiness** | Nested dictionaries (hard to index) | **Fully Flattened (Perfect for Vector DBs)** |
|
|
43
|
-
| **Stability** | Prone to XML namespace errors | **Resilient to OAGIS schema variations** |
|
|
44
|
-
| **Deployment** | Manual script copying | **Standardized Wheel/Source distribution** |
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 🛠️ Installation
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
pip install BODtoJSON
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## 📂 Project Structure
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
BODtoJSON/
|
|
60
|
-
├── src/
|
|
61
|
-
│ └── BODtoJSON/
|
|
62
|
-
│ ├── __init__.py # Version & API exposure
|
|
63
|
-
│ └── mapper.py # Core Transformation Engine
|
|
64
|
-
├── tests/
|
|
65
|
-
│ ├── config.py # OAGIS XML Test Samples
|
|
66
|
-
│ └── test_mapper.py # Automated Unit Tests (unittest)
|
|
67
|
-
├── scripts/
|
|
68
|
-
│ └── inspect_payload.py # Manual Business Validation Utility
|
|
69
|
-
├── pyproject.toml # Modern Build Configuration
|
|
70
|
-
└── README.md
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 🧪 Validation & Testing
|
|
76
|
-
|
|
77
|
-
### 1. Automated Testing (QA)
|
|
78
|
-
To run the standardized test suite and verify engine logic:
|
|
79
|
-
```powershell
|
|
80
|
-
python -m unittest discover tests
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 2. Manual Payload Inspection (UAT)
|
|
84
|
-
To verify the commercial value of the output and inspect the flattened JSON:
|
|
85
|
-
```powershell
|
|
86
|
-
python scripts/inspect_payload.py
|
|
87
|
-
```
|
|
88
|
-
This script generates an `inspection_result.json` in the root directory for side-by-side audit with the original XML.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 💻 Code Example
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
from BODtoJSON import convert
|
|
96
|
-
|
|
97
|
-
# Sample OAGIS XML
|
|
98
|
-
xml_input = """<SyncPurchaseOrder>...</SyncPurchaseOrder>"""
|
|
99
|
-
|
|
100
|
-
# High-fidelity conversion
|
|
101
|
-
# Strips 'Sync' verb, flattens 'PurchaseOrder' noun
|
|
102
|
-
flattened_json = convert(xml_input, verb="Sync")
|
|
103
|
-
|
|
104
|
-
print(flattened_json)
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## 🤝 Commercial Value
|
|
110
|
-
By normalizing complex ERP structures into flat, labeled JSON, **BODtoJSON** reduces the engineering overhead of AI data preparation by up to 90%. It is built to be a stable foundation for forward-thinking AI strategies and automated business intelligence.
|
|
111
|
-
|
|
112
|
-
**Author:** Niraj Kakodkar
|
|
113
|
-
**License:** MIT
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import json
|
|
3
|
-
import sys
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
# Align pathing for local testing
|
|
7
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
|
|
8
|
-
|
|
9
|
-
from BODtoJSON import convert
|
|
10
|
-
from config import Config
|
|
11
|
-
|
|
12
|
-
class TestBODtoJSON(unittest.TestCase):
|
|
13
|
-
def test_sync_purchase_order(self):
|
|
14
|
-
result_json = convert(Config.INPUT_VAR, "Sync")
|
|
15
|
-
data = json.loads(result_json)
|
|
16
|
-
|
|
17
|
-
# ALIGNED KEY: Engine includes the Noun name (PurchaseOrder)
|
|
18
|
-
expected_key = "PurchaseOrder_PurchaseOrderHeader_DocumentID_ID_#text"
|
|
19
|
-
|
|
20
|
-
# Verification
|
|
21
|
-
self.assertEqual(
|
|
22
|
-
data.get(expected_key),
|
|
23
|
-
"PSU000049",
|
|
24
|
-
f"Key mismatch. Available keys: {list(data.keys())}"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
if __name__ == "__main__":
|
|
28
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|