from setuptools import setup, Command from setuptools.command.egg_info import egg_info from subprocess import check_call from distutils import log import versioneer import os import sys import platform import json here = os.path.dirname(os.path.abspath(__file__)) project_root = os.path.dirname(os.path.dirname(os.path.dirname(here))) node_root = os.path.join(project_root, "packages", "javascript", "plotlywidget") is_repo = os.path.exists(os.path.join(project_root, ".git")) npm_path = os.pathsep.join( [ os.path.join(node_root, "node_modules", ".bin"), os.environ.get("PATH", os.defpath), ] ) # Load plotly.js version from js/package.json def plotly_js_version(): path = os.path.join( project_root, "packages", "javascript", "plotlywidget", "package.json" ) with open(path, "rt") as f: package_json = json.load(f) version = package_json["dependencies"]["plotly.js"] version = version.replace("^", "") return version def readme(): with open(os.path.join(here, "README.md")) as f: return f.read() def js_prerelease(command, strict=False): """decorator for building minified js/css prior to another command""" class DecoratedCommand(command): def run(self): jsdeps = self.distribution.get_command_obj("jsdeps") if not is_repo and all(os.path.exists(t) for t in jsdeps.targets): # sdist, nothing to do command.run(self) return try: self.distribution.run_command("jsdeps") except Exception as e: missing = [t for t in jsdeps.targets if not os.path.exists(t)] if strict or missing: log.warn("rebuilding js and css failed") if missing: log.error("missing files: %s" % missing) raise e else: log.warn("rebuilding js and css failed (not a problem)") log.warn(str(e)) command.run(self) update_package_data(self.distribution) return DecoratedCommand def update_package_data(distribution): """update package_data to catch changes during setup""" build_py = distribution.get_command_obj("build_py") # distribution.package_data = find_package_data() # re-init build_py options which load package_data build_py.finalize_options() class NPM(Command): description = "install package.json dependencies using npm" user_options = [] node_modules = os.path.join(node_root, "node_modules") targets = [ os.path.join(here, "plotlywidget", "static", "extension.js"), os.path.join(here, "plotlywidget", "static", "index.js"), ] def initialize_options(self): pass def finalize_options(self): pass def get_npm_name(self): npmName = "npm" if platform.system() == "Windows": npmName = "npm.cmd" return npmName def has_npm(self): npmName = self.get_npm_name() try: check_call([npmName, "--version"]) return True except: return False def should_run_npm_install(self): package_json = os.path.join(node_root, "package.json") node_modules_exists = os.path.exists(self.node_modules) return self.has_npm() def run(self): has_npm = self.has_npm() if not has_npm: log.error( "`npm` unavailable. If you're running this command using sudo, make sure `npm` is available to sudo" ) env = os.environ.copy() env["PATH"] = npm_path if self.should_run_npm_install(): log.info( "Installing build dependencies with npm. This may take a while..." ) npmName = self.get_npm_name() check_call( [npmName, "install"], cwd=node_root, stdout=sys.stdout, stderr=sys.stderr, ) os.utime(self.node_modules, None) for t in self.targets: if not os.path.exists(t): msg = "Missing file: %s" % t if not has_npm: msg += "\nnpm is required to build a development version of widgetsnbextension" raise ValueError(msg) # update package data in case this created new files update_package_data(self.distribution) class CodegenCommand(Command): description = "Generate class hierarchy from Plotly JSON schema" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): if sys.version_info.major != 3 or sys.version_info.minor < 6: raise ImportError("Code generation must be executed with Python >= 3.6") from codegen import perform_codegen perform_codegen() def overwrite_schema(url): import requests req = requests.get(url) assert req.status_code == 200 path = os.path.join(here, "codegen", "resources", "plot-schema.json") with open(path, "wb") as f: f.write(req.content) def overwrite_bundle(url): import requests req = requests.get(url) assert req.status_code == 200 path = os.path.join(here, "plotly", "package_data", "plotly.min.js") with open(path, "wb") as f: f.write(req.content) def overwrite_plotlyjs_version_file(plotlyjs_version): path = os.path.join(here, "plotly", "offline", "_plotlyjs_version.py") with open(path, "w") as f: f.write( """\ # DO NOT EDIT # This file is generated by the updatebundle setup.py command __plotlyjs_version__ = "{plotlyjs_version}" """.format( plotlyjs_version=plotlyjs_version ) ) def overwrite_plotlywidget_version_file(version): path = os.path.join(here, "plotly", "_widget_version.py") with open(path, "w") as f: f.write( """\ # This file is generated by the updateplotlywidgetversion setup.py command # for automated dev builds # # It is edited by hand prior to official releases __frontend_version__ = "{version}" """.format( version=version ) ) def request_json(url): import requests req = requests.get(url) return json.loads(req.content.decode("utf-8")) def get_latest_publish_build_info(branch): url = ( r"https://circleci.com/api/v1.1/project/github/" r"plotly/plotly.js/tree/{branch}?limit=10000\&filter=completed" ).format(branch=branch) branch_jobs = request_json(url) # Get most recent successful publish build for branch builds = [ j for j in branch_jobs if j.get("workflows", {}).get("job_name", None) == "publish" and j.get("status", None) == "success" ] build = builds[0] # Extract build info return {p: build[p] for p in ["vcs_revision", "build_num", "committer_date"]} def get_bundle_schema_urls(build_num): url = ( "https://circleci.com/api/v1.1/project/github/" "plotly/plotly.js/{build_num}/artifacts" ).format(build_num=build_num) artifacts = request_json(url) # Find archive archives = [a for a in artifacts if a.get("path", None) == "plotly.js.tgz"] archive = archives[0] # Find bundle bundles = [a for a in artifacts if a.get("path", None) == "dist/plotly.min.js"] bundle = bundles[0] # Find schema schemas = [a for a in artifacts if a.get("path", None) == "dist/plot-schema.json"] schema = schemas[0] return archive["url"], bundle["url"], schema["url"] class UpdateSchemaCommand(Command): description = "Download latest version of the plot-schema JSON file" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): url = ( "https://raw.githubusercontent.com/plotly/plotly.js/" "v%s/dist/plot-schema.json" % plotly_js_version() ) overwrite_schema(url) class UpdateBundleCommand(Command): description = "Download latest version of the plot-schema JSON file" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): url = ( "https://raw.githubusercontent.com/plotly/plotly.js/" "v%s/dist/plotly.min.js" % plotly_js_version() ) overwrite_bundle(url) # Write plotly.js version file plotlyjs_version = plotly_js_version() overwrite_plotlyjs_version_file(plotlyjs_version) class UpdatePlotlyJsCommand(Command): description = "Update project to a new version of plotly.js" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): self.run_command("updatebundle") self.run_command("updateschema") self.run_command("codegen") class UpdateBundleSchemaDevCommand(Command): description = "Update the plotly.js schema and bundle from master" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): branch = "master" build_info = get_latest_publish_build_info(branch) archive_url, bundle_url, schema_url = get_bundle_schema_urls( build_info["build_num"] ) # Update bundle in package data overwrite_bundle(bundle_url) # Update schema in package data overwrite_schema(schema_url) # Update plotly.js url in package.json package_json_path = os.path.join(node_root, "package.json") with open(package_json_path, "r") as f: package_json = json.load(f) # Replace version with bundle url package_json["dependencies"]["plotly.js"] = archive_url with open(package_json_path, "w") as f: json.dump(package_json, f, indent=2) # update plotly.js version in _plotlyjs_version rev = build_info["vcs_revision"] date = build_info["committer_date"] version = "_".join([branch, date[:10], rev[:8]]) overwrite_plotlyjs_version_file(version) class UpdatePlotlyJsDevCommand(Command): description = "Update project to a new development version of plotly.js" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): self.run_command("updatebundleschemadev") self.run_command("jsdeps") self.run_command("codegen") class UpdatePlotlywidgetVersionCommand(Command): description = "Update package.json version of plotlywidget" user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): from plotly._version import git_pieces_from_vcs, render # Update plotly.js url in package.json package_json_path = os.path.join(node_root, "package.json") with open(package_json_path, "r") as f: package_json = json.load(f) # Replace version with bundle url pieces = git_pieces_from_vcs("widget-v", project_root, False) pieces["dirty"] = False widget_ver = render(pieces, "pep440")["version"] package_json["version"] = widget_ver with open(package_json_path, "w") as f: json.dump(package_json, f, indent=2) # write _widget_version overwrite_plotlywidget_version_file(widget_ver) graph_objs_packages = [ d[0].replace("/", ".") for d in os.walk("plotly/graph_objs") if not d[0].endswith("__pycache__") ] validator_packages = [ d[0].replace("/", ".") for d in os.walk("plotly/validators") if not d[0].endswith("__pycache__") ] versioneer_cmds = versioneer.get_cmdclass() setup( name="plotly", version=versioneer.get_version(), use_2to3=False, author="Chris P", author_email="chris@plot.ly", maintainer="Jon Mease", maintainer_email="jon@plot.ly", url="https://plot.ly/python/", project_urls={"Github": "https://github.com/plotly/plotly.py"}, description="An open-source, interactive graphing library for Python", long_description=readme(), long_description_content_type="text/markdown", classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Scientific/Engineering :: Visualization", ], license="MIT", packages=[ "plotly", "plotlywidget", "plotly.plotly", "plotly.offline", "plotly.io", "plotly.matplotlylib", "plotly.matplotlylib.mplexporter", "plotly.matplotlylib.mplexporter.renderers", "plotly.figure_factory", "plotly.data", "plotly.express", "_plotly_utils", "_plotly_utils.colors", "_plotly_future_", ] + graph_objs_packages + validator_packages, package_data={ "plotly": [ "package_data/*", "package_data/templates/*", "package_data/datasets/*", ], "plotlywidget": ["static/extension.js", "static/index.js"], }, data_files=[ ( "share/jupyter/nbextensions/plotlywidget", ["plotlywidget/static/extension.js", "plotlywidget/static/index.js"], ), ("etc/jupyter/nbconfig/notebook.d", ["plotlywidget.json"]), ], install_requires=["retrying>=1.3.3", "six"], zip_safe=False, cmdclass=dict( build_py=js_prerelease(versioneer_cmds["build_py"]), egg_info=js_prerelease(egg_info), sdist=js_prerelease(versioneer_cmds["sdist"], strict=True), jsdeps=NPM, codegen=CodegenCommand, updateschema=UpdateSchemaCommand, updatebundle=UpdateBundleCommand, updateplotlyjs=js_prerelease(UpdatePlotlyJsCommand), updatebundleschemadev=UpdateBundleSchemaDevCommand, updateplotlyjsdev=UpdatePlotlyJsDevCommand, updateplotlywidgetversion=UpdatePlotlywidgetVersionCommand, version=versioneer_cmds["version"], ), )