# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
# $Id: CsvWrite.py 10330 2009-12-24 22:13:38Z grisha $
"""
CsvWrite Component.
This component provides a single input view and the data sent to this
view is written out to a csv format file.
"""
import codecs
import os
from decimal import Decimal
from datetime import datetime
from snaplogic.common.data_types import Record
from snaplogic.common.snap_exceptions import *
from snaplogic.common.data_types import SnapNumber,SnapString,SnapDateTime
from snaplogic.common import version_info
from snaplogic.snapi_base import keys
from snaplogic.cc import component_api
from snaplogic.cc.component_api import ComponentAPI
import snaplogic.cc.prop as prop
from snaplogic.components import FileUtils
from snaplogic.common import snap_log
class CsvWrite(ComponentAPI):
"""
This class implements the CSV write component.
It provides the functionality of writing output in CSV format to a file.
"""
api_version = '1.0'
component_version = '1.2'
capabilities = {
ComponentAPI.CAPABILITY_INPUT_VIEW_LOWER_LIMIT : 1,
ComponentAPI.CAPABILITY_INPUT_VIEW_UPPER_LIMIT : 1,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_LOWER_LIMIT : 0,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT : 0
}
component_description = "Writes CSV format output to files"
component_label = "CSV Writer"
component_doc_uri = "https://www.snaplogic.org/trac/wiki/Documentation/%s/ComponentRef/CsvWrite" % \
version_info.doc_uri_version
def validate_config_file(self):
"""If a config file is provided, then validate its contents."""
for k in self.config_dictionary:
if k == "root_directory":
if not os.path.exists(self.config_dictionary[k]):
raise SnapComponentConfigError("The path specified for root (%s) is not valid" %
self.config_dictionary[k])
elif k == "schemes":
# Make sure "schemes" contains only the schemes we support
FileUtils.validate_schemes(self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
else:
# No other config file param is supported.
raise SnapComponentConfigError("Unexpected config file entry (%s) encountered" % k)
def create_resource_template(self):
"""
Create CsvWrite resource definition template. It consists of
filename : The name of the file to write into.
delimiter : The field delimiter that should be used (defaults to ",").
header : Set this to True, if a header with all the field names need to be written out in the first line.
"""
self.set_property_def('filename',
prop.SimpleProp("File name", SnapString, "The name of the file that is written to",
None, True))
self.set_property_def('delimiter', prop.SimpleProp("Delimiter", SnapString, "The field delimiter", None, True))
self.set_property_value('delimiter', ",")
self.set_property_def('header', prop.SimpleProp("Header Present", "boolean", "Does the file have a header",
None, True))
self.set_property_value('header', False)
self._add_quote_prop()
def _add_quote_prop(self):
"""
Adds quote property
"""
self.set_property_def('quote', prop.SimpleProp("Quote character", SnapString,
"Character used to quote an entry if a delimiter appears in it",
None))
def upgrade_1_0_to_1_1(self):
"""
Add quote property
"""
self._add_quote_prop()
delim = self.get_property_value('delimiter')
def upgrade_1_1_to_1_2(self):
"""
No-op upgrade only to change component doc URI during the upgrade
which will be by cc_info before calling this method.
"""
pass
def suggest_resource_values(self, err_obj):
"""Suggest that "," be used as delimiter, if no delimiter value has been selected."""
val = self.get_property_value("delimiter")
if not val:
self.set_property_value("delimiter", ",")
def validate(self, err_obj):
"""
Component-specific validation logic.
Validate that the URI scheme specified for the filename is one of the allowed
schemes as specified in the component config file.
@param err_obj: Object for error reporting
@type err_obj: L{SimplePropErr} or L{ListPropErr} or L{DictPropErr}
"""
# Validate that the filename complies with the allowed URI schemes,
# unless it's specified via a parameter
FileUtils.validate_filename_property(self.get_property_value("filename"), "filename", err_obj,
self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
def execute(self, input_views, output_views):
"""Execute the CSV writing functionality of the component."""
try:
input_view = input_views.values()[keys.SINGLE_VIEW]
except IndexError:
raise SnapComponentError("No input view connected")
self._filename = self.get_property_value("filename")
self._delim = self.get_property_value("delimiter")
self._header = self.get_property_value("header")
self._filename = FileUtils.get_file_location(self._filename, self.config_dictionary)
# Validate filename URI scheme
error = FileUtils.validate_file_scheme(self._filename, self.config_dictionary.get("schemes"), FileUtils.writer_schemes)
if error is not None:
raise SnapComponentError(error)
if self._filename.startswith('file://'):
if os.name == 'nt':
self._filename = self._filename[len('file:///'):]
else:
self._filename = self._filename[len('file://'):]
self._file = codecs.open(self._filename, "w", 'utf-8')
self.log(snap_log.LEVEL_INFO, "Writing: %s" % self._filename)
quote = self.get_property_value('quote')
if not quote:
# If quote isn't provided, use double quote
quote = '"'
quoted_quote = "%s%s" % (quote, quote)
try:
first_record = True
record = input_view.read_record()
while record is not None:
first_field = True
for field_name in record.field_names:
if first_record:
first_record = False
if self._header:
self._file.write(self._delim.join(record.field_names))
self._file.write(os.linesep)
if first_field:
first_field = False
else:
self._file.write(self._delim)
val = record[field_name]
if val is not None:
if isinstance(val, datetime) or isinstance(val, Decimal):
val = str(val)
has_quotes = quote in val
if self._delim in val or has_quotes or '\r' in val or '\n' in val:
if has_quotes:
val = val.replace(quote, quoted_quote)
val = quote + val + quote
self._file.write(val)
self._file.write(os.linesep)
record = input_view.read_record()
finally:
self._file.close()
|