diff --git a/CHANGELOG.md b/CHANGELOG.md index cc524abd..c25c6662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ This project (loosely) adheres to [Semantic Versioning](https://semver.org/spec/ ### Added * Add backend option to `highlight_code` which can be "pygments" or "rich". +* Support for Python 3.12 ### Changed * Improve speed of inplace dictionary set operations. diff --git a/pyproject.toml b/pyproject.toml index 777bed08..f1aa39f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,13 +13,26 @@ rel_mod_parent_dpath = "." os = ["all"] min_python = 3.6 author = "Jon Crall" +typed = "partial" author_email = "erotemic@gmail.com" description = "A Python utility belt containing simple tools, a stdlib like feel, and extra batteries" license = "Apache 2" dev_status = "stable" +classifiers = [ + # List of classifiers available at: + # https://pypi.python.org/pypi?%3Aaction=list_classifiers + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Utilities', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: MacOS', + 'Operating System :: POSIX :: Linux', + 'Typing :: Stubs Only', +] [tool.xcookie.setuptools] -keywords = ["utility", "python", "hashing", "caching", "stdlib", "path", "pathlib"] +keywords = ["utility", "python", "hashing", "caching", "stdlib", "path", "pathlib", "dictionary", "download"] [tool.pytest.ini_options] addopts = "-p no:doctest --xdoctest --xdoctest-style=google --ignore-glob=setup.py --ignore-glob=docs" diff --git a/setup.py b/setup.py index 09ba4c28..07498bc8 100755 --- a/setup.py +++ b/setup.py @@ -1,16 +1,9 @@ #!/usr/bin/env python -""" -Installation: - pip install git+https://github.com/Erotemic/ubelt.git - -Developing: - git clone https://github.com/Erotemic/ubelt.git - cd ubelt - pip install -e ubelt[all] -""" +# Generated by ~/code/xcookie/xcookie/builders/setup.py +# based on part ~/code/xcookie/xcookie/rc/setup.py.in import sys -from os.path import exists - +import re +from os.path import exists, dirname, join from setuptools import find_packages from setuptools import setup @@ -19,26 +12,38 @@ 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_: + raise ValueError("fpath={!r} does not exist".format(fpath)) + with open(fpath, "r") as file_: sourcecode = file_.read() pt = ast.parse(sourcecode) - class Finished(Exception): - pass - class VersionVisitor(ast.NodeVisitor): + + class StaticVisitor(ast.NodeVisitor): def visit_Assign(self, node): for target in node.targets: - if getattr(target, 'id', None) == '__version__': - self.version = node.value.s - raise Finished - visitor = VersionVisitor() + if getattr(target, "id", None) == varname: + self.static_value = node.value.s + + visitor = StaticVisitor() + visitor.visit(pt) try: - visitor.visit(pt) - except Finished: - pass - return visitor.version + value = visitor.static_value + except AttributeError: + import warnings + + value = "Unknown {}".format(varname) + warnings.warn(value) + return value def parse_description(): @@ -49,17 +54,16 @@ def parse_description(): 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') + 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: + with open(readme_fpath, "r") as f: text = f.read() return text - return '' + return "" -def parse_requirements(fname='requirements.txt', versions=False): +def parse_requirements(fname="requirements.txt", versions=False): """ Parse the package dependencies listed in a requirements file but strips specific versioning information. @@ -72,12 +76,13 @@ def parse_requirements(fname='requirements.txt', versions=False): Returns: List[str]: list of requirements items + + CommandLine: + python -c "import setup, ubelt; print(ubelt.urepr(setup.parse_requirements()))" """ - from os.path import exists, dirname, join - import re require_fpath = fname - def parse_line(line, dpath=''): + def parse_line(line, dpath=""): """ Parse information from a line in a requirements text file @@ -85,136 +90,183 @@ def parse_line(line, dpath=''): line = '-e git+https://a.com/somedep@sometag#egg=SomeDep' """ # Remove inline comments - comment_pos = line.find(' #') + comment_pos = line.find(" #") if comment_pos > -1: line = line[:comment_pos] - if line.startswith('-r '): + if line.startswith("-r "): # Allow specifying requirements in other files - target = join(dpath, line.split(' ')[1]) + 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] + info = {"line": line} + if line.startswith("-e "): + info["package"] = line.split("#egg=")[1] else: - if ';' in line: - pkgpart, platpart = line.split(';') + 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 + info["platform_deps"] = plat_deps else: pkgpart = line platpart = None # Remove versioning from the package - pat = '(' + '|'.join(['>=', '==', '>']) + ')' + pat = "(" + "|".join([">=", "==", ">"]) + ")" parts = re.split(pat, pkgpart, maxsplit=1) parts = [p.strip() for p in parts] - info['package'] = parts[0] + info["package"] = parts[0] if len(parts) > 1: op, rest = parts[1:] version = rest # NOQA - info['version'] = (op, version) + info["version"] = (op, version) yield info def parse_require_file(fpath): dpath = dirname(fpath) - with open(fpath, 'r') as f: + with open(fpath, "r") as f: for line in f.readlines(): line = line.strip() - if line and not line.startswith('#'): + 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': + parts = [info["package"]] + if versions and "version" in info: + if versions == "strict": # In strict mode, we pin to the minimum version - if info['version']: + if info["version"]: # Only replace the first >= instance - verstr = ''.join(info['version']).replace('>=', '==', 1) + verstr = "".join(info["version"]).replace(">=", "==", 1) parts.append(verstr) else: - parts.extend(info['version']) - if not sys.version.startswith('3.4'): + 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') + plat_deps = info.get("platform_deps") if plat_deps is not None: - parts.append(';' + plat_deps) - item = ''.join(parts) + parts.append(";" + plat_deps) + item = "".join(parts) yield item packages = list(gen_packages_items()) return packages -NAME = 'ubelt' -VERSION = parse_version('ubelt/__init__.py') +# # Maybe use in the future? But has private deps +# 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 + +NAME = "ubelt" +INIT_PATH = "ubelt/__init__.py" +VERSION = parse_version(INIT_PATH) -if __name__ == '__main__': +if __name__ == "__main__": setupkw = {} - setupkw['install_requires'] = parse_requirements('requirements/runtime.txt') - setupkw['extras_require'] = { - 'all': parse_requirements('requirements.txt'), - 'tests': parse_requirements('requirements/tests.txt'), - 'optional': parse_requirements('requirements/optional.txt'), - 'docs': parse_requirements('requirements/docs.txt'), - 'all-strict': parse_requirements('requirements.txt', versions='strict'), - 'runtime-strict': parse_requirements( - 'requirements/runtime.txt', versions='strict' + + setupkw["install_requires"] = parse_requirements( + "requirements/runtime.txt", versions="loose" + ) + setupkw["extras_require"] = { + "all": parse_requirements("requirements.txt", versions="loose"), + "tests": parse_requirements("requirements/tests.txt", versions="loose"), + "optional": parse_requirements("requirements/optional.txt", versions="loose"), + "all": parse_requirements("requirements.txt", versions="loose"), + "runtime": parse_requirements("requirements/runtime.txt", versions="loose"), + "tests": parse_requirements("requirements/tests.txt", versions="loose"), + "optional": parse_requirements("requirements/optional.txt", versions="loose"), + "docs": parse_requirements("requirements/docs.txt", versions="loose"), + "types": parse_requirements("requirements/types.txt", versions="loose"), + "all-strict": parse_requirements("requirements.txt", versions="strict"), + "runtime-strict": parse_requirements( + "requirements/runtime.txt", versions="strict" ), - 'tests-strict': parse_requirements('requirements/tests.txt', versions='strict'), - 'docs-strict': parse_requirements('requirements/docs.txt', versions='strict'), - 'optional-strict': parse_requirements( - 'requirements/optional.txt', versions='strict' + "tests-strict": parse_requirements("requirements/tests.txt", versions="strict"), + "optional-strict": parse_requirements( + "requirements/optional.txt", versions="strict" ), + "docs-strict": parse_requirements("requirements/docs.txt", versions="strict"), + "types-strict": parse_requirements("requirements/types.txt", versions="strict"), } - setup( - name=NAME, - version=VERSION, - author='Jon Crall', - description=('A Python utility belt containing simple tools, ' - 'a stdlib like feel, and extra batteries.'), - long_description=parse_description(), - long_description_content_type='text/x-rst', - package_data={ - 'ubelt': ['py.typed', '*.pyi'], - }, - author_email='erotemic@gmail.com', - url='https://github.com/Erotemic/ubelt', - license='Apache 2', - packages=find_packages('.'), - python_requires='>=3.6', - classifiers=[ - # List of classifiers available at: - # https://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS', - 'Operating System :: POSIX :: Linux', - 'Typing :: Stubs Only', - # This should be interpreted as Apache License v2.0 - 'License :: OSI Approved :: Apache Software License', - # Supported Python versions - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - ], - **setupkw, - ) + setupkw["name"] = NAME + setupkw["version"] = VERSION + setupkw["author"] = "Jon Crall" + setupkw["author_email"] = "erotemic@gmail.com" + setupkw["url"] = "https://github.com/Erotemic/ubelt" + setupkw[ + "description" + ] = "A Python utility belt containing simple tools, a stdlib like feel, and extra batteries" + setupkw["long_description"] = parse_description() + setupkw["long_description_content_type"] = "text/x-rst" + setupkw["license"] = "Apache 2" + setupkw["packages"] = find_packages(".") + setupkw["python_requires"] = ">=3.6" + setupkw["classifiers"] = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", + "License :: OSI Approved :: Apache Software License", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Typing :: Stubs Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + ] + setupkw["package_data"] = {"ubelt": ["py.typed", "*.pyi"]} + setupkw["keywords"] = [ + "utility", + "python", + "hashing", + "caching", + "stdlib", + "path", + "pathlib", + "dictionary", + "download", + ] + setup(**setupkw)