From f4506a16aa332e8f7de4c4d84f994650feb2c1e4 Mon Sep 17 00:00:00 2001 From: Daniel K Lyons <dlyons@nrao.edu> Date: Wed, 12 Aug 2020 16:18:28 -0600 Subject: [PATCH] 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 --- src/setup_to_meta/setup_to_meta.py | 115 ++++++++++++----------------- tools/parse_setup.py | 26 +++---- 2 files changed, 60 insertions(+), 81 deletions(-) diff --git a/src/setup_to_meta/setup_to_meta.py b/src/setup_to_meta/setup_to_meta.py index 9fc2826e6..af65485ab 100644 --- a/src/setup_to_meta/setup_to_meta.py +++ b/src/setup_to_meta/setup_to_meta.py @@ -1,3 +1,5 @@ +import json + import setuptools, importlib, subprocess, os, re PYTHON_VERSION = '3.8' @@ -14,46 +16,51 @@ def write_metafile(metadata, filepath): with open(filepath, 'w') as f: f.write(metadata) -def generate_metadata(setup_dict, path): + +class MetadataGenerator: """ 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. :return: Formatted string if entry points exists; else empty 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' - for ep in setup_dict['entry_points']: + for ep in self.setup['entry_points']['console_scripts']: ep_string += ' - {}\n'.format(ep) ep_string += ' ' return ep_string - def fmt_reqs(): + + def fmt_reqs(self): """ Format requirements section of metadata. :return: Formatted string if requirements exists; else empty string. """ reqs_string = '' reqs_list = '' - if 'install_requires' in setup_dict.keys(): + if 'install_requires' in self.setup.keys(): reqs_string += 'requirements:\n' build_reqs = ' build:\n' run_reqs = ' run:\n' host_reqs = ' host:\n' 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_string += build_reqs + reqs_list + \ run_reqs + reqs_list + \ host_reqs + reqs_list + \ '\n' return reqs_string - def fmt_test(): + + def fmt_test(self): """ Format test section of metadata. NOTE: May need further tweaking to be smarter based on individual project @@ -61,7 +68,7 @@ def generate_metadata(setup_dict, path): :return: Formatted string if tests_require exists; else empty string. """ test_string = '' - if 'tests_require' in setup_dict.keys(): + if 'tests_require' in self.setup.keys(): test_string += ( 'test:\n' ' source_files:\n' @@ -69,7 +76,7 @@ def generate_metadata(setup_dict, path): ' requires:\n' ) - for req in setup_dict['tests_require'].split(', '): + for req in self.setup['tests_require']: test_string += ' - {}\n'.format(req) test_string += ( @@ -79,55 +86,33 @@ def generate_metadata(setup_dict, path): ) return test_string - name = setup_dict['name'] - version = setup_dict['version'] - entry_points = fmt_ep() - pth = path.replace("./", "") - requirements = fmt_reqs() - test = fmt_test() - lic = setup_dict['license'] - summary = setup_dict['description'] - - with open('tools/metafile_template.txt', 'r') as f: - metadata = f.read() - - return metadata.format( - name = name, - version = version, - entry_points = entry_points, - path = "../../" + pth, - requirements = requirements, - test = test, - license = lic, - summary = summary - ) - -def data_to_dict(setup_data): - """ - 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 generate(self): + # Filter numpy etc. out of the requirements + self.setup['install_requires'] = [req for req in self.setup['install_requires'] if req != 'numpy'] + + name = self.setup['name'] + version = self.setup['version'] + entry_points = self.fmt_ep() + pth = self.path.replace("./", "") + requirements = self.fmt_reqs() + test = self.fmt_test() + lic = self.setup['license'] + summary = self.setup['description'] + + with open('tools/metafile_template.txt', 'r') as f: + metadata = f.read() + + return metadata.format( + name = name, + version = version, + entry_points = entry_points, + path = "../../" + pth, + requirements = requirements, + test = test, + license = lic, + summary = summary + ) + def parse_setup(d): """ @@ -139,11 +124,10 @@ def parse_setup(d): subprocess.run(['cp', 'tools/parse_setup.py', d]) os.chdir(d) proc = subprocess.run(['python3', 'parse_setup.py'], stdout=subprocess.PIPE) - setup_data = (proc.stdout.decode('utf-8')).split('\n') os.chdir(root) subprocess.run(['rm', '{}/parse_setup.py'.format(d)]) - return setup_data + return json.loads(proc.stdout) def get_outputs(names): """ @@ -233,8 +217,7 @@ class Recipe: for i, d in enumerate(self.dirs): if d != '': setup_data = parse_setup(d) - data_dict = data_to_dict(setup_data) - metadata = generate_metadata(data_dict, d) + metadata = MetadataGenerator(setup_data, d).generate() write_metafile(metadata, self.outputs[i]) # Pass created file into options.created() self.options.created(self.outputs[i]) diff --git a/tools/parse_setup.py b/tools/parse_setup.py index b9e5f8e4b..8acd807b9 100644 --- a/tools/parse_setup.py +++ b/tools/parse_setup.py @@ -1,16 +1,6 @@ +import sys import setuptools - -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 +import json data = {} def my_setup(*args, **kwargs): @@ -28,17 +18,23 @@ def my_setup(*args, **kwargs): ] for field in fields: - data[field] = get_data(field, kwargs) + data[field] = kwargs.get(field) def main(): # 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 setuptools.setup = my_setup + # Load the setup.py file 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 - for field, datum in data.items(): - print('{}: {}'.format(field, datum)) + json.dump(data, sys.stdout) if __name__ == "__main__": main() \ No newline at end of file -- GitLab