setup.py (312 lines of code) (raw):
#!/usr/bin/env python
# Copyright 1999-2022 Alibaba Group Holding Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Parts of this file were taken from the pandas project
# (https://github.com/pandas-dev/pandas), which is permitted for use under
# the BSD 3-Clause License
import os
import platform
import shutil
import sys
from setuptools import Extension, find_packages, setup
from setuptools.command.install import install
try:
from setuptools import Command
except ImportError:
from distutils.cmd import Command
try:
from sysconfig import get_config_var
except ImportError:
from distutils.sysconfig import get_config_var
try:
from packaging.version import Version
except ImportError:
from distutils.version import LooseVersion as Version
# From https://github.com/pandas-dev/pandas/pull/24274:
# For mac, ensure extensions are built for macos 10.9 when compiling on a
# 10.9 system or above, overriding distuitls behaviour which is to target
# the version that python was built for. This may be overridden by setting
# MACOSX_DEPLOYMENT_TARGET before calling setup.py
if sys.platform == "darwin":
if "MACOSX_DEPLOYMENT_TARGET" not in os.environ:
current_system = Version(platform.mac_ver()[0])
python_target = Version(get_config_var("MACOSX_DEPLOYMENT_TARGET"))
if python_target < Version("10.9") and current_system >= Version("10.9"):
os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.9"
repo_root = os.path.dirname(os.path.abspath(__file__))
try:
execfile
except NameError:
def execfile(fname, globs, locs=None):
locs = locs or globs
exec(compile(open(fname).read(), fname, "exec"), globs, locs)
version_ns = {}
execfile(os.path.join(repo_root, "odps", "_version.py"), version_ns)
extra_install_cmds = []
def which(program):
import os
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
# http://stackoverflow.com/questions/12683834/how-to-copy-directory-recursively-in-python-and-overwrite-all
def recursive_overwrite(src, dest, filter_func=None):
destinations = []
filter_func = filter_func or (lambda s: True)
if os.path.isdir(src):
if not os.path.isdir(dest):
os.makedirs(dest)
files = os.listdir(src)
for f in files:
if not filter_func(f):
continue
destinations.extend(
recursive_overwrite(os.path.join(src, f), os.path.join(dest, f))
)
else:
shutil.copyfile(src, dest)
destinations.append(dest)
return destinations
class CustomInstall(install):
def run(self):
global extra_install_cmds
install.run(self)
[self.run_command(cmd) for cmd in extra_install_cmds]
version = sys.version_info
PY2 = version[0] == 2
PY3 = version[0] == 3
PYPY = platform.python_implementation().lower() == "pypy"
if PY2 and version[:2] < (2, 7):
raise Exception("PyODPS supports Python 2.7+ (including Python 3+).")
try:
import distribute
raise Exception(
"PyODPS cannot be installed when 'distribute' is installed. "
"Please uninstall it before installing PyODPS."
)
except ImportError:
pass
try:
import pip
for pk in pip.get_installed_distributions():
if pk.key == "odps":
raise Exception(
"Package `odps` collides with PyODPS. Please uninstall it before installing PyODPS."
)
except (ImportError, AttributeError):
pass
try:
from jupyter_core.paths import jupyter_data_dir
has_jupyter = True
except ImportError:
has_jupyter = False
try:
from jupyterlab import __version__
has_jupyterlab = True
except ImportError:
has_jupyterlab = False
if len(sys.argv) > 1 and sys.argv[1] == "clean":
build_cmd = sys.argv[1]
else:
build_cmd = None
requirements = []
with open("requirements.txt") as f:
requirements.extend(f.read().splitlines())
full_requirements = [
"jupyter>=1.0.0",
"ipython>=4.0.0",
"numpy>=1.6.0",
"pandas>=0.17.0",
"matplotlib>=1.4",
"graphviz>=0.4",
"greenlet>=0.4.10",
"ipython<6.0.0; python_version < \"3\"",
"cython>=0.20; sys_platform != \"win32\"",
]
mars_requirements = [
"pymars>=0.5.4",
"protobuf>=3.6,<4.0",
]
long_description = None
if os.path.exists("README.rst"):
with open("README.rst") as f:
long_description = f.read()
setup_options = dict(
name="pyodps",
version=version_ns["__version__"],
description="ODPS Python SDK and data analysis framework",
long_description=long_description,
author="Wu Wei",
author_email="weiwu@cacheme.net",
maintainer="Wenjun Si",
maintainer_email="wenjun.swj@alibaba-inc.com",
url="http://github.com/aliyun/aliyun-odps-python-sdk",
license="Apache License 2.0",
classifiers=[
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"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 :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Libraries",
],
cmdclass={"install": CustomInstall},
packages=find_packages(exclude=("*.tests.*", "*.tests")),
include_package_data=True,
install_requires=requirements,
include_dirs=[],
extras_require={"full": full_requirements, "mars": mars_requirements},
entry_points={
"sqlalchemy.dialects": [
"odps = odps.sqlalchemy_odps:ODPSDialect",
"maxcompute = odps.sqlalchemy_odps:ODPSDialect",
],
"superset.db_engine_specs": [
"odps = odps.superset_odps:ODPSEngineSpec",
"maxcompute = odps.superset_odps:ODPSEngineSpec",
],
"console_scripts": [
"pyou = odps_scripts.pyou:main",
"pyodps-pack = odps_scripts.pyodps_pack:main",
],
},
)
if build_cmd != "clean" and not PYPY: # skip cython in pypy
try:
import cython
from Cython.Build import cythonize
from Cython.Distutils import build_ext
# detect if cython works
if sys.platform == "win32":
cython.inline("return a + b", a=1, b=1)
cythonize_kw = dict(language_level=sys.version_info[0])
extension_kw = dict(language="c++", include_dirs=[])
if "MSC" in sys.version:
extra_compile_args = ["/Ot", "/I" + os.path.join(repo_root, "misc")]
extension_kw["extra_compile_args"] = extra_compile_args
else:
extra_compile_args = ["-O3"]
extension_kw["extra_compile_args"] = extra_compile_args
if os.environ.get("CYTHON_TRACE"):
extension_kw["define_macros"] = [
("CYTHON_TRACE_NOGIL", "1"),
("CYTHON_TRACE", "1"),
]
cythonize_kw["compiler_directives"] = {"linetrace": True}
extensions = [
Extension("odps.src.types_c", ["odps/src/types_c.pyx"], **extension_kw),
Extension("odps.src.crc32c_c", ["odps/src/crc32c_c.pyx"], **extension_kw),
Extension("odps.src.utils_c", ["odps/src/utils_c.pyx"], **extension_kw),
Extension(
"odps.tunnel.pb.encoder_c",
["odps/tunnel/pb/encoder_c.pyx"],
**extension_kw
),
Extension(
"odps.tunnel.pb.decoder_c",
["odps/tunnel/pb/decoder_c.pyx"],
**extension_kw
),
Extension(
"odps.tunnel.io.writer_c",
["odps/tunnel/io/writer_c.pyx"],
**extension_kw
),
Extension(
"odps.tunnel.io.reader_c",
["odps/tunnel/io/reader_c.pyx"],
**extension_kw
),
Extension(
"odps.tunnel.checksum_c", ["odps/tunnel/checksum_c.pyx"], **extension_kw
),
Extension(
"odps.tunnel.hasher_c", ["odps/tunnel/hasher_c.pyx"], **extension_kw
),
]
setup_options["cmdclass"].update({"build_ext": build_ext})
force_recompile = bool(int(os.getenv("CYTHON_FORCE_RECOMPILE", "0")))
setup_options["ext_modules"] = cythonize(
extensions, force=force_recompile, **cythonize_kw
)
except:
pass
if build_cmd != "clean" and has_jupyter:
class InstallJS(Command):
description = "install JavaScript extensions"
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
src_dir = os.path.join(repo_root, "odps", "static", "ui", "target")
dest_dir = os.path.join(jupyter_data_dir(), "nbextensions", "pyodps")
if os.path.exists(dest_dir):
shutil.rmtree(dest_dir)
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
recursive_overwrite(src_dir, dest_dir)
try:
from notebook.nbextensions import enable_nbextension
except ImportError:
return
enable_nbextension("notebook", "pyodps/main")
class BuildJS(Command):
description = "build JavaScript files"
user_options = [("registry=", None, "npm registry")]
def initialize_options(self):
self.registry = None
def finalize_options(self):
pass
def run(self):
if not which("npm"):
raise Exception("You need to install npm before building the scripts.")
cwd = os.getcwd()
os.chdir(os.path.join(os.path.abspath(os.getcwd()), "odps", "static", "ui"))
cmd = "npm install"
if getattr(self, "registry", None):
cmd += " --registry=" + self.registry
print("executing " + cmd)
ret = os.system(cmd)
ret >>= 8
if ret != 0:
print(cmd + " exited with error: %d" % ret)
print("executing grunt")
ret = os.system("npm run grunt")
ret >>= 8
if ret != 0:
print("grunt exited with error: %d" % ret)
os.chdir(cwd)
setup_options["cmdclass"].update({"install_js": InstallJS, "build_js": BuildJS})
extra_install_cmds.append("install_js")
setup(**setup_options)
if build_cmd == "clean":
for root, dirs, files in os.walk(os.path.normpath("odps/")):
pyx_files = set()
c_file_pairs = []
if "__pycache__" in dirs:
full_path = os.path.join(root, "__pycache__")
print("removing '%s'" % full_path)
shutil.rmtree(full_path)
for f in files:
fn, ext = os.path.splitext(f)
# delete compiled binaries
if ext.lower() in (".pyd", ".so", ".pyc"):
full_path = os.path.join(root, f)
print("removing '%s'" % full_path)
os.unlink(full_path)
elif ext.lower() == ".pyx":
pyx_files.add(fn)
elif ext.lower() in (".c", ".cpp", ".cc"):
c_file_pairs.append((fn, f))
# remove cython-generated files
for cfn, cf in c_file_pairs:
if cfn in pyx_files:
full_path = os.path.join(root, cf)
print("removing '%s'" % full_path)
os.unlink(full_path)