#!/usr/bin/env python3 # Copyright (C) 2018 # Associated Universities, Inc. Washington DC, USA. # # This library is free software; you can redistribute it and/or modify it # under the terms of the GNU Library General Public License as published by # the Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This library is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public # License for more details. # # You should have received a copy of the GNU Library General Public License # along with this library; if not, write to the Free Software Foundation, # Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA. # # Correspondence concerning AIPS++ should be addressed as follows: # Internet email: aips2-request@nrao.edu. # Postal address: AIPS++ Project Office # National Radio Astronomy Observatory # 520 Edgemont Road # Charlottesville, VA 22903-2475 USA """CASAtasks Python Module This is a standard python module that provides CASA tools and tasks without regular CASA's bespoke CLI. """ from __future__ import division, print_function classifiers = """\ Development Status :: 3 - Alpha Intended Audience :: Developers License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) Programming Language :: Python :: 2.7 Programming Language :: C++ Topic :: Software Development Topic :: Scientific/Engineering :: Astronomy Topic :: Software Development :: Libraries :: Python Modules Operating System :: MacOS :: MacOS X Operating System :: POSIX """ ## ## Without this zlib import at the beginning, building the casataks ## wheel on RHEL7 (w/ rh-python36-python-3.6.3-3.el7.x86_64) dies ## with a segmentation violation at: ## ## #0 0x00007f1d192ecf89 in fill_window () from /lib64/libz.so.1 ## #1 0x00007f1d192ed590 in deflate_slow () from /lib64/libz.so.1 ## #2 0x00007f1d192ee624 in deflate () from /lib64/libz.so.1 ## #3 0x00007f1d0851a195 in zlib_Compress_compress () from /opt/rh/rh-python36/root/usr/lib64/python3.6/lib-dynload/zlib.cpython-36m-x86_64-linux-gnu.so ## import zlib import sys import os try: import casatools from casatools.config import build as tools_config except: print("cannot find CASAtools (https://open-bitbucket.nrao.edu/projects/CASA/repos/casatools/browse) in PYTHONPATH") os._exit(1) from setuptools import setup, find_packages from distutils.dir_util import copy_tree, remove_tree from distutils.util import get_platform from distutils.cmd import Command from distutils.command.build import build from subprocess import Popen, PIPE from subprocess import call as Proc from datetime import datetime from textwrap import dedent from shutil import copy2 import subprocess import sysconfig import pickle import errno import time import re from os import listdir from os.path import isfile, join, islink from itertools import chain import argparse parser=argparse.ArgumentParser() parser.add_argument('--version', help='version') parser.add_argument('bdist_wheel', help='bdist_wheel') args=parser.parse_args() print (args.version) module_name = 'casatasks' pyversion = float(sys.version_info[0]) + float(sys.version_info[1]) / 10.0 if pyversion < 3: str_encode = str str_decode = str def pipe_decode(output): return output else: def str_encode(s): return bytes(s,sys.getdefaultencoding()) def str_decode(bs): return bs.decode(sys.getdefaultencoding(),"strict") def pipe_decode(output): if isinstance(output,bytes) or isinstance(output,bytearray): return str_decode(output) elif isinstance(output,tuple): return (str_decode(output[0]),str_decode(output[1])) else: return ("","") def compute_version( ): if (args.version != None ): print (args.version.split(".")) (major, minor, patch, feature) = args.version.split(".") return(int(major), int(minor), int(patch), int(feature),"","","") else: proc = Popen( [ "./version" ], stdout=PIPE, stderr=PIPE ) out,err = pipe_decode(proc.communicate( )) print(out) devbranchtag = out.split(" ")[0].strip() print(devbranchtag) releasetag = out.split(" ")[1].strip() dirty="" if (len(out.split(" ")) == 3): print("Latest commit doesn't have a tag. Adding -dirty flag to version string.") dirty="+" + out.split(" ")[2].strip() # "+" denotes local version identifier as described in PEP440 print(releasetag) devbranchversion = "" devbranchrevision = "" if (devbranchtag != releasetag): devbranchrevision = devbranchtag.split("-")[-1] if (devbranchtag.startswith("CAS-")): devbranchversion=devbranchtag.split("-")[1] else: devbranchversion=100 devbranchrevision = devbranchtag.split("-")[-1] else: isDevBranch = False (major, minor, patch, feature) = releasetag.split(".") return(int(major), int(minor), int(patch), int(feature), devbranchversion, devbranchrevision, dirty) (casatasks_major,casatasks_minor,casatasks_patch,casatasks_feature, devbranchversion, devbranchrevision, dirty) = compute_version( ) casatasks_version = '%d.%d.%d.%d%s' % (casatasks_major,casatasks_minor,casatasks_patch,casatasks_feature, dirty) if devbranchversion !="": casatasks_version = '%d.%d.%d.%da%s.dev%s%s' % (casatasks_major,casatasks_minor,casatasks_patch,casatasks_feature,devbranchversion,devbranchrevision,dirty) private_modules = [ 'src/modules/parallel', 'src/modules/imagerhelpers' ] xml_files = [ 'xml/imhead.xml', 'xml/immoments.xml', 'xml/imhistory.xml', 'xml/applycal.xml', 'xml/bandpass.xml', 'xml/blcal.xml', 'xml/calstat.xml', 'xml/concat.xml', 'xml/split.xml', 'xml/listobs.xml', 'xml/flagdata.xml', 'xml/flagcmd.xml', 'xml/setjy.xml', 'xml/cvel.xml', 'xml/cvel2.xml', 'xml/importuvfits.xml', 'xml/importfits.xml', 'xml/exportfits.xml', 'xml/exportuvfits.xml', 'xml/partition.xml', 'xml/listpartition.xml', 'xml/flagmanager.xml', 'xml/mstransform.xml', 'xml/tclean.xml', 'xml/immath.xml', 'xml/vishead.xml', 'xml/uvsub.xml', 'xml/spxfit.xml', 'xml/splattotable.xml', 'xml/specsmooth.xml', 'xml/specflux.xml', 'xml/smoothcal.xml', 'xml/specfit.xml', 'xml/imstat.xml', 'xml/slsearch.xml', 'xml/delmod.xml', 'xml/imsubimage.xml', 'xml/accor.xml', 'xml/asdmsummary.xml', 'xml/clearcal.xml', 'xml/conjugatevis.xml', 'xml/exportasdm.xml', 'xml/importasdm.xml', 'xml/clearstat.xml', 'xml/fixplanets.xml', 'xml/fixvis.xml', 'xml/phaseshift.xml', 'xml/fluxscale.xml', 'xml/ft.xml', 'xml/gaincal.xml', 'xml/gencal.xml', 'xml/uvcontsub3.xml', 'xml/testconcat.xml', 'xml/apparentsens.xml', 'xml/hanningsmooth.xml', 'xml/imcollapse.xml', 'xml/imcontsub.xml', 'xml/imdev.xml', 'xml/imfit.xml', 'xml/impbcor.xml', 'xml/importasap.xml', 'xml/importatca.xml', 'xml/importfitsidi.xml', 'xml/importgmrt.xml', 'xml/importnro.xml', 'xml/importvla.xml', 'xml/impv.xml', 'xml/imrebin.xml', 'xml/imreframe.xml', 'xml/imregrid.xml', 'xml/imsmooth.xml', 'xml/imtrans.xml', 'xml/imval.xml', 'xml/initweights.xml', 'xml/listcal.xml', 'xml/listfits.xml', 'xml/listhistory.xml', 'xml/listsdm.xml', 'xml/listvis.xml', 'xml/makemask.xml', 'xml/polcal.xml', 'xml/polfromgain.xml', 'xml/predictcomp.xml', 'xml/rerefant.xml', 'xml/rmfit.xml', 'xml/rmtables.xml', 'xml/sdatmcor.xml', 'xml/sdbaseline.xml', 'xml/sdcal.xml', 'xml/sdfit.xml', 'xml/sdfixscan.xml', 'xml/sdgaincal.xml', 'xml/sdimaging.xml', 'xml/sdsmooth.xml', 'xml/tsdimaging.xml', 'xml/nrobeamaverage.xml', 'xml/sdtimeaverage.xml', 'xml/simalma.xml', 'xml/simobserve.xml', 'xml/simanalyze.xml', 'xml/feather.xml', 'xml/statwt.xml', 'xml/virtualconcat.xml', 'xml/uvcontsub.xml', 'xml/uvmodelfit.xml', 'xml/visstat.xml', 'xml/widebandpbcor.xml', 'xml/importmiriad.xml', 'xml/plotweather.xml', 'xml/plotants.xml', 'xml/fringefit.xml', 'xml/plotbandpass.xml', 'xml/sdintimaging.xml', 'xml/sdpolaverage.xml', 'xml/sdsidebandsplit.xml', 'xml/plotprofilemap.xml', 'xml/imbaseline.xml', ] if pyversion < 3: str_encode = str str_decode = str def pipe_decode(output): return output else: def str_encode(s): return bytes(s,sys.getdefaultencoding()) def str_decode(bs): return bs.decode(sys.getdefaultencoding(),"strict") def pipe_decode(output): if isinstance(output,bytes) or isinstance(output,bytearray): return str_decode(output) elif isinstance(output,tuple): return ( None if output[0] is None else str_decode(output[0]), None if output[1] is None else str_decode(output[1]) ) else: return ("","") if sys.platform == 'darwin': def islib(l): return l.endswith('.dylib') elif sys.platform == 'linux2' or sys.platform == 'linux': def islib(l): # 'lib_....so return l.find('.so') > 3 else: sys.exit("oops, had not planned on building on %s" % sys.platform) def distutils_dir_name(dname): """Returns the name of a distutils build directory""" f = "{dirname}.{platform}-{version[0]}.{version[1]}" return f.format(dirname=dname,platform=get_platform(),version=sys.version_info) def mkpath(path): try: os.makedirs(path) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise def generate_pyinit(moduledir,tasks): """Generate __init__.py for the module """ outfile = os.path.join(moduledir,'__init__.py') task_files_dict = list(map(lambda p: "'%s': %s" % p, zip(tasks,map(lambda x: "__os.path.join(__cwd,'%s')" % x.replace('xml','__xml__',1),xml_files)))) with open(outfile, "w") as fd: fd.write("""###########################################################################\n""") fd.write("""########################## generated by setup.py ##########################\n""") fd.write("""###########################################################################\n""") fd.write("from __future__ import absolute_import\n") fd.write("from casatools import logsink as _logsink\n") fd.write("import os as __os\n") fd.write("__cwd = __os.path.dirname(__os.path.abspath(__file__))\n") fd.write("__name__ = '%s'\n" % module_name) fd.write("__all__ = [ \"casalog\", \"version\", \"version_string\",\n") for task in tasks: fd.write(" '%s',\n" % task) fd.write(" ]\n\n") fd.write("""from . import config\n""") fd.write("""casalog = _logsink( config.logfile )\n\n""") for task in tasks: fd.write("from .%s import %s\n" % (task,task)) fd.write("\n") fd.write("def version( ): return [ %d, %d, %d, %d ]\n" % (casatasks_major,casatasks_minor,casatasks_patch,casatasks_feature)) fd.write("def version_string( ): return \"%s\"\n" % casatasks_version) fd.write("casalog.setglobal(True)\n") fd.write("\n") fd.write("def xml_interface_defs( ): return { %s }\n" % ", ".join(task_files_dict)) fd.write("\n") # Check if MPIEnvironment can be imported at all fd.write('mpi_env_found=False\n') fd.write('try:\n') fd.write(' from casampi.MPIEnvironment import MPIEnvironment\n') fd.write(' mpi_env_found=True\n') fd.write('except:\n') fd.write(' mpi_env_found=False\n') mpi_import_str = '\n'.join(( "# When in MPI mode, this will put servers into their serve() loop.", "# From this point on user scripts can use tclean parallelization, Tier0 parallelization,", "# and MMS-parallel tasks", "try:", " import importlib", " _clith_spec = importlib.util.find_spec('casalith')", " # Defer to later if in casalith", " if _clith_spec is None:", " import casampi.private.start_mpi", "except ImportError:", " pass\n")) fd.write(mpi_import_str) fd.write("package_variant='modular'\n") fd.write("try:\n") fd.write(" if _clith_spec is not None:\n") fd.write(" package_variant='casalith'\n") fd.write("except:\n") fd.write(" pass\n") fd.write("try:\n") fd.write(" _pipe_spec = importlib.util.find_spec('pipeline')\n") fd.write(" if _pipe_spec is not None:\n") fd.write(" package_variant='pipeline'\n") fd.write("except:\n") fd.write(" pass\n") # Only the mpi "client" should write the version information (otherwise the logsink will crash) fd.write("import platform\n") fd.write('if mpi_env_found and MPIEnvironment.is_mpi_enabled:\n') fd.write(' if MPIEnvironment.is_mpi_client:\n') fd.write(' try:\n') fd.write(' casalog.post("Python version " + platform.python_version())\n') fd.write(' casalog.post("CASA Version " + package_variant.upper() + " %s")\n' % casatasks_version) fd.write(' casalog.post("MPI Enabled")\n') fd.write(' except:\n') fd.write(' print("Error: the logfile is not writable")\n') fd.write('else:\n') fd.write(' try:\n') fd.write(' casalog.post("Python version " + platform.python_version())\n') fd.write(' casalog.post("CASA Version " + package_variant.upper() + " %s")\n' % casatasks_version) fd.write(' except:\n') fd.write(' print("Error: the logfile is not writable")\n') fd.write("\n") fd.write("from datetime import datetime as _time\n") fd.write("telemetry_starttime = str(_time.now())\n") fd.write("import os\n") fd.write("serial_run = mpi_env_found and not MPIEnvironment.is_mpi_enabled\n") fd.write("mpi_run_client = mpi_env_found and MPIEnvironment.is_mpi_enabled and MPIEnvironment.is_mpi_client\n") fd.write("nompi_or_serial_or_client = not mpi_env_found or serial_run or mpi_run_client\n") fd.write("telemetry_available=False\n") fd.write("if nompi_or_serial_or_client:\n") fd.write(" try:\n") fd.write(" import casatelemetry\n") fd.write(" telemetry_available=True\n") fd.write(" except:\n") fd.write(' casalog.post("Can\'t import casatelemetry module.")\n') fd.write("if telemetry_available and config.telemetry_enabled and nompi_or_serial_or_client:\n") fd.write(" try:\n") fd.write(" telemetrylogdirectory = None\n") fd.write(" if config.rcdir != None:\n") fd.write(" telemetrylogdirectory = config.rcdir\n") fd.write(" if config.telemetry_log_directory != None:\n") fd.write(" telemetrylogdirectory = casatasks.config.telemetry_log_directory\n") fd.write(" telemetrylogger = casatelemetry.casatelemetry.telemetry(telemetrylogdirectory)\n") fd.write(" except:\n") fd.write(" telemetrylogger = casatelemetry.casatelemetry.telemetry()\n") fd.write(" def logstop():\n") # Telemetry may be stopped during runtime so check if it is still enabled fd.write(' if telemetrylogger.telemetry_enabled:\n') fd.write(" telemetry_stoptime = str(_time.now())\n") fd.write(' telemetrylogger.logger.info(telemetry_stoptime + " :: " + str(os.getpid()) + " :: CASAStop :: Stopping CASA at: " + telemetry_stoptime + \n') fd.write(' " Version " + version_string() + " Platform: " + platform.platform() + " Start time: " + telemetry_starttime + " Variant: " + package_variant)\n') #fd.write(' print("mpi_env " + str(mpi_env_found))\n') fd.write(' if not mpi_env_found or (mpi_env_found and MPIEnvironment.is_mpi_client):\n') fd.write(' telemetrylogger.submitStatistics()\n') fd.write(' if telemetrylogger.telemetry_enabled:\n') fd.write(' telemetrylogger.logger.info(telemetry_starttime + " :: " + str(os.getpid()) + " :: CASAStart :: Starting CASA at: " + telemetry_starttime + \n') fd.write(' " Version " + version_string() + " Platform: " + platform.platform() + " Variant: " + package_variant)\n') fd.write(" import atexit\n") fd.write(" atexit.register(logstop)\n") fd.write('if telemetry_available and config.crashreporter_enabled:\n') fd.write(" casatelemetry.CrashReporter.init(config.logfile)\n") class BuildCasa(build): description = "Description of the command" user_options = [] # This method must be implemented def initialize_options(self): print("initializing options...") build.initialize_options(self) # This method must be implemented def finalize_options(self): #self.plat_name = get_platform( ) print("finalizing options...") build.finalize_options(self) def run(self): libdir = os.path.join("build",distutils_dir_name('lib')) moduledir = os.path.join(libdir,"casatasks") xmldir = os.path.join(moduledir,"__xml__") copy_tree('src',moduledir) print("generating task python files...") proc = Popen( [tools_config['build.compiler.xml-casa'], "output-task=%s" % moduledir, "-task"] + xml_files, stdout=subprocess.PIPE ) (output, error) = pipe_decode(proc.communicate( )) exit_code = proc.wait( ) if exit_code != 0: sys.exit('python file generation failed') tasks = output.split( ) generate_pyinit(moduledir,tasks) mkpath(xmldir) for x in xml_files: copy2(x,xmldir) class TestCasa(Command): user_options = [] def initialize_options(self): self.__test_dir = "build/%s" % distutils_dir_name('testing') self.__lib_dir = os.path.abspath("build/%s" % distutils_dir_name('lib')) self.__env = os.environ.copy( ) if 'PYTHONPATH' in self.__env: existing_paths = ":".join(list(map(os.path.abspath,self.__env['PYTHONPATH'].split(':')))) self.__env['PYTHONPATH'] = "%s:%s" % (self.__lib_dir,existing_paths) else: self.__env['PYTHONPATH'] = self.__lib_dir self.__regression_dir = "build/%s" % distutils_dir_name('regression') #self.__regression_ref_dir = "tests/output-regression/reference-%d.%d" % (sys.version_info[0],sys.version_info[1]) self.__regression_ref_dir = "tests/output-regression/reference" self.__regression_sample_gen = "scripts/output-snapshot" self.__regression_sample_dir = "%s/%s" % (self.__regression_dir,"output") def finalize_options(self): pass def __dump_output(self, working_dir, bname, out, err): stdout_path = "%s/%s-stdout.txt" % (working_dir,bname) stdout_fd = open(stdout_path, 'w') stdout_fd.write(out) stdout_fd.close( ) stderr_path = "%s/%s-stderr.txt" % (working_dir,bname) stderr_fd = open(stderr_path, 'w') stderr_fd.write(err) stderr_fd.close( ) return (stdout_path,stderr_path) def __run_test(self,tabwidth,test_path,working_dir): label = '.'.join(os.path.basename(test_path).split('.')[:-1]) sys.stdout.write(label + '.' * (tabwidth - len(label))) sys.stdout.flush( ) proc = Popen( [sys.executable,test_path], cwd=working_dir, env=self.__env, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) (output, error) = pipe_decode(proc.communicate( )) exit_code = proc.wait( ) (stdout_path,stderr_path) = self.__dump_output(working_dir,"log",output,error) print(" ok" if exit_code == 0 else " fail") return (exit_code, label, stdout_path, stderr_path) def __generate_sample(self): if os.path.exists(self.__regression_dir): remove_tree(self.__regression_dir) mkpath(self.__regression_sample_dir) proc = Popen( [self.__regression_sample_gen,"out=%s" % self.__regression_sample_dir], env=self.__env, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) (output, error) = pipe_decode(proc.communicate( )) exit_code = proc.wait( ) self.__dump_output(self.__regression_dir,"sample-generation",output,error) return exit_code def __compare_reg(self, tabwidth, label, refpath, samplepath): sys.stdout.write(label + '.' * (tabwidth - len(label))) sys.stdout.flush( ) proc = Popen( ["/usr/bin/diff",refpath,samplepath], env=self.__env, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) (output, error) = pipe_decode(proc.communicate( )) exit_code = proc.wait( ) print(" ok" if exit_code == 0 else " fail") (op,ep) = self.__dump_output(os.path.dirname(samplepath),label,output,error) return (exit_code, label, op, ep) def __collect_tests(self, testdir): tests = [ ] for dir, subdirs, files in os.walk(testdir): for f in files: if f.endswith(".py") and f.startswith("test_"): workingdir = "%s/%s" % (self.__test_dir,f[:-3]) mkpath(workingdir) tests.append((os.path.abspath("%s/%s" % (dir,f)),workingdir)) return tests def __collect_regression_files(self, regdir): regression = { } for dir, subdirs, files in os.walk(regdir): for f in files: if f != "log.txt": regression[f] = "%s/%s" % (dir,f) return regression def __collect_regression(self): regression_ref = { } regression_sample = { } if os.path.isdir(self.__regression_ref_dir): if isexe(self.__regression_sample_gen): if self.__generate_sample( ) == 0: regression_ref = self.__collect_regression_files(self.__regression_ref_dir) regression_sample = self.__collect_regression_files(self.__regression_sample_dir) else: print("warning, generation of regression sample failed; skipping regression test") else: print( "warning, regression sample generator (%s) does not exist; skipping regression test" % self.__regression_sample_gen) else: print("warning, regression reference (%s) does not exist; skipping regression test" % self.__regression_ref_dir) return (regression_ref, regression_sample) def run(self): if os.path.exists(self.__test_dir): remove_tree(self.__test_dir) mkpath(self.__test_dir) tests = self.__collect_tests("tests/tasks") (regression_ref, regression_sample) = self.__collect_regression( ) testwidth = 0 if len(tests) == 0 else max(map(lambda x: len(os.path.basename(x[0]))+3,tests)) regressionwidth = 0 if len(regression_ref) == 0 else max(map(lambda x: len(x)+3,regression_ref.keys( ))) tabwidth = max(testwidth,regressionwidth,45) testresults = list(map(lambda params: self.__run_test(tabwidth,*params),tests)) len_message = "regression file count" print( len_message + '.' * (tabwidth - len(len_message)) + (" ok" if len(regression_ref) == len(regression_sample) else " fail") ) regression_keys = filter(lambda k: k in regression_sample, regression_ref.keys( )) regressionresults = list(map(lambda k: self.__compare_reg(tabwidth,k,regression_ref[k],regression_sample[k]), regression_keys)) results = testresults + regressionresults print('-' * (tabwidth + 8)) passed = list(filter(lambda v: v[0] == 0,results)) failed = list(filter(lambda v: v[0] != 0,results)) print("test summary %d passed, %d failed" % (len(passed),len(failed))) print("OK" if len(failed) == 0 else "FAIL") sys.exit(0 if len(failed) == 0 else 1) cmd_setup = { 'build': BuildCasa, 'test': TestCasa } try: from wheel.bdist_wheel import bdist_wheel class casa_binary_wheel(bdist_wheel): user_options = bdist_wheel.user_options + [ # The format is (long option, short option, description). ('debug', None, 'build a debugging version'), ('version=', None, 'Silence distutils when passing version for bdist_wheel from the command line'), ] def initialize_options(self): self.version = None bdist_wheel.initialize_options(self) if '--debug' in sys.argv: global _debug_build_ _debug_build_ = True self.debug = 1 else: self.debug = 0 def run(self): global doing_wheel_build doing_wheel_build = True bdist_wheel.run(self) doing_wheel_build = False wheel_build = casa_binary_wheel cmd_setup['bdist_wheel'] = casa_binary_wheel except ImportError: pass # custom command not needed if wheel is not installed setup( name=module_name,version=casatasks_version, maintainer="Darrell Schiebel", maintainer_email="drs@nrao.edu", author="CASA development team", author_email="aips2-request@nrao.edu", url="https://open-bitbucket.nrao.edu/projects/CASA/repos/casatools/browse", download_url="https://casa.nrao.edu/download/", license="GNU Library or Lesser General Public License (LGPL)", packages=[ module_name, "%s.__xml__" % module_name, "%s.private" % module_name, "%s.private.parallel" % module_name, "%s.private.imagerhelpers" % module_name ], classifiers=[ 'Programming Language :: Python :: %s' % pyversion ], description="the CASA tasks", long_description="The CASAtasks are a collection of (mostly) stateless functions for\nthe analysis of radio astronomy observations.", cmdclass=cmd_setup, package_dir={module_name: os.path.join('build',distutils_dir_name('lib'), module_name)}, package_data={'': ['*.xml','*.txt']}, install_requires=['casatools==%s' % casatools.version_string( ), 'astropy', 'certifi', 'matplotlib', 'scipy', 'bokeh>=2.2.3', 'dask>=2.13.0', 'distributed>=2.9.3', 'numcodecs>=0.6.3', 'numpy>=1.19.5', 'pandas>=0.25.2', 'xarray>=0.16.1', 'zarr>=2.3.2'] )