cim-loader 0.1.0b0__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.
- cim_loader-0.1.0b0/LICENSE +28 -0
- cim_loader-0.1.0b0/PKG-INFO +78 -0
- cim_loader-0.1.0b0/README.md +55 -0
- cim_loader-0.1.0b0/cimloader/batch_handlers/blazegraph_to_mysql.py +18 -0
- cim_loader-0.1.0b0/cimloader/batch_handlers/naerm_to_neo4j.py +80 -0
- cim_loader-0.1.0b0/cimloader/databases/__init__.py +49 -0
- cim_loader-0.1.0b0/cimloader/databases/blazegraph.py +45 -0
- cim_loader-0.1.0b0/cimloader/databases/mysql.py +186 -0
- cim_loader-0.1.0b0/cimloader/databases/neo4j.py +69 -0
- cim_loader-0.1.0b0/cimloader/downloaders/__init__.py +0 -0
- cim_loader-0.1.0b0/cimloader/downloaders/blazegraph.py +0 -0
- cim_loader-0.1.0b0/cimloader/downloaders/graphdb.py +0 -0
- cim_loader-0.1.0b0/cimloader/downloaders/mysql.py +0 -0
- cim_loader-0.1.0b0/cimloader/downloaders/naerm_api.py +66 -0
- cim_loader-0.1.0b0/cimloader/downloaders/neo4j.py +0 -0
- cim_loader-0.1.0b0/cimloader/serializations/encoders/__init__.py +0 -0
- cim_loader-0.1.0b0/cimloader/serializations/translators/__init__.py +0 -0
- cim_loader-0.1.0b0/cimloader/uploaders/__init__.py +2 -0
- cim_loader-0.1.0b0/cimloader/uploaders/blazegraph.py +25 -0
- cim_loader-0.1.0b0/cimloader/uploaders/graphdb.py +0 -0
- cim_loader-0.1.0b0/cimloader/uploaders/mysql.py +0 -0
- cim_loader-0.1.0b0/cimloader/uploaders/neo4j.py +50 -0
- cim_loader-0.1.0b0/pyproject.toml +37 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, PNNL-CIM-Tools
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cim-loader
|
|
3
|
+
Version: 0.1.0b0
|
|
4
|
+
Summary: CIM Database Uploade & Downloader
|
|
5
|
+
Author: A. Anderson
|
|
6
|
+
Author-email: 19935503+AAndersn@users.noreply.github.com
|
|
7
|
+
Requires-Python: >=3.8.1,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Provides-Extra: gridappsd
|
|
14
|
+
Requires-Dist: SPARQLWrapper (>=2.0.0,<3.0.0)
|
|
15
|
+
Requires-Dist: cim-graph (>=0.1.5a0,<0.2.0)
|
|
16
|
+
Requires-Dist: mysql-connector-python (>=8.0.0,<9.0.0)
|
|
17
|
+
Requires-Dist: neo4j (>=5.10.0,<6.0.0)
|
|
18
|
+
Requires-Dist: rdflib (>=7.0.0,<8.0.0)
|
|
19
|
+
Requires-Dist: rdflib-neo4j (>=0.0.1b6,<0.0.2)
|
|
20
|
+
Requires-Dist: requests (>=2.28.0,<3.0.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# CIM-Loader
|
|
24
|
+
Automated scripts for
|
|
25
|
+
* uploading and downloading CIM files from various databases
|
|
26
|
+
* converting CIM files between common formats (XML, TTL, etc.)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
PyPi version coming soon.
|
|
31
|
+
|
|
32
|
+
Clone the repo and run
|
|
33
|
+
`pip install -e CIM-Loader`
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
To use CIM-Loader for bulk upload and download, set the connection parameters with the correct url / host / port / username / password and then invoke the associated upload / download method
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from cimloader.databases import ConnectionParameters
|
|
42
|
+
from cimloader.uploaders import BlazegraphUploader
|
|
43
|
+
params = ConnectionParameters(url = "http://localhost:8889/bigdata/namespace/kb/sparql")
|
|
44
|
+
loader = BlazegraphUploader(params)
|
|
45
|
+
|
|
46
|
+
loader.upload_from_file(filepath='./test_models', filename='ieee13_seto.xml')
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Databases Supported
|
|
51
|
+
Databases to be supported in first full release:
|
|
52
|
+
* Blazegraph
|
|
53
|
+
* Neo4J
|
|
54
|
+
* GraphDB
|
|
55
|
+
* MySQL
|
|
56
|
+
|
|
57
|
+
Support may be added in the future for:
|
|
58
|
+
* Apache Tinkerpop
|
|
59
|
+
* SQlite
|
|
60
|
+
* AVEVA PI Historian
|
|
61
|
+
* Others as requested
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Attribution and Disclaimer
|
|
65
|
+
|
|
66
|
+
This software was created under a project sponsored by the U.S. Department of Energy’s Office of Electricity, an agency of the United States Government. Neither the United States Government nor the United States Department of Energy, nor Battelle, nor any of their employees, nor any jurisdiction or organization that has cooperated in the development of these materials, makes any warranty, express or implied, or assumes any legal liability or responsibility for the accuracy, completeness, or usefulness or any information, apparatus, product, software, or process disclosed, or represents that its use would not infringe privately owned rights.
|
|
67
|
+
|
|
68
|
+
Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or any agency thereof, or Battelle Memorial Institute. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or any agency thereof.
|
|
69
|
+
|
|
70
|
+
PACIFIC NORTHWEST NATIONAL LABORATORY
|
|
71
|
+
operated by
|
|
72
|
+
BATTELLE
|
|
73
|
+
for the
|
|
74
|
+
UNITED STATES DEPARTMENT OF ENERGY
|
|
75
|
+
under Contract DE-AC05-76RL01830
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# CIM-Loader
|
|
2
|
+
Automated scripts for
|
|
3
|
+
* uploading and downloading CIM files from various databases
|
|
4
|
+
* converting CIM files between common formats (XML, TTL, etc.)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
PyPi version coming soon.
|
|
9
|
+
|
|
10
|
+
Clone the repo and run
|
|
11
|
+
`pip install -e CIM-Loader`
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
To use CIM-Loader for bulk upload and download, set the connection parameters with the correct url / host / port / username / password and then invoke the associated upload / download method
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from cimloader.databases import ConnectionParameters
|
|
20
|
+
from cimloader.uploaders import BlazegraphUploader
|
|
21
|
+
params = ConnectionParameters(url = "http://localhost:8889/bigdata/namespace/kb/sparql")
|
|
22
|
+
loader = BlazegraphUploader(params)
|
|
23
|
+
|
|
24
|
+
loader.upload_from_file(filepath='./test_models', filename='ieee13_seto.xml')
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Databases Supported
|
|
29
|
+
Databases to be supported in first full release:
|
|
30
|
+
* Blazegraph
|
|
31
|
+
* Neo4J
|
|
32
|
+
* GraphDB
|
|
33
|
+
* MySQL
|
|
34
|
+
|
|
35
|
+
Support may be added in the future for:
|
|
36
|
+
* Apache Tinkerpop
|
|
37
|
+
* SQlite
|
|
38
|
+
* AVEVA PI Historian
|
|
39
|
+
* Others as requested
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Attribution and Disclaimer
|
|
43
|
+
|
|
44
|
+
This software was created under a project sponsored by the U.S. Department of Energy’s Office of Electricity, an agency of the United States Government. Neither the United States Government nor the United States Department of Energy, nor Battelle, nor any of their employees, nor any jurisdiction or organization that has cooperated in the development of these materials, makes any warranty, express or implied, or assumes any legal liability or responsibility for the accuracy, completeness, or usefulness or any information, apparatus, product, software, or process disclosed, or represents that its use would not infringe privately owned rights.
|
|
45
|
+
|
|
46
|
+
Reference herein to any specific commercial product, process, or service by trade name, trademark, manufacturer, or otherwise does not necessarily constitute or imply its endorsement, recommendation, or favoring by the United States Government or any agency thereof, or Battelle Memorial Institute. The views and opinions of authors expressed herein do not necessarily state or reflect those of the United States Government or any agency thereof.
|
|
47
|
+
|
|
48
|
+
PACIFIC NORTHWEST NATIONAL LABORATORY
|
|
49
|
+
operated by
|
|
50
|
+
BATTELLE
|
|
51
|
+
for the
|
|
52
|
+
UNITED STATES DEPARTMENT OF ENERGY
|
|
53
|
+
under Contract DE-AC05-76RL01830
|
|
54
|
+
|
|
55
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# from cimloader.databases.blazegraph import Blazegraph
|
|
3
|
+
from cimloader.databases.mysql import MySQLConnection
|
|
4
|
+
from cimloader.databases import ConnectionInterface, ConnectionParameters, Parameter, QueryResponse
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class BlazegraphtoMySQL():
|
|
14
|
+
def __init__(self, naerm_params:ConnectionInterface, neo4j_params:ConnectionInterface,
|
|
15
|
+
tmp_dir:str, docker_container:str):
|
|
16
|
+
# self.NaermDownloader = NAERM(naerm_params)
|
|
17
|
+
# self.Neo4jConnection= Neo4j(neo4j_params)
|
|
18
|
+
pass
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from cimloader.web_apis.naerm_api import NAERM
|
|
2
|
+
from cimloader.databases.neo4j import Neo4jConnection
|
|
3
|
+
from cimloader.converters.dss_to_cim import DSStoCIM
|
|
4
|
+
from cimloader.databases import ConnectionInterface, ConnectionParameters, Parameter, QueryResponse
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import logging
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class NAERMtoNeo4j():
|
|
14
|
+
def __init__(self, naerm_params:ConnectionInterface, neo4j_params:ConnectionInterface,
|
|
15
|
+
tmp_dir:str, docker_container:str):
|
|
16
|
+
self.NaermDownloader = NAERM(naerm_params)
|
|
17
|
+
# self.Neo4jConnection= Neo4jConnection(neo4j_params)
|
|
18
|
+
self.dss_converter = DSStoCIM()
|
|
19
|
+
self.tmp_dir = tmp_dir
|
|
20
|
+
self.docker_container = docker_container
|
|
21
|
+
|
|
22
|
+
def upload_all_dss(self, limit):
|
|
23
|
+
# self.Neo4jConnection.connect()
|
|
24
|
+
case_uuid_list, case_uuid_names = self.NaermDownloader.download_all_dss(destination=self.tmp_dir, limit=limit)
|
|
25
|
+
for case_uuid in case_uuid_list:
|
|
26
|
+
self.dss_converter.convert_file(file_path=f"{self.tmp_dir}/{case_uuid}", feeder_mrid=case_uuid,
|
|
27
|
+
sub_name=case_uuid_names[case_uuid], sub_geo="SFO", geo="CA", uuids_file=f"{self.tmp_dir}/sfo_uuid.dat")
|
|
28
|
+
if self.docker_container is not None:
|
|
29
|
+
subprocess.call(["docker", "cp", f"{self.tmp_dir}/{case_uuid}/Master.xml", f"{self.docker_container}:import/{case_uuid}/Master.xml"])
|
|
30
|
+
self.Neo4jConnection.upload(filepath=f"/import/{case_uuid}", filename="Master.xml", format="RDF/XML")
|
|
31
|
+
_log.info(f"Successfully uploaded feeder id {case_uuid}")
|
|
32
|
+
# self.Neo4jConnection.disconnect()
|
|
33
|
+
|
|
34
|
+
def upload_case(self, case_uuid):
|
|
35
|
+
# self.Neo4jConnection.connect()
|
|
36
|
+
self.NaermDownloader.download_case(case_uuid=case_uuid, destination=self.tmp_dir)
|
|
37
|
+
|
|
38
|
+
self.dss_converter.convert_file(file_path=f"{self.tmp_dir}/{case_uuid}", feeder_mrid=case_uuid, sub_geo="SFO", geo="CA", uuids_file=f"{self.tmp_dir}/sfo_uuid.dat")
|
|
39
|
+
if self.docker_container is not None:
|
|
40
|
+
subprocess.call(["docker", "cp", f"{self.tmp_dir}/{case_uuid}/Master.xml", f"{self.docker_container}:import/{case_uuid}/Master.xml"])
|
|
41
|
+
self.Neo4jConnection.upload(filepath=f"/import/{case_uuid}", filename="Master.xml", format="RDF/XML")
|
|
42
|
+
_log.info(f"Successfully uploaded feeder id {case_uuid}")
|
|
43
|
+
# self.Neo4jConnection.disconnect()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _main():
|
|
50
|
+
|
|
51
|
+
neo4j_params = ConnectionParameters(url = "neo4j://localhost:7687", database="neo4j", username="neo4j", password="neo4j",
|
|
52
|
+
cim_profile='cimhub_2023', namespace="http://iec.ch/TC57/CIM100#")
|
|
53
|
+
|
|
54
|
+
naerm_params = ConnectionParameters(url = "https://api.develop.naerm.team/data/bes/case_files")
|
|
55
|
+
|
|
56
|
+
tmp_dir = "/home/ande188/naerm"
|
|
57
|
+
|
|
58
|
+
sfo_uuid = """
|
|
59
|
+
Station=Station=1 {ad4e7f6e-b4eb-44cb-a75e-ac7f3e2e41c5}
|
|
60
|
+
GeoRgn=GeoRgn=1 {731690f4-13e0-4555-8084-3cc05d1000f8}
|
|
61
|
+
SubGeoRgn=SubGeoRgn=1 {277ebb75-f974-4de6-8a85-8526d0aa3351}"""
|
|
62
|
+
|
|
63
|
+
with open(f"{tmp_dir}/sfo_uuid.dat", "w") as fp:
|
|
64
|
+
fp.write(sfo_uuid)
|
|
65
|
+
|
|
66
|
+
docker_container = "gridappsd-docker-neo4j-apoc_1"
|
|
67
|
+
|
|
68
|
+
nn4j = NAERMtoNeo4j(naerm_params=naerm_params, neo4j_params=neo4j_params, tmp_dir=tmp_dir, docker_container=docker_container)
|
|
69
|
+
|
|
70
|
+
nn4j.upload_all_dss(limit=10)
|
|
71
|
+
|
|
72
|
+
# case_uuid = "accdf54c-c90c-4c41-9d7a-5e5d3d17a4e5"
|
|
73
|
+
case_uuid = "1b5adb54-dcc3-472b-a5fd-aaab635857e1"
|
|
74
|
+
nn4j.upload_case(case_uuid=case_uuid)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
_main()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Parameter:
|
|
10
|
+
key: Any
|
|
11
|
+
value: Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ConnectionParameters:
|
|
16
|
+
url: str = field(default_factory=str)
|
|
17
|
+
host: str = field(default_factory=str)
|
|
18
|
+
port: str = field(default_factory=str)
|
|
19
|
+
filename: str = field(default_factory=str)
|
|
20
|
+
username: str = field(default_factory=str)
|
|
21
|
+
password: str = field(default_factory=str)
|
|
22
|
+
database: str = field(default_factory=str)
|
|
23
|
+
container: str = field(default_factory=str)
|
|
24
|
+
namespace: str = field(default="<http://iec.ch/TC57/CIM100#>")
|
|
25
|
+
cim_profile: str = field(default_factory=str)
|
|
26
|
+
iec61970_301: int = field(default=7)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class QueryResponse:
|
|
31
|
+
response: Any
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ConnectionInterface:
|
|
36
|
+
connection_params: ConnectionParameters
|
|
37
|
+
|
|
38
|
+
def connect(self):
|
|
39
|
+
raise RuntimeError("Must have implemented connect in inherited class")
|
|
40
|
+
|
|
41
|
+
def disconnect(self):
|
|
42
|
+
raise RuntimeError("Must have implemented disconnect in inherited class")
|
|
43
|
+
|
|
44
|
+
def execute(self, query: str) -> QueryResponse:
|
|
45
|
+
raise RuntimeError("Must have implemented query in the inherited class")
|
|
46
|
+
|
|
47
|
+
from cimloader.databases.blazegraph import BlazegraphConnection
|
|
48
|
+
from cimloader.databases.neo4j import Neo4jConnection
|
|
49
|
+
from cimloader.databases.mysql import MySQLConnection
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from cimloader.databases import ConnectionInterface, QueryResponse
|
|
5
|
+
from SPARQLWrapper import JSON, POST, SPARQLWrapper
|
|
6
|
+
|
|
7
|
+
_log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class BlazegraphConnection(ConnectionInterface):
|
|
10
|
+
def __init__(self, connection_params: ConnectionInterface) -> None:
|
|
11
|
+
self.cim_profile = connection_params.cim_profile
|
|
12
|
+
# self.cim = importlib.import_module('cimgraph.data_profile.' + self.cim_profile)
|
|
13
|
+
self.namespace = connection_params.namespace
|
|
14
|
+
self.iec61970_301 = connection_params.iec61970_301
|
|
15
|
+
self.url = connection_params.url
|
|
16
|
+
self.connection_params = connection_params
|
|
17
|
+
self.sparql_obj = None
|
|
18
|
+
|
|
19
|
+
def connect(self):
|
|
20
|
+
if not self.sparql_obj:
|
|
21
|
+
self.sparql_obj = SPARQLWrapper(self.connection_params.url)
|
|
22
|
+
self.sparql_obj.setReturnFormat(JSON)
|
|
23
|
+
|
|
24
|
+
def configure(self):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def drop_all(self):
|
|
28
|
+
self.update('drop all')
|
|
29
|
+
|
|
30
|
+
def disconnect(self):
|
|
31
|
+
self.sparql_obj = None
|
|
32
|
+
|
|
33
|
+
def execute(self, query_message: str) -> QueryResponse:
|
|
34
|
+
self.connect()
|
|
35
|
+
self.sparql_obj.setQuery(query_message)
|
|
36
|
+
self.sparql_obj.setMethod(POST)
|
|
37
|
+
query_output = self.sparql_obj.query().convert()
|
|
38
|
+
return query_output
|
|
39
|
+
|
|
40
|
+
def update(self, query_message:str) -> QueryResponse:
|
|
41
|
+
self.connect()
|
|
42
|
+
self.sparql_obj.setQuery(query_message)
|
|
43
|
+
self.sparql_obj.setMethod(POST)
|
|
44
|
+
query_output = self.sparql_obj.query()
|
|
45
|
+
return query_output
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import mysql.connector
|
|
3
|
+
import logging
|
|
4
|
+
import importlib
|
|
5
|
+
import json
|
|
6
|
+
import enum
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from cimloader.databases import ConnectionInterface, ConnectionParameters, Parameter, QueryResponse
|
|
10
|
+
from cimgraph.data_profile.known_problem_classes import ClassesWithoutMRID
|
|
11
|
+
from cimgraph.models import GraphModel
|
|
12
|
+
|
|
13
|
+
_log = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class MySQLConnection(ConnectionInterface):
|
|
16
|
+
def __init__(self, connection_parameters: ConnectionParameters):
|
|
17
|
+
self.connection_parameters = connection_parameters
|
|
18
|
+
self.cim_profile = connection_parameters.cim_profile
|
|
19
|
+
self.cim = importlib.import_module('cimgraph.data_profile.' + self.cim_profile)
|
|
20
|
+
self.namespace = connection_parameters.namespace
|
|
21
|
+
self.host = connection_parameters.host
|
|
22
|
+
self.port = connection_parameters.port
|
|
23
|
+
self.username = connection_parameters.username
|
|
24
|
+
self.password = connection_parameters.password
|
|
25
|
+
self.database = connection_parameters.database
|
|
26
|
+
self.connection = None
|
|
27
|
+
self.cursor = None
|
|
28
|
+
|
|
29
|
+
def connect(self):
|
|
30
|
+
if not self.cursor:
|
|
31
|
+
if not self.database: # Set database name to CIM Profile name if not specified
|
|
32
|
+
self.database = self.cim_profile
|
|
33
|
+
try:
|
|
34
|
+
self.connection = mysql.connector.connect(host = self.host, user = self.username, password = self.password, database = self.database)
|
|
35
|
+
self.cursor = self.connection.cursor(buffered = True)
|
|
36
|
+
except:
|
|
37
|
+
_log.error('Could not connect to database')
|
|
38
|
+
|
|
39
|
+
def disconnect(self):
|
|
40
|
+
self.cursor = None
|
|
41
|
+
|
|
42
|
+
def execute(self, query_message: str) -> QueryResponse:
|
|
43
|
+
self.connect()
|
|
44
|
+
self.cursor.execute(query_message)
|
|
45
|
+
response = self.cursor.fetchall()
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
def create_database(self, database:str = None, overwrite:bool = True):
|
|
49
|
+
if database is None:
|
|
50
|
+
if self.database is None:
|
|
51
|
+
database = self.cim_profile
|
|
52
|
+
else:
|
|
53
|
+
database = self.database
|
|
54
|
+
self.database = database
|
|
55
|
+
connection = mysql.connector.connect(host = self.host, user = self.username, password = self.password)
|
|
56
|
+
cursor = connection.cursor(buffered = True)
|
|
57
|
+
if overwrite:
|
|
58
|
+
# try:
|
|
59
|
+
cursor.execute(f"DROP DATABASE {database}")
|
|
60
|
+
# except:
|
|
61
|
+
# _log.error("Unable to connect to database")
|
|
62
|
+
|
|
63
|
+
# try:
|
|
64
|
+
cursor.execute(f"CREATE DATABASE {database}")
|
|
65
|
+
# except:
|
|
66
|
+
# _log.error("Unable to create new database")
|
|
67
|
+
|
|
68
|
+
def configure(self, overwrite:bool = True):
|
|
69
|
+
class_list = self.cim.__all__
|
|
70
|
+
classes_without_mrid = ClassesWithoutMRID()
|
|
71
|
+
|
|
72
|
+
for class_name in class_list:
|
|
73
|
+
cim_class = eval(f"self.cim.{class_name}")
|
|
74
|
+
# print(cim_class.__name__)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
self.connect()
|
|
78
|
+
self.cursor.execute(f"DROP TABLE {cim_class.__name__}")
|
|
79
|
+
except:
|
|
80
|
+
pass
|
|
81
|
+
# Get all attribute types from CIMantic Graphs dataclasses
|
|
82
|
+
try:
|
|
83
|
+
if type(cim_class) == enum.EnumMeta:
|
|
84
|
+
values = cim_class._member_names_
|
|
85
|
+
sql_query = f"CREATE TABLE {cim_class.__name__} (enumeration VARCHAR(255))"
|
|
86
|
+
self.cursor.execute(sql_query) # create table
|
|
87
|
+
for value in values:
|
|
88
|
+
sql_insert = f"""INSERT INTO {cim_class.__name__} (enumeration) VALUES ("{value}")"""
|
|
89
|
+
self.cursor.execute(sql_insert)
|
|
90
|
+
else:
|
|
91
|
+
# Check if is class and has class fields
|
|
92
|
+
fields = cim_class.__dataclass_fields__
|
|
93
|
+
sql_query = f"CREATE TABLE {cim_class.__name__} ("
|
|
94
|
+
|
|
95
|
+
sql_query = sql_query + "username VARCHAR(255), "
|
|
96
|
+
sql_query = sql_query + "timestamp INT, "
|
|
97
|
+
|
|
98
|
+
# Handling for problem classes that don't inherit from IdentifiedObject
|
|
99
|
+
if cim_class in classes_without_mrid.classes:
|
|
100
|
+
sql_query = sql_query + "_mRID VARCHAR(255), "
|
|
101
|
+
# Iterate through all attributes and add to table
|
|
102
|
+
for attr in list(fields.keys()):
|
|
103
|
+
attribute_name = fields[attr].type
|
|
104
|
+
# Determine type of attribute value and use correct column type in table
|
|
105
|
+
if "List" in attribute_name:
|
|
106
|
+
sql_query = sql_query + f"_{attr} JSON, "
|
|
107
|
+
elif "float" in attribute_name:
|
|
108
|
+
sql_query = sql_query + f"_{attr} FLOAT, "
|
|
109
|
+
elif 'Optional' in attribute_name: #check if attribute is association to a class object
|
|
110
|
+
if '\'' in attribute_name: #handling inconsistent '' marks in data profile
|
|
111
|
+
at_cls = re.match(r'Optional\[\'(.*)\']',attribute_name)
|
|
112
|
+
attribute_name = at_cls.group(1)
|
|
113
|
+
else:
|
|
114
|
+
at_cls = re.match(r'Optional\[(.*)]',attribute_name)
|
|
115
|
+
attribute_name = at_cls.group(1)
|
|
116
|
+
# If a CIM class, then use JSON-LD
|
|
117
|
+
if attribute_name in self.cim.__all__:
|
|
118
|
+
attribute_class = eval(f"self.cim.{attribute_name}")
|
|
119
|
+
if type(attribute_class) == enum.EnumMeta:
|
|
120
|
+
sql_query = sql_query + f"_{attr} FLOAT, "
|
|
121
|
+
else:
|
|
122
|
+
sql_query = sql_query + f"_{attr} JSON, "
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
sql_query = sql_query + f"_{attr} VARCHAR(255), "
|
|
126
|
+
else:
|
|
127
|
+
sql_query = sql_query + f"_{attr} VARCHAR(255), "
|
|
128
|
+
sql_query = sql_query[:-2] # remove extra punctuation
|
|
129
|
+
sql_query = sql_query + ")"
|
|
130
|
+
# print(sql_query)
|
|
131
|
+
self.cursor.execute(sql_query) # create table
|
|
132
|
+
_log.info(f"Created table for class {class_name}")
|
|
133
|
+
except:
|
|
134
|
+
_log.warning(f"Unable to create table for class {class_name}")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def upload_from_file(self):
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def upload_from_url(self):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
def upload_from_rdflib(self, rdflib_graph):
|
|
149
|
+
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
def upload_from_cimgraph(self, network:GraphModel):
|
|
153
|
+
|
|
154
|
+
class_list = list(network.graph.keys())
|
|
155
|
+
for cim_class in class_list:
|
|
156
|
+
# Get JSON-LD representation of model
|
|
157
|
+
table = network.cim_dump(cim_class)
|
|
158
|
+
fields = cim_class.__dataclass_fields__
|
|
159
|
+
# Insert each power system object in CIMantic Graphs model
|
|
160
|
+
for obj in table.values():
|
|
161
|
+
# Create SQL Query
|
|
162
|
+
sql_insert = f"INSERT INTO {cim_class.__name__} ("
|
|
163
|
+
sql_values = " VALUES ("
|
|
164
|
+
sql_params = [self.username, int(time.time())]
|
|
165
|
+
|
|
166
|
+
sql_insert = sql_insert + "username, timestamp, "
|
|
167
|
+
sql_values = sql_values + f"%s, %s, "
|
|
168
|
+
# Iterate through each attribute
|
|
169
|
+
for attr in list(obj.keys()):
|
|
170
|
+
sql_insert = sql_insert + f"_{attr}, "
|
|
171
|
+
sql_values = sql_values + f"%s, "
|
|
172
|
+
|
|
173
|
+
if "List" in fields[attr].type:
|
|
174
|
+
sql_params.append(json.dumps(obj[attr])) # JSON-LD List of RDF links
|
|
175
|
+
elif "float" in fields[attr].type:
|
|
176
|
+
sql_params.append(obj[attr]) # float
|
|
177
|
+
else:
|
|
178
|
+
sql_params.append(str(obj[attr])) # free text
|
|
179
|
+
|
|
180
|
+
sql_insert = sql_insert[:-2] # remove extra punctuation
|
|
181
|
+
sql_insert = sql_insert + ")"
|
|
182
|
+
sql_values = sql_values[:-2] # remove extra punctuation
|
|
183
|
+
sql_values = sql_values + ")"
|
|
184
|
+
sql_query = sql_insert + sql_values # append query message
|
|
185
|
+
sql_params = tuple(sql_params)
|
|
186
|
+
self.cursor.execute(sql_query, sql_params) # insert all CIM objects
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import logging
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from neo4j import GraphDatabase
|
|
9
|
+
from neo4j.exceptions import DriverError, Neo4jError
|
|
10
|
+
|
|
11
|
+
from cimloader.databases import ConnectionInterface, ConnectionParameters, Parameter, QueryResponse
|
|
12
|
+
|
|
13
|
+
import rdflib
|
|
14
|
+
# from rdflib import Graph, Namespace
|
|
15
|
+
from rdflib.namespace import RDF
|
|
16
|
+
|
|
17
|
+
_log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
class Neo4jConnection(ConnectionInterface):
|
|
20
|
+
def __init__(self, connection_parameters):
|
|
21
|
+
self.connection_parameters = connection_parameters
|
|
22
|
+
self.cim_profile = connection_parameters.cim_profile
|
|
23
|
+
self.namespace = connection_parameters.namespace
|
|
24
|
+
self.url = connection_parameters.url
|
|
25
|
+
self.username = connection_parameters.username
|
|
26
|
+
self.password = connection_parameters.password
|
|
27
|
+
self.database = connection_parameters.database
|
|
28
|
+
self.container = connection_parameters.container
|
|
29
|
+
self.driver = None
|
|
30
|
+
|
|
31
|
+
def connect(self):
|
|
32
|
+
if not self.driver:
|
|
33
|
+
self.driver = GraphDatabase.driver(self.url, auth=(self.username, self.password))
|
|
34
|
+
self.driver.verify_connectivity()
|
|
35
|
+
|
|
36
|
+
def disconnect(self):
|
|
37
|
+
self.driver.close()
|
|
38
|
+
self.driver = None
|
|
39
|
+
|
|
40
|
+
def execute(self, query_message: str) -> QueryResponse:
|
|
41
|
+
self.connect()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
records, summary, keys = self.driver.execute_query(query_message, database_=self.database )
|
|
45
|
+
return records, summary, keys
|
|
46
|
+
# Capture any errors along with the query and data for traceability
|
|
47
|
+
except (DriverError, Neo4jError) as exception:
|
|
48
|
+
_log.error("%s raised an error: \n%s", query_message, exception)
|
|
49
|
+
|
|
50
|
+
def configure(self):
|
|
51
|
+
if self.cim_profile is not None and self.namespace is not None:
|
|
52
|
+
# self.execute("CALL n10s.nsprefixes.add(\""+self.cim_profile+"\",\""+self.namespace+"\");")
|
|
53
|
+
self.execute("CREATE CONSTRAINT n10s_unique_uri FOR (r:Resource) REQUIRE r.uri IS UNIQUE;")
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
_log.exception("CIM profile and namespace must be defined in ConnectionParameters")
|
|
57
|
+
|
|
58
|
+
graph_config = """call n10s.graphconfig.init({
|
|
59
|
+
handleMultival: "OVERWRITE",
|
|
60
|
+
handleVocabUris: "IGNORE",
|
|
61
|
+
keepCustomDataTypes: true,
|
|
62
|
+
handleRDFTypes: "LABELS"})"""
|
|
63
|
+
self.execute(graph_config)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def drop_all(self):
|
|
68
|
+
self.execute("MATCH (n) DETACH DELETE n")
|
|
69
|
+
self.execute("DROP CONSTRAINT n10s_unique_uri")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from cimloader.databases import ConnectionInterface, ConnectionParameters
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import zipfile
|
|
5
|
+
import os
|
|
6
|
+
import requests
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
_log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class NAERM(ConnectionInterface):
|
|
12
|
+
def __init__(self, connection_parameters:ConnectionParameters):
|
|
13
|
+
self.connection_parameters = connection_parameters
|
|
14
|
+
self.url = connection_parameters.url
|
|
15
|
+
|
|
16
|
+
def connect(self):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
def disconnect(self):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def download_case(self, case_uuid, destination):
|
|
23
|
+
casefile_url = self.url + "/uuid/" + case_uuid
|
|
24
|
+
case_info = requests.get(casefile_url).json()
|
|
25
|
+
file_url = case_info["file_url"]
|
|
26
|
+
file_path = f"{destination}/{case_uuid}"
|
|
27
|
+
|
|
28
|
+
r = requests.get(file_url)
|
|
29
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
30
|
+
z.extractall(file_path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def download_all_dss(self, destination, limit:int = 10000):
|
|
35
|
+
counter = 0
|
|
36
|
+
case_list = requests.get(self.url).json()
|
|
37
|
+
case_uuid_list = []
|
|
38
|
+
case_uuid_names = {}
|
|
39
|
+
for case in case_list:
|
|
40
|
+
if "simulator_type" in case["case"]:
|
|
41
|
+
if case["case"]["simulator_type"] == "opendss":
|
|
42
|
+
counter = counter+1
|
|
43
|
+
case_uuid_list.append(case["bes_case_uuid"])
|
|
44
|
+
case_uuid_names[case["bes_case_uuid"]] = case["case"]["feeder_name"]
|
|
45
|
+
|
|
46
|
+
if counter == limit:
|
|
47
|
+
break
|
|
48
|
+
|
|
49
|
+
counter = 0
|
|
50
|
+
for case_uuid in case_uuid_list:
|
|
51
|
+
counter = counter+1
|
|
52
|
+
casefile_url = self.url + "/uuid/" + case_uuid
|
|
53
|
+
case_info = requests.get(casefile_url).json()
|
|
54
|
+
file_url = case_info["file_url"]
|
|
55
|
+
file_path = f"{destination}/{case_uuid}"
|
|
56
|
+
|
|
57
|
+
r = requests.get(file_url)
|
|
58
|
+
z = zipfile.ZipFile(io.BytesIO(r.content))
|
|
59
|
+
z.extractall(file_path)
|
|
60
|
+
|
|
61
|
+
print(f"Successfully downloaded {counter} case files")
|
|
62
|
+
|
|
63
|
+
return case_uuid_list, case_uuid_names
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from cimloader.databases import ConnectionInterface, QueryResponse
|
|
5
|
+
from cimloader.databases.blazegraph import BlazegraphConnection
|
|
6
|
+
from SPARQLWrapper import JSON, POST, SPARQLWrapper
|
|
7
|
+
|
|
8
|
+
_log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class BlazegraphUploader(ConnectionInterface):
|
|
11
|
+
def __init__(self, connection_params: ConnectionInterface) -> None:
|
|
12
|
+
self.url = connection_params.url
|
|
13
|
+
self.connection_params = connection_params
|
|
14
|
+
self.connection = BlazegraphConnection(connection_params)
|
|
15
|
+
self.connection.connect()
|
|
16
|
+
|
|
17
|
+
def upload_from_file(self, filepath:str, filename:str) -> None:
|
|
18
|
+
subprocess.call(["curl", "-s", "-D-", "-H", "Content-Type: application/xml", "--upload-file", f"{filepath}/{filename}", "-X", "Post", self.url])
|
|
19
|
+
|
|
20
|
+
def upload_from_url(self):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def upload_from_rdflib(self, rdflib_graph):
|
|
24
|
+
|
|
25
|
+
pass
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from cimloader.databases import ConnectionInterface, QueryResponse
|
|
5
|
+
from cimloader.databases.neo4j import Neo4jConnection
|
|
6
|
+
from SPARQLWrapper import JSON, POST, SPARQLWrapper
|
|
7
|
+
|
|
8
|
+
_log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
class Neo4jUploader(ConnectionInterface):
|
|
11
|
+
def __init__(self, connection_params: ConnectionInterface) -> None:
|
|
12
|
+
self.url = connection_params.url
|
|
13
|
+
self.connection_params = connection_params
|
|
14
|
+
self.container = connection_params.container
|
|
15
|
+
self.connection = Neo4jConnection(connection_params)
|
|
16
|
+
self.connection.connect()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def upload_from_file(self, filename, filepath):
|
|
20
|
+
if '.xml' in filename or '.XML' in filename:
|
|
21
|
+
format = 'RDF/XML'
|
|
22
|
+
elif '.ttl' in filename:
|
|
23
|
+
#TODO
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
if self.container:
|
|
27
|
+
subprocess.call(["docker", "cp", f"{filepath}/{filename}", f"{self.container}:/var/lib/neo4j/import/{filename}"])
|
|
28
|
+
records=self.connection.execute(f"""call n10s.rdf.import.fetch( "file:///var/lib/neo4j/import//{filename}", "{format}"); """)
|
|
29
|
+
else:
|
|
30
|
+
records=self.connection.execute(f"""call n10s.rdf.import.fetch( "file://{filepath}/{filename}", "{format}"); """)
|
|
31
|
+
return records
|
|
32
|
+
|
|
33
|
+
def upload_from_url(self, url):
|
|
34
|
+
if '.xml' in url or '.XML' in url:
|
|
35
|
+
format = 'RDF/XML'
|
|
36
|
+
elif '.ttl' in url:
|
|
37
|
+
pass
|
|
38
|
+
records=self.connection.execute(f'''call n10s.rdf.import.fetch("{url}", "{format}"); ''')
|
|
39
|
+
return records
|
|
40
|
+
|
|
41
|
+
def upload_from_rdflib(self, rdflib_graph):
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
def upload_from_cimgraph(self):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def upload_from_rdflib(self, rdflib_graph):
|
|
49
|
+
|
|
50
|
+
pass
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "cim-loader"
|
|
3
|
+
version = "0.1.0b0"
|
|
4
|
+
description = "CIM Database Uploade & Downloader"
|
|
5
|
+
authors = ["A. Anderson <19935503+AAndersn@users.noreply.github.com>"]
|
|
6
|
+
packages = [
|
|
7
|
+
{ include = "cimloader" }
|
|
8
|
+
]
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
|
|
11
|
+
[tool.poetry.dependencies]
|
|
12
|
+
python = ">=3.8.1,<4.0"
|
|
13
|
+
SPARQLWrapper = "^2.0.0"
|
|
14
|
+
neo4j = "^5.10.0"
|
|
15
|
+
rdflib = "^7.0.0"
|
|
16
|
+
rdflib-neo4j = "^0.0.1b6"
|
|
17
|
+
#OpenDSSDirect.py = "^0.8.2"
|
|
18
|
+
requests = "^2.28.0"
|
|
19
|
+
mysql-connector-python = "^8.0.0"
|
|
20
|
+
|
|
21
|
+
cim-graph = "^0.1.5a0"
|
|
22
|
+
#git = "https://github.com/PNNL-CIM-Tools/CIM-Graph/tree/main", branch = "v_1_0_0a", optional = true }
|
|
23
|
+
|
|
24
|
+
[tool.poetry.extras]
|
|
25
|
+
gridappsd = ["gridappsd-python"]
|
|
26
|
+
|
|
27
|
+
[tool.poetry.group.dev.dependencies]
|
|
28
|
+
pre-commit = "^2.17.0"
|
|
29
|
+
xsdata = {extras = ["cli"], version = "^22.2"}
|
|
30
|
+
xsdata-plantuml = "^21.4"
|
|
31
|
+
graphviz = "^0.19.1"
|
|
32
|
+
ipykernel = "^6.25.1"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["poetry-core>=1.2.0"]
|
|
37
|
+
build-backend = "poetry.core.masonry.api"
|