#!/usr/bin/python
# -*- coding: utf-8 -*-
"""SpacePy: Space Science Tools for Python
SpacePy is a package of tools primarily aimed at the space science community.
This __init__.py file sets the parameters for import statements.
If running the ipython shell, simply type '?' after any command for help.
ipython also offers tab completion, so hitting tab after '<module name>.'
will list all available functions, classes and variables.
Detailed HTML documentation is available online by typing:
>>> spacepy.help()
Most functionality is in spacepy's submodules. Each module has specific
help available:
Copyright 2010-2016 Los Alamos National Security, LLC.
"""
import configparser
import errno
import functools
import multiprocessing
import os
import os.path
import re
import shutil
import sys
import warnings
import webbrowser
[docs]
def help(keyword=None):
"""Launches web browser with SpacePy documentation
Parameters
----------
keyword : str, optional
.. versionadded:: 0.7.0
Search for this keyword. If not specified, opens the front page
of the documentation.
Notes
-----
Online help is always for the latest release of SpacePy.
Examples
--------
>>> import spacepy
>>> spacepy.help("coordinates")
"""
print('Opening docs for latest release. Installed SpacePy is {}.'.format(
__version__))
url = 'https://spacepy.github.io/' if keyword is None\
else f'https://spacepy.github.io/search.html?q={keyword}'
webbrowser.open(url)
# put modules here that you want to be accessible through 'from spacepy import *'
__all__ = ["seapy", "toolbox", "poppy", "coordinates", "time", "omni",
"irbempy", "empiricals", "pycdf",
"datamanager", "datamodel", "ae9ap9"]
# Make sure the Fortran and other runtime libs from binary wheel are findable
libs = os.path.join(os.path.dirname(__file__), 'libs')
if sys.platform == 'win32' and os.path.isdir(libs):
if os.environ.get('PATH'):
if not libs in os.environ['PATH']:
os.environ['PATH'] += (';' + libs)
else: # empty or nonexistent PATH
os.environ['PATH'] = libs
try:
os.add_dll_directory(libs)
except AttributeError: # Python 3.8+ only
pass
#actual deprecation decorator
def _deprecator(version, message, docstring, func):
#this is the actual, deprecated function
@functools.wraps(func)
def _deprecated(*args, **kwargs):
warnings.warn(message, DeprecationWarning, stacklevel=2)
return func(*args, **kwargs)
if func.__doc__ is None:
doclines = []
else:
doclines = func.__doc__.split('\n')
# Docstring SHOULD be a single non-blank line with summary, blank line,
# and then the rest of the content. Want to put the deprecation
# information just before the first line of "rest of content"
isblank = [not bool(l.strip()) for l in doclines]
# All places with a non-blank line following a blank
paragraphs = [i for i in range(len(isblank))
if not isblank[i] and
(i == 0 or isblank[i - 1])]
if not paragraphs: # No non-blank, guess indentation, insert at end
leading = ' '
insert_at = len(doclines) #insert at end
elif len(paragraphs) == 1: # Get indent from only para, insert at end
l = doclines[paragraphs[0]]
leading = l[:len(l) - len(l.lstrip())]
insert_at = len(doclines)
else: # Get indent from 2nd paragraph, insert just before it
l = doclines[paragraphs[1]]
leading = l[:len(l) - len(l.lstrip())]
# Insert before blank line before the paragraph.
insert_at = paragraphs[1] - 1
to_insert = [
'',
leading + '.. deprecated:: ' + version,
] \
+ [leading + ' ' + d for d in docstring.split('\n')]
doclines[insert_at:insert_at] = to_insert
_deprecated.__doc__ = '\n'.join(doclines)
return _deprecated
[docs]
def deprecated(version, message, docstring=None):
"""Decorator to deprecate a function/method
Modifies a function so that calls to it raise
``DeprecationWarning`` and the docstring has a deprecation
note added in accordance with `numpydoc format
<https://numpydoc.readthedocs.io/en/latest/format.html#sections>`_
Parameters
==========
version : str
What is the first version where this was deprecated?
message : str
Message to include in the deprecation warning and in the
docstring.
Other Parameters
================
docstring : str
.. versionadded:: 0.2.2
If specified, ``docstring`` will be added to the modified function's
docstring instead of ``message`` (which will only be used in the
deprecation warning.) It can be a multi-line string (separated with
``\\n``). It will be indented to match the existing docstring.
Notes
=====
On Python 2, the deprecated function's signature won't be preserved.
The function will work but will not have proper argument names listed
in e.g. ``help``.
This warning will show as coming from SpacePy, not the deprecated
function.
Examples
========
>>> import spacepy
>>> @spacepy.deprecated('0.2.1', 'Use a different function instead',
... docstring='A different function is better\\n'
... 'because of reasons xyz')
... def foo(x):
... '''This is a test function
...
... It may do many useful things.
... '''
... return x + 1
>>> help(foo)
Help on function foo in module __main__:
foo(x)
This is a test function
.. deprecated:: 0.2.1
A different function is better
because of reasons xyz
It may do many useful things.
>>> foo(2)
DeprecationWarning: Use a different function instead
3
"""
message = str(message)
version = str(version)
if docstring is None:
docstring = message
return functools.partial(_deprecator, version, message, docstring)
# Expose definitions from modules in this package.
# since datamodel depends on top level, delay the variable binding
from . import datamodel
dmarray = datamodel.dmarray
SpaceData = datamodel.SpaceData
#package info
__version__ = '0.7.0'
__author__ = 'The SpacePy Team'
__team__ = ['Steve Morley', 'Josef Koller', 'Dan Welling', 'Brian Larsen', 'Jon Niehof', 'Mike Henderson']
__contact__ = 'spacepy@lanl.gov'
__license__ = """SpacePy: Space Science Tools for Python
Copyright 2010 Triad National Security, LLC.
All Rights Reserved.
This is open source software; you can redistribute it and/or modify it under the
terms of the Python Software Foundation License. If software is modified to
produce derivative works, such modified software should be clearly marked, so
as not to confuse it with the version available from LANL. Full text of the
Python Software Foundation License can be found in the LICENSE.md file in the
main development branch of the repository (https://github.com/spacepy/spacepy).
CDF library support is provided by an unmodified library subject to the following license:
Common Data Format (CDF)
Space Physics Data Facility
NASA/Goddard Space Flight Center
This software may be copied or redistributed as long as it is not sold
for profit, but it can be incorporated into any other substantive
product with or without modifications for profit or non-profit. If the
software is modified, it must include the following notices:
- The software is not the original (for protection of the original
author's reputations from any problems introduced by others)
- Change history (e.g. date, functionality, etc.)
This copyright notice must be reproduced on each copy made. This software is
provided as is without any express or implied warranties whatsoever.
"""
if sys.platform == 'win32':
__license__ += \
"""
Fortran library support provided by MinGW. The MinGW base runtime package has been placed in the public domain, and is not governed by copyright.
"""
else:
__license__ += \
"""
Fortran library support in binary wheels is distributed under the GCC Runtime Library exception.
"""
__citation__ = """When publishing research which used SpacePy, please provide appropriate
credit to the SpacePy team via citation or acknowledgement.
To cite SpacePy in publications, use (BibTeX code):
@INPROCEEDINGS{spacepy11,
author = {{Morley}, S.~K. and {Koller}, J. and {Welling}, D.~T. and {Larsen}, B.~A. and {Henderson}, M.~G. and {Niehof}, J.~T.},
title = "{Spacepy - A Python-based library of tools for the space sciences}",
booktitle = "{Proceedings of the 9th Python in science conference (SciPy 2010)}",
year = 2011,
address = {Austin, TX}
}
Certain modules may provide additional citations in the __citation__
attribute. Contact a module's author before publication or public
presentation of analysis performed by that module. This allows the author
to validate the analysis and receive appropriate credit for his or her
work.
"""
#Global spacepy configuration information
config = {}
def _write_defaults(rcfile, defaults, section='spacepy'):
"""Write configuration defaults out to a file if they aren't there"""
f = open(rcfile, 'r+t') #Avoid race condition, open for read and write
try:
rclines = f.readlines()
writeme = list(defaults)
#Places where sections start
secidx = [i for i in range(len(rclines))
if re.match(r"^\[[^\[\]]*\]\n$", rclines[i])]
#Line containing the start of this section
thissec = next((i for i in secidx
if rclines[i] == '[{section}]\n'.format(
section=section)), -1)
if thissec != -1: #this section exists, read it
#Find the line that starts the next section
nextsecidx = secidx.index(thissec) + 1
if nextsecidx < len(secidx):
nextsec = secidx[nextsecidx]
else:
nextsec = None
#Read only this section for lines matching default values
for l in rclines[thissec:nextsec]:
#For each key, does the line match, commented or not?
for k in writeme:
if re.match(r'(#\s?)?{key}\s?[:=]'.format(key=k),
l):
writeme.remove(k)
break
#This is what we'll actually write to the file
writeme_verb = [f"#SpacePy {__version__} default {k}: {defaults[k]}\n"
f"#{k}: {defaults[k]}\n"
for k in sorted(writeme)]
#Add writeme to end of this section
rclines[nextsec:nextsec] = writeme_verb
#Write the new contents
f.seek(0)
f.writelines(rclines)
finally:
f.close()
# import some settings
def _read_config(rcfile):
"""Read configuration information from a file"""
global config
defaults = {'keepalive': str(True),
'ncpus': str(multiprocessing.cpu_count()),
'qindenton_url': 'http://virbo.org/ftp/QinDenton/hour/merged/latest/WGhour-latest.d.zip',
'qd_daily_url': 'https://rbsp-ect.newmexicoconsortium.org/data_pub/QinDenton/',
'omni2_url': 'https://spdf.gsfc.nasa.gov/pub/data/omni/omni_cdaweb/hourly/',
'leapsec_url': 'https://maia.usno.navy.mil/ser7/tai-utc.dat',
'psddata_url': 'http://spacepy.lanl.gov/repository/psd_dat.sqlite',
'support_notice': str(True),
'enable_old_data_warning': str(True),
}
#Functions to cast a config value; if not specified, value is a string
str2bool = lambda x: x.lower() in ('1', 'yes', 'true', 'on')
caster = {'keepalive': str2bool,
'ncpus': int,
'support_notice': str2bool,
'enable_old_data_warning': str2bool,
}
cp = configparser.ConfigParser(defaults)
try:
successful = cp.read([rcfile])
except configparser.Error:
successful = []
if successful: # New file structure
try:
config = dict(cp.items('spacepy'))
except configparser.NoSectionError:
successful = []
if not successful: # Old or bad file structure, wipe it out
config = {}
cp = configparser.ConfigParser()
cp.add_section('spacepy')
with open(rcfile, 'w') as cf:
cp.write(cf)
for k in defaults:
if not k in config:
config[k] = defaults[k]
_write_defaults(rcfile, defaults)
for k in caster:
config[k] = caster[k](config[k])
def _find_spacepy_dir():
"""Determine the .spacepy directory (DOT_FLN)
This does not create the directory (although will create the parent
directory if ``SPACEPY`` environment variable is set).
Returns
-------
DOT_FLN : str
Full path to the .spacepy directory.
"""
if 'SPACEPY' in os.environ:
parentdir = os.path.abspath(os.path.expanduser(os.environ['SPACEPY']))
if not os.path.exists(parentdir):
try:
os.makedirs(parentdir)
except OSError as e:
if e.errno != errno.EEXIST: # FileExistsError in 3.3+
raise
elif 'HOME' in os.environ:
parentdir = os.environ['HOME']
elif 'HOMEDRIVE' in os.environ and 'HOMEPATH' in os.environ:
parentdir = os.path.join(
os.environ['HOMEDRIVE'], os.environ['HOMEPATH'])
else:
parentdir = os.path.expanduser('~')
return os.path.join(parentdir, '.spacepy')
def _populate_spacepy_dir(DOT_FLN):
"""Create .spacepy directory and populate.
Makes sure created and data directory exists.
Parameters
----------
DOT_FLN : str
Full path to the .spacepy directory.
"""
if not os.path.exists(DOT_FLN):
try:
os.mkdir(DOT_FLN)
except OSError as e:
if e.errno != errno.EEXIST:
raise
dataout = os.path.join(DOT_FLN, 'data')
if not os.path.exists(dataout):
datadir = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'data')
try:
os.mkdir(dataout)
except OSError as e:
if e.errno != errno.EEXIST:
raise
shutil.copy2(os.path.join(datadir, 'tai-utc.dat'), dataout)
# Set permissions based on umask, not perms in SpacePy install
u = os.umask(0)
os.umask(u)
os.chmod(os.path.join(dataout, 'tai-utc.dat'), 0o666 & ~u)
DOT_FLN = _find_spacepy_dir()
_populate_spacepy_dir(DOT_FLN)
rcfile = os.path.join(DOT_FLN, 'spacepy.rc')
_read_config(rcfile)
if __version__ == 'UNRELEASED' and config['support_notice']:
print('This unreleased version of SpacePy is not supported '
'by the SpacePy team.')