#!/usr/bin/env python3 """The setup script.""" import re import sys from os.path import exists, dirname, join from setuptools import setup, find_packages def parse_version(fpath): """ Statically parse the version number from a python file """ value = static_parse('__version__', fpath) return value def static_parse(varname, fpath): """ Statically parse the a constant variable from a python file """ import ast if not exists(fpath): raise ValueError('fpath={!r} does not exist'.format(fpath)) with open(fpath, 'r') as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: if getattr(target, 'id', None) == varname: self.static_value = node.value.s visitor = StaticVisitor() visitor.visit(pt) try: value = visitor.static_value except AttributeError: import warnings value = 'Unknown {}'.format(varname) warnings.warn(value) return value def parse_description(): """ Parse the description in the README file CommandLine: pandoc --from=markdown --to=rst --output=README.rst README.md python -c "import setup; print(setup.parse_description())" """ from os.path import dirname, join, exists readme_fpath = join(dirname(__file__), 'README.rst') # This breaks on pip install, so check that it exists. if exists(readme_fpath): with open(readme_fpath, 'r') as f: text = f.read() return text return '' def parse_requirements(fname='requirements.txt', versions='loose'): """ Parse the package dependencies listed in a requirements file but strips specific versioning information. Args: fname (str): path to requirements file versions (bool | str, default=False): If true include version specs. If strict, then pin to the minimum version. Returns: List[str]: list of requirements items """ require_fpath = fname def parse_line(line, dpath=''): """ Parse information from a line in a requirements text file line = 'git+https://a.com/somedep@sometag#egg=SomeDep' line = '-e git+https://a.com/somedep@sometag#egg=SomeDep' """ # Remove inline comments comment_pos = line.find(' #') if comment_pos > -1: line = line[:comment_pos] if line.startswith('-r '): # Allow specifying requirements in other files target = join(dpath, line.split(' ')[1]) for info in parse_require_file(target): yield info else: # See: https://www.python.org/dev/peps/pep-0508/ info = {'line': line} if line.startswith('-e '): info['package'] = line.split('#egg=')[1] else: if '--find-links' in line: # setuptools doesnt seem to handle find links line = line.split('--find-links')[0] if ';' in line: pkgpart, platpart = line.split(';') # Handle platform specific dependencies # setuptools.readthedocs.io/en/latest/setuptools.html # #declaring-platform-specific-dependencies plat_deps = platpart.strip() info['platform_deps'] = plat_deps else: pkgpart = line platpart = None # Remove versioning from the package pat = '(' + '|'.join(['>=', '==', '>']) + ')' parts = re.split(pat, pkgpart, maxsplit=1) parts = [p.strip() for p in parts] info['package'] = parts[0] if len(parts) > 1: op, rest = parts[1:] version = rest # NOQA info['version'] = (op, version) yield info def parse_require_file(fpath): dpath = dirname(fpath) with open(fpath, 'r') as f: for line in f.readlines(): line = line.strip() if line and not line.startswith('#'): for info in parse_line(line, dpath=dpath): yield info def gen_packages_items(): if exists(require_fpath): for info in parse_require_file(require_fpath): parts = [info['package']] if versions and 'version' in info: if versions == 'strict': # In strict mode, we pin to the minimum version if info['version']: # Only replace the first >= instance verstr = ''.join(info['version']).replace('>=', '==', 1) parts.append(verstr) else: parts.extend(info['version']) if not sys.version.startswith('3.4'): # apparently package_deps are broken in 3.4 plat_deps = info.get('platform_deps') if plat_deps is not None: parts.append(';' + plat_deps) item = ''.join(parts) yield item packages = list(gen_packages_items()) return packages # def parse_requirements_alt(fpath='requirements.txt', versions='loose'): # """ # Args: # versions (str): can be # False or "free" - remove all constraints # True or "loose" - use the greater or equal (>=) in the req file # strict - replace all greater equal with equals # """ # # Note: different versions of pip might have different internals. # # This may need to be fixed. # from pip._internal.req import parse_requirements # from pip._internal.network.session import PipSession # requirements = [] # for req in parse_requirements(fpath, session=PipSession()): # if not versions or versions == 'free': # req_name = req.requirement.split(' ')[0] # requirements.append(req_name) # elif versions == 'loose' or versions is True: # requirements.append(req.requirement) # elif versions == 'strict': # part1, *rest = req.requirement.split(';') # strict_req = ';'.join([part1.replace('>=', '==')] + rest) # requirements.append(strict_req) # else: # raise KeyError(versions) # requirements = [r.replace(' ', '') for r in requirements] # return requirements VERSION = parse_version('geowatch/__init__.py') try: README = parse_description() except Exception: README = '' # try: REQUIREMENTS = ( parse_requirements('requirements/runtime.txt') ) __autogen__ = """ import ubelt as ub dpath =ub.Path('requirements') for p in dpath.ls(): print(f'{p.stem!r},') """ nameable_requirements = [ 'aws', 'cold', 'development', 'dvc', 'gdal', 'linting', 'materials', 'mmcv', 'optional', 'python_build_tools', 'runtime', 'tensorflow', 'tests', 'graphics', 'headless', 'compat', ] EXTRAS_REQUIRES = { 'all': parse_requirements('requirements.txt'), } for key in nameable_requirements: EXTRAS_REQUIRES[key] = parse_requirements(f'requirements/{key}.txt', versions='loose') EXTRAS_REQUIRES[key + '-strict'] = parse_requirements(f'requirements/{key}.txt', versions='strict') NAME = 'geowatch' if __name__ == '__main__': # Hacks for finding tpl packages included_tpl_dpaths = [ 'geowatch_tpl/submodules_static/torchview', 'geowatch_tpl/submodules_static/segment-anything', 'geowatch_tpl/submodules_static/scale-mae', 'geowatch_tpl/submodules_static/loss-of-plasticity', 'geowatch_tpl/modules', ] tpl_packages = [] for tpl_dpath in included_tpl_dpaths: result = find_packages(tpl_dpath) tpl_packages.extend( [tpl_dpath.replace('/', '.') + '.' + s for s in result] ) packages = find_packages(include=[ 'geowatch', 'geowatch.*', # TPL 'geowatch_tpl', 'geowatch_tpl.*', # Alias of the old module name to maintain backwards compatability # while we transition. # 'watch', 'watch.*', ]) + tpl_packages print(f'packages={packages}') setup( name=NAME, author="GeoWATCH developers", author_email='kitware@kitware.com', python_requires='>=3.10', # https://pypi.org/classifiers/ classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', 'Natural Language :: English', 'Programming Language :: Python :: 3', # 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', ], description="", entry_points={ 'console_scripts': [ 'geowatch= geowatch.cli.__main__:main', 'geowatch_dvc= geowatch.cli.find_dvc:_CLI.main', # 'gwmlops= watch.mlops.__main__:main', ], }, install_requires=REQUIREMENTS, extras_require=EXTRAS_REQUIRES, long_description_content_type='text/x-rst', long_description=README, include_package_data=True, package_data={ 'geowatch.tasks.depth': [ 'config.json' ], 'geowatch.rc': [ 'site-model.schema.json', 'region-model.schema.json', 'job.schema.json', # 'dem.xml' do we want to include this? # 'egm96_15.gtx' do we want to include this? ], 'geowatch.rc.requirements': [ 'aws.txt', 'cold.txt', 'development.txt', 'docs.txt', 'dvc.txt', 'gdal-strict.txt', 'gdal.txt', 'graphics.txt', 'headless.txt', 'linting.txt', 'materials.txt', 'mmcv.txt', 'optional.txt', 'python_build_tools.txt', 'runtime.txt', 'tensorflow.txt', 'tests.txt', 'transitive_runtime.txt', ], }, packages=packages, url='https://gitlab.kitware.com/computer-vision/geowatch.git', version=VERSION, zip_safe=False, license='Apache 2', )