mirror of
				https://github.com/explosion/spaCy.git
				synced 2025-11-04 09:57:26 +03:00 
			
		
		
		
	<!--- Provide a general summary of your changes in the title. --> ## Description - [x] Use [`black`](https://github.com/ambv/black) to auto-format all `.py` files. - [x] Update flake8 config to exclude very large files (lemmatization tables etc.) - [x] Update code to be compatible with flake8 rules - [x] Fix various small bugs, inconsistencies and messy stuff in the language data - [x] Update docs to explain new code style (`black`, `flake8`, when to use `# fmt: off` and `# fmt: on` and what `# noqa` means) Once #2932 is merged, which auto-formats and tidies up the CLI, we'll be able to run `flake8 spacy` actually get meaningful results. At the moment, the code style and linting isn't applied automatically, but I'm hoping that the new [GitHub Actions](https://github.com/features/actions) will let us auto-format pull requests and post comments with relevant linting information. ### Types of change enhancement, code style ## Checklist <!--- Before you submit the PR, go over this checklist and make sure you can tick off all the boxes. [] -> [x] --> - [x] I have submitted the spaCy Contributor Agreement. - [x] I ran the tests, and all new and existing tests passed. - [x] My changes don't require a change to the documentation, or if they do, I've added all required information.
		
			
				
	
	
		
			170 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
""" cythonize.py
 | 
						|
 | 
						|
Cythonize pyx files into C++ files as needed.
 | 
						|
 | 
						|
Usage: cythonize.py [root]
 | 
						|
 | 
						|
Checks pyx files to see if they have been changed relative to their
 | 
						|
corresponding C++ files. If they have, then runs cython on these files to
 | 
						|
recreate the C++ files.
 | 
						|
 | 
						|
Additionally, checks pxd files and setup.py if they have been changed. If
 | 
						|
they have, rebuilds everything.
 | 
						|
 | 
						|
Change detection based on file hashes stored in JSON format.
 | 
						|
 | 
						|
For now, this script should be run by developers when changing Cython files
 | 
						|
and the resulting C++ files checked in, so that end-users (and Python-only
 | 
						|
developers) do not get the Cython dependencies.
 | 
						|
 | 
						|
Based upon:
 | 
						|
 | 
						|
https://raw.github.com/dagss/private-scipy-refactor/cythonize/cythonize.py
 | 
						|
https://raw.githubusercontent.com/numpy/numpy/master/tools/cythonize.py
 | 
						|
 | 
						|
Note: this script does not check any of the dependent C++ libraries.
 | 
						|
"""
 | 
						|
from __future__ import print_function
 | 
						|
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import json
 | 
						|
import hashlib
 | 
						|
import subprocess
 | 
						|
import argparse
 | 
						|
 | 
						|
 | 
						|
HASH_FILE = "cythonize.json"
 | 
						|
 | 
						|
 | 
						|
def process_pyx(fromfile, tofile, language_level="-2"):
 | 
						|
    print("Processing %s" % fromfile)
 | 
						|
    try:
 | 
						|
        from Cython.Compiler.Version import version as cython_version
 | 
						|
        from distutils.version import LooseVersion
 | 
						|
 | 
						|
        if LooseVersion(cython_version) < LooseVersion("0.19"):
 | 
						|
            raise Exception("Require Cython >= 0.19")
 | 
						|
 | 
						|
    except ImportError:
 | 
						|
        pass
 | 
						|
 | 
						|
    flags = ["--fast-fail", language_level]
 | 
						|
    if tofile.endswith(".cpp"):
 | 
						|
        flags += ["--cplus"]
 | 
						|
 | 
						|
    try:
 | 
						|
        try:
 | 
						|
            r = subprocess.call(
 | 
						|
                ["cython"] + flags + ["-o", tofile, fromfile], env=os.environ
 | 
						|
            )  # See Issue #791
 | 
						|
            if r != 0:
 | 
						|
                raise Exception("Cython failed")
 | 
						|
        except OSError:
 | 
						|
            # There are ways of installing Cython that don't result in a cython
 | 
						|
            # executable on the path, see gh-2397.
 | 
						|
            r = subprocess.call(
 | 
						|
                [
 | 
						|
                    sys.executable,
 | 
						|
                    "-c",
 | 
						|
                    "import sys; from Cython.Compiler.Main import "
 | 
						|
                    "setuptools_main as main; sys.exit(main())",
 | 
						|
                ]
 | 
						|
                + flags
 | 
						|
                + ["-o", tofile, fromfile]
 | 
						|
            )
 | 
						|
            if r != 0:
 | 
						|
                raise Exception("Cython failed")
 | 
						|
    except OSError:
 | 
						|
        raise OSError("Cython needs to be installed")
 | 
						|
 | 
						|
 | 
						|
def preserve_cwd(path, func, *args):
 | 
						|
    orig_cwd = os.getcwd()
 | 
						|
    try:
 | 
						|
        os.chdir(path)
 | 
						|
        func(*args)
 | 
						|
    finally:
 | 
						|
        os.chdir(orig_cwd)
 | 
						|
 | 
						|
 | 
						|
def load_hashes(filename):
 | 
						|
    try:
 | 
						|
        return json.load(open(filename))
 | 
						|
    except (ValueError, IOError):
 | 
						|
        return {}
 | 
						|
 | 
						|
 | 
						|
def save_hashes(hash_db, filename):
 | 
						|
    with open(filename, "w") as f:
 | 
						|
        f.write(json.dumps(hash_db))
 | 
						|
 | 
						|
 | 
						|
def get_hash(path):
 | 
						|
    return hashlib.md5(open(path, "rb").read()).hexdigest()
 | 
						|
 | 
						|
 | 
						|
def hash_changed(base, path, db):
 | 
						|
    full_path = os.path.normpath(os.path.join(base, path))
 | 
						|
    return not get_hash(full_path) == db.get(full_path)
 | 
						|
 | 
						|
 | 
						|
def hash_add(base, path, db):
 | 
						|
    full_path = os.path.normpath(os.path.join(base, path))
 | 
						|
    db[full_path] = get_hash(full_path)
 | 
						|
 | 
						|
 | 
						|
def process(base, filename, db):
 | 
						|
    root, ext = os.path.splitext(filename)
 | 
						|
    if ext in [".pyx", ".cpp"]:
 | 
						|
        if hash_changed(base, filename, db) or not os.path.isfile(
 | 
						|
            os.path.join(base, root + ".cpp")
 | 
						|
        ):
 | 
						|
            preserve_cwd(base, process_pyx, root + ".pyx", root + ".cpp")
 | 
						|
            hash_add(base, root + ".cpp", db)
 | 
						|
            hash_add(base, root + ".pyx", db)
 | 
						|
 | 
						|
 | 
						|
def check_changes(root, db):
 | 
						|
    res = False
 | 
						|
    new_db = {}
 | 
						|
 | 
						|
    setup_filename = "setup.py"
 | 
						|
    hash_add(".", setup_filename, new_db)
 | 
						|
    if hash_changed(".", setup_filename, db):
 | 
						|
        res = True
 | 
						|
 | 
						|
    for base, _, files in os.walk(root):
 | 
						|
        for filename in files:
 | 
						|
            if filename.endswith(".pxd"):
 | 
						|
                hash_add(base, filename, new_db)
 | 
						|
                if hash_changed(base, filename, db):
 | 
						|
                    res = True
 | 
						|
 | 
						|
    if res:
 | 
						|
        db.clear()
 | 
						|
        db.update(new_db)
 | 
						|
    return res
 | 
						|
 | 
						|
 | 
						|
def run(root):
 | 
						|
    db = load_hashes(HASH_FILE)
 | 
						|
 | 
						|
    try:
 | 
						|
        check_changes(root, db)
 | 
						|
        for base, _, files in os.walk(root):
 | 
						|
            for filename in files:
 | 
						|
                process(base, filename, db)
 | 
						|
    finally:
 | 
						|
        save_hashes(db, HASH_FILE)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    parser = argparse.ArgumentParser(
 | 
						|
        description="Cythonize pyx files into C++ files as needed"
 | 
						|
    )
 | 
						|
    parser.add_argument("root", help="root directory")
 | 
						|
    args = parser.parse_args()
 | 
						|
    run(args.root)
 |