Skip to content
Snippets Groups Projects
Commit f4506a16 authored by Daniel Lyons's avatar Daniel Lyons
Browse files

Refactor and remove numpy dependencies

 - Make communication between parse_setup and setup_to_meta JSON
 - Refactor generate_metadata() into class
 - Filter out numpy, because it is implied by Conda
parent a037cf3d
No related branches found
No related tags found
No related merge requests found
import json
import setuptools, importlib, subprocess, os, re import setuptools, importlib, subprocess, os, re
PYTHON_VERSION = '3.8' PYTHON_VERSION = '3.8'
...@@ -14,46 +16,51 @@ def write_metafile(metadata, filepath): ...@@ -14,46 +16,51 @@ def write_metafile(metadata, filepath):
with open(filepath, 'w') as f: with open(filepath, 'w') as f:
f.write(metadata) f.write(metadata)
def generate_metadata(setup_dict, path):
class MetadataGenerator:
""" """
Uses given info extracted from setup.py file to fill out metadata template. Uses given info extracted from setup.py file to fill out metadata template.
:param setup_dict: Dictionary of info extracted from setup.py.
:param path: Path to root directory of the subproject.
:return: String of metadata, ready to be written to a file.
""" """
def fmt_ep():
def __init__(self, setup, path):
self.setup = setup
self.path = path
def fmt_ep(self):
""" """
Format entry points section of metadata. Format entry points section of metadata.
:return: Formatted string if entry points exists; else empty string. :return: Formatted string if entry points exists; else empty string.
""" """
ep_string = '' ep_string = ''
if 'entry_points' in setup_dict.keys(): if 'entry_points' in self.setup.keys() and 'console_scripts' in self.setup['entry_points']:
ep_string += 'entry_points:\n' ep_string += 'entry_points:\n'
for ep in setup_dict['entry_points']: for ep in self.setup['entry_points']['console_scripts']:
ep_string += ' - {}\n'.format(ep) ep_string += ' - {}\n'.format(ep)
ep_string += ' ' ep_string += ' '
return ep_string return ep_string
def fmt_reqs():
def fmt_reqs(self):
""" """
Format requirements section of metadata. Format requirements section of metadata.
:return: Formatted string if requirements exists; else empty string. :return: Formatted string if requirements exists; else empty string.
""" """
reqs_string = '' reqs_string = ''
reqs_list = '' reqs_list = ''
if 'install_requires' in setup_dict.keys(): if 'install_requires' in self.setup.keys():
reqs_string += 'requirements:\n' reqs_string += 'requirements:\n'
build_reqs = ' build:\n' build_reqs = ' build:\n'
run_reqs = ' run:\n' run_reqs = ' run:\n'
host_reqs = ' host:\n' host_reqs = ' host:\n'
reqs_list += ' - python={}\n'.format(PYTHON_VERSION) reqs_list += ' - python={}\n'.format(PYTHON_VERSION)
for req in setup_dict['install_requires'].split(', '): for req in self.setup['install_requires']:
reqs_list += ' - {}\n'.format(req) reqs_list += ' - {}\n'.format(req)
reqs_string += build_reqs + reqs_list + \ reqs_string += build_reqs + reqs_list + \
run_reqs + reqs_list + \ run_reqs + reqs_list + \
host_reqs + reqs_list + \ host_reqs + reqs_list + \
'\n' '\n'
return reqs_string return reqs_string
def fmt_test():
def fmt_test(self):
""" """
Format test section of metadata. Format test section of metadata.
NOTE: May need further tweaking to be smarter based on individual project NOTE: May need further tweaking to be smarter based on individual project
...@@ -61,7 +68,7 @@ def generate_metadata(setup_dict, path): ...@@ -61,7 +68,7 @@ def generate_metadata(setup_dict, path):
:return: Formatted string if tests_require exists; else empty string. :return: Formatted string if tests_require exists; else empty string.
""" """
test_string = '' test_string = ''
if 'tests_require' in setup_dict.keys(): if 'tests_require' in self.setup.keys():
test_string += ( test_string += (
'test:\n' 'test:\n'
' source_files:\n' ' source_files:\n'
...@@ -69,7 +76,7 @@ def generate_metadata(setup_dict, path): ...@@ -69,7 +76,7 @@ def generate_metadata(setup_dict, path):
' requires:\n' ' requires:\n'
) )
for req in setup_dict['tests_require'].split(', '): for req in self.setup['tests_require']:
test_string += ' - {}\n'.format(req) test_string += ' - {}\n'.format(req)
test_string += ( test_string += (
...@@ -79,55 +86,33 @@ def generate_metadata(setup_dict, path): ...@@ -79,55 +86,33 @@ def generate_metadata(setup_dict, path):
) )
return test_string return test_string
name = setup_dict['name'] def generate(self):
version = setup_dict['version'] # Filter numpy etc. out of the requirements
entry_points = fmt_ep() self.setup['install_requires'] = [req for req in self.setup['install_requires'] if req != 'numpy']
pth = path.replace("./", "")
requirements = fmt_reqs() name = self.setup['name']
test = fmt_test() version = self.setup['version']
lic = setup_dict['license'] entry_points = self.fmt_ep()
summary = setup_dict['description'] pth = self.path.replace("./", "")
requirements = self.fmt_reqs()
with open('tools/metafile_template.txt', 'r') as f: test = self.fmt_test()
metadata = f.read() lic = self.setup['license']
summary = self.setup['description']
return metadata.format(
name = name, with open('tools/metafile_template.txt', 'r') as f:
version = version, metadata = f.read()
entry_points = entry_points,
path = "../../" + pth, return metadata.format(
requirements = requirements, name = name,
test = test, version = version,
license = lic, entry_points = entry_points,
summary = summary path = "../../" + pth,
) requirements = requirements,
test = test,
def data_to_dict(setup_data): license = lic,
""" summary = summary
Translates parsed setup.py data from unformatted list to formatted dictionary. )
:param setup_data: Data extracted from setup.py using parse_setup.py
:return: Dictionary of formatted data.
"""
data_dict = {}
r_entry = r"([a-z-_]+): "
r_id = r"[a-zA-Z0-9-_]"
r_ep = r"{{(?:[a-zA-Z0-9-_.]+):\s*(?P<values>(?:{0}+\s*=\s*[a-zA-Z0-9-_.]+:{0}+(?:,\s*)?)*)}}".format(r_id)
for d in setup_data:
result = re.match(r_entry, d)
if result:
substrings = [result.group(0), '[', ']', '\'', '"']
field = result.group(1)
value = del_substrings(d, substrings)
if value == "None":
continue
if re.match(r_ep, value):
# Parse entry points
value = re.match(r_ep, value).group('values')
value = re.split(',\s*', value)
data_dict[field] = value
return data_dict
def parse_setup(d): def parse_setup(d):
""" """
...@@ -139,11 +124,10 @@ def parse_setup(d): ...@@ -139,11 +124,10 @@ def parse_setup(d):
subprocess.run(['cp', 'tools/parse_setup.py', d]) subprocess.run(['cp', 'tools/parse_setup.py', d])
os.chdir(d) os.chdir(d)
proc = subprocess.run(['python3', 'parse_setup.py'], stdout=subprocess.PIPE) proc = subprocess.run(['python3', 'parse_setup.py'], stdout=subprocess.PIPE)
setup_data = (proc.stdout.decode('utf-8')).split('\n')
os.chdir(root) os.chdir(root)
subprocess.run(['rm', '{}/parse_setup.py'.format(d)]) subprocess.run(['rm', '{}/parse_setup.py'.format(d)])
return setup_data return json.loads(proc.stdout)
def get_outputs(names): def get_outputs(names):
""" """
...@@ -233,8 +217,7 @@ class Recipe: ...@@ -233,8 +217,7 @@ class Recipe:
for i, d in enumerate(self.dirs): for i, d in enumerate(self.dirs):
if d != '': if d != '':
setup_data = parse_setup(d) setup_data = parse_setup(d)
data_dict = data_to_dict(setup_data) metadata = MetadataGenerator(setup_data, d).generate()
metadata = generate_metadata(data_dict, d)
write_metafile(metadata, self.outputs[i]) write_metafile(metadata, self.outputs[i])
# Pass created file into options.created() # Pass created file into options.created()
self.options.created(self.outputs[i]) self.options.created(self.outputs[i])
......
import sys
import setuptools import setuptools
import json
def get_data(field, data):
"""
Get data from field if it exists.
:return: None if not found. Datum of field if found.
"""
datum = None
try:
datum = data[field]
except KeyError as e:
pass
return datum
data = {} data = {}
def my_setup(*args, **kwargs): def my_setup(*args, **kwargs):
...@@ -28,17 +18,23 @@ def my_setup(*args, **kwargs): ...@@ -28,17 +18,23 @@ def my_setup(*args, **kwargs):
] ]
for field in fields: for field in fields:
data[field] = get_data(field, kwargs) data[field] = kwargs.get(field)
def main(): def main():
# Author of these shenanigans: Daniel Lyons (but you already knew that) # Author of these shenanigans: Daniel Lyons (but you already knew that)
global data
# Monkey-patch over the setuptools setup() function to do our function instead # Monkey-patch over the setuptools setup() function to do our function instead
setuptools.setup = my_setup setuptools.setup = my_setup
# Load the setup.py file # Load the setup.py file
import setup import setup
# Remove None-flavored values
data = {k: v for k, v in data.items() if v is not None}
# Instead of exiting, we now have populated our global variable, without doing any parsing # Instead of exiting, we now have populated our global variable, without doing any parsing
for field, datum in data.items(): json.dump(data, sys.stdout)
print('{}: {}'.format(field, datum))
if __name__ == "__main__": if __name__ == "__main__":
main() main()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment