Source code for foundations.io

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
**io.py**

**Platform:**
	Windows, Linux, Mac Os X.

**Description:**
	Provides file input / output objects and resources manipulation objects.

**Others:**

"""

#**********************************************************************************************************************
#***	Future imports.
#**********************************************************************************************************************
from __future__ import unicode_literals

#**********************************************************************************************************************
#***	External imports.
#**********************************************************************************************************************
import codecs
import os
import shutil
import urllib2

#**********************************************************************************************************************
#***	Internal imports.
#**********************************************************************************************************************
import foundations.common
import foundations.verbose
import foundations.exceptions
import foundations.strings
from foundations.globals.constants import Constants

#**********************************************************************************************************************
#***	Module attributes.
#**********************************************************************************************************************
__author__ = "Thomas Mansencal"
__copyright__ = "Copyright (C) 2008 - 2014 - Thomas Mansencal"
__license__ = "GPL V3.0 - http://www.gnu.org/licenses/"
__maintainer__ = "Thomas Mansencal"
__email__ = "[email protected]"
__status__ = "Production"

__all__ = ["LOGGER", "File", "setDirectory", "copy", "remove", "isWritable", "isReadable", "isBinaryFile"]

LOGGER = foundations.verbose.installLogger()

#**********************************************************************************************************************
#***	Module classes and definitions.
#**********************************************************************************************************************
[docs]class File(object): """ Defines methods to read / write and append to files or retrieve online file content. """ def __init__(self, path=None, content=None): """ Initializes the class. Usage:: >>> file = File(u"file.txt") >>> file.content = [u"Some file content ...\\n", u"... ready to be saved!\\n"] >>> file.write() True >>> file.read() u'Some file content ...\\n... ready to be saved!\\n' :param path: File path. :type path: unicode :param content: Content. :type content: list """ LOGGER.debug("> Initializing '{0}()' class.".format(self.__class__.__name__)) # --- Setting class attributes. --- self.__path = None self.path = path self.__content = None self.content = content or [] #****************************************************************************************************************** #*** Attributes properties. #****************************************************************************************************************** @property def path(self): """ Property for **self.__path** attribute. :return: self.__path. :rtype: unicode """ return self.__path @path.setter # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError) def path(self, value): """ Setter for **self.__path** attribute. :param value: Attribute value. :type value: unicode """ if value is not None: assert type(value) is unicode, "'{0}' attribute: '{1}' type is not 'unicode'!".format("path", value) self.__path = value @path.deleter # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def path(self): """ Deleter for **self.__path** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "path"))
@property def content(self): """ Property for **self.__content** attribute. :return: self.__content. :rtype: list """ return self.__content @content.setter # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(AssertionError) def content(self, value): """ Setter for **self.__content** attribute. :param value: Attribute value. :type value: list """ if value is not None: assert type(value) is list, "'{0}' attribute: '{1}' type is not 'list'!".format("content", value) self.__content = value @content.deleter # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.ProgrammingError)
[docs] def content(self): """ Deleter for **self.__content** attribute. """ raise foundations.exceptions.ProgrammingError( "{0} | '{1}' attribute is not deletable!".format(self.__class__.__name__, "content")) #****************************************************************************************************************** #*** Class methods. #****************************************************************************************************************** # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.UrlReadError, # Oncilla: Statement commented by auto-documentation process: foundations.exceptions.FileReadError, # Oncilla: Statement commented by auto-documentation process: IOError)
[docs] def cache(self, mode="r", encoding=Constants.defaultCodec, errors=Constants.codecError): """ Reads given file content and stores it in the content cache. :param mode: File read mode. :type mode: unicode :param encoding: File encoding codec. :type encoding: unicode :param errors: File encoding errors handling. :type errors: unicode :return: Method success. :rtype: bool """ self.uncache() if foundations.strings.isWebsite(self.__path): try: LOGGER.debug("> Caching '{0}' online file content.".format(self.__path)) self.__content = urllib2.urlopen(self.__path).readlines() return True except urllib2.URLError as error: raise foundations.exceptions.UrlReadError( "!> {0} | '{1}' url is not readable: '{2}'.".format(self.__class__.__name__, self.__path, error)) elif foundations.common.pathExists(self.__path): if not isReadable(self.__path): raise foundations.exceptions.FileReadError( "!> {0} | '{1}' file is not readable!".format(self.__class__.__name__, self.__path)) with codecs.open(self.__path, mode, encoding, errors) as file: LOGGER.debug("> Caching '{0}' file content.".format(self.__path)) self.__content = file.readlines() return True return False
[docs] def uncache(self): """ Uncaches the cached content. :return: Method success. :rtype: bool """ LOGGER.debug("> Uncaching '{0}' file content.".format(self.__path)) self.__content = [] return True
[docs] def read(self): """ Returns defined file content. :return: File content. :rtype: unicode """ return "".join(self.__content) if self.cache() else "" # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.UrlWriteError, foundations.exceptions.FileWriteError)
[docs] def write(self, mode="w", encoding=Constants.defaultCodec, errors=Constants.codecError): """ Writes content to defined file. :param mode: File write mode. :type mode: unicode :param encoding: File encoding codec. :type encoding: unicode :param errors: File encoding errors handling. :type errors: unicode :return: Method success. :rtype: bool """ if foundations.strings.isWebsite(self.__path): raise foundations.exceptions.UrlWriteError( "!> {0} | '{1}' url is not writable!".format(self.__class__.__name__, self.__path)) if foundations.common.pathExists(self.__path): if not isWritable(self.__path): raise foundations.exceptions.FileWriteError( "!> {0} | '{1}' file is not writable!".format(self.__class__.__name__, self.__path)) with codecs.open(self.__path, mode, encoding, errors) as file: LOGGER.debug("> Writing '{0}' file content.".format(self.__path)) for line in self.__content: file.write(line) return True return False # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.UrlWriteError, foundations.exceptions.FileWriteError)
[docs] def append(self, mode="a", encoding=Constants.defaultCodec, errors=Constants.codecError): """ Appends content to defined file. :param mode: File write mode. :type mode: unicode :param encoding: File encoding codec. :type encoding: unicode :param errors: File encoding errors handling. :type errors: unicode :return: Method success. :rtype: bool """ if foundations.strings.isWebsite(self.__path): raise foundations.exceptions.UrlWriteError( "!> {0} | '{1}' url is not writable!".format(self.__class__.__name__, self.__path)) if foundations.common.pathExists(self.__path): if not isWritable(self.__path): raise foundations.exceptions.FileWriteError( "!> {0} | '{1}' file is not writable!".format(self.__class__.__name__, self.__path)) with codecs.open(self.__path, mode, encoding, errors) as file: LOGGER.debug("> Appending to '{0}' file content.".format(self.__path)) for line in self.__content: file.write(line) return True return False # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.UrlWriteError)
[docs] def clear(self, encoding=Constants.defaultCodec): """ Clears the defined file content. :param encoding: File encoding codec. :type encoding: unicode :return: Method success. :rtype: bool """ if foundations.strings.isWebsite(self.__path): raise foundations.exceptions.UrlWriteError( "!> {0} | '{1}' url is not writable!".format(self.__class__.__name__, self.__path)) if self.uncache(): LOGGER.debug("> Clearing '{0}' file content.".format(self.__path)) return self.write(encoding=encoding) else: return False # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.DirectoryCreationError)
[docs]def setDirectory(path): """ | Creates a directory with given path. | The directory creation is delegated to Python :func:`os.makedirs` definition so that directories hierarchy is recursively created. :param path: Directory path. :type path: unicode :return: Definition success. :rtype: bool """ try: if not foundations.common.pathExists(path): LOGGER.debug("> Creating directory: '{0}'.".format(path)) os.makedirs(path) return True else: LOGGER.debug("> '{0}' directory already exist, skipping creation!".format(path)) return True except Exception as error: raise foundations.exceptions.DirectoryCreationError("!> {0} | Cannot create '{1}' directory: '{2}'".format(__name__, path, error)) # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.PathCopyError)
[docs]def copy(source, destination): """ Copies given file or directory to destination. :param source: Source to copy from. :type source: unicode :param destination: Destination to copy to. :type destination: unicode :return: Method success. :rtype: bool """ try: if os.path.isfile(source): LOGGER.debug("> Copying '{0}' file to '{1}'.".format(source, destination)) shutil.copyfile(source, destination) else: LOGGER.debug("> Copying '{0}' directory to '{1}'.".format(source, destination)) shutil.copytree(source, destination) return True except Exception as error: raise foundations.exceptions.PathCopyError("!> {0} | Cannot copy '{1}' path: '{2}'".format(__name__, source, error)) # Oncilla: Statement commented by auto-documentation process: # Oncilla: Statement commented by auto-documentation process: @foundations.exceptions.handleExceptions(foundations.exceptions.PathRemoveError)
[docs]def remove(path): """ Removes given path. :param path: Path to remove. :type path: unicode :return: Method success. :rtype: bool """ try: if os.path.isfile(path): LOGGER.debug("> Removing '{0}' file.".format(path)) os.remove(path) elif os.path.isdir(path): LOGGER.debug("> Removing '{0}' directory.".format(path)) shutil.rmtree(path) return True except Exception as error: raise foundations.exceptions.PathRemoveError("!> {0} | Cannot remove '{1}' path: '{2}'".format(__name__, path, error))
[docs]def isReadable(path): """ Returns if given path is readable. :param path: Path to check access. :type path: unicode :return: Is path writable. :rtype: bool """ if os.access(path, os.R_OK): LOGGER.debug("> '{0}' path is readable.".format(path)) return True else: LOGGER.debug("> '{0}' path is not readable.".format(path)) return False
[docs]def isWritable(path): """ Returns if given path is writable. :param path: Path to check access. :type path: unicode :return: Is path writable. :rtype: bool """ if os.access(path, os.W_OK): LOGGER.debug("> '{0}' path is writable.".format(path)) return True else: LOGGER.debug("> '{0}' path is not writable.".format(path)) return False
[docs]def isBinaryFile(file): """ Returns if given file is a binary file. :param file: File path. :type file: unicode :return: Is file binary. :rtype: bool """ fileHandle = open(file, "rb") try: chunkSize = 1024 while True: chunk = fileHandle.read(chunkSize) if chr(0) in chunk: return True if len(chunk) < chunkSize: break finally: fileHandle.close() return False