ontologytoapi 0.0.5__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.
- ontologytoapi-0.0.5/LICENSE +21 -0
- ontologytoapi-0.0.5/MANIFEST.in +6 -0
- ontologytoapi-0.0.5/PKG-INFO +106 -0
- ontologytoapi-0.0.5/README.md +54 -0
- ontologytoapi-0.0.5/core/APIGenerator.py +99 -0
- ontologytoapi-0.0.5/core/Connectors/IndentifyConnector.py +18 -0
- ontologytoapi-0.0.5/core/Connectors/Stateful/SocketConnection.py +16 -0
- ontologytoapi-0.0.5/core/Connectors/Stateless/APIConnection.py +12 -0
- ontologytoapi-0.0.5/core/Connectors/Stateless/MYSQLConnection.py +27 -0
- ontologytoapi-0.0.5/core/Connectors/Stateless/MongoDBConnection.py +21 -0
- ontologytoapi-0.0.5/core/DTO/BusinessModel.py +11 -0
- ontologytoapi-0.0.5/core/DTO/ExternalCode.py +7 -0
- ontologytoapi-0.0.5/core/DTO/Metadata.py +8 -0
- ontologytoapi-0.0.5/core/DTO/Source.py +6 -0
- ontologytoapi-0.0.5/core/Ontology.py +78 -0
- ontologytoapi-0.0.5/core/Queries.py +51 -0
- ontologytoapi-0.0.5/core/Utility.py +64 -0
- ontologytoapi-0.0.5/core/__init__.py +2 -0
- ontologytoapi-0.0.5/ontologytoapi.egg-info/PKG-INFO +106 -0
- ontologytoapi-0.0.5/ontologytoapi.egg-info/SOURCES.txt +25 -0
- ontologytoapi-0.0.5/ontologytoapi.egg-info/dependency_links.txt +1 -0
- ontologytoapi-0.0.5/ontologytoapi.egg-info/requires.txt +11 -0
- ontologytoapi-0.0.5/ontologytoapi.egg-info/top_level.txt +1 -0
- ontologytoapi-0.0.5/requirements.txt +12 -0
- ontologytoapi-0.0.5/setup.cfg +7 -0
- ontologytoapi-0.0.5/setup.py +28 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Julio César G. Costa
|
|
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,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ontologytoapi
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: Multi purpose API Generator based on an Ontology Framework.
|
|
5
|
+
Home-page: https://github.com/JCGCosta/OntologyToAPI
|
|
6
|
+
Author: Júlio César Guimarães Costa
|
|
7
|
+
Author-email: juliocesargcosta123@gmail.com
|
|
8
|
+
License: MIT License
|
|
9
|
+
Keywords: Ontology,API
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: SQLAlchemy
|
|
19
|
+
Requires-Dist: pydantic
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: pymongo
|
|
22
|
+
Requires-Dist: rdflib
|
|
23
|
+
Requires-Dist: fastapi
|
|
24
|
+
Requires-Dist: uvicorn
|
|
25
|
+
Requires-Dist: aiomysql
|
|
26
|
+
Requires-Dist: motor
|
|
27
|
+
Requires-Dist: aiofiles
|
|
28
|
+
Requires-Dist: python-multipart
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
# OntologyToAPI
|
|
42
|
+
> This project is an ontology-driven API generator designed for
|
|
43
|
+
> backend development by transforming structured domain
|
|
44
|
+
> knowledge into fully functional APIs. The tool accepts ontologies
|
|
45
|
+
> specified in Turtle (.ttl), Resource Description Framework (.rdf)
|
|
46
|
+
> and Web Ontology Language (.owl).
|
|
47
|
+
|
|
48
|
+
> [](https://github.com/JCGCosta/OntologyToAPI/actions/workflows/python-publish.yml)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Ontological Framework:
|
|
52
|
+
|
|
53
|
+
- The following classes, relationships and data properties serve as a semantic blueprint for both metadata and business models.
|
|
54
|
+
|
|
55
|
+
<img src="https://github.com/JCGCosta/OntologyToAPI/blob/master/OntologicalFramework.jpg?raw=true" alt="AbstractOntologyClasses" title="Abstract Ontology Classes.">
|
|
56
|
+
|
|
57
|
+
The ontological framework is composed of two main modules:
|
|
58
|
+
|
|
59
|
+
- **Metadata Ontology Module:** This module defines the essential classes and properties required to describe the metadata and its sources (e.g. Query to be executed on the CommunicationTechnology).
|
|
60
|
+
- **BusinessModel Ontology Module:** This module captures the specific business logic and rules governing some operation, it requires an ExternalCode concretization, and it can require any metadata or parameter (To be sent in the API request).
|
|
61
|
+
- **ExternalCode Ontology Module:** This module has all the technical details to connect to an external code, it also adds the possibility to dynamically require python packages.
|
|
62
|
+
- **Communications Ontology Module:** This module describes the communication technologies that can be used to fetch the data of some metadata in multiple forms (e. g).
|
|
63
|
+
|
|
64
|
+
> From now on you must be ready to go and create your own ontological specification importing the Ontology Modules and extending it. You can do this by using the Protégé ontology editor (https://protege.stanford.edu/). Or if you prefer you can use any text editor to create your ontology files in the supported formats (.ttl, .rdf, .owl).
|
|
65
|
+
|
|
66
|
+
### Step 1: Installing the Package
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Now inside the environment install the python package
|
|
70
|
+
pip install ontologytoapi
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Step 2: Running
|
|
75
|
+
|
|
76
|
+
- If you want to see a quick ontology sample in .ttl please access the following link: https://github.com/JCGCosta/OntologyToAPI/tree/master/samples
|
|
77
|
+
- With you metadata and business models ontologies implemented you can generate your API by having the following python file as an entry point:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import uvicorn
|
|
81
|
+
from core.APIGenerator import Generator
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
APIGen = Generator(showLogs=True)
|
|
85
|
+
APIGen.load_ontologies(paths=[
|
|
86
|
+
"Your/Metadata/Ontology/.ttl.owl.rdf"
|
|
87
|
+
])
|
|
88
|
+
APIGen.load_ontologies(paths=[
|
|
89
|
+
"Your/BusinessModel/Ontology/.ttl.owl.rdf"
|
|
90
|
+
])
|
|
91
|
+
APIGen.serialize_ontologies()
|
|
92
|
+
api_app = APIGen.generate_api_routes()
|
|
93
|
+
uvicorn.run(api_app, host="127.0.0.1", port=5000)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Change Log
|
|
98
|
+
===============
|
|
99
|
+
0.0.5 (27/10/2025)
|
|
100
|
+
------------------
|
|
101
|
+
FIRST RELEASE
|
|
102
|
+
- First fixes on README file.
|
|
103
|
+
- Added Ontological Framework diagram.
|
|
104
|
+
- Added ontology concrete sample.
|
|
105
|
+
------------------
|
|
106
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# OntologyToAPI
|
|
2
|
+
> This project is an ontology-driven API generator designed for
|
|
3
|
+
> backend development by transforming structured domain
|
|
4
|
+
> knowledge into fully functional APIs. The tool accepts ontologies
|
|
5
|
+
> specified in Turtle (.ttl), Resource Description Framework (.rdf)
|
|
6
|
+
> and Web Ontology Language (.owl).
|
|
7
|
+
|
|
8
|
+
> [](https://github.com/JCGCosta/OntologyToAPI/actions/workflows/python-publish.yml)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Ontological Framework:
|
|
12
|
+
|
|
13
|
+
- The following classes, relationships and data properties serve as a semantic blueprint for both metadata and business models.
|
|
14
|
+
|
|
15
|
+
<img src="https://github.com/JCGCosta/OntologyToAPI/blob/master/OntologicalFramework.jpg?raw=true" alt="AbstractOntologyClasses" title="Abstract Ontology Classes.">
|
|
16
|
+
|
|
17
|
+
The ontological framework is composed of two main modules:
|
|
18
|
+
|
|
19
|
+
- **Metadata Ontology Module:** This module defines the essential classes and properties required to describe the metadata and its sources (e.g. Query to be executed on the CommunicationTechnology).
|
|
20
|
+
- **BusinessModel Ontology Module:** This module captures the specific business logic and rules governing some operation, it requires an ExternalCode concretization, and it can require any metadata or parameter (To be sent in the API request).
|
|
21
|
+
- **ExternalCode Ontology Module:** This module has all the technical details to connect to an external code, it also adds the possibility to dynamically require python packages.
|
|
22
|
+
- **Communications Ontology Module:** This module describes the communication technologies that can be used to fetch the data of some metadata in multiple forms (e. g).
|
|
23
|
+
|
|
24
|
+
> From now on you must be ready to go and create your own ontological specification importing the Ontology Modules and extending it. You can do this by using the Protégé ontology editor (https://protege.stanford.edu/). Or if you prefer you can use any text editor to create your ontology files in the supported formats (.ttl, .rdf, .owl).
|
|
25
|
+
|
|
26
|
+
### Step 1: Installing the Package
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Now inside the environment install the python package
|
|
30
|
+
pip install ontologytoapi
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Step 2: Running
|
|
35
|
+
|
|
36
|
+
- If you want to see a quick ontology sample in .ttl please access the following link: https://github.com/JCGCosta/OntologyToAPI/tree/master/samples
|
|
37
|
+
- With you metadata and business models ontologies implemented you can generate your API by having the following python file as an entry point:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import uvicorn
|
|
41
|
+
from core.APIGenerator import Generator
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
APIGen = Generator(showLogs=True)
|
|
45
|
+
APIGen.load_ontologies(paths=[
|
|
46
|
+
"Your/Metadata/Ontology/.ttl.owl.rdf"
|
|
47
|
+
])
|
|
48
|
+
APIGen.load_ontologies(paths=[
|
|
49
|
+
"Your/BusinessModel/Ontology/.ttl.owl.rdf"
|
|
50
|
+
])
|
|
51
|
+
APIGen.serialize_ontologies()
|
|
52
|
+
api_app = APIGen.generate_api_routes()
|
|
53
|
+
uvicorn.run(api_app, host="127.0.0.1", port=5000)
|
|
54
|
+
```
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from fastapi import FastAPI, UploadFile
|
|
2
|
+
from fastapi.responses import RedirectResponse
|
|
3
|
+
from typing import List
|
|
4
|
+
import pprint, logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from inspect import Signature, Parameter
|
|
7
|
+
from Settings import auto_config as cfg
|
|
8
|
+
from datetime import date
|
|
9
|
+
|
|
10
|
+
from core.Utility import *
|
|
11
|
+
from core.Ontology import Ontology
|
|
12
|
+
|
|
13
|
+
UPLOAD_DIR = Path(cfg.UPLOAD_DIR)
|
|
14
|
+
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
def create_metadata_handler(connector, query: str, name: str):
|
|
17
|
+
async def handler():
|
|
18
|
+
try:
|
|
19
|
+
result = await connector.exec_query(query)
|
|
20
|
+
return result
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logging.exception(f"Error running query for {name}")
|
|
23
|
+
return {"error": str(e)}
|
|
24
|
+
return handler
|
|
25
|
+
|
|
26
|
+
def create_business_model_handler(required_metadata: list, requiresParameters:dict, external_function):
|
|
27
|
+
async def handler(**kwargs):
|
|
28
|
+
aggregated_res = {"FromParameters": {**kwargs}, "FromMetadata": {}, "UPLOAD_DIR": UPLOAD_DIR}
|
|
29
|
+
for md in required_metadata:
|
|
30
|
+
pkg, name = md.name.split(":")
|
|
31
|
+
try:
|
|
32
|
+
aggregated_res["FromMetadata"][name] = await md.hasSource.comm_technology.exec_query(md.hasSource.query)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logging.exception(f"Error running query for {name}")
|
|
35
|
+
return {"error": str(e)}
|
|
36
|
+
result = await external_function(aggregated_res)
|
|
37
|
+
return result
|
|
38
|
+
params = []
|
|
39
|
+
for pl, pt in requiresParameters.items(): params.append(Parameter(pl, Parameter.POSITIONAL_OR_KEYWORD, default=None, annotation=pt))
|
|
40
|
+
handler.__signature__ = Signature(parameters=params)
|
|
41
|
+
return handler
|
|
42
|
+
|
|
43
|
+
class APIGenerator:
|
|
44
|
+
def __init__(self, showLogs: bool = False):
|
|
45
|
+
self.ontology = Ontology()
|
|
46
|
+
self.routes = []
|
|
47
|
+
self.app = FastAPI()
|
|
48
|
+
logging.basicConfig(
|
|
49
|
+
level=logging.INFO if showLogs else logging.ERROR,
|
|
50
|
+
format="%(asctime)s %(levelname)s %(message)s",
|
|
51
|
+
datefmt="%d-%m-%Y %H:%M:%S"
|
|
52
|
+
)
|
|
53
|
+
logging.info(f'The {cfg.UPLOAD_DIR} directory is configured for uploaded files.')
|
|
54
|
+
@self.app.get("/", include_in_schema=False)
|
|
55
|
+
async def root():
|
|
56
|
+
return RedirectResponse(url="/docs")
|
|
57
|
+
|
|
58
|
+
def load_ontologies(self, paths: List[str]):
|
|
59
|
+
for path in paths:
|
|
60
|
+
if not Path(path).exists():
|
|
61
|
+
raise FileNotFoundError(f"The path does not exist: {path}")
|
|
62
|
+
self.ontology.parse_ontology(path=path)
|
|
63
|
+
|
|
64
|
+
def serialize_ontologies(self):
|
|
65
|
+
self.ontology.serialize_metadata()
|
|
66
|
+
self.ontology.serialize_business_models()
|
|
67
|
+
|
|
68
|
+
def generate_api_routes(self) -> FastAPI:
|
|
69
|
+
logging.info(f'Generating API routes for the available metadata...')
|
|
70
|
+
for pkg_name, pkg_data in self.ontology.data.items():
|
|
71
|
+
for md in pkg_data:
|
|
72
|
+
if not md.hasSource or not md.hasSource.query:
|
|
73
|
+
continue # Skip metadata without source query
|
|
74
|
+
route_path = f"/{pkg_name}/{md.name.split(':')[-1]}"
|
|
75
|
+
query = md.hasSource.query
|
|
76
|
+
db_connector = md.hasSource.comm_technology
|
|
77
|
+
handler = create_metadata_handler(db_connector, query, md.name)
|
|
78
|
+
self.app.add_api_route(
|
|
79
|
+
path=route_path,
|
|
80
|
+
endpoint=handler,
|
|
81
|
+
methods=["GET"],
|
|
82
|
+
name=md.hasSource.desc,
|
|
83
|
+
tags=[f"Metadata ({pkg_name})"],
|
|
84
|
+
)
|
|
85
|
+
logging.info(f'Added {md.name} API route...')
|
|
86
|
+
logging.info(f'Generating API routes for the business models...')
|
|
87
|
+
for bm_name, bm_dt in self.ontology.bms.items():
|
|
88
|
+
name = bm_name.split(":")
|
|
89
|
+
ec_func = import_function_from_file(filepath=bm_dt.externalCode.pythonFile, function_name=bm_dt.externalCode.function)
|
|
90
|
+
handler = create_business_model_handler(bm_dt.requiresMetadata, bm_dt.requiresParameters, ec_func)
|
|
91
|
+
self.app.add_api_route(
|
|
92
|
+
path=f"/{name[0]}/{name[1]}/run",
|
|
93
|
+
endpoint=handler,
|
|
94
|
+
methods=["POST"],
|
|
95
|
+
name=f"This endpoint executes the {bm_dt.name} business model, and retrieve it`s results.",
|
|
96
|
+
tags=[f"Business Model ({bm_dt.name})"],
|
|
97
|
+
)
|
|
98
|
+
logging.info(f'Added {bm_name} running API route...')
|
|
99
|
+
return self.app
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from core.Connectors.Stateful.SocketConnection import SocketConnection
|
|
2
|
+
from core.Connectors.Stateless.APIConnection import APIConnection
|
|
3
|
+
from core.Connectors.Stateless.MYSQLConnection import MySQLConnection
|
|
4
|
+
from core.Connectors.Stateless.MongoDBConnection import MongoDBConnection
|
|
5
|
+
|
|
6
|
+
SUPPORTED_CONNECTIONS = {
|
|
7
|
+
"API": APIConnection,
|
|
8
|
+
"MYSQL": MySQLConnection,
|
|
9
|
+
"MONGODB": MongoDBConnection,
|
|
10
|
+
"Socket": SocketConnection
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def identifyConnector(CommunicationTechnology, args):
|
|
14
|
+
try:
|
|
15
|
+
connector_class = SUPPORTED_CONNECTIONS[str(CommunicationTechnology)]
|
|
16
|
+
return connector_class(args)
|
|
17
|
+
except KeyError as e:
|
|
18
|
+
raise ValueError(f"Unsupported type or technology: {e}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import socket as soc
|
|
2
|
+
|
|
3
|
+
class SocketConnection:
|
|
4
|
+
def __init__(self, args):
|
|
5
|
+
try:
|
|
6
|
+
self.socket = soc.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
7
|
+
self.socket.connect((args["hasHost"], args["hasPort"]))
|
|
8
|
+
except Exception as e:
|
|
9
|
+
print("Connection failed:", e)
|
|
10
|
+
|
|
11
|
+
def exec_query(self, message: str):
|
|
12
|
+
try:
|
|
13
|
+
self.socket.sendall(message.encode())
|
|
14
|
+
return self.socket.recv(8192).decode()
|
|
15
|
+
except Exception as e:
|
|
16
|
+
return {'error': str(e)}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import requests as req
|
|
2
|
+
|
|
3
|
+
class APIConnection:
|
|
4
|
+
def __init__(self, args):
|
|
5
|
+
self.baseURL = args["hasRequestURL"]
|
|
6
|
+
|
|
7
|
+
def exec_query(self, endpoint_params: str):
|
|
8
|
+
fullURL = self.baseURL + endpoint_params
|
|
9
|
+
try:
|
|
10
|
+
return req.get(fullURL).json()
|
|
11
|
+
except Exception as e:
|
|
12
|
+
return {'error': str(e)}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from sqlalchemy import text, create_engine
|
|
2
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
async def datetime_handler(row, keys):
|
|
6
|
+
return {
|
|
7
|
+
key: (val.isoformat() if isinstance(val, datetime) else val)
|
|
8
|
+
for key, val in zip(keys, row)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class MySQLConnection:
|
|
12
|
+
def __init__(self, args):
|
|
13
|
+
self.engine = create_async_engine(args["hasConnectionString"])
|
|
14
|
+
|
|
15
|
+
async def exec_query(self, query: str):
|
|
16
|
+
async with self.engine.connect() as connection:
|
|
17
|
+
result = await connection.execute(text(query))
|
|
18
|
+
rows = result.fetchall()
|
|
19
|
+
keys = result.keys()
|
|
20
|
+
return [await datetime_handler(row, keys) for row in rows]
|
|
21
|
+
|
|
22
|
+
async def exec_insert(self, query: str):
|
|
23
|
+
async with self.engine.begin() as connection:
|
|
24
|
+
await connection.execute(text(query))
|
|
25
|
+
result = await connection.execute(text("SELECT LAST_INSERT_ID() AS id"))
|
|
26
|
+
row = result.fetchone()
|
|
27
|
+
return row.id
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from pymongo import AsyncMongoClient
|
|
3
|
+
import json, logging
|
|
4
|
+
|
|
5
|
+
class MongoDBConnection:
|
|
6
|
+
def __init__(self, args):
|
|
7
|
+
connString = args["hasConnectionString"].split('/')
|
|
8
|
+
self.client = AsyncMongoClient("/".join(connString[:-1]))
|
|
9
|
+
self.db = self.client[connString[-1]]
|
|
10
|
+
|
|
11
|
+
async def exec_query(self, collection_query: str):
|
|
12
|
+
query_info = collection_query.split(".")
|
|
13
|
+
collection = self.db[query_info[0]]
|
|
14
|
+
query = json.loads(query_info[1])
|
|
15
|
+
cursor = collection.find({}, query)
|
|
16
|
+
results = await cursor.to_list()
|
|
17
|
+
if not results: return []
|
|
18
|
+
if "_id" in results[0].keys():
|
|
19
|
+
for i in results:
|
|
20
|
+
i["_id"] = str(i["_id"])
|
|
21
|
+
return results
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from core.DTO.Metadata import Metadata
|
|
2
|
+
from core.DTO.ExternalCode import ExternalCode
|
|
3
|
+
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
class BusinessModel(BaseModel):
|
|
8
|
+
name: str
|
|
9
|
+
requiresMetadata: Optional[List[Metadata]]
|
|
10
|
+
requiresParameters: Optional[dict]
|
|
11
|
+
externalCode: Optional[ExternalCode]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from rdflib import Graph, URIRef
|
|
2
|
+
import logging
|
|
3
|
+
import pprint
|
|
4
|
+
|
|
5
|
+
logging.getLogger("rdflib").setLevel(logging.ERROR)
|
|
6
|
+
|
|
7
|
+
from core.Queries import *
|
|
8
|
+
from core.DTO.Source import *
|
|
9
|
+
from core.DTO.BusinessModel import *
|
|
10
|
+
|
|
11
|
+
from core.Connectors.IndentifyConnector import identifyConnector
|
|
12
|
+
from core.Utility import ensure_package_installed
|
|
13
|
+
|
|
14
|
+
class Ontology:
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.g = Graph()
|
|
17
|
+
self.data = {}
|
|
18
|
+
self.bms = {}
|
|
19
|
+
|
|
20
|
+
def parse_ontology(self, path: str):
|
|
21
|
+
if path.endswith('.ttl'):
|
|
22
|
+
self.g.parse(path, format='turtle')
|
|
23
|
+
elif path.endswith('.owl') or path.endswith('.rdf'):
|
|
24
|
+
self.g.parse(path, format='xml')
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(f'File extension must be .ttl or .owl or .rdf ({path} could not be parsed)')
|
|
27
|
+
logging.info(f'Ontology \"{path}\" parsed successfully.')
|
|
28
|
+
|
|
29
|
+
def __repr__(self):
|
|
30
|
+
return (f"data(\n{pprint.pformat(self.data, indent=2)}\n)\n"
|
|
31
|
+
f"business_models(\n{pprint.pformat(self.bms, indent=2)}\n)")
|
|
32
|
+
|
|
33
|
+
def _get_metadata_by_name(self, name: str):
|
|
34
|
+
for namespace, content in self.data.items():
|
|
35
|
+
for metadata in content:
|
|
36
|
+
if metadata.name == name:
|
|
37
|
+
return metadata
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def _verify_metadata(self, q: str):
|
|
41
|
+
md = []
|
|
42
|
+
for row in self.g.query(q):
|
|
43
|
+
metadata_obj = self._get_metadata_by_name(self.g.qname(row[1]))
|
|
44
|
+
if not metadata_obj:
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f'The {self.g.qname(row[1])} metadata which is required by the {self.g.qname(row[0])} could not be found in the ontology.')
|
|
47
|
+
md.append(metadata_obj)
|
|
48
|
+
return md
|
|
49
|
+
|
|
50
|
+
def serialize_metadata(self):
|
|
51
|
+
data = {}
|
|
52
|
+
for row in self.g.query(GET_METADATA_QUERY):
|
|
53
|
+
args = {self.g.qname(row[0]).split(":")[-1]: row[1] for row in
|
|
54
|
+
self.g.query(GET_COMMUNICATION_TECH_ARGS_QUERY + URIRef(row[4]) + ">)}")}
|
|
55
|
+
comm_tech = identifyConnector(row[5], args)
|
|
56
|
+
source = Source(desc=row[2], query=row[3], comm_technology=comm_tech)
|
|
57
|
+
onto_pkg = self.g.qname(row[0]).split(":")[0]
|
|
58
|
+
if onto_pkg not in data.keys(): data[onto_pkg] = []
|
|
59
|
+
data[onto_pkg].append(Metadata(name=self.g.qname(row[0]), type=row[1], hasSource=source))
|
|
60
|
+
logging.info(f'{self.g.qname(row[0])} METADATA serialized successfully;')
|
|
61
|
+
self.data = data
|
|
62
|
+
|
|
63
|
+
def serialize_business_models(self):
|
|
64
|
+
BMs = {}
|
|
65
|
+
for ec in self.g.query(GET_EXTERNAL_CODE_FOR_BM_QUERY):
|
|
66
|
+
bm_name = self.g.qname(ec[0])
|
|
67
|
+
required_metadata = self._verify_metadata(GET_REQUIRED_MD_FOR_BM_QUERY + URIRef(ec[0]) + ">)}")
|
|
68
|
+
required_parameters = self.g.query(GET_REQUIRED_PARAMETERS_FOR_BM_QUERY + URIRef(ec[0]) + ">)}")
|
|
69
|
+
for module in str(ec[3]).split(","): ensure_package_installed(module)
|
|
70
|
+
BMs[bm_name] = BusinessModel(name=bm_name.split(":")[-1],
|
|
71
|
+
requiresMetadata=required_metadata,
|
|
72
|
+
requiresParameters={str(l): t for _, l, t in required_parameters},
|
|
73
|
+
externalCode=ExternalCode(
|
|
74
|
+
pythonFile=str(ec[2]),
|
|
75
|
+
function=str(ec[4]),
|
|
76
|
+
requiresLib=str(ec[3]).split(",")))
|
|
77
|
+
logging.info(f'{bm_name} BUSINESS MODEL serialized successfully;')
|
|
78
|
+
self.bms = BMs
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
GET_METADATA_QUERY = """
|
|
2
|
+
SELECT ?m ?t ?d ?q ?ct ?tc
|
|
3
|
+
WHERE {
|
|
4
|
+
?m <http://www.cedri.com/SmartLEM-Metadata#hasSource> ?s .
|
|
5
|
+
?m <http://www.cedri.com/SmartLEM-Metadata#hasType> ?t .
|
|
6
|
+
?s <http://www.cedri.com/SmartLEM-Metadata#hasQuery> ?q .
|
|
7
|
+
?s <http://www.cedri.com/SmartLEM-Metadata#hasDescription> ?d .
|
|
8
|
+
?s <http://www.cedri.com/SmartLEM-Metadata#hasCommunicationTechnology> ?ct .
|
|
9
|
+
?ct <http://www.cedri.com/SmartLEM-Communications#usesTechnology> ?tc .
|
|
10
|
+
}
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
GET_COMMUNICATION_TECH_ARGS_QUERY = """
|
|
14
|
+
SELECT ?arg ?argv
|
|
15
|
+
WHERE {
|
|
16
|
+
?s <http://www.cedri.com/SmartLEM-Metadata#hasCommunicationTechnology> ?ct .
|
|
17
|
+
?ct ?arg ?argv .
|
|
18
|
+
FILTER(?arg != <http://www.cedri.com/SmartLEM-Communications#usesTechnology> &&
|
|
19
|
+
?arg != <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> &&
|
|
20
|
+
?ct = <"""
|
|
21
|
+
|
|
22
|
+
GET_BM_NAME_QUERY = """
|
|
23
|
+
SELECT ?bm
|
|
24
|
+
WHERE {
|
|
25
|
+
?bm rdfs:subClassOf <http://www.cedri.com/SmartLEM-BusinessModel#BusinessModel> .
|
|
26
|
+
}
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
GET_REQUIRED_MD_FOR_BM_QUERY = """
|
|
30
|
+
SELECT ?bm ?rm
|
|
31
|
+
WHERE {
|
|
32
|
+
?bm <http://www.cedri.com/SmartLEM-BusinessModel#requiresMetadata> ?rm .
|
|
33
|
+
FILTER (?bm = <"""
|
|
34
|
+
|
|
35
|
+
GET_REQUIRED_PARAMETERS_FOR_BM_QUERY = """
|
|
36
|
+
SELECT ?bm ?pl ?pt
|
|
37
|
+
WHERE {
|
|
38
|
+
?bm <http://www.cedri.com/SmartLEM-BusinessModel#hasParameter> ?p .
|
|
39
|
+
?p <http://www.cedri.com/SmartLEM-BusinessModel#hasParameterLabel> ?pl .
|
|
40
|
+
?p <http://www.cedri.com/SmartLEM-BusinessModel#hasParameterType> ?pt .
|
|
41
|
+
FILTER (?bm = <"""
|
|
42
|
+
|
|
43
|
+
GET_EXTERNAL_CODE_FOR_BM_QUERY = """
|
|
44
|
+
SELECT ?bm ?ec ?pf ?rl ?hf
|
|
45
|
+
WHERE {
|
|
46
|
+
?bm <http://www.cedri.com/SmartLEM-BusinessModel#hasExternalCode> ?ec .
|
|
47
|
+
?ec <http://www.cedri.com/SmartLEM-ExternalCode#hasPythonFile> ?pf .
|
|
48
|
+
?ec <http://www.cedri.com/SmartLEM-ExternalCode#requiresLib> ?rl .
|
|
49
|
+
?ec <http://www.cedri.com/SmartLEM-ExternalCode#hasFunction> ?hf .
|
|
50
|
+
}
|
|
51
|
+
"""
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
import os
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from Settings import auto_config as cfg
|
|
8
|
+
|
|
9
|
+
def handle_upload_file(uploaded_file):
|
|
10
|
+
UPLOAD_DIR = Path(cfg.UPLOAD_DIR)
|
|
11
|
+
if not os.path.exists(UPLOAD_DIR):
|
|
12
|
+
os.makedirs(UPLOAD_DIR)
|
|
13
|
+
|
|
14
|
+
file_path = os.path.join(UPLOAD_DIR, uploaded_file.filename)
|
|
15
|
+
|
|
16
|
+
with open(file_path, "wb") as out_file:
|
|
17
|
+
while chunk := uploaded_file.file.read(1024 * 1024): # Read in 1 MiB chunks
|
|
18
|
+
out_file.write(chunk)
|
|
19
|
+
out_file.close()
|
|
20
|
+
|
|
21
|
+
uploaded_file.file.close()
|
|
22
|
+
|
|
23
|
+
return file_path
|
|
24
|
+
|
|
25
|
+
def import_function_from_file(filepath, function_name):
|
|
26
|
+
# Check if file exists
|
|
27
|
+
if not os.path.isfile(filepath):
|
|
28
|
+
raise FileNotFoundError(f"File not found: {filepath}")
|
|
29
|
+
|
|
30
|
+
module_name = os.path.splitext(os.path.basename(filepath))[0]
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
spec = importlib.util.spec_from_file_location(module_name, filepath)
|
|
34
|
+
if spec is None:
|
|
35
|
+
raise ImportError(f"Could not load module spec from {filepath}")
|
|
36
|
+
|
|
37
|
+
module = importlib.util.module_from_spec(spec)
|
|
38
|
+
sys.modules[module_name] = module
|
|
39
|
+
spec.loader.exec_module(module)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise ImportError(f"Failed to load module '{module_name}': {e}")
|
|
42
|
+
|
|
43
|
+
# Check if function exists
|
|
44
|
+
if not hasattr(module, function_name):
|
|
45
|
+
raise AttributeError(f"Function '{function_name}' not found in '{filepath}'")
|
|
46
|
+
|
|
47
|
+
func = getattr(module, function_name)
|
|
48
|
+
if not callable(func):
|
|
49
|
+
raise TypeError(f"'{function_name}' in '{filepath}' is not a callable function")
|
|
50
|
+
|
|
51
|
+
return func
|
|
52
|
+
|
|
53
|
+
def install_package(package_name):
|
|
54
|
+
try:
|
|
55
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
|
|
56
|
+
logging.info(f"Package '{package_name}' installed successfully.")
|
|
57
|
+
except subprocess.CalledProcessError as e:
|
|
58
|
+
logging.info(f"Failed to install '{package_name}': {e}")
|
|
59
|
+
|
|
60
|
+
def ensure_package_installed(package_name):
|
|
61
|
+
if importlib.util.find_spec(package_name) is None:
|
|
62
|
+
install_package(package_name)
|
|
63
|
+
else:
|
|
64
|
+
logging.info(f"Package '{package_name}' is already installed.")
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ontologytoapi
|
|
3
|
+
Version: 0.0.5
|
|
4
|
+
Summary: Multi purpose API Generator based on an Ontology Framework.
|
|
5
|
+
Home-page: https://github.com/JCGCosta/OntologyToAPI
|
|
6
|
+
Author: Júlio César Guimarães Costa
|
|
7
|
+
Author-email: juliocesargcosta123@gmail.com
|
|
8
|
+
License: MIT License
|
|
9
|
+
Keywords: Ontology,API
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: SQLAlchemy
|
|
19
|
+
Requires-Dist: pydantic
|
|
20
|
+
Requires-Dist: requests
|
|
21
|
+
Requires-Dist: pymongo
|
|
22
|
+
Requires-Dist: rdflib
|
|
23
|
+
Requires-Dist: fastapi
|
|
24
|
+
Requires-Dist: uvicorn
|
|
25
|
+
Requires-Dist: aiomysql
|
|
26
|
+
Requires-Dist: motor
|
|
27
|
+
Requires-Dist: aiofiles
|
|
28
|
+
Requires-Dist: python-multipart
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: keywords
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
# OntologyToAPI
|
|
42
|
+
> This project is an ontology-driven API generator designed for
|
|
43
|
+
> backend development by transforming structured domain
|
|
44
|
+
> knowledge into fully functional APIs. The tool accepts ontologies
|
|
45
|
+
> specified in Turtle (.ttl), Resource Description Framework (.rdf)
|
|
46
|
+
> and Web Ontology Language (.owl).
|
|
47
|
+
|
|
48
|
+
> [](https://github.com/JCGCosta/OntologyToAPI/actions/workflows/python-publish.yml)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
## Ontological Framework:
|
|
52
|
+
|
|
53
|
+
- The following classes, relationships and data properties serve as a semantic blueprint for both metadata and business models.
|
|
54
|
+
|
|
55
|
+
<img src="https://github.com/JCGCosta/OntologyToAPI/blob/master/OntologicalFramework.jpg?raw=true" alt="AbstractOntologyClasses" title="Abstract Ontology Classes.">
|
|
56
|
+
|
|
57
|
+
The ontological framework is composed of two main modules:
|
|
58
|
+
|
|
59
|
+
- **Metadata Ontology Module:** This module defines the essential classes and properties required to describe the metadata and its sources (e.g. Query to be executed on the CommunicationTechnology).
|
|
60
|
+
- **BusinessModel Ontology Module:** This module captures the specific business logic and rules governing some operation, it requires an ExternalCode concretization, and it can require any metadata or parameter (To be sent in the API request).
|
|
61
|
+
- **ExternalCode Ontology Module:** This module has all the technical details to connect to an external code, it also adds the possibility to dynamically require python packages.
|
|
62
|
+
- **Communications Ontology Module:** This module describes the communication technologies that can be used to fetch the data of some metadata in multiple forms (e. g).
|
|
63
|
+
|
|
64
|
+
> From now on you must be ready to go and create your own ontological specification importing the Ontology Modules and extending it. You can do this by using the Protégé ontology editor (https://protege.stanford.edu/). Or if you prefer you can use any text editor to create your ontology files in the supported formats (.ttl, .rdf, .owl).
|
|
65
|
+
|
|
66
|
+
### Step 1: Installing the Package
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Now inside the environment install the python package
|
|
70
|
+
pip install ontologytoapi
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Step 2: Running
|
|
75
|
+
|
|
76
|
+
- If you want to see a quick ontology sample in .ttl please access the following link: https://github.com/JCGCosta/OntologyToAPI/tree/master/samples
|
|
77
|
+
- With you metadata and business models ontologies implemented you can generate your API by having the following python file as an entry point:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import uvicorn
|
|
81
|
+
from core.APIGenerator import Generator
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
APIGen = Generator(showLogs=True)
|
|
85
|
+
APIGen.load_ontologies(paths=[
|
|
86
|
+
"Your/Metadata/Ontology/.ttl.owl.rdf"
|
|
87
|
+
])
|
|
88
|
+
APIGen.load_ontologies(paths=[
|
|
89
|
+
"Your/BusinessModel/Ontology/.ttl.owl.rdf"
|
|
90
|
+
])
|
|
91
|
+
APIGen.serialize_ontologies()
|
|
92
|
+
api_app = APIGen.generate_api_routes()
|
|
93
|
+
uvicorn.run(api_app, host="127.0.0.1", port=5000)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
Change Log
|
|
98
|
+
===============
|
|
99
|
+
0.0.5 (27/10/2025)
|
|
100
|
+
------------------
|
|
101
|
+
FIRST RELEASE
|
|
102
|
+
- First fixes on README file.
|
|
103
|
+
- Added Ontological Framework diagram.
|
|
104
|
+
- Added ontology concrete sample.
|
|
105
|
+
------------------
|
|
106
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
requirements.txt
|
|
5
|
+
setup.cfg
|
|
6
|
+
setup.py
|
|
7
|
+
core/APIGenerator.py
|
|
8
|
+
core/Ontology.py
|
|
9
|
+
core/Queries.py
|
|
10
|
+
core/Utility.py
|
|
11
|
+
core/__init__.py
|
|
12
|
+
core/Connectors/IndentifyConnector.py
|
|
13
|
+
core/Connectors/Stateful/SocketConnection.py
|
|
14
|
+
core/Connectors/Stateless/APIConnection.py
|
|
15
|
+
core/Connectors/Stateless/MYSQLConnection.py
|
|
16
|
+
core/Connectors/Stateless/MongoDBConnection.py
|
|
17
|
+
core/DTO/BusinessModel.py
|
|
18
|
+
core/DTO/ExternalCode.py
|
|
19
|
+
core/DTO/Metadata.py
|
|
20
|
+
core/DTO/Source.py
|
|
21
|
+
ontologytoapi.egg-info/PKG-INFO
|
|
22
|
+
ontologytoapi.egg-info/SOURCES.txt
|
|
23
|
+
ontologytoapi.egg-info/dependency_links.txt
|
|
24
|
+
ontologytoapi.egg-info/requires.txt
|
|
25
|
+
ontologytoapi.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
core
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
classifiers = [
|
|
4
|
+
'Development Status :: 3 - Alpha',
|
|
5
|
+
'Intended Audience :: Developers',
|
|
6
|
+
'Operating System :: Microsoft :: Windows :: Windows 11',
|
|
7
|
+
'License :: OSI Approved :: MIT License',
|
|
8
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
9
|
+
'Programming Language :: Python :: 3.11'
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
setup(
|
|
13
|
+
name='ontologytoapi',
|
|
14
|
+
version='0.0.5',
|
|
15
|
+
description='Multi purpose API Generator based on an Ontology Framework.',
|
|
16
|
+
long_description=
|
|
17
|
+
f"{open('README.md').read()}\n\n" +
|
|
18
|
+
f"{open('CHANGELOG.txt').read()}\n\n",
|
|
19
|
+
long_description_content_type="text/markdown",
|
|
20
|
+
url='https://github.com/JCGCosta/OntologyToAPI',
|
|
21
|
+
author='Júlio César Guimarães Costa',
|
|
22
|
+
author_email='juliocesargcosta123@gmail.com',
|
|
23
|
+
license='MIT License',
|
|
24
|
+
classifiers=classifiers,
|
|
25
|
+
keywords=['Ontology', 'API'],
|
|
26
|
+
packages=find_packages(),
|
|
27
|
+
install_requires=["SQLAlchemy","pydantic","requests","pymongo","rdflib","fastapi","uvicorn","aiomysql","motor","aiofiles","python-multipart"]
|
|
28
|
+
)
|