#!/usr/bin/python

# tz_import.py
#       --copyright--                   Copyright 2009 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       March 27, 2009          bar
#       November 28, 2009       bar     add the re_import_file_to_globals() routine to here for safe-keeping
#       November 30, 2009       bar     do a little dance to allow this program file to be inserted inside another so that the other can bootstrap itself from the net with only one .py file resident.
#                                       notes about basic auth hits
#                                       handle single file string passed to download_module_files()
#       November 12, 2011       bar     take the s out of the url
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       August 2, 2014          bar     values in dict returned by import_files are the original values
#       January 18, 2016        bar     except syntax change
#       --eodstamps--
##      \file
#       \namespace              tzpython.tz_import
#
#
#       Code to import modules under program control, including downloading them from tranzoa.net
#
#       Keep in mind that there's a bootstrap problem here. If this can't be imported, it's hard for it to do the download logic.
#
#       How to get password protected stuff:
#
#           url             = "https://www.tranzoa.net/somewhere/"
#           auth_handler    = urllib2.HTTPBasicAuthHandler()
#           auth_handler.add_password('Realm. The AuthName name from .htaccess - assuming that AuthType is Basic', 'www.tranzoa.net', username, password)
#           opener = urllib2.build_opener(auth_handler)
#           urllib2.install_opener(opener)
#           fls             = tz_import.download_module_files([ "x.py" ], url = url, print_exceptions = True)
#
#


import  os
from    types   import  ListType, TupleType


def import_files(files, err = False) :
    """
        Try to import all the modules in the given list or dictionary-keys of modules.
        If a module is successfully imported, get rid of its entry in the returned list/dictionary.

        \returns
            The updated dictionary or list of un-imported modules in whichever form it was passed (dictionary values will be the original values).

    """

    rl          = 0
    try         :
        files   = files.keys()
    except AttributeError :
        rl      = 1
        files   = files

    fls         = []
    for fn in files :
        mn      = fn
        if  mn.lower().endswith(".py") :
            mn  = fn[:-3]                           # just in case
        try     :
            m   = __import__(mn, globals(), locals(), [])
            globals()[mn] = m
        except ImportError :
            if  err :
                raise
            fls.append(fn)
        pass

    retval      = fls
    if  not rl  :
        retval  = {}
        for fn in fls   :
            retval[fn]  = files[fn]
        pass

    return(retval)




def download_module_files(files, local_dir = None, url = None, print_exceptions = False) :
    """
        Try to download the given .py modules (assume a .py extension in the file's url and file name even if it's not given in 'files').
        'files' can be a list or dictionary.

        The files are stored in the given directory (default ".").

        \returns
            List of files successfully downloaded.
    """

    import  httplib
    import  socket
    import  sys
    import  traceback
    import  urllib2

    local_dir   = local_dir or "."
    url         = url       or "https://www.tranzoa.net/alex/tzpython_public/"
    if  not url.endswith("/") :
        url    += "/"

    try         :
        files   = files.keys()
    except AttributeError :
        files   = files

    if  isinstance(files, basestring) :
        files   = [ files ]
    if  not isinstance(files, ( ListType, TupleType ) ) :
        files   = [ files ]

    files_gotten    = []

    for mn in files :
        fn          = mn
        if  not fn.lower().endswith('.py') :
            fn      = fn + ".py"
        try         :
            getter  = urllib2.urlopen(url + fn)         # urllib2 seems to pick up on 404's, which urllib doesn't appear to do
            s       = getter.read()
            getter  = None

            fn      = os.path.join(local_dir, fn)
            f       = open(fn + ".tmp", "wb")
            f.write(s)
            f.close()
            os.rename(fn + ".tmp", fn)
            files_gotten.append(mn)
        except socket.error :
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except socket.herror :
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except socket.gaierror :
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except         OSError :
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except         IOError :                        # HTTPError is raised by urllib2 on 404's. But it doesn't come with info - and somehow IOError gets triggered ok
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except     MemoryError :                        # we've run out of memory during the read or what?
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except  AttributeError :                        # something is very wrong with the server
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        except httplib.HTTPException :
            if  print_exceptions :
                e   = sys.exc_info()
                traceback.print_exception(e[0], e[1], e[2])
            pass
        pass

    return(files_gotten)




import_sigs     = { }

def re_import_file_to_globals(file_name, force = False) :
    """
        Code to show how to do it. Re-import modules, that is.
        NEEDS tzlib - which code in this module imports or downloads!
        This doesn't work when called from outside module. That is, the globals do not seem to pick up the new module.
    """

    import      sys

    import_files([ 'tzlib.py' ])
    sig         = tzlib.file_signature(file_name)

    mn          = file_name
    mn          = os.path.splitext(os.path.basename(mn))[0]

    if  force or (mn not in import_sigs) or (import_sigs[mn] != sig) :
        if  mn in sys.modules :
            del(sys.modules[mn])
            del(globals()  [mn])

        sys.modules[mn]  = __import__( mn)
        globals()  [mn]  = sys.modules[mn]

        import_sigs[mn] = sig

        return(True)

    return(False)





if  __name__ == '__main__' :

    try :

        #
        #               Allow this script to be inserted inside another script.
        #               The other one would set this 'do_not_do_tz_import_main' value at the top, globally.
        #               And then it could have its own __main__ logic after this module's routines are all compiled.
        #
        don_t_do_it     = do_not_do_tz_import_main

    except NameError    :

        don_t_do_it     = False

    if  not don_t_do_it :
        import_files([ 'tzlib.py'])

        print tzlib.c_string("It worked!\n")

        re_import_file_to_globals("latlon.py")
        print "Should be  90: ", latlon.lat_in_world(200)

        re_import_file_to_globals("latlon.py")
        print "Should be -90:", latlon.lat_in_world(-210)
    pass

#
#
# eof
