sqlalchemy-odata 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brandon Jones
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlalchemy-odata
3
+ Version: 0.1.0
4
+ Summary: SQLAlchemy dialect and Shillelagh adapter for OData v4 — query OData APIs with SQL in Apache Superset
5
+ Author: Brandon Jones
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/brandonjjon/sqlalchemy-odata
8
+ Project-URL: Repository, https://github.com/brandonjjon/sqlalchemy-odata
9
+ Project-URL: Issues, https://github.com/brandonjjon/sqlalchemy-odata/issues
10
+ Project-URL: Changelog, https://github.com/brandonjjon/sqlalchemy-odata/blob/main/CHANGELOG.md
11
+ Keywords: odata,sqlalchemy,shillelagh,superset,sql,api,odata-v4,connector,rest-api,business-intelligence,data-integration
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Database
21
+ Classifier: Topic :: Database :: Front-Ends
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.9
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: shillelagh<3.0,>=1.2.0
27
+ Requires-Dist: requests>=2.28.0
28
+ Provides-Extra: superset
29
+ Requires-Dist: apache-superset>=3.0.0; extra == "superset"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
33
+ Requires-Dist: responses>=0.23.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
35
+ Requires-Dist: build>=1.0.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # sqlalchemy-odata
39
+
40
+ [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-odata.svg)](https://pypi.org/project/sqlalchemy-odata/)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/sqlalchemy-odata.svg)](https://pypi.org/project/sqlalchemy-odata/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+
44
+ Open-source OData v4 connector for SQLAlchemy and [Apache Superset](https://superset.apache.org/).
45
+
46
+ A [Shillelagh](https://github.com/betodealmeida/shillelagh) adapter that lets you query any OData v4 API with SQL.
47
+
48
+ ## What it does
49
+
50
+ - Connects to any OData v4 service and reads `$metadata` to **auto-discover** all entity sets and their schemas
51
+ - Exposes each entity set as a SQL table (e.g. `Products`, `Orders`, `Customers`)
52
+ - Fetches data via `$top`/`$skip` and `@odata.nextLink` pagination
53
+ - SQLite (via [Shillelagh](https://github.com/betodealmeida/shillelagh)/APSW) handles all SQL operations locally — `SELECT`, `WHERE`, `GROUP BY`, `JOIN`, subqueries, etc.
54
+ - Registers an `odata://` SQLAlchemy dialect for easy connection strings
55
+ - Includes a Superset engine spec so it appears in the "Add Database" dialog
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install sqlalchemy-odata
61
+ ```
62
+
63
+ For Apache Superset, add to your `requirements-local.txt` or Docker image:
64
+
65
+ ```
66
+ sqlalchemy-odata
67
+ ```
68
+
69
+ ## Quick start
70
+
71
+ Try it with the public [Northwind OData service](https://services.odata.org/) — no auth required:
72
+
73
+ ```python
74
+ from sqlalchemy import create_engine, text
75
+
76
+ engine = create_engine("odata://services.odata.org/V4/Northwind/Northwind.svc")
77
+
78
+ with engine.connect() as conn:
79
+ result = conn.execute(text("SELECT ProductName, UnitPrice FROM Products LIMIT 5"))
80
+ for row in result:
81
+ print(row)
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ ### Connection string
87
+
88
+ ```
89
+ odata://username:password@hostname/service-path
90
+ ```
91
+
92
+ The username and password are passed as HTTP Basic Auth credentials. The service path is the OData service root (everything before the entity set names).
93
+
94
+ HTTPS is used by default. For local development servers (`localhost` / `127.0.0.1`), HTTP is used automatically.
95
+
96
+ > **Note:** Credentials are embedded in the connection string. If you're using Superset, be aware that connection strings are stored in Superset's metadata database. Consider using Superset's [secrets management](https://superset.apache.org/docs/configuration/configuring-superset/#secret-management) for production deployments.
97
+
98
+ ### In Python
99
+
100
+ ```python
101
+ from sqlalchemy import create_engine, text
102
+
103
+ engine = create_engine(
104
+ "odata://myuser:mypassword@api.example.com/odata/v1"
105
+ )
106
+
107
+ with engine.connect() as conn:
108
+ # Auto-discovers tables from $metadata
109
+ result = conn.execute(text("SELECT * FROM Products LIMIT 10"))
110
+ for row in result:
111
+ print(row)
112
+
113
+ # Full SQL support — GROUP BY, JOIN, subqueries, etc.
114
+ result = conn.execute(text("""
115
+ SELECT Category, COUNT(*) as cnt, AVG(Price) as avg_price
116
+ FROM Products
117
+ WHERE InStock = 1
118
+ GROUP BY Category
119
+ ORDER BY cnt DESC
120
+ """))
121
+ ```
122
+
123
+ ### With HammerTech
124
+
125
+ ```python
126
+ engine = create_engine(
127
+ "odata://myuser:api_key@us-reporting-01.hammertechonline.com/v0.1"
128
+ )
129
+
130
+ with engine.connect() as conn:
131
+ result = conn.execute(text("SELECT * FROM incidents LIMIT 10"))
132
+ ```
133
+
134
+ ### In Apache Superset
135
+
136
+ 1. Go to **Settings > Database Connections > + Database**
137
+ 2. Select **OData** (or use "Other" and enter the URI manually)
138
+ 3. Enter the connection string: `odata://user:pass@host/path`
139
+ 4. Click **Connect** — all entity sets appear as tables in SQL Lab
140
+
141
+ ### Table discovery
142
+
143
+ Tables are automatically discovered from the OData `$metadata` endpoint. You can also inspect them programmatically:
144
+
145
+ ```python
146
+ from sqlalchemy import create_engine, inspect
147
+
148
+ engine = create_engine("odata://user:pass@host/path")
149
+ inspector = inspect(engine)
150
+ print(inspector.get_table_names())
151
+ # ['Customers', 'Orders', 'Products', ...]
152
+ ```
153
+
154
+ ## How it works
155
+
156
+ ```
157
+ ┌──────────────────────────────────────────────────────────┐
158
+ │ Your SQL query │
159
+ │ SELECT * FROM Products WHERE Price > 100 │
160
+ └────────────────────┬─────────────────────────────────────┘
161
+
162
+ ┌───────────▼───────────┐
163
+ │ SQLite (via APSW) │ Handles SQL parsing,
164
+ │ + Shillelagh │ filtering, joins, etc.
165
+ └───────────┬───────────┘
166
+
167
+ ┌───────────▼───────────┐
168
+ │ sqlalchemy-odata │ Fetches data from the
169
+ │ ODataAdapter │ OData API via HTTP
170
+ └───────────┬───────────┘
171
+
172
+ ┌───────────▼───────────┐
173
+ │ OData v4 Service │ $metadata for schema,
174
+ │ (any provider) │ $top/$skip for data
175
+ └───────────────────────┘
176
+ ```
177
+
178
+ 1. On first query, the adapter fetches the `$metadata` EDMX document to discover entity types, properties, and their EDM types
179
+ 2. EDM types are mapped to SQLite types (`Edm.String` -> `TEXT`, `Edm.Int32` -> `INTEGER`, `Edm.DateTimeOffset` -> `TIMESTAMP`, etc.)
180
+ 3. Data is fetched via paginated `GET` requests with `$top`/`$skip` parameters (or `@odata.nextLink` if the server provides it)
181
+ 4. SQLite handles all query operations (filtering, sorting, grouping, joins) locally
182
+ 5. Results are returned through the standard SQLAlchemy/DB-API interface
183
+
184
+ ## Supported OData features
185
+
186
+ | Feature | Status |
187
+ |---------|--------|
188
+ | `$metadata` schema discovery | Supported |
189
+ | `$top` / `$skip` pagination | Supported |
190
+ | `@odata.nextLink` pagination | Supported |
191
+ | `@odata.count` | Not yet |
192
+ | Basic Auth | Supported |
193
+ | Bearer Token Auth | Not yet |
194
+ | OAuth2 | Not yet |
195
+ | `$filter` pushdown | Not yet (filtered locally by SQLite) |
196
+ | `$select` pushdown | Not yet (all columns fetched) |
197
+ | `$orderby` pushdown | Not yet (sorted locally by SQLite) |
198
+ | `$expand` (relationships) | Not yet |
199
+ | Write operations (POST/PATCH/DELETE) | Not supported (read-only) |
200
+
201
+ > **Note:** Even without server-side pushdown, all SQL operations work because SQLite handles them locally. Pushdown is a performance optimization for large datasets.
202
+
203
+ ## Limitations
204
+
205
+ - **Performance on large datasets:** Without `$filter` pushdown, the adapter fetches all rows from an entity set and filters locally. For entity sets with hundreds of thousands of rows, this can be slow and memory-intensive. Pushdown support is planned for a future release.
206
+ - **Auth:** Only HTTP Basic Auth is currently supported. Bearer tokens and OAuth2 are planned.
207
+ - **Read-only:** Write operations (INSERT, UPDATE, DELETE) are not supported.
208
+
209
+ ## Architecture
210
+
211
+ This package provides three components built on the [Shillelagh](https://github.com/betodealmeida/shillelagh) framework:
212
+
213
+ | Component | Purpose |
214
+ |-----------|---------|
215
+ | `shillelagh_odata.adapter` | [Shillelagh adapter](https://shillelagh.readthedocs.io/en/latest/development.html) — fetches data from OData, parses `$metadata` |
216
+ | `shillelagh_odata.dialect` | [SQLAlchemy dialect](https://shillelagh.readthedocs.io/en/latest/development.html#creating-a-custom-sqlalchemy-dialect) (`odata://`) — handles connection strings, table discovery |
217
+ | `shillelagh_odata.engine_spec` | Superset `BaseEngineSpec` subclass — registers OData in the Superset UI |
218
+
219
+ These register via entry points:
220
+
221
+ ```toml
222
+ [project.entry-points."shillelagh.adapter"]
223
+ odataapi = "shillelagh_odata.adapter:ODataAdapter"
224
+
225
+ [project.entry-points."sqlalchemy.dialects"]
226
+ odata = "shillelagh_odata.dialect:APSWODataDialect"
227
+
228
+ [project.entry-points."superset.db_engine_specs"]
229
+ odata = "shillelagh_odata.engine_spec:ODataEngineSpec"
230
+ ```
231
+
232
+ ## Troubleshooting
233
+
234
+ **No tables found / empty table list**
235
+ - Verify your OData service URL is correct and the `$metadata` endpoint is accessible
236
+ - Check credentials — a 401/403 response will result in an empty table list
237
+ - Try accessing `https://your-host/your-path/$metadata` in a browser to verify the service
238
+
239
+ **Empty query results**
240
+ - The entity set may exist in `$metadata` but contain no data
241
+ - Check that the entity set name is spelled exactly as it appears in `$metadata` (case-sensitive)
242
+
243
+ **Connection timeouts**
244
+ - The default timeout is 30 seconds for metadata and 60 seconds for data requests
245
+ - Large entity sets with many pages may take time to fully load
246
+
247
+ **Can't connect to local development server**
248
+ - `localhost` and `127.0.0.1` automatically use HTTP instead of HTTPS
249
+ - For other local hostnames, ensure your server supports HTTPS or use localhost
250
+
251
+ ## Development
252
+
253
+ ```bash
254
+ git clone https://github.com/brandonjjon/sqlalchemy-odata.git
255
+ cd sqlalchemy-odata
256
+ pip install -e ".[dev]"
257
+ pytest
258
+ ```
259
+
260
+ ## Related projects
261
+
262
+ - [Shillelagh](https://github.com/betodealmeida/shillelagh) — the framework this adapter is built on
263
+ - [Apache Superset](https://github.com/apache/superset) — the BI platform this integrates with
264
+ - [graphql-db-api](https://github.com/cancan101/graphql-db-api) — similar adapter for GraphQL APIs (also built on Shillelagh)
265
+
266
+ ## License
267
+
268
+ MIT
@@ -0,0 +1,231 @@
1
+ # sqlalchemy-odata
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/sqlalchemy-odata.svg)](https://pypi.org/project/sqlalchemy-odata/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/sqlalchemy-odata.svg)](https://pypi.org/project/sqlalchemy-odata/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ Open-source OData v4 connector for SQLAlchemy and [Apache Superset](https://superset.apache.org/).
8
+
9
+ A [Shillelagh](https://github.com/betodealmeida/shillelagh) adapter that lets you query any OData v4 API with SQL.
10
+
11
+ ## What it does
12
+
13
+ - Connects to any OData v4 service and reads `$metadata` to **auto-discover** all entity sets and their schemas
14
+ - Exposes each entity set as a SQL table (e.g. `Products`, `Orders`, `Customers`)
15
+ - Fetches data via `$top`/`$skip` and `@odata.nextLink` pagination
16
+ - SQLite (via [Shillelagh](https://github.com/betodealmeida/shillelagh)/APSW) handles all SQL operations locally — `SELECT`, `WHERE`, `GROUP BY`, `JOIN`, subqueries, etc.
17
+ - Registers an `odata://` SQLAlchemy dialect for easy connection strings
18
+ - Includes a Superset engine spec so it appears in the "Add Database" dialog
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ pip install sqlalchemy-odata
24
+ ```
25
+
26
+ For Apache Superset, add to your `requirements-local.txt` or Docker image:
27
+
28
+ ```
29
+ sqlalchemy-odata
30
+ ```
31
+
32
+ ## Quick start
33
+
34
+ Try it with the public [Northwind OData service](https://services.odata.org/) — no auth required:
35
+
36
+ ```python
37
+ from sqlalchemy import create_engine, text
38
+
39
+ engine = create_engine("odata://services.odata.org/V4/Northwind/Northwind.svc")
40
+
41
+ with engine.connect() as conn:
42
+ result = conn.execute(text("SELECT ProductName, UnitPrice FROM Products LIMIT 5"))
43
+ for row in result:
44
+ print(row)
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### Connection string
50
+
51
+ ```
52
+ odata://username:password@hostname/service-path
53
+ ```
54
+
55
+ The username and password are passed as HTTP Basic Auth credentials. The service path is the OData service root (everything before the entity set names).
56
+
57
+ HTTPS is used by default. For local development servers (`localhost` / `127.0.0.1`), HTTP is used automatically.
58
+
59
+ > **Note:** Credentials are embedded in the connection string. If you're using Superset, be aware that connection strings are stored in Superset's metadata database. Consider using Superset's [secrets management](https://superset.apache.org/docs/configuration/configuring-superset/#secret-management) for production deployments.
60
+
61
+ ### In Python
62
+
63
+ ```python
64
+ from sqlalchemy import create_engine, text
65
+
66
+ engine = create_engine(
67
+ "odata://myuser:mypassword@api.example.com/odata/v1"
68
+ )
69
+
70
+ with engine.connect() as conn:
71
+ # Auto-discovers tables from $metadata
72
+ result = conn.execute(text("SELECT * FROM Products LIMIT 10"))
73
+ for row in result:
74
+ print(row)
75
+
76
+ # Full SQL support — GROUP BY, JOIN, subqueries, etc.
77
+ result = conn.execute(text("""
78
+ SELECT Category, COUNT(*) as cnt, AVG(Price) as avg_price
79
+ FROM Products
80
+ WHERE InStock = 1
81
+ GROUP BY Category
82
+ ORDER BY cnt DESC
83
+ """))
84
+ ```
85
+
86
+ ### With HammerTech
87
+
88
+ ```python
89
+ engine = create_engine(
90
+ "odata://myuser:api_key@us-reporting-01.hammertechonline.com/v0.1"
91
+ )
92
+
93
+ with engine.connect() as conn:
94
+ result = conn.execute(text("SELECT * FROM incidents LIMIT 10"))
95
+ ```
96
+
97
+ ### In Apache Superset
98
+
99
+ 1. Go to **Settings > Database Connections > + Database**
100
+ 2. Select **OData** (or use "Other" and enter the URI manually)
101
+ 3. Enter the connection string: `odata://user:pass@host/path`
102
+ 4. Click **Connect** — all entity sets appear as tables in SQL Lab
103
+
104
+ ### Table discovery
105
+
106
+ Tables are automatically discovered from the OData `$metadata` endpoint. You can also inspect them programmatically:
107
+
108
+ ```python
109
+ from sqlalchemy import create_engine, inspect
110
+
111
+ engine = create_engine("odata://user:pass@host/path")
112
+ inspector = inspect(engine)
113
+ print(inspector.get_table_names())
114
+ # ['Customers', 'Orders', 'Products', ...]
115
+ ```
116
+
117
+ ## How it works
118
+
119
+ ```
120
+ ┌──────────────────────────────────────────────────────────┐
121
+ │ Your SQL query │
122
+ │ SELECT * FROM Products WHERE Price > 100 │
123
+ └────────────────────┬─────────────────────────────────────┘
124
+
125
+ ┌───────────▼───────────┐
126
+ │ SQLite (via APSW) │ Handles SQL parsing,
127
+ │ + Shillelagh │ filtering, joins, etc.
128
+ └───────────┬───────────┘
129
+
130
+ ┌───────────▼───────────┐
131
+ │ sqlalchemy-odata │ Fetches data from the
132
+ │ ODataAdapter │ OData API via HTTP
133
+ └───────────┬───────────┘
134
+
135
+ ┌───────────▼───────────┐
136
+ │ OData v4 Service │ $metadata for schema,
137
+ │ (any provider) │ $top/$skip for data
138
+ └───────────────────────┘
139
+ ```
140
+
141
+ 1. On first query, the adapter fetches the `$metadata` EDMX document to discover entity types, properties, and their EDM types
142
+ 2. EDM types are mapped to SQLite types (`Edm.String` -> `TEXT`, `Edm.Int32` -> `INTEGER`, `Edm.DateTimeOffset` -> `TIMESTAMP`, etc.)
143
+ 3. Data is fetched via paginated `GET` requests with `$top`/`$skip` parameters (or `@odata.nextLink` if the server provides it)
144
+ 4. SQLite handles all query operations (filtering, sorting, grouping, joins) locally
145
+ 5. Results are returned through the standard SQLAlchemy/DB-API interface
146
+
147
+ ## Supported OData features
148
+
149
+ | Feature | Status |
150
+ |---------|--------|
151
+ | `$metadata` schema discovery | Supported |
152
+ | `$top` / `$skip` pagination | Supported |
153
+ | `@odata.nextLink` pagination | Supported |
154
+ | `@odata.count` | Not yet |
155
+ | Basic Auth | Supported |
156
+ | Bearer Token Auth | Not yet |
157
+ | OAuth2 | Not yet |
158
+ | `$filter` pushdown | Not yet (filtered locally by SQLite) |
159
+ | `$select` pushdown | Not yet (all columns fetched) |
160
+ | `$orderby` pushdown | Not yet (sorted locally by SQLite) |
161
+ | `$expand` (relationships) | Not yet |
162
+ | Write operations (POST/PATCH/DELETE) | Not supported (read-only) |
163
+
164
+ > **Note:** Even without server-side pushdown, all SQL operations work because SQLite handles them locally. Pushdown is a performance optimization for large datasets.
165
+
166
+ ## Limitations
167
+
168
+ - **Performance on large datasets:** Without `$filter` pushdown, the adapter fetches all rows from an entity set and filters locally. For entity sets with hundreds of thousands of rows, this can be slow and memory-intensive. Pushdown support is planned for a future release.
169
+ - **Auth:** Only HTTP Basic Auth is currently supported. Bearer tokens and OAuth2 are planned.
170
+ - **Read-only:** Write operations (INSERT, UPDATE, DELETE) are not supported.
171
+
172
+ ## Architecture
173
+
174
+ This package provides three components built on the [Shillelagh](https://github.com/betodealmeida/shillelagh) framework:
175
+
176
+ | Component | Purpose |
177
+ |-----------|---------|
178
+ | `shillelagh_odata.adapter` | [Shillelagh adapter](https://shillelagh.readthedocs.io/en/latest/development.html) — fetches data from OData, parses `$metadata` |
179
+ | `shillelagh_odata.dialect` | [SQLAlchemy dialect](https://shillelagh.readthedocs.io/en/latest/development.html#creating-a-custom-sqlalchemy-dialect) (`odata://`) — handles connection strings, table discovery |
180
+ | `shillelagh_odata.engine_spec` | Superset `BaseEngineSpec` subclass — registers OData in the Superset UI |
181
+
182
+ These register via entry points:
183
+
184
+ ```toml
185
+ [project.entry-points."shillelagh.adapter"]
186
+ odataapi = "shillelagh_odata.adapter:ODataAdapter"
187
+
188
+ [project.entry-points."sqlalchemy.dialects"]
189
+ odata = "shillelagh_odata.dialect:APSWODataDialect"
190
+
191
+ [project.entry-points."superset.db_engine_specs"]
192
+ odata = "shillelagh_odata.engine_spec:ODataEngineSpec"
193
+ ```
194
+
195
+ ## Troubleshooting
196
+
197
+ **No tables found / empty table list**
198
+ - Verify your OData service URL is correct and the `$metadata` endpoint is accessible
199
+ - Check credentials — a 401/403 response will result in an empty table list
200
+ - Try accessing `https://your-host/your-path/$metadata` in a browser to verify the service
201
+
202
+ **Empty query results**
203
+ - The entity set may exist in `$metadata` but contain no data
204
+ - Check that the entity set name is spelled exactly as it appears in `$metadata` (case-sensitive)
205
+
206
+ **Connection timeouts**
207
+ - The default timeout is 30 seconds for metadata and 60 seconds for data requests
208
+ - Large entity sets with many pages may take time to fully load
209
+
210
+ **Can't connect to local development server**
211
+ - `localhost` and `127.0.0.1` automatically use HTTP instead of HTTPS
212
+ - For other local hostnames, ensure your server supports HTTPS or use localhost
213
+
214
+ ## Development
215
+
216
+ ```bash
217
+ git clone https://github.com/brandonjjon/sqlalchemy-odata.git
218
+ cd sqlalchemy-odata
219
+ pip install -e ".[dev]"
220
+ pytest
221
+ ```
222
+
223
+ ## Related projects
224
+
225
+ - [Shillelagh](https://github.com/betodealmeida/shillelagh) — the framework this adapter is built on
226
+ - [Apache Superset](https://github.com/apache/superset) — the BI platform this integrates with
227
+ - [graphql-db-api](https://github.com/cancan101/graphql-db-api) — similar adapter for GraphQL APIs (also built on Shillelagh)
228
+
229
+ ## License
230
+
231
+ MIT
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sqlalchemy-odata"
7
+ version = "0.1.0"
8
+ description = "SQLAlchemy dialect and Shillelagh adapter for OData v4 — query OData APIs with SQL in Apache Superset"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ keywords = ["odata", "sqlalchemy", "shillelagh", "superset", "sql", "api", "odata-v4", "connector", "rest-api", "business-intelligence", "data-integration"]
13
+ authors = [
14
+ {name = "Brandon Jones"},
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Database",
26
+ "Topic :: Database :: Front-Ends",
27
+ "Topic :: Software Development :: Libraries :: Python Modules",
28
+ ]
29
+ dependencies = [
30
+ "shillelagh>=1.2.0,<3.0",
31
+ "requests>=2.28.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ superset = [
36
+ "apache-superset>=3.0.0",
37
+ ]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "pytest-cov>=4.0",
41
+ "responses>=0.23.0",
42
+ "ruff>=0.4.0",
43
+ "build>=1.0.0",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/brandonjjon/sqlalchemy-odata"
48
+ Repository = "https://github.com/brandonjjon/sqlalchemy-odata"
49
+ Issues = "https://github.com/brandonjjon/sqlalchemy-odata/issues"
50
+ Changelog = "https://github.com/brandonjjon/sqlalchemy-odata/blob/main/CHANGELOG.md"
51
+
52
+ [project.entry-points."shillelagh.adapter"]
53
+ odataapi = "shillelagh_odata.adapter:ODataAdapter"
54
+
55
+ [project.entry-points."sqlalchemy.dialects"]
56
+ odata = "shillelagh_odata.dialect:APSWODataDialect"
57
+
58
+ [project.entry-points."superset.db_engine_specs"]
59
+ odata = "shillelagh_odata.engine_spec:ODataEngineSpec"
60
+
61
+ [tool.setuptools.packages.find]
62
+ where = ["src"]
63
+
64
+ [tool.pytest.ini_options]
65
+ testpaths = ["tests"]
66
+
67
+ [tool.ruff]
68
+ target-version = "py39"
69
+
70
+ [tool.ruff.lint]
71
+ select = ["E", "F", "I", "W", "UP"]
72
+
73
+ [tool.ruff.format]
74
+ quote-style = "double"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """SQLAlchemy dialect and Shillelagh adapter for OData v4 APIs."""
2
+
3
+ __version__ = "0.1.0" # x-release-please-version