Skip to content

Commit ef6f45c

Browse files
committed
Merge branch 'develop' into main
2 parents f7eef15 + db8df88 commit ef6f45c

20 files changed

+883
-328
lines changed

.DS_Store

-6 KB
Binary file not shown.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ dmypy.json
133133

134134
# PyCharm
135135
.idea/
136+
.DS_Store
137+
138+
# Database
139+
*.db
136140

137141
# todo
138142
todo.md

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ results in a database. Errors that occur during execution are also captured by t
88
## Installation and configuration
99
In order to install the package via pip use the command
1010
```
11-
pip install py_experimenter
11+
pip install py-experimenter
12+
```
13+
You can then import the PyExperimenter by using
14+
```
15+
from py_experimenter import PyExperimenter
1216
```
1317

1418
### Configuration file
@@ -23,12 +27,15 @@ In the following we will go through these sections and go into more detail about
2327
The database section contains the credentials for the database where the experiment parameters, results, potential errors and meta information of the experiments are written.
2428
```
2529
[DATABASE]
26-
host=<hostname>
27-
user=<username>
30+
provider=<sqlite or mysql>
31+
host=<hostname> (only required when using mysql as provider)
32+
user=<username> (only required when using mysql as provider)
33+
password=<password> (only required when using mysql as provider)
2834
database=<databasename>
29-
password=<password>
3035
table=<tablename>
3136
```
37+
You can use `sqlite` as well as `mysql` as provider for the database. As you can see above, for mysql you need to provide a host, user and password for the database,
38+
while sqlit only requires a database and table name.
3239
Note that the table is automatically generated by PyExperimenter.
3340
In the configuration file you should therefore only enter the desired name.
3441

@@ -67,8 +74,6 @@ custom.setting1=<value>
6774
custom.setting2=<value>
6875
```
6976

70-
An example of a configuration file can be found in `config/example_config.cfg`.
71-
7277
## How should the function to be executed look like?
7378
To use the PyExperimenter for your experiments, you need to create a function (again, we call this function `own_function`) with 3 parameters.
7479
This function will be called by the PyExperimenter to process each instance of the experiment.
@@ -97,7 +102,7 @@ file to the experiment. Note that this parameter is needed even if you have not
97102
## How to run the PyExperimenter
98103
To run the PyExperimenter you need to initialize a `PyExperimenter` object:
99104
```python
100-
import PyExperimenter
105+
from py_experimenter import PyExperimenter
101106

102107
# initialize the PyExperiementer
103108
experimenter = PyExperimenter()
@@ -186,3 +191,8 @@ so the machine id is identical for each experiment. The status values for the `s
186191

187192
This is followed by the `creation_date`,`start_date` and `end_date` columns, which contain the creation, start and end times.
188193
The last column contains the error message of the exercise. If no error occurred, this column contains the entry `NULL`.
194+
195+
196+
###Examples
197+
In the `examples` folder you can find examples for most of the functionality explained here. Note that `example1.py`, `example2.py` and `example4.py` are sqlite examples,
198+
while `example3.py` is a mysql example, where you need to add your database credentials in the config file.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from base_folder.py_experimenter.py_experimenter import PyExperimenter
1+
from py_experimenter.experimenter import PyExperimenter
Lines changed: 8 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,16 @@
1-
import logging
2-
import sys
31
from typing import List
42

5-
import mysql
6-
import numpy as np
7-
from mysql.connector import connect, ProgrammingError
8-
from datetime import datetime
9-
import pandas as pd
10-
import base_folder.py_experimenter.utils as utils
11-
123

134
class DatabaseConnector:
145

15-
def __init__(self, config):
16-
self.config = config
17-
self.table_name, host, user, database, password = utils.extract_db_credentials_and_table_name_from_config(
18-
config)
19-
self._db_credentials = dict(host=host, user=user, database=database, password=password)
20-
21-
# try connection to database and exit program if connection not possible
22-
try:
23-
connection = connect(**self._db_credentials)
24-
25-
except mysql.connector.Error as err:
26-
logging.error(err)
27-
sys.exit(1)
28-
29-
else:
30-
connection.close()
31-
326
def create_table_if_not_exists(self) -> None:
337
"""
348
Check if tables does exist. If not, a new table will be created.
359
:param mysql_connection: mysql_connector to the database
3610
:param table_name: name of the table from the config
3711
:param experiment_config: experiment section of the config file
3812
"""
39-
experiment_config = self.config['PY_EXPERIMENTER']
40-
41-
try:
42-
connection = connect(**self._db_credentials)
43-
cursor = connection.cursor()
44-
45-
cursor.execute(f"SHOW TABLES LIKE '{self.table_name}'")
46-
47-
# load column names from config
48-
fields = experiment_config['keyfields'].split(',') + experiment_config['resultfields'].split(',')
49-
50-
# remove whitespace
51-
clean_fields = [field.replace(' ', '') for field in fields]
52-
53-
# (name, type) - default type is 'VARCHAR(255)'
54-
typed_fields = [tuple(field.split(':')) if len(field.split(':')) == 2 else (field, 'VARCHAR(255)') for
55-
field in clean_fields]
56-
57-
# exit if table already exist
58-
if cursor.fetchall():
59-
cursor.execute(f"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{self.table_name}'")
60-
columns = [k[0] for k in cursor.fetchall()][:-6]
61-
config_columns = [k[0] for k in typed_fields]
62-
63-
# check if fields in config match with columns in database
64-
if set(columns) == set(config_columns):
65-
return
66-
67-
logging.error("Keyfields or resultfields from the configuration do not match columns in the existing "
68-
"table. Please change your configuration or delete the table in your database.")
69-
sys.exit()
70-
71-
# extend experiment columns by pyexperimenter columns
72-
typed_fields.extend(
73-
[('status', 'VARCHAR(255)'), ('machine', 'VARCHAR(255)'), ('creation_date', 'VARCHAR(255)'),
74-
('start_date', 'VARCHAR(255)'),
75-
('end_date', 'VARCHAR(255)'), ('error', 'LONGTEXT')])
76-
77-
# set default value for each column to NULL
78-
columns = ['%s %s DEFAULT NULL' % (field, datatype) for field, datatype in typed_fields]
79-
80-
stmt = f"CREATE TABLE {self.table_name} ({','.join(columns)})"
81-
82-
try:
83-
cursor.execute(stmt)
84-
except ProgrammingError:
85-
logging.error("An error occurred while creating the table in the database. Please check the "
86-
"configuration file for allowed characters and data types for the keyfields and resultfields "
87-
"as well as the table name.")
88-
sys.exit(1)
89-
90-
except mysql.connector.Error as err:
91-
logging.error(err)
92-
sys.exit(1)
93-
94-
else:
95-
connection.close()
13+
pass
9614

9715
def fill_table(self, individual_parameters=None, parameters=None) -> None:
9816
"""
@@ -102,133 +20,22 @@ def fill_table(self, individual_parameters=None, parameters=None) -> None:
10220
:param config: config file
10321
10422
"""
105-
106-
# ref: https://www.kite.com/python/answers/how-to-get-all-element-combinations-of-two-numpy-arrays-in-python
107-
keyfield_names = utils.get_keyfields(self.config)
108-
109-
if individual_parameters is None:
110-
if parameters is None:
111-
keyfield_data = utils.get_keyfield_data(self.config)
112-
else:
113-
keyfield_data = list(parameters.values())
114-
115-
combinations = np.array(np.meshgrid(*keyfield_data)).T.reshape(-1, len(keyfield_data))
116-
combinations = [dict(zip(keyfield_names, combination)) for combination in combinations]
117-
else:
118-
combinations = individual_parameters
119-
120-
# check if combinations exist
121-
if len(combinations) == 0:
122-
return
123-
124-
if individual_parameters is not None:
125-
_number_of_keys = 0
126-
for key in combinations[0].keys():
127-
128-
if key not in keyfield_names:
129-
logging.error(f"Keyfield '{key}' is not defined in configuration file")
130-
sys.exit()
131-
132-
_number_of_keys += 1
133-
134-
if _number_of_keys != len(keyfield_names):
135-
logging.error(f"{len(keyfield_names) - _number_of_keys} keyfield(s) missing! Please check passed parameters contain all keyfields defined in the configuration file.")
136-
sys.exit()
137-
138-
139-
140-
columns_names = np.array2string(np.array(keyfield_names), separator=',') \
141-
.replace('[', '') \
142-
.replace(']', '') \
143-
.replace("'", "")
144-
145-
try:
146-
connection = connect(**self._db_credentials)
147-
cursor = connection.cursor()
148-
cursor.execute(f"SELECT {columns_names} FROM {self.table_name}")
149-
existing_rows = list(map(np.array2string, np.array(cursor.fetchall())))
150-
151-
except mysql.connector.Error as err:
152-
logging.error(err)
153-
sys.exit(1)
154-
155-
else:
156-
connection.close()
157-
158-
columns_names += ",status"
159-
columns_names += ",creation_date"
160-
161-
time = datetime.now()
162-
for combination in combinations:
163-
if ("['" + "' '".join([str(value) for value in combination.values()]) + "']") in existing_rows:
164-
continue
165-
values = list(combination.values())
166-
values.append("created")
167-
values.append("%s" % time.strftime("%m/%d/%Y, %H:%M:%S"))
168-
169-
self._write_to_database(columns_names.split(', '), values)
23+
pass
17024

17125
def get_parameters_to_execute(self) -> List[dict]:
172-
experiment_config = self.config['PY_EXPERIMENTER']
173-
174-
execute_condition = "status='created'"
175-
176-
keyfields = experiment_config['keyfields'].split(',')
177-
keyfield_names = utils.get_field_names(keyfields)
178-
179-
stmt = f"SELECT {', '.join(keyfield_names)} FROM {self.table_name} WHERE {execute_condition}"
180-
181-
try:
182-
connection = connect(**self._db_credentials)
183-
cursor = connection.cursor()
184-
185-
try:
186-
cursor.execute(stmt)
187-
except ProgrammingError as e:
188-
logging.error(str(e) + "\nPlease check if 'fill_table()' was called correctly.")
189-
sys.exit(1)
190-
191-
parameters = pd.DataFrame(cursor.fetchall())
192-
if parameters.empty:
193-
return []
194-
parameters.columns = [i[0] for i in cursor.description]
195-
196-
except mysql.connector.Error as err:
197-
logging.error(err)
198-
sys.exit(1)
199-
200-
else:
201-
connection.close()
202-
203-
named_parameters = [dict(parameter.to_dict()) for _, parameter in parameters.iterrows()]
204-
205-
return named_parameters
26+
pass
20627

20728
def _write_to_database(self, keys, values) -> None:
20829
"""
20930
Write new row(s) to database
21031
:param keys: Column names
21132
:param values: Values for column names
21233
"""
213-
if len(keys[0].split(',')) != len(values):
214-
logging.error('Keyfield(s) missing! Please check passed parameters contain all keyfields defined in the configuration file.')
215-
sys.exit(1)
216-
217-
keys = ", ".join(keys)
218-
values = "'" + "', '".join([str(value) for value in values]) + "'"
219-
220-
stmt = f"INSERT INTO {self.table_name} ({keys}) VALUES ({values})"
221-
222-
try:
223-
connection = connect(**self._db_credentials)
224-
cursor = connection.cursor()
34+
pass
22535

226-
cursor.execute(stmt)
227-
connection.commit()
22836

229-
except mysql.connector.Error as err:
230-
logging.error(err)
231-
sys.exit(1)
37+
def _update_database(self, keys, values, where):
38+
pass
23239

233-
else:
234-
connection.close()
40+
def get_not_executed_yet(self, where) -> bool:
41+
pass

0 commit comments

Comments
 (0)