Skip to content

Commit f3397e8

Browse files
authored
Merge pull request #25 from OpenEnergyPlatform/release/v0.3.0
Release/v0.3.0
2 parents 655bf32 + 1c4aec7 commit f3397e8

13 files changed

+1168
-96
lines changed

.github/workflows/pypi-publish.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Build 📦 and release on pypi
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
build-n-publish:
9+
name: Build and publish Python 🐍 distributions 📦 to PyPI
10+
runs-on: ubuntu-latest
11+
environment: pypi-publish
12+
steps:
13+
- uses: actions/checkout@master
14+
- name: Set up Python 3.10
15+
uses: actions/setup-python@v3
16+
with:
17+
python-version: "3.10"
18+
19+
- name: Install pypa/build
20+
run: >-
21+
python -m
22+
pip install
23+
build
24+
--user
25+
- name: Build a binary wheel and a source tarball
26+
run: >-
27+
python -m
28+
build
29+
--sdist
30+
--wheel
31+
--outdir dist/
32+
- name: Publish distribution 📦 to PyPI
33+
if: startsWith(github.ref, 'refs/tags')
34+
uses: pypa/gh-action-pypi-publish@release/v1
35+
with:
36+
password: ${{ secrets.PYPI }}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Build and release on pypi tests
2+
3+
on:
4+
push:
5+
branches:
6+
- test-release
7+
- 'release/*'
8+
9+
jobs:
10+
build-n-publish:
11+
name: Build and publish Python 🐍 distributions 📦 to TestPyPI
12+
runs-on: ubuntu-latest
13+
environment: pypi-publish
14+
steps:
15+
- uses: actions/checkout@master
16+
- name: Set up Python 3.10
17+
uses: actions/setup-python@v3
18+
with:
19+
python-version: "3.10"
20+
21+
- name: Install pypa/build
22+
run: >-
23+
python -m
24+
pip install
25+
build
26+
--user
27+
- name: Build a binary wheel and a source tarball
28+
run: >-
29+
python -m
30+
build
31+
--sdist
32+
--wheel
33+
--outdir dist/
34+
- name: Publish distribution 📦 to Test PyPI
35+
uses: pypa/gh-action-pypi-publish@release/v1
36+
with:
37+
password: ${{ secrets.PYPI_TEST }}
38+
repository_url: https://test.pypi.org/legacy/

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lib
1616
lib64
1717
venv*/
1818
pyvenv*/
19+
/env
1920

2021
# Installer logs
2122
pip-log.txt
@@ -39,6 +40,8 @@ htmlcov
3940
.idea
4041
*.iml
4142
*.komodoproject
43+
/.vscode
44+
/oem2orm/__pycache__
4245

4346
# Complexity
4447
output/*.html

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,24 @@ Template:
3131
### Added
3232

3333
### Changed
34-
------------
34+
---
3535

36+
## [0.3.0] - 2022-10-24
37+
### Added
38+
- Option to create tables from OEM JSON instead of file
39+
- New module to check if metadata is oep compliant. Can check (omi's 1 parse 2 compile) oemetadata v1.5 and v1.4 (PR#23)
40+
- Add PYPI release workflow to automate python package releases for pypi test and pypi official (PR#22)
41+
42+
### Changed
43+
- omi_validateMD was outdated and now runs the new oep compliance checks.
44+
45+
______________________________________________________________________
3646
## [0.2.7] - 2021-03-11
3747

3848
### Added
3949
- enable console usage [PR#8]
4050
- new package requirement "omi"
51+
- support for sqlachemy "numeric" data type
4152
### Changed
4253
- fix missing dependency that made pip install fail [ISSUE#1;PR#8]
4354
### Removed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ Create database tables (and schema) from oemetadata json file(s)
44

55
## Installation:
66

7+
You can install pacakge using standard python installation:
78
`
89
pip install oem2orm
910
`
1011

12+
or if you interested in CLI-version only you can install it using pipx (pipx must be installed):
13+
`
14+
pipx install oem2orm
15+
`
16+
see [Pipx-Documentation](https://pypa.github.io/pipx/) for further information.
17+
1118

1219
## Usage:
1320

oem2orm/main.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
import click
3+
import pathlib
4+
5+
from oem2orm import oep_oedialect_oem2orm
6+
7+
8+
@click.group()
9+
def cli():
10+
pass
11+
12+
13+
@cli.command()
14+
@click.argument('metadata-folder', type=click.Path(exists=True))
15+
def create_tables(metadata_folder):
16+
db = oep_oedialect_oem2orm.setup_db_connection()
17+
folder = pathlib.Path.cwd() / metadata_folder
18+
tables = oep_oedialect_oem2orm.collect_tables_from_oem(db, folder)
19+
oep_oedialect_oem2orm.create_tables(db, tables)
20+
21+
22+
@cli.command()
23+
@click.argument('metadata-folder', type=click.Path(exists=True))
24+
def delete_tables(metadata_folder):
25+
db = oep_oedialect_oem2orm.setup_db_connection()
26+
folder = pathlib.Path.cwd() / metadata_folder
27+
tables = oep_oedialect_oem2orm.collect_tables_from_oem(db, folder)
28+
oep_oedialect_oem2orm.delete_tables(db, tables)
29+
30+
31+
if __name__ == '__main__':
32+
cli()

oem2orm/oep_compliance.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""
2+
Module for all functionality regarding the compliance of the integrated standards and
3+
requirments from other oefamily software modules like omi.
4+
5+
- check if the metadata will pass oep's metadata check that is performed before the saves the metadata.
6+
7+
8+
"""
9+
10+
from distutils.errors import CompileError
11+
import pathlib
12+
from omi.dialects.oep.parser import ParserException
13+
from omi.structure import Compilable
14+
15+
from omi.dialects.oep import OEP_V_1_4_Dialect, OEP_V_1_5_Dialect
16+
from omi.dialects.oep.compiler import JSONCompiler
17+
18+
import json
19+
20+
# instances of metadata parsers / compilers, order of priority
21+
METADATA_PARSERS = [OEP_V_1_5_Dialect(), OEP_V_1_4_Dialect()]
22+
METADATA_COMPILERS = [OEP_V_1_5_Dialect(), OEP_V_1_4_Dialect(), JSONCompiler()]
23+
24+
25+
def read_input_json(file_path: pathlib.Path = "tests/data/metadata_v15.json"):
26+
with open(file_path, "r", encoding="utf-8") as f:
27+
jsn = json.load(f)
28+
29+
return jsn
30+
31+
32+
def try_parse_metadata(inp):
33+
"""
34+
Args:
35+
inp: string or dict or OEPMetadata
36+
Returns:
37+
Tuple[OEPMetadata or None, string or None]:
38+
The first component is the result of the parsing procedure or `None` if
39+
the parsing failed. The second component is None, if the parsing failed,
40+
otherwise an error message.
41+
Examples:
42+
>>> from api.actions import try_parse_metadata
43+
>>> result, error = try_parse_metadata('{"id":"id"}')
44+
>>> error is None
45+
True
46+
"""
47+
48+
if isinstance(inp, Compilable):
49+
# already parsed
50+
return inp, None
51+
elif not isinstance(inp, (str, bytes)):
52+
# in order to use the omi parsers, input needs to be str (or bytes)
53+
try:
54+
inp = json.dumps(inp)
55+
except Exception:
56+
return None, "Could not serialize json"
57+
58+
last_err = None
59+
# try all the dialects
60+
for parser in METADATA_PARSERS:
61+
try:
62+
return parser.parse(inp), None
63+
except ParserException as e:
64+
return None, str(e)
65+
except Exception as e:
66+
last_err = e
67+
# APIError(f"Metadata could not be parsed: {last_err}")
68+
# try next dialect
69+
70+
raise Exception(f"Metadata could not be parsed: {last_err}")
71+
72+
73+
def try_compile_metadata(inp):
74+
"""
75+
Args:
76+
inp: OEPMetadata
77+
Returns:
78+
Tuple[str or None, str or None]:
79+
The first component is the result of the compiling procedure or `None` if
80+
the compiling failed. The second component is None if the compiling failed,
81+
otherwise an error message.
82+
"""
83+
last_err = None
84+
# try all the dialects
85+
for compiler in METADATA_COMPILERS:
86+
try:
87+
return compiler.compile(inp), None
88+
except Exception as e:
89+
last_err = e
90+
# APIError(f"Metadata could not be compiled: {last_err}")
91+
# try next dialect
92+
93+
raise Exception(f"Metadata could not be compiled: {last_err}")
94+
95+
96+
def check_oemetadata_is_oep_compatible(metadata):
97+
"""Check if metadata is oep compliant. Metadata can be oemetadata version 1.4 or 1.5.
98+
Args:
99+
metadata: OEPMetadata or metadata object (dict) or metadata str
100+
"""
101+
102+
# ---------------------------------------
103+
# metadata parsing
104+
# ---------------------------------------
105+
106+
# parse the metadata object (various types) into proper OEPMetadata instance
107+
metadata_oep, err = try_parse_metadata(metadata)
108+
if err:
109+
raise ValueError(err)
110+
# compile OEPMetadata instance back into native python object (dict)
111+
# TODO: we should try to convert to the latest standard in this step?
112+
metadata_obj, err = try_compile_metadata(metadata_oep)
113+
if err:
114+
raise ValueError(err)
115+
# dump the metadata dict into json string
116+
try:
117+
metadata_str = json.dumps(metadata_obj, ensure_ascii=False)
118+
except Exception:
119+
raise TypeError("Cannot serialize metadata")
120+
121+
122+
# ----------- USE Cecks and Validation ------
123+
# Make use of omis validation to mock the
124+
# oemetadata check that is performed on the
125+
# OEP for ech metadata upload
126+
# -------------------------------------------
127+
128+
129+
def run_metadata_checks(oemetadata: dict = None, oemetadata_path: str = None):
130+
"""
131+
Runs metadata checks includes:
132+
- basic oep compliant check - tested by using omi's parsing and compiling
133+
134+
Not included:
135+
- jsonschema valdiation
136+
137+
Args:
138+
oemetadata (dict, optional): OEPMetadata or metadata object (dict) or metadata str. Defaults to None.
139+
oemetadata_path (str, optional): Relative path to file as string. Defaults to None.
140+
141+
Raises:
142+
Exception: _description_
143+
Exception: _description_
144+
"""
145+
146+
if oemetadata and oemetadata_path:
147+
raise Exception(
148+
"Providing both parmaters at the same time is not permitted. Please provide a oemetadata dict or a oemetadata json file path"
149+
)
150+
151+
if oemetadata is None and oemetadata_path is None:
152+
raise Exception(
153+
"Please provide a parsed oemetadata dict or a path to the oemetadata json file"
154+
)
155+
156+
if oemetadata_path is not None and oemetadata is None:
157+
metadata = read_input_json(oemetadata_path)
158+
159+
if oemetadata is not None and oemetadata_path is None:
160+
metadata = oemetadata
161+
162+
check_oemetadata_is_oep_compatible(metadata=metadata)
163+
164+
165+
if __name__ == "__main__":
166+
correct_v15_test_data = "tests/data/metadata_v15.json"
167+
false_v15_test_data = "tests/data/bad_metadata_v15.json"
168+
v14_test_data = "tests/data/metadata_v14.json"
169+
170+
meta = read_input_json(file_path=correct_v15_test_data)
171+
print("Check v15 metadata from file!")
172+
run_metadata_checks(oemetadata_path=correct_v15_test_data)
173+
print("Check v15 metadata from object!")
174+
run_metadata_checks(oemetadata=meta)
175+
176+
print("Check v14 metadata!")
177+
meta = read_input_json(file_path=correct_v15_test_data)
178+
179+
# print("test expected error case: false usage")
180+
# run_metadata_checks(oemetadata=meta, oemetadata_path=correct_test_data)
181+
# print("test expected error case: bad data")
182+
# run_metadata_checks(oemetadata_path=false_v15_test_data)

0 commit comments

Comments
 (0)