Skip to content

Commit 7f173ea

Browse files
authored
Merge pull request #50 from OpenEnergyPlatform/release-v0.4.0
Release v0.4.0
2 parents 0a8253e + f18778a commit 7f173ea

14 files changed

+190
-94
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,23 @@ Template:
4242

4343
______________________________________________________________________
4444

45+
## [v0.4.0] - 2024-04-16
46+
47+
### Added
48+
49+
- Metadata upload in case of single table in OEM [#21](https://github.com/OpenEnergyPlatform/oem2orm/pull/21)
50+
51+
### Changed
52+
53+
- Reworked repo file structure & extend documentation [#49](https://github.com/OpenEnergyPlatform/oem2orm/pull/49)
54+
55+
### Fixed
56+
57+
- oedialect version to v0.1.1 [#47](https://github.com/OpenEnergyPlatform/oem2orm/pull/47)
58+
- sqlalchemy version to v1.3.16 [#47](https://github.com/OpenEnergyPlatform/oem2orm/pull/47)
59+
60+
______________________________________________________________________
61+
4562
## [v0.3.3] - 2024-04-15
4663

4764
### Added

README.md

Lines changed: 53 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# OEM to ORM
1+
# oem 2 orm
22

3-
Create database tables (and schema) from oemetadata json file(s)
3+
Create database tables (and schema) from oemetadata json file(s). This tool is part of the open-energy-metadata (OEM) integration into the [OEP](https://openenergyplatform.org/).
44

5-
## Installation:
5+
## Installation
66

77
You can install pacakge using standard python installation:
88
`
@@ -15,94 +15,78 @@ pipx install oem2orm
1515
`
1616
see [Pipx-Documentation](https://pypa.github.io/pipx/) for further information.
1717

18+
## Usage
1819

19-
## Usage:
20+
Read the Restrictions section and have a look at our [tutorial](./tutorial/USAGE.md) section to get more information about the usage of oem2orm either as code module or CLI tool. The tutorials also provide information how to validate your oemetadata files.
2021

21-
This tool is part of the open-energy-metadata (OEM) integration into the [OEP](https://openenergyplatform.org/).
22-
To use this tool with the OEP API you need to be signed up to the OEP since
23-
you need to provide an API-Token.
22+
### Restrictions
2423

25-
If you want to upload OEM that was officially reviewed you must clone the
26-
OEP data-preprocessing repository on [GitHub](https://github.com/OpenEnergyPlatform/data-preprocessing).
27-
The data-review folder contains all of the successfully reviewed OEM files.
24+
To use this tool with the OEP API you need to be signed up to the OEP since
25+
you need to provide an API-Token.
2826

29-
For security reasons, tables can only be created in existing
27+
For security reasons, tables can only be created in existing
3028
schemas and just in the schemas "model_draft" and "sandbox".
3129

32-
Keep in mind the current state is not fully tested. The code is
33-
still quit error prone f.e. the postgres types (column datatype) are not fully
30+
Keep in mind that f.e. the postgres types (column datatype) are not fully
3431
supported by the [oedialct](https://pypi.org/project/oedialect/) - work in progress.
3532

36-
### Terminal/CLI-Application
37-
Step-by-Step:
38-
0. pip and python have to be installed and setup on your machine
39-
1. Create env from requirements.txt, and activate
40-
2. Put the metadata file in the folder metadata or put your own folder in this
41-
directory
42-
3. execute the following in a terminal:
43-
```
44-
pipx install oem2orm
45-
oem2orm
46-
Enter metadata folder name:
47-
...
48-
```
49-
4. Provide credentials and folder name in prompt
50-
5. The table will be created
33+
## Docs
5134

52-
### Import as Module
35+
### Database connection
5336

54-
You can simply import this module in your Python script.py like this:
37+
We use a global namedtuple called "DB" To store the sqlalchemy connection objects engine and metadata.
38+
The namedtuple is available wen import oem2orm in a script. To establish the namedtuple use the function
39+
setup_db_connection(). Now you can use DB.engine or DB.metadata. In the background the connection is established
40+
using oedialect and the http API of the oeplatform website.
5541

56-
```python
57-
from oem2orm import oep_oedialect_oem2orm as oem2orm
58-
```
42+
### oem2orm generator
5943

60-
Now just call the functions provided in oem2orm like this:
44+
The table objects (ORM) are generated on the fly from an oemetadata.json file. [oemetadata](https://github.com/OpenEnergyPlatform/oemetadata) is a metadata specification of the Open Energy Family. It includes about 50 fields that can be used to provide metadata for tabular data resources.
45+
A subset of these fields are grouped in the key "resources" ([see out example](https://github.com/OpenEnergyPlatform/oemetadata/blob/develop/metadata/v160/example.json#L237-L388)) in the metadata. These fields describe the schema of
46+
the data table (like table name, columns, data types & table relations).
6147

62-
Recommended execution order:
63-
- Setup the logger
64-
```python
65-
oem2orm.setup_logger()
66-
```
48+
The method oem2orm provides to create data tables on the OEP. It is especially useful if you attempt to automate the table creation and already use python or already have a oemetadata file available. The alternatives are:
6749

68-
- Setup the Database API connection as Namedtuple storing the SQLAlchemy engine and metadata:
69-
```python
70-
db = oem2orm.setup_db_connection()
71-
```
50+
1. [manually describing](https://openenergyplatform.github.io/academy/tutorials/01_api/02_api_upload/#create-table) the table object in JSON and then use the oep HTTP API directly to create a table.
51+
2. Use the [User Interface of the oeplatform website](https://openenergyplatform.org/dataedit/wizard/) to create a table and upload data.
7252

73-
- Provide the oem files in a folder (in the current directory).
74-
- Pass the folder name to the function:
75-
```python
76-
metadata_folder = oem2orm.select_oem_dir(oem_folder_name="folder_name")
77-
```
53+
### Oemetadata format
7854

79-
- Setup a SQLAlchemy ORM including all data-model in the provided oem files:
80-
```python
81-
orm = oem2orm.collect_ordered_tables_from_oem(db, metadata_folder)
82-
```
55+
[Specification for the oemetadata](https://github.com/OpenEnergyPlatform/oemetadata)
8356

84-
- Create the tables on the Database:
85-
```python
86-
oem2orm.create_tables(db, orm)
87-
```
57+
#### Oemetadata validation
8858

89-
- Delete all tables that have been created (all tables available in sa.metadata)
90-
```python
91-
oem2orm.delete_tables(db, orm)
92-
```
59+
The oemetadata specification is integrated into the open energy platform using a tool called [omi (metadata integration)](https://github.com/OpenEnergyPlatform/omi). OMI provides functionality to run validation checks on the metadata up to the oemetadata version 1.6.0. oem2orm also provides a minimal oep compliance check that mocks the checks that are run on the oep website once the metadata is uploaded to a table.
9360

94-
## Docs:
61+
#### Supported column data types
9562

96-
### Database connection
97-
We use a global namedtuple called "DB" To store the sqlalchemy connection objects engine and metadata.
98-
The namedtuple is available wen import oem2orm in a script. To establish the namedtuple use the function
99-
setup_db_connection(). Now you can use DB.engine or DB.metadata.
100-
101-
### oem2orm generator
63+
Currently oem2orm supports
10264

103-
#### Supported datatypes
65+
"bigint"
66+
"int":
67+
"integer"
68+
"varchar"
69+
"json"
70+
"text"
71+
"timestamp"
72+
"interval"
73+
"string"
74+
"float"
75+
"boolean"
76+
"date"
77+
"hstore"
78+
"decimal"
79+
"numeric"
80+
"double precision"
10481

10582
#### Spatial Types
106-
We create columns with spatial datatypes using Geoalchemy2.
83+
84+
"geometry point": Geometry("POINT", spatial_index=False),
85+
"geom": Geometry("GEOMETRY", spatial_index=False),
86+
"geometry": Geometry("GEOMETRY", spatial_index=False),
87+
88+
We create columns with spatial datatypes using Geoalchemy2.
10789

10890
## Database support
91+
92+
We only tested this tool with PostgreSQL & sqlalchemy version 1.3

oem2orm/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import click
32
import pathlib
43

@@ -11,22 +10,26 @@ def cli():
1110

1211

1312
@cli.command()
14-
@click.argument('metadata-folder', type=click.Path(exists=True))
13+
@click.argument("metadata-folder", type=click.Path(exists=True))
1514
def create_tables(metadata_folder):
1615
db = oep_oedialect_oem2orm.setup_db_connection()
1716
folder = pathlib.Path.cwd() / metadata_folder
1817
tables = oep_oedialect_oem2orm.collect_tables_from_oem(db, folder)
1918
oep_oedialect_oem2orm.create_tables(db, tables)
19+
if len(tables) == 1:
20+
# Upload metadata for single table
21+
metadata = oep_oedialect_oem2orm.mdToDict(metadata_folder)
22+
oep_oedialect_oem2orm.api_updateMdOnTable(metadata)
2023

2124

2225
@cli.command()
23-
@click.argument('metadata-folder', type=click.Path(exists=True))
26+
@click.argument("metadata-folder", type=click.Path(exists=True))
2427
def delete_tables(metadata_folder):
2528
db = oep_oedialect_oem2orm.setup_db_connection()
2629
folder = pathlib.Path.cwd() / metadata_folder
2730
tables = oep_oedialect_oem2orm.collect_tables_from_oem(db, folder)
2831
oep_oedialect_oem2orm.delete_tables(db, tables)
2932

3033

31-
if __name__ == '__main__':
34+
if __name__ == "__main__":
3235
cli()

oem2orm/oep_compliance.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
88
"""
9+
910
import logging
1011
import pathlib
1112
import json
@@ -24,6 +25,7 @@
2425

2526
logger = logging.getLogger()
2627

28+
2729
def read_input_json(file_path: pathlib.Path = "tests/data/metadata_v15.json"):
2830
with open(file_path, "r", encoding="utf-8") as f:
2931
jsn = json.load(f)
@@ -123,7 +125,9 @@ def check_oemetadata_is_oep_compatible(metadata):
123125
# -------------------------------------------
124126

125127

126-
def run_metadata_checks(oemetadata: dict = None, oemetadata_path: str = None, check_jsonschema: bool = False):
128+
def run_metadata_checks(
129+
oemetadata: dict = None, oemetadata_path: str = None, check_jsonschema: bool = False
130+
):
127131
"""
128132
Runs metadata checks includes:
129133
- basic oep compliant check - tested by using omi's parsing and compiling
@@ -165,10 +169,12 @@ def run_metadata_checks(oemetadata: dict = None, oemetadata_path: str = None, c
165169
schema = parser_validation.get_schema_by_metadata_version(metadata=metadata)
166170
result = parser_validation.is_valid(inp=metadata, schema=schema)
167171
if result is False:
168-
result = result, parser_validation.validate(metadata=metadata, save_report=True)
169-
172+
result = result, parser_validation.validate(
173+
metadata=metadata, save_report=True
174+
)
175+
170176
return result
171-
177+
172178

173179
if __name__ == "__main__":
174180
correct_v15_test_data = "tests/data/metadata_v15.json"
@@ -177,7 +183,9 @@ def run_metadata_checks(oemetadata: dict = None, oemetadata_path: str = None, c
177183

178184
meta = read_input_json(file_path=correct_v15_test_data)
179185
print("Check v15 metadata from file!")
180-
result = run_metadata_checks(oemetadata_path=correct_v15_test_data, check_jsonschema=True)
186+
result = run_metadata_checks(
187+
oemetadata_path=correct_v15_test_data, check_jsonschema=True
188+
)
181189
print("Check v15 metadata from object!")
182190
run_metadata_checks(oemetadata=meta)
183191

oem2orm/oep_oedialect_oem2orm.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ def create_tables(db: DB, tables: List[sa.Table]):
109109
for table in tables:
110110
logging.info(f"Working on table: {table}")
111111
if not db.engine.dialect.has_schema(db.engine, table.schema):
112-
error_msg = f'The provided database schema: "{table.schema}" does not exist. Please use an existing ' \
113-
f'schema from the `name` column from: {OEP_URL}/dataedit/schemas'
112+
error_msg = (
113+
f'The provided database schema: "{table.schema}" does not exist. Please use an existing '
114+
f"schema from the `name` column from: {OEP_URL}/dataedit/schemas"
115+
)
114116
logging.info(error_msg)
115117
raise DatabaseError(error_msg)
116118
else:
@@ -119,7 +121,9 @@ def create_tables(db: DB, tables: List[sa.Table]):
119121
table.create(checkfirst=True)
120122
logging.info(f"Created table {table.name}")
121123
except oedialect.engine.ConnectionException as ce:
122-
error_msg = f'Error when uploading table "{table.name}". Reason: {ce}.'
124+
error_msg = (
125+
f'Error when uploading table "{table.name}". Reason: {ce}.'
126+
)
123127
logging.error(error_msg)
124128
raise DatabaseError(error_msg) from ce
125129
except sa.exc.ProgrammingError as pe:
@@ -225,7 +229,8 @@ def create_tables_from_metadata_file(
225229
column = sa.Column(
226230
field["name"],
227231
column_type,
228-
primary_key=field["name"] in primary_keys,
232+
primary_key=field["name"]
233+
in primary_keys, # TODO: Should be fixed, see https://github.com/OpenEnergyPlatform/oedialect/issues/43
229234
comment=field["description"],
230235
)
231236
columns.append(column)

oem2orm/postgresql_types.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__copyright__ = "Reiner Lemoine Institut"
2-
__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)"
3-
__url__ = "https://github.com/openego/data_processing/blob/master/LICENSE"
4-
__author__ = "henhuy"
2+
__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)"
3+
__url__ = "https://github.com/openego/data_processing/blob/master/LICENSE"
4+
__author__ = "henhuy"
55

66
import sqlalchemy as sa
77
import sqlalchemy.dialects.postgresql as psql
@@ -27,16 +27,13 @@ class DatabaseTypes:
2727
"hstore": HSTORE,
2828
"decimal": sa.DECIMAL,
2929
"numeric": sa.NUMERIC,
30-
3130
# Spatial types
32-
"geometry point": Geometry("POINT", spatial_index=False),
33-
"geom": Geometry("GEOMETRY", spatial_index=False),
34-
"geometry": Geometry("GEOMETRY", spatial_index=False),
35-
31+
"geometry point": Geometry("POINT", spatial_index=False),
32+
"geom": Geometry("GEOMETRY", spatial_index=False),
33+
"geometry": Geometry("GEOMETRY", spatial_index=False),
3634
# not support with oedialect
37-
"double precision": psql.DOUBLE_PRECISION
35+
"double precision": psql.DOUBLE_PRECISION,
3836
# "double precision array": sa.ARRAY("DOUBLE_PRECISION"),
39-
4037
}
4138

4239
def __getitem__(self, item):

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setuptools.setup(
1010
name="oem2orm",
11-
version="0.3.3",
11+
version="0.4.0",
1212
author="henhuy, jh-RLI",
1313
author_email="Hendrik.Huyskens@rl-institut.de",
1414
description="SQLAlchemy module to generate ORM, read from data model (oedatamodel) in open-energy-metadata JSON format",
@@ -28,7 +28,7 @@
2828
"Operating System :: OS Independent",
2929
],
3030
python_requires='>=3.6',
31-
install_requires=['sqlalchemy==1.3.14', 'oedialect==1.1', 'requests', 'jmespath', 'omi', 'click'], # Optional
31+
install_requires=['sqlalchemy==1.3.16', 'oedialect==0.1.1', 'requests', 'jmespath', 'omi', 'click'], # Optional
3232
project_urls={ # Optional
3333
'Bug Reports': 'https://github.com/OpenEnergyPlatform/oem2orm/issues',
3434
'Source': 'https://github.com/OpenEnergyPlatform/oem2orm/tree/develop/oem2orm',

0 commit comments

Comments
 (0)