# Copyright (c) 2013, Combine Control Systems AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the Combine Control Systems AB nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.
# IN NO EVENT SHALL COMBINE CONTROL SYSTEMS AB BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
API for working with the Datasource type.
Import this module like this::
from sympathy.api import datasource
Class :class:`datasource.File`
------------------------------
.. autoclass:: File
:members:
:special-members:
"""
import collections
import json
import os
import numpy as np
from .. datasources.info import get_fileinfo_from_scheme
from .. utils import prim, filebase
from .. utils.context import inherit_doc
from . import text
def is_datasource(fq_filename):
fileinfo = get_fileinfo_from_scheme('text')(fq_filename)
try:
return fileinfo.type() == str(File.container_type)
except (KeyError, AttributeError, TypeError):
pass
try:
data = File(filename=fq_filename, scheme='text')
data.decode_path()
data.decode_type()
return True
except Exception:
pass
return False
def is_datasources(fq_filename):
fileinfo = get_fileinfo_from_scheme('text')(fq_filename)
try:
return fileinfo.type() == str(FileList.container_type)
except (KeyError, AttributeError, TypeError):
pass
try:
data = FileList(filename=fq_filename, scheme='text')
data[0].decode_path()
data[0].decode_type()
return True
except Exception:
pass
return False
DatasourceModes = collections.namedtuple(
'DatasourceModes', ['file', 'db', 'db_sqlalchemy', 'url'])
[docs]@filebase.typeutil('sytypealias datasource = sytext')
@inherit_doc
class File(text.File):
"""
A Datasource representing a sources of data. It can currently point to
either a file on disk or to a database.
Any node port with the *Datasource* type will produce an object of this
kind.
"""
modes = DatasourceModes(
'FILE', 'DATABASE', 'DATABASE SQLALCHEMY', 'URL')
[docs] def get(self):
datasource_json = super(File, self).get()
if not datasource_json:
return {}
datasource_dict = json.loads(datasource_json)
if datasource_dict.get('type') == self.modes.file:
datasource_dict['path'] = prim.nativepath(datasource_dict['path'])
return datasource_dict
[docs] def set(self, data):
datasource_dict = dict(data)
if datasource_dict.get('type') == self.modes.file:
datasource_dict['path'] = prim.nativepath(
os.path.abspath(datasource_dict['path']))
super(File, self).set(json.dumps(datasource_dict))
[docs] def decode_path(self):
"""
Return the path.
In a file data source this corresponds to the path of a file. In a data
base data source this corresponds to a connection string. That can be
used for accessing the data base. Returns None if path hasn't been set.
.. versionchanged:: 1.2.5
Return None instead of raising KeyError if path hasn't been set.
"""
return self.decode().get('path')
[docs] def decode_type(self):
"""
Return the type of this data source.
It can be either ``'FILE'`` or ``'DATABASE'``. Returns None if type
hasn't been set.
.. versionchanged:: 1.2.5
Return None instead of raising KeyError if type hasn't been set.
"""
return self.decode().get('type')
[docs] def decode(self):
"""Return the full dictionary for this data source."""
return self.get()
[docs] @staticmethod
def to_file_dict(fq_filename):
"""
Create a dictionary to be used for creating a file data source.
You usually want to use the convenience method :meth:`encode_path`
instead of calling this method directly.
"""
return collections.OrderedDict(
[('path', fq_filename), ('type', File.modes.file)])
@staticmethod
def _to_odbc_database_dict(db_driver,
db_servername,
db_databasename,
db_user,
db_password='',
db_connection_string=''):
"""Create a dictionary to be used for creating a data base data source.
You usually want to use the convenience method :meth:`encode_database`
instead of calling this method directly.
"""
connection_string = db_connection_string
if not connection_string:
# Add driver, server and database information.
connection_string = 'DRIVER={{{}}};SERVER={};DATABASE={}'.format(
db_driver, db_servername, db_databasename)
# Add authentication information.
connection_string = '{0};UID={1};PWD={2}'.format(
connection_string, db_user, db_password)
return collections.OrderedDict(
[('path', connection_string), ('type', File.modes.db)])
@staticmethod
def _to_sqlalchemy_database_dict(db_url, *args, **kwarg):
return collections.OrderedDict(
[('path', db_url), ('type', File.modes.db_sqlalchemy)])
@classmethod
def to_database_dict(cls, *args, **kwargs):
db_method = kwargs.pop('db_method', 'ODBC')
if db_method == 'ODBC':
return cls._to_odbc_database_dict(*args, **kwargs)
elif db_method == 'SQLAlchemy':
return cls._to_sqlalchemy_database_dict(*args, **kwargs)
@classmethod
def to_url_dict(cls, *args, **kwargs):
url = kwargs.pop('url', '')
env = kwargs.pop('env', '')
return collections.OrderedDict(
[('path', url), ('type', cls.modes.url), ('env', env)])
def encode_url(self, url_dict):
self.set(url_dict)
[docs] def encode(self, datasource_dict):
"""Store the info from datasource_dict in this datasource.
:param datasource_dict: should be a dictionary of the same format that
you get from :meth:`to_file_dict` or :meth:`to_database_dict`.
"""
self.set(datasource_dict)
[docs] def encode_path(self, filename):
"""Store a path to a file in this datasource.
:param filename: should be a string containing the path. Can be
relative or absolute.
"""
self.encode(self.to_file_dict(filename))
[docs] def encode_database(self, *args, **kwargs):
"""Store data base access info."""
db_method = kwargs.pop('db_method', 'ODBC')
self.encode(
self.to_database_dict(*args, db_method=db_method, **kwargs))
[docs] def names(self, kind=None, fields=None, **kwargs):
"""
Return a formatted list with a name and type of the data.
columns.
"""
names = filebase.names(kind, fields)
kind, fields = names.updated_args()
fields_list = names.fields()
if kind == 'cols':
item = names.create_item()
for f in fields_list:
if f == 'name':
item[f] = 'type'
elif f == 'type':
item[f] = np.dtype('U')
elif f == 'expr':
item[f] = "['type']"
item = names.create_item()
for f in fields_list:
if f == 'name':
item[f] = 'path'
elif f == 'type':
item[f] = np.dtype('U')
elif f == 'expr':
item[f] = "['path']"
return names.created_items_to_result_list()
def __getitem__(self, key):
return self.get()[key]
[docs] @classmethod
def viewer(cls):
from .. platform import datasource_viewer
return datasource_viewer.DatasourceViewer
[docs] @classmethod
def icon(cls):
return 'ports/datasource.svg'
@inherit_doc
class FileList(filebase.FileListBase):
"""
FileList has been changed and is now just a function which creates
generators to sybase types.
Old documentation follows:
The :class:`FileList` class is used when working with lists of Datasources.
The main interfaces in :class:`FileList` are indexing or iterating for
reading (see the :meth:`__getitem__()` method) and the :meth:`append()`
method for writing.
"""
sytype = '[text]'
scheme = 'text'