ServerPortfolio  2.0
Python parsers and server
 All Classes Namespaces Files Functions Variables Properties Pages
Abstract.py
Go to the documentation of this file.
1 ## @package serverportfolio.Parsers.Abstract
2 # Define an abstract base class for specific Parsers
3 #
4 # Last Changed $Id: Abstract.py 27 2015-04-25 16:16:47Z michael $
5 
6 # https://docs.python.org/2/library/abc.html
7 # http://stackoverflow.com/questions/13646245/is-it-possible-to-make-abstract-classes-in-python
8 
9 from abc import ABCMeta, abstractmethod
10 import types, logging
11 
12 
13 from serverportfolio import GlobalDicts
14 from serverportfolio.GlobalDicts import EAction
15 from serverportfolio import Utils
16 from serverportfolio.Parsers import UtilsParsers
17 
18 from serverportfolio.DictionaryStocks import DictionaryStocks
19 # only needed for Stock.to_list ?
20 from serverportfolio.Stock import Stock, InvalidStock
21 # only for assert, may be deleted later
22 #from serverportfolio.Stock import InvalidStock
23 from serverportfolio.PortfolioException import ParserError, QueryError, PortfolioError
24 
25 ## @class AbstractParser
26 #
27 # Define 2 abstract methods which need to be overridden by the Parsers\n
28 # and a generic algorithm (run_parser) for parsing the data
30  __metaclass__ = ABCMeta
31 
32  ## Constructor
33  # @param e_action GlobalDicts.EAction
34  def __init__( self, e_action ):
35 
36  ## save url, useful for reporting errors and exceptions
37  self.url = None
38  # action convenient for Yahoo_CSV (multiple action), set in constructor
39  ## enumeration (EAction) of the type of query to perform
40  self.e_action = e_action
41 
42  # TO DELETE AT THE END, end ParserStocks, end UpdateStock
43  ## @brief Store links to stocks to update, dictionary 'symbol' : Stock object
44  self.local_stock = None
45 
46  ## @brief Contain all parsed data by one query, include multiple stocks query
47  # List of template_parser
48  self.list_return_data = None
49 
50  ## @brief Source of the data is the parser name : YQL/YCSV/BOURSO, set in ParserFactory
51  self.source = None
52 
53  ## Abstract method to create the URL
54  @abstractmethod
55  def create_url(self):
56  pass
57 
58  ## Parse the text file (html, CSV,...)
59  @abstractmethod
60  def parse(self, s):
61  pass
62 
63 # ################ possibility for some parser to overwrite
64 
65 # option_post, post-process in update_stock to_save(default config, ok?), case to_save easier to implement with access to config
66 # interactive, important, case multi-thread, need to protect, how ? case interactive, more tricky (with mutlithread)
67 # need singleton, serialize access to TkValid interface
68 
69  ## \brief Main function to call for running the parser.
70  # Regroup the different steps in one call:
71  # - store_stock_copy (in AbstractParser)
72  # - create_url (abstractmethod, to implement in each parser)
73  # - web_query (in UtilsParsers)
74  # - parse (abstractmethod, to implement in each parser)
75  # - update_stock (in AbstractParser)
76  # - (post_process?)
77  # - clean (in AbstractParser)
78  #
79  # @param stock a stock symbol or a list of st.symb. "CAC40" or ["CAC40","GSZ"]
80  # @return dict_data : dictionary with extracted data
81  # throw PortfolioError
82  def run_parser( self, stock, option_post=None ):
83  self.logger.debug("run_parser stock: %s", Utils.to_list( stock ) )
84 
85  if option_post != None:
86  self.option_post = option_post
87  self.logger.debug('option_post is present: %s' % option_post)
88 
89  # store a linked to the stock(s) to update
90  # Check the size, if no a valid stock, just return (no crash)
91  try :
92  self.store_stock_copy( Utils.to_list(stock) )
93  # could return an exception... but tricky to run the post-processing
94  if len(self.local_stock) == 0:
95  self.logger.debug("No valid Stock after store_stock_copy")
96  return
97  except Exception as ex:
98  self.logger.debug("Caught Exception in store_stock_copy: ex", ex)
99  raise
100 
101  self.logger.debug("\n== create_url")
102  try :
103  self.create_url()
104  # AssertionError is forwarded
105  except Exception as ex:
106  self.logger.debug("Caught Exception in create_url: %s", ex)
107  raise ParserError( ex, stock, self.e_action.name, self.url)
108 
109  # load web page, works for all
110  s = None
111  self.logger.debug("\n== web_query")
112  try :
113  s = UtilsParsers.web_query( self.url )
114  except Exception as ex:
115  self.logger.error("Got a general Exception from web_query %s" % ex )
116  # should raise a QueryError normally
117  raise ParserError( ex, stock, self.e_action.name, self.url )
118 
119  # parse the page
120  self.logger.debug("\n== parse the page")
121  try:
122  self.parse(s)
123 
124  except ParserError:
125  self.logger.error("run_parser() catch a ParserError")
126  raise
127  # check for an other specific error, not sure used
128  except PortfolioError as ex:
129  self.logger.error("run_parser() catch a PortfolioError: %s", ex)
130  raise
131 
132  # many error of parsing may happen, catch all
133  except Exception as ex :
134  # critical for parsing and AutoParser
135  self.logger.error("Catch exception in run_parser from parse(): %s " % ex)
136  raise ParserError( 'ParserError from myparser.parse(): ' + str(ex), stock, self.e_action.name, self.url )
137 
138  # new add a new commom part, to insert with the other ? at the end of parser ?
139  # to do in DictionnaryStocks ? not really. But Dictionary Main Functions could check for error, message, write, TCP...
140  # to do inside parse ?
141  self.logger.debug("\n== update the stock(s)")
142  self.update_stock()
143 
144  # clean parser in case of re-use, maybe for re-use it is better
145  # maybe on top of the function, to use some data in post-processing
146  self.logger.debug("\n== clean parser for re-use")
147  self.clean()
148 
149  ## @brief Reset internal data members after a parsing.
150  # Because parsers can be re-used, it deletes previous data\n
151  # Most (if not all) of the data member belongs to the abtract base class, the default may be sufficient for most of the specific parsers
152  def clean(self):
153  # cannot reset e_action defined in the init function if reuse, other ok
154  self.url = None
155  #self.e_action = None
156  self.local_stock = None
157  # delete all associated data
158  self.list_return_data = None
159 
160  ## @brief Make a local copy of the Stock objects (linked of the original in DictionaryStocks in fact) into a local dictionary.
161  # @pre Stock must be valid (not InvalidStock) changed accept InvalidStock but does not add to the local dictionary
162  # @param list of stock symbols
163  def store_stock_copy( self, list_stock ):
164  self.logger.debug("Entry store_stock_copy")
165  self.logger.debug("list_stock: %s" % list_stock)
166 
167  list_obj_stocks = DictionaryStocks().get_stocks( list_stock )
168  self.local_stock = {}
169 
170  for elem in Stock.to_list(list_obj_stocks):
171  # allow InvalidStock, no need to filter before, but check the size before running parser
172  if ( isinstance(elem, InvalidStock) == False ):
173  # new added, pre_process here
174  # try
175  elem.pre_process( self.e_action )
176  self.local_stock[ elem.symbol ] = elem
177 
178  self.logger.debug("local_stock %s" % self.local_stock)
179 
180  ## @brief Update the local stocks with the new retrieved data.
181  # This version in the base class calls Stock.add_dict_stock(), which implements the update. \n
182  # More tests could be done if a specific parser override it.\n
183  def update_stock( self ):
184  self.logger.debug("Entry update_stock")
185 
186  list_dict = Utils.to_list( self.list_return_data )
187 
188  # a list of template dictionary, added source parser
189  for elem in list_dict:
190  symbol = elem['symbol']
191  self.local_stock[ symbol ].add_dict_stock( elem['action_templ'], self.source )
192  # post-process can be run from here now, if config is correct
193  print "\n== After add_dict_stok"
194  # which action ? self.e_action ok + option_post from update_stock
195  #print "stock._dict_stock", self.local_stock[symbol].get_action()
196 
197  # new added, post_process can be done by each parser
198  #if self.option_post:
199  if hasattr(self,'option_post'):
200  print "option_post present"
201  self.local_stock[ symbol ].post_process( self.e_action, self.option_post )
202  else:
203  print "option_post not present"
204 
205 # To move into DictionaryStocks if used by others modules that parsers
206 
207  ## @brief Retrieve the symbol (or Stock) from the code_yahoo or code_bourso.
208  # Needed for parsing multiple CSV data, if order is not fixed.\n
209  # @param code read from the CSV line
210  # @param code_type 'code_yahoo' or 'code_bourso'
211  def get_symbol_from_code( self, code, code_type ):
212  self.logger.debug("get_symbol_from_code: %s" % code )
213  assert( ( code_type == 'code_yahoo') | (code_type == 'code_bourso') ),\
214  "Error in argument for code_type: %s" % code_type
215 
216  for key, value in self.local_stock.iteritems():
217  if value.get_action('Static',code_type) == code:
218  self.logger.debug("return symbol: %s" % key)
219  return key
220  # do not generate traceback
221  raise ParserError("Cannot retrieve symbol from '%s': %s" %(code_type, code), \
222  self.local_stock.keys(), self.e_action.name, self.url)
223 
224 
def clean
Reset internal data members after a parsing.
Definition: Abstract.py:152
def run_parser
Main function to call for running the parser.
Definition: Abstract.py:82
e_action
enumeration (EAction) of the type of query to perform
Definition: Abstract.py:40
def store_stock_copy
Make a local copy of the Stock objects (linked of the original in DictionaryStocks in fact) into a lo...
Definition: Abstract.py:163
Define 2 abstract methods which need to be overridden by the Parsers and a generic algorithm (run_pa...
Definition: Abstract.py:29
Define custom and specific exceptions for the complete package.
Derived class specific to the parsers.
def get_symbol_from_code
Retrieve the symbol (or Stock) from the code_yahoo or code_bourso.
Definition: Abstract.py:211
Container of all Stocks objects, it also reads the static stocks configuration file "dictstocks...
Define the classes Stock and InvalidStock.
Definition: Stock.py:1
Global variables for configuration: paths, TCP ports and generic definitions.
Definition: GlobalDicts.py:1
def parse
Parse the text file (html, CSV,...)
Definition: Abstract.py:60
url
save url, useful for reporting errors and exceptions
Definition: Abstract.py:37
def update_stock
Update the local stocks with the new retrieved data.
Definition: Abstract.py:183
def create_url
Store links to stocks to update, dictionary 'symbol' : Stock object.
Definition: Abstract.py:55
Define singleton class DictionaryStocks, act as the main container of Stocks objects.