Source code for spinrewriter

# -*- coding: utf-8 -*-

import json
import re
import string
import urllib
import urllib2

from collections import namedtuple

from spinrewriter import exceptions as ex


[docs]class Api(object): """A class representing the Spin Rewriter API (http://www.spinrewriter.com/). """ URL = 'http://www.spinrewriter.com/action/api' """URL for invoking the API""" _tmp_list = ['api_quota', 'text_with_spintax', 'unique_variation', 'unique_variation_from_spintax'] ACTION = namedtuple('ACTION', _tmp_list)(*_tmp_list) """collection of possible values for the action parameter""" _tmp_list = ['low', 'medium', 'high'] CONFIDENCE_LVL = namedtuple('CONFIDENCE_LVL', ['low', 'medium', 'high'])( *_tmp_list) """collection of possible values for the confidence_level parameter""" SPINTAX_FORMAT = namedtuple( 'SPINTAX_FORMAT', ['pipe_curly', 'tilde_curly', 'pipe_square', 'spin_tag'] )(*['{|}', '{~}', '[|]', '[spin]']) """collection of possible values for the spintax_format parameter""" _tmp_list = ['email_address', 'api_key', 'action', 'text', 'protected_terms', 'confidence_level', 'nested_spintax', 'spintax_format'] REQ_P_NAMES = namedtuple('REQ_P_NAMES', _tmp_list)(*_tmp_list) """collection of all request parameters' names""" _tmp_list = ['status', 'response', 'api_requests_made', 'api_requests_available', 'protected_terms', 'confidence_level'] RESP_P_NAMES = namedtuple('RESP_P_NAMES', _tmp_list)(*_tmp_list) """collection of all response fields' names""" _tmp_list = ['ok', 'error'] STATUS = namedtuple('STATUS', ['ok', 'error'])(*map( string.upper, _tmp_list)) """possible response status strings returned by API""" def __init__(self, email_address, api_key): self.email_address = email_address self.api_key = api_key
[docs] def api_quota(self): """Return the number of made and remaining API calls for the 24-hour period. :return: remaining API quota :rtype: dictionary """ params = ( (self.REQ_P_NAMES.email_address, self.email_address), (self.REQ_P_NAMES.api_key, self.api_key), (self.REQ_P_NAMES.action, self.ACTION.api_quota), ) response = self._send_request(params) return response
[docs] def text_with_spintax(self, text, protected_terms=None, confidence_level=CONFIDENCE_LVL.medium, nested_spintax=False, spintax_format=SPINTAX_FORMAT.pipe_curly): """Return processed spun text with spintax. :param text: original text that needs to be changed :type text: string :param protected_terms: (optional) keywords and key phrases that should be left intact :type protected_terms: list of strings :param confidence_level: (optional) the confidence level of the One-Click Rewrite process :type confidence_level: string :param nested_spintax: (optional) whether or not to also spin single words inside already spun phrases :type nested_spintax: boolean :param spintax_format: (optional) spintax format to use in returned text :type spintax_format: string :return: processed text and some other meta info :rtype: dictionary """ response = self._transform_plain_text( self.ACTION.text_with_spintax, text, protected_terms, confidence_level, nested_spintax, spintax_format ) if response[self.RESP_P_NAMES.status] == self.STATUS.error: self._raise_error(response) else: return response
[docs] def unique_variation( self, text, protected_terms=None, confidence_level=CONFIDENCE_LVL.medium, nested_spintax=False, spintax_format=SPINTAX_FORMAT.pipe_curly ): """Return a unique variation of the given text. :param text: original text that needs to be changed :type text: string :param protected_terms: (optional) keywords and key phrases that should be left intact :type protected_terms: list of strings :param confidence_level: (optional) the confidence level of the One-Click Rewrite process :type confidence_level: string :param nested_spintax: (optional) whether or not to also spin single words inside already spun phrases :type nested_spintax: boolean :param spintax_format: (optional) (probably not relevant here? But API documentation not clear here ...) :type spintax_format: string :return: processed text and some other meta info :rtype: dictionary """ response = self._transform_plain_text( self.ACTION.unique_variation, text, protected_terms, confidence_level, nested_spintax, spintax_format ) if response[self.RESP_P_NAMES.status] == self.STATUS.error: self._raise_error(response) else: return response
[docs] def unique_variation_from_spintax( self, text, nested_spintax=False, spintax_format=SPINTAX_FORMAT.pipe_curly ): """Return a unique variation of an already spun text. :param text: text to process :type text: string :param nested_spintax: whether or not to also spin single words inside already spun phrases :type nested_spintax: boolean :param spintax_format: (probably not relevant here? But API documentation not clear here ...) :type spintax_format: string :return: processed text and some other meta info :rtype: dictionary """ params = ( (self.REQ_P_NAMES.email_address, self.email_address), (self.REQ_P_NAMES.api_key, self.api_key), ( self.REQ_P_NAMES.action, self.ACTION.unique_variation_from_spintax ), (self.REQ_P_NAMES.text, text.encode('utf-8')), (self.REQ_P_NAMES.nested_spintax, nested_spintax), (self.REQ_P_NAMES.spintax_format, spintax_format), ) response = self._send_request(params) if response[self.RESP_P_NAMES.status] == self.STATUS.error: self._raise_error(response) else: return response
[docs] def _send_request(self, params): """Invoke Spin Rewriter API with given parameters and return its response. :param params: parameters to pass along with the request :type params: tuple of 2-tuples :return: API's response (already JSON-decoded) :rtype: dictionary """ con = urllib2.urlopen(self.URL, urllib.urlencode(params)) response = con.read() return json.loads(response)
[docs] def _raise_error(self, api_response): """Examine the API response and raise exception of the appropriate type. NOTE: usage of this method only makes sense when API response's status indicates an error :param api_response: API's response fileds :type api_response: dictionary """ error_msg = api_response[self.RESP_P_NAMES.response] if ( re.match( r'Authentication failed. ' r'No user with this email address found.', error_msg, re.IGNORECASE ) or re.match( r'Authentication failed. ' r'Unique API key is not valid for this user.', error_msg, re.IGNORECASE ) or re.match( r'This user does not have a valid Spin Rewriter subscription.', error_msg, re.IGNORECASE ) ): raise ex.AuthenticationError(error_msg) elif re.match( r'API quota exceeded. ' r'You can make \d+ requests per day.', error_msg, re.IGNORECASE ): raise ex.QuotaLimitError(error_msg) elif re.match( r'You can only submit entirely new text ' r'for analysis once every \d+ seconds.', error_msg, re.IGNORECASE ): raise ex.UsageFrequencyError(error_msg) elif re.match( r'Requested action does not exist. ' r'Please refer to the Spin Rewriter API documentation.', error_msg, re.IGNORECASE ): # NOTE: This should never occur unless # there is a bug in the API library. raise ex.UnknownActionError(error_msg) elif re.match( r'Email address and unique API key are both required. ' r'At least one is missing.', error_msg, re.IGNORECASE ): # NOTE: This should never occur unless # there is a bug in the API library. raise ex.MissingParameterError(error_msg) elif ( re.match( r'Original text too short.', error_msg, re.IGNORECASE ) or re.match( r'Original text too long. Text can have up to 4,000 words.', error_msg, re.IGNORECASE ) or re.match( r'Original text after analysis too long. ' r'Text can have up to 4,000 words.', error_msg, re.IGNORECASE ) or re.match( r'Spinning syntax invalid. ' r'With this action you should provide text with existing valid' r'{first option|second option} spintax.', error_msg, re.IGNORECASE ) or re.match( r'The {first|second} spinning syntax invalid. ' r'Re-check the syntax, i.e. curly brackets and pipes\.', error_msg, re.IGNORECASE ) or re.match( r'Spinning syntax invalid.', error_msg, re.IGNORECASE ) ): raise ex.ParamValueError(error_msg) elif ( re.match( r'Analysis of your text failed. ' r'Please inform us about this.', error_msg, re.IGNORECASE ) or re.match( r'Synonyms for your text could not be loaded. ' r'Please inform us about this.', error_msg, re.IGNORECASE ) or re.match( r'Unable to load your new analyzed project.', error_msg, re.IGNORECASE ) or re.match( r'Unable to load your existing analyzed project.', error_msg, re.IGNORECASE ) or re.match( r'Unable to find your project in the database.', error_msg, re.IGNORECASE ) or re.match( r'Unable to load your analyzed project.', error_msg, re.IGNORECASE ) or re.match( r'One-Click Rewrite failed.', error_msg, re.IGNORECASE ) ): raise ex.InternalApiError(error_msg) else: raise ex.UnknownApiError(error_msg)
[docs] def _transform_plain_text( self, action, text, protected_terms, confidence_level, nested_spintax, spintax_format ): """Transform plain text using SpinRewriter API. Pack parameters into format as expected by the _send_request method and invoke the action method to get transformed text from the API. :param action: name of the action that will be requested from API :type action: string :param text: text to process :type text: string :param protected_terms: keywords and key phrases that should be left intact :type protected_terms: list of strings :param confidence_level: the confidence level of the One-Click Rewrite process :type confidence_level: string :param nested_spintax: whether or not to also spin single words inside already spun phrases :type nested_spintax: boolean :param spintax_format: spintax format to use in returned text :type spintax_format: string :return: processed text and some other meta info :rtype: dictionary """ if protected_terms: # protected_terms could be separated by other characters too, # like commas protected_terms = [ term.encode('utf-8') for term in protected_terms] protected_terms = '\n'.join(protected_terms) else: protected_terms = '' params = ( (self.REQ_P_NAMES.email_address, self.email_address), (self.REQ_P_NAMES.api_key, self.api_key), (self.REQ_P_NAMES.action, action), (self.REQ_P_NAMES.text, text.encode('utf-8')), (self.REQ_P_NAMES.protected_terms, protected_terms), (self.REQ_P_NAMES.confidence_level, confidence_level), (self.REQ_P_NAMES.nested_spintax, nested_spintax), (self.REQ_P_NAMES.spintax_format, spintax_format), ) return self._send_request(params)
[docs]class SpinRewriter(object): """A facade for easier usage of the raw Spin Rewriter API.""" def __init__(self, email_address, api_key): self.email_address = email_address self.api_key = api_key self.api = Api(email_address, api_key)
[docs] def unique_variation( self, text, confidence_level=Api.CONFIDENCE_LVL.medium): """Return unique variation of the given text. :param text: text to process :type text: string :param confidence_level: how 'confident' the spinner API is when transforming the text :type confidence_level: Api.CONFIDENCE_LVL :return: spinned version of the original text :rtype: string """ response = self.api.unique_variation(text, confidence_level) return response[Api.RESP_P_NAMES.response]
[docs] def text_with_spintax( self, text, confidence_level=Api.CONFIDENCE_LVL.medium): """Return text with spintax elements inserted. :param text: text to process :type text: string :param confidence_level: how 'confident' the spinner API is when transforming the text :type confidence_level: Api.CONFIDENCE_LVL :return: original text with spintax elements :rtype: string """ response = self.api.text_with_spintax(text, confidence_level) return response[Api.RESP_P_NAMES.response]