Skip to content

Commit 87dbc18

Browse files
committed
test model dump and fix, add tests to pr workflow
1 parent 78dd1f8 commit 87dbc18

File tree

7 files changed

+255
-57
lines changed

7 files changed

+255
-57
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Pull Request Test Update Workflow
2+
on:
3+
pull_request:
4+
types:
5+
- opened
6+
- synchronize
7+
- reopened
8+
9+
jobs:
10+
test_run:
11+
name: Test that update contributions workflow runs
12+
# Only run this job if the issue has the 'new contribution' label
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout sources
16+
uses: actions/checkout@v4
17+
18+
- name: Setup Python
19+
uses: actions/setup-python@v5
20+
with:
21+
python-version: 3.x
22+
23+
- name: Install dependencies
24+
run: pip install -r requirements.txt
25+
26+
- name: run unit tests
27+
run: pytest
28+
29+
- name: fetch updates on contributions
30+
run: python -u scripts/fetch_updates.py
31+
32+
- name: write contribs.txt file
33+
run: python -u scripts/to_contribs_txt.py
34+
35+
- name: write source json files
36+
run: python -u scripts/to_sources_jsons.py

scripts/add_new_contribution_to_yaml.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,31 @@
88

99
from ruamel.yaml import YAML
1010

11+
12+
def split_categories(categories):
13+
categories = sorted(categories.replace('"', '').split(','))
14+
categories = [category.strip() for category in categories]
15+
return categories
16+
17+
18+
def postprocess_properties(properties_dict):
19+
if 'categories' in properties_dict and properties_dict['categories']:
20+
properties_dict['categories'] = split_categories(properties_dict['categories'])
21+
else:
22+
properties_dict['categories'] = None
23+
24+
# add download
25+
if 'download' not in properties_dict:
26+
properties_dict['download'] = properties_dict['source'][:properties_dict['source'].rfind('.')] + '.zip'
27+
28+
1129
if __name__ == "__main__":
1230
if len(argv) < 2:
1331
print("script takes json string as argument.\nStopping...")
1432
raise ValueError
1533

1634
props = json.loads(argv[1])
17-
# process category list
18-
if 'categories' in props and props['categories']:
19-
props['categories'] = sorted(props['categories'].replace('"', '').split(','))
20-
props['categories'] = [category.strip() for category in props['categories']]
21-
else:
22-
props['categories'] = None
23-
24-
# add download
25-
if 'download' not in props:
26-
props['download'] = props['source'][:props['source'].rfind('.')] + '.zip'
35+
postprocess_properties(props)
2736

2837
# open database
2938
database_file = pathlib.Path(__file__).parent.parent / 'contributions.yaml'

scripts/fetch_updates.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ def process_contribution(item):
7878
return index, contribution
7979

8080

81+
def process_all(contributions_list):
82+
total = len(contributions_list)
83+
completed = 0
84+
print(f"Starting processing of {total} contributions...")
85+
86+
with Pool(processes=256) as pool:
87+
for index, contribution in pool.imap_unordered(process_contribution, enumerate(contributions_list)):
88+
contributions_list[index] = contribution
89+
completed += 1
90+
print(f"Progress: {completed}/{total} ({(completed / total * 100):.1f}%)")
91+
92+
8193
if __name__ == "__main__":
8294
parser = argparse.ArgumentParser()
8395
parser.add_argument('--index')
@@ -97,16 +109,7 @@ def process_contribution(item):
97109
contributions_list = data['contributions']
98110

99111
if index == 'all':
100-
total = len(contributions_list)
101-
completed = 0
102-
print(f"Starting processing of {total} contributions...")
103-
104-
with Pool(processes=256) as pool:
105-
for index, contribution in pool.imap_unordered(process_contribution, enumerate(contributions_list)):
106-
contributions_list[index] = contribution
107-
completed += 1
108-
print(f"Progress: {completed}/{total} ({(completed/total*100):.1f}%)")
109-
112+
process_all(contributions_list)
110113
print("All processing complete")
111114
else:
112115
# update only contribution with id==index

scripts/parse_and_validate_properties_txt.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,19 @@ def validate_existing(properties_dict):
7575
# validation on existing contribution is weaker
7676
properties = PropertiesExisting.model_validate(properties_dict)
7777

78-
return properties.model_dump(exclude_unset=True)
78+
return properties.model_dump()
7979

8080
def validate_new(properties_dict):
8181
# new contribution has stronger validation
8282
properties = PropertiesBase.model_validate(properties_dict)
8383

84-
return properties.model_dump(exclude_unset=True)
84+
return properties.model_dump()
8585

8686
def validate_new_library(properties_dict):
8787
# new contribution has stronger validation
8888
properties = LibraryPropertiesNew.model_validate(properties_dict)
8989

90-
return properties.model_dump(exclude_unset=True)
90+
return properties.model_dump()
9191

9292
def set_output(output_object):
9393
with open(os.environ['GITHUB_OUTPUT'],'a') as f:

scripts/to_contribs_txt.py

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import pathlib
77
import shutil
88
from collections import defaultdict
9+
from typing import List
910

1011
from utils import get_valid_contributions
1112

@@ -45,6 +46,40 @@ def read_contribs_text(filepath):
4546
return contribs_list
4647

4748

49+
def preprocess_contributions() -> List:
50+
all_contributions = get_valid_contributions()
51+
52+
# sort contributions list by type
53+
def sort_key(d):
54+
return type_list.index(d['type'])
55+
all_contributions = sorted(all_contributions, key=sort_key)
56+
57+
return all_contributions
58+
59+
60+
def write_contribs(all_contributions, fh):
61+
for contribution in all_contributions:
62+
fh.write(contribution['type'] + '\n')
63+
for field in contribs_fields_list:
64+
if field in contribution:
65+
if field == 'id':
66+
fh.write(f'{field}={contribution[field]:03}\n')
67+
elif field == 'categories':
68+
if contribution['type'] == 'library':
69+
fh.write(f'{field}={",".join(contribution[field]) if contribution[field] else ""}\n')
70+
else:
71+
# categories are only relevant for libraries, except for examples with "Books" as category
72+
if contribution[field] and 'Books' in contribution[field]:
73+
fh.write(f'{field}={",".join(contribution[field]) if contribution[field] else ""}\n')
74+
else:
75+
fh.write(f'{field}=\n')
76+
elif field == 'compatibleModesList':
77+
fh.write(f'modes={contribution[field]}\n')
78+
else:
79+
fh.write(f'{field}={"" if contribution[field] is None else contribution[field]}\n')
80+
fh.write('\n')
81+
82+
4883
if __name__ == "__main__":
4984
pde_folder = pathlib.Path(__file__).parent.parent / 'pde/'
5085
# remove sources folder if it already exists
@@ -54,34 +89,10 @@ def read_contribs_text(filepath):
5489

5590
contribs_text_file = pde_folder / 'contribs.txt'
5691

57-
contributions_list = get_valid_contributions()
58-
59-
# sort contributions list by type
60-
def sort_key(d):
61-
return type_list.index(d['type'])
62-
contributions_list = sorted(contributions_list, key=sort_key)
92+
contributions_list = preprocess_contributions()
6393

6494
# write contribs.txt file
6595
with open(contribs_text_file, 'w+') as f:
66-
for contribution in contributions_list:
67-
f.write(contribution['type']+'\n')
68-
for field in contribs_fields_list:
69-
if field in contribution:
70-
if field == 'id':
71-
f.write(f'{field}={contribution[field]:03}\n')
72-
elif field == 'categories':
73-
if contribution['type'] == 'library':
74-
f.write(f'{field}={",".join(contribution[field]) if contribution[field] else ""}\n')
75-
else:
76-
# categories are only relevant for libraries, except for examples with "Books" as category
77-
if contribution[field] and 'Books' in contribution[field]:
78-
f.write(f'{field}={",".join(contribution[field]) if contribution[field] else ""}\n')
79-
else:
80-
f.write(f'{field}=\n')
81-
elif field == 'compatibleModesList':
82-
f.write(f'modes={contribution[field]}\n')
83-
else:
84-
f.write(f'{field}={"" if contribution[field] is None else contribution[field]}\n')
85-
f.write('\n')
96+
write_contribs(contributions_list, f)
8697

8798

scripts/to_sources_jsons.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ def to_sources_dict(contribution_dict):
6363
return sources_dict
6464

6565

66+
def write_json_for_each_contribution_in_list(all_contributions, folder_path):
67+
for contribution in all_contributions:
68+
if 'name' in contribution:
69+
# output zero padded string for id
70+
contribution['id'] = f"{contribution['id']:03}"
71+
filename = contribution['name'].replace(':', '').replace('/', '').replace(' ', '_') + '.json'
72+
this_filepath = folder_path / filename
73+
with open(this_filepath, 'w') as f:
74+
json.dump(to_sources_dict(contribution), f, indent=2)
75+
76+
6677
if __name__ == "__main__":
6778
sources_folder = pathlib.Path(__file__).parent.parent / 'sources/'
6879

@@ -74,11 +85,4 @@ def to_sources_dict(contribution_dict):
7485
sources_folder.mkdir(parents=True, exist_ok=True)
7586

7687
# create a json file in the sources folder for each contribution
77-
for contribution in contributions_list:
78-
if 'name' in contribution:
79-
# output zero padded string for id
80-
contribution['id'] = f"{contribution['id']:03}"
81-
filename = contribution['name'].replace(':','').replace('/','').replace(' ','_') + '.json'
82-
this_filepath = sources_folder / filename
83-
with open(this_filepath, 'w') as f:
84-
json.dump(to_sources_dict(contribution),f,indent=2)
88+
write_json_for_each_contribution_in_list(contributions_list, sources_folder)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import pytest
2+
from pydantic import ValidationError
3+
from scripts.parse_and_validate_properties_txt import validate_new, validate_existing, validate_new_library
4+
5+
6+
# Test Cases
7+
class TestValidateAndExport:
8+
9+
def test_validate_existing_complete_data(self, valid_properties_data):
10+
"""Test validate_existing with complete data"""
11+
props = validate_existing(valid_properties_data)
12+
13+
assert props['name'] == valid_properties_data['name']
14+
assert props['authors'] == valid_properties_data['authors']
15+
assert props['url'] == valid_properties_data['url']
16+
assert props['categories'] == valid_properties_data['categories']
17+
assert props['sentence'] == valid_properties_data['sentence']
18+
assert props['paragraph'] == valid_properties_data['paragraph']
19+
assert props['version'] == valid_properties_data['version']
20+
assert props['prettyVersion'] == valid_properties_data['prettyVersion']
21+
assert props['minRevision'] == int(valid_properties_data['minRevision'])
22+
assert props['maxRevision'] == int(valid_properties_data['maxRevision'])
23+
assert props['modes'] == valid_properties_data['modes']
24+
25+
26+
def test_validate_existing_minimal_required_data(self, minimal_properties_existing_data):
27+
"""Test validate_existing with minimal data"""
28+
props = validate_existing(minimal_properties_existing_data)
29+
30+
assert props['name'] == minimal_properties_existing_data['name']
31+
assert props['authors'] == minimal_properties_existing_data['authors']
32+
assert props['url'] == minimal_properties_existing_data['url']
33+
assert props['categories'] is None
34+
assert props['sentence'] == minimal_properties_existing_data['sentence']
35+
assert props['paragraph'] is None
36+
assert props['version'] == minimal_properties_existing_data['version']
37+
assert props['prettyVersion'] is None
38+
assert props['minRevision'] == 0 # Default value
39+
assert props['maxRevision'] == 0 # Default value
40+
assert props['modes'] is None
41+
42+
43+
def test_validate_existing_extra_fields_allowed(self, properties_with_extra_fields):
44+
"""Test validate_existing with extra fields"""
45+
props = validate_existing(properties_with_extra_fields)
46+
47+
assert props['name'] == properties_with_extra_fields['name']
48+
assert props['customField'] == properties_with_extra_fields['customField']
49+
assert props['anotherExtra'] == properties_with_extra_fields['anotherExtra']
50+
51+
52+
def test_validate_new_complete_data(self, valid_properties_data):
53+
"""Test validate_new with complete data"""
54+
props = validate_new(valid_properties_data)
55+
56+
assert props['name'] == valid_properties_data['name']
57+
assert props['authors'] == valid_properties_data['authors']
58+
assert props['url'] == valid_properties_data['url']
59+
assert props['categories'] == valid_properties_data['categories']
60+
assert props['sentence'] == valid_properties_data['sentence']
61+
assert props['paragraph'] == valid_properties_data['paragraph']
62+
assert props['version'] == int(valid_properties_data['version'])
63+
assert props['prettyVersion'] == valid_properties_data['prettyVersion']
64+
assert props['minRevision'] == int(valid_properties_data['minRevision'])
65+
assert props['maxRevision'] == int(valid_properties_data['maxRevision'])
66+
assert props['modes'] == valid_properties_data['modes']
67+
68+
69+
def test_validate_new_minimal_required_data(self, minimal_properties_base_data):
70+
"""Test validate_new with minimal data"""
71+
props = validate_new(minimal_properties_base_data)
72+
73+
assert props['name'] == minimal_properties_base_data['name']
74+
assert props['authors'] == minimal_properties_base_data['authors']
75+
assert props['url'] == minimal_properties_base_data['url']
76+
assert props['categories'] is None
77+
assert props['sentence'] == minimal_properties_base_data['sentence']
78+
assert props['paragraph'] is None
79+
assert props['version'] == int(minimal_properties_base_data['version'])
80+
assert props['prettyVersion'] == minimal_properties_base_data['prettyVersion']
81+
assert props['minRevision'] == 0 # Default value
82+
assert props['maxRevision'] == 0 # Default value
83+
assert props['modes'] is None
84+
85+
86+
def test_validate_new_extra_fields_allowed(self, properties_with_extra_fields):
87+
"""Test validate_new with extra fields"""
88+
props = validate_new(properties_with_extra_fields)
89+
90+
assert props['name'] == properties_with_extra_fields['name']
91+
assert props['customField'] == properties_with_extra_fields['customField']
92+
assert props['anotherExtra'] == properties_with_extra_fields['anotherExtra']
93+
94+
95+
def test_validate_new_library_complete_data(self, valid_properties_data):
96+
"""Test validate_new_library with complete data"""
97+
props = validate_new_library(valid_properties_data)
98+
99+
assert props['name'] == valid_properties_data['name']
100+
assert props['authors'] == valid_properties_data['authors']
101+
assert props['url'] == valid_properties_data['url']
102+
assert props['categories'] == valid_properties_data['categories']
103+
assert props['sentence'] == valid_properties_data['sentence']
104+
assert props['paragraph'] == valid_properties_data['paragraph']
105+
assert props['version'] == int(valid_properties_data['version'])
106+
assert props['prettyVersion'] == valid_properties_data['prettyVersion']
107+
assert props['minRevision'] == int(valid_properties_data['minRevision'])
108+
assert props['maxRevision'] == int(valid_properties_data['maxRevision'])
109+
assert props['modes'] == valid_properties_data['modes']
110+
111+
112+
def test_validate_new_library_minimal_required_data(self, minimal_properties_library_data):
113+
"""Test validate_new_library with minimal data"""
114+
props = validate_new_library(minimal_properties_library_data)
115+
116+
assert props['name'] == minimal_properties_library_data['name']
117+
assert props['authors'] == minimal_properties_library_data['authors']
118+
assert props['url'] == minimal_properties_library_data['url']
119+
assert props['categories'] == minimal_properties_library_data['categories']
120+
assert props['sentence'] == minimal_properties_library_data['sentence']
121+
assert props['paragraph'] is None
122+
assert props['version'] == int(minimal_properties_library_data['version'])
123+
assert props['prettyVersion'] == minimal_properties_library_data['prettyVersion']
124+
assert props['minRevision'] == 0 # Default value
125+
assert props['maxRevision'] == 0 # Default value
126+
assert props['modes'] is None
127+
128+
129+
def test_validate_new_library_extra_fields_allowed(self, properties_with_extra_fields):
130+
"""Test validate_new_library with extra fields"""
131+
props = validate_new_library(properties_with_extra_fields)
132+
133+
assert props['name'] == properties_with_extra_fields['name']
134+
assert props['customField'] == properties_with_extra_fields['customField']
135+
assert props['anotherExtra'] == properties_with_extra_fields['anotherExtra']

0 commit comments

Comments
 (0)