ServerPortfolio  2.0
Python parsers and server
 All Classes Namespaces Files Functions Variables Properties Pages
DictionaryStocks.py
Go to the documentation of this file.
1 ## @package serverportfolio.DictionaryStocks
2 #
3 # Define singleton class @ref serverportfolio.DictionaryStocks.DictionaryStocks "DictionaryStocks",
4 # act as the main container of Stocks objects.
5 #
6 # Last Changed $Id: DictionaryStocks.py 13 2015-04-12 19:45:14Z michael $
7 
8 # why not use singleton
9 # http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python
10 # http://c2.com/cgi/wiki?PythonSingleton
11 
12 # trick for python package, seems obsolete
13 # http://stackoverflow.com/questions/6654951/doxygen-chokes-on-init-py-files
14 
15 import os, re, types, logging
16 import traceback
17 import time, datetime
18 
19 # solved bug, unit_test can change the global variable DICT_PATH
20 # from serverportfolio.GlobalDicts import *
21 import serverportfolio.GlobalDicts as GlobalDicts
22 
23 from serverportfolio import Utils
24 from serverportfolio.PortfolioException import PortfolioError, ParserError
25 from serverportfolio.StockTemplates import StTmpl
26 from serverportfolio.Stock import Stock, InvalidStock
27 from serverportfolio.ModClientTCP import TCPClient
28 
29 # to put in class, for extending with portfolio
30 # save the name of the dictionary, in case of multiple dictionary.txt
31 # set up when called the first time, used when a reload is needed
32 # name_dict = None
33 
34 ## @class Singleton
35 # @brief Define a metaclass Singleton object.
36 #
37 # @note Due to the naming convention in python, a "main" cannot be run inside the same file.\n
38 # The object will be named main.DictionaryStocks inside the file, and a different instance would be created by other modules.\n
39 # @note See unit-test for _drop() function in order to "delete" a Singleton.
40 class Singleton(type):
41  _instances = {}
42 
43  ## @brief Executed at the object creation, created only once. Then return the saved instance.
44  def __call__(cls, *args, **kwargs):
45  #print "cls._instances ", cls._instances
46  if cls not in cls._instances:
47  # better here, should be created only once
48  cls.logger=logging.getLogger("SP.Singleton")
49  cls.logger.debug("create new instance singleton")
50  # call DictStocks.__init__()
51  cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
52  else:
53  cls.logger.debug("return already created instance")
54  return cls._instances[cls]
55 
56 # note python 3 would be
57 #class MyClass(BaseClass, metaclass=Singleton):
58 
59 ## @class DictionaryStocks
60 # @brief Container of all Stocks objects, it also reads the static stocks configuration file "dictstocks.txt"
61 #
62 # It is a singleton object, responsible of:
63 # - reading static definition in the configuration file
64 # - the Stock objects creation, initialisation and destruction
65 # - the connection to the GUIServer (in ROOT_application)
66 #
67 # The singleton will be normally initialized at its first use (with the reading of the Static stocks data).\n
68 # The Stock objects are then initialised on demand by calls to get_stocks()\n
69 #
70 # To force the initialisation of all the stocks in the configuration file, one must call explicitely the constructor:
71 # @code DictionaryStocks.DictionaryStocks(load_all_stock=True) @endcode
72 #
73 # Functionalities:
74 # - print stock data to screen
75 # - send new data to the GUIServer
76 #
77 # The container is basically implemented as a dictionary of Stock.
79  ## @brief Define DictionaryStocks as a Singleton.
80  # A call to DictionaryStocks() will always return the unique instance of the class
81  __metaclass__ = Singleton
82 
83  ## @brief Initialise the singleton dictionary and read the configuration text file.
84  #
85  # By default store the content of the stocks definition, but do not initialise the Stock objects.\n
86  # These objects will be created on demand by calls to get_stocks().\n
87  # The boolean option load_all_stocks will force to create and initialise all Stock present in the configuration file.
88  # @param file_stocks optional with default name 'dictstocks.txt'
89  # @param load_all_stocks if True will create and initialise all Stock objects.
90  def __init__(self, file_stocks=GlobalDicts.DEFAULT_DICTIONARY, load_all_stocks = False):
91  self.logger = logging.getLogger("SP.DictStocks")
92  self.logger.debug("Init DictionaryStocks")
93 
94  ## @brief Dictionary containing Stock objects with key/value = 'symbol' : Stock object.
95  # __getitem__ / __setitem__ are implemented to avoid a call to the private data member
96  self._dictstocks={}
97 
98  ## @brief Store the content of the static stocks definitions (dictstocks.txt).
99  # 'Static' data are ead from this configuration file and stored in the 'Static' template
101 
102  ## @brief Store the name of the configuration file.
103  # The full path is created in the function _read_config_txt()\n
104  # multiple configuration files not implemented yet
105  self._file_stocks = file_stocks
106 
107  # read the definition of stocks, static data stored in _dictstocks_config
108  try :
109  list_stocks = self._read_config_txt( file_stocks )
110  # wrong filename
111  except PortfolioError:
112  self.logger.error("Caught PortfolioError in _read_config_txt. Should not happen !")
113  raise
114  # other exception in parsing ?
115  except Exception:
116  self.logger.error("Catch Exception in _read_config_txt. Should not happen !")
117  raise
118 
119  self.logger.debug("list of stocks loaded by configuration %s" % list_stocks)
120 
121  # If True all Stock objects will be created.
122  # Not necessary if only one query is needed, will be created later on demand.
123  if load_all_stocks == True:
124  self._create_stocks(stock_symbol=None)
125 
126  ## @brief Access to the TCPClient for sending update to the GUIServer.
127  # If data do not come back after an update, it should be done by Stock or Parser.\n
128  # keep here is not bad, or independent singleton... but activated only if GUI or receive a message
129  # @todo to re-implement, test functionalities
131  # check the connection is possible, set activity
132  # self.tcp_client.active = self.tcp_client.Connect( only_check=True )
133 
134  ## @name Basic dictionary functionalities to access the Stock objects
135  ## @{
136 
137  ## @brief Access by key.
138  # return a Stock object, may be an InvalidStock if not present in the configuration file
139  def __getitem__(self, item):
140 
141  if item in self._dictstocks:
142  return self._dictstocks[item]
143 
144  # create on demand, return only one stock (not a list)
145  return self.get_stocks( item )
146 
147  ## @brief Set with key / value
148  def __setitem__(self, key, value):
149  assert isinstance(value,Stock), 'Only Stock object can be assigned'
150  self._dictstocks[key] = value
151  ## @}
152 
153 # ############# Getter/Setter functions, public interface, some to embed in C++ ##############
154 
155  ## @brief Return a Stock (or a list of Stock) object(s) from a symbol (or a list of symbols).
156  # Initialise the Stock object if necessary.\n
157  #
158  # @post Always return the required stocks, maybe an InvalidStock\nNo exception but assert\n
159  #
160  # Return an InvalidStock if the stock symbol is not in the configuration file
161  # @param input_stock a stock symbol or a list of symbols
162  # @return a stock object or a list of stock objects (depends on input)
163  def get_stocks(self, input_stock): # = None):
164  self.logger.debug("get_stocks: %s" % input_stock)
165 
166  list_stock = Utils.to_list( input_stock )
167  # list of objects to return
168  list_obj_stock = []
169 
170  # test, stocks should be loaded only once, correct but not convenient
171  #list_to_create = [symbol for symbol in list_stock if symbol not in self._dictstocks]
172  #print "list_to_create ", list_to_create
173  #self._create_stocks( list_to_create )
174 
175  for symbol in list_stock:
176  # check if Stock already exists or need to be created
177  if symbol not in self._dictstocks:
178  # couple of asserts in _create_stocks
179  self._create_stocks( symbol )
180 
181  # exist for sure, maybe InvalidStock
182  list_obj_stock.append( self[symbol] )
183 
184  # return 1 object, not a list, if only one was asked in input
185  if len(list_stock) == 1:
186  return list_obj_stock[0]
187  else:
188  return list_obj_stock
189 
190 # created for UpdateStocks get_data
191  ## @brief Return the list of stock symbols loaded from the configuration file, do not create the Stock objects.
192  # @param all_stocks True : return all stocks read from the config file, False : only Stock in memory
193  def get_stock_keys(self, all_stocks = False):
194  self.logger.debug("get_stock_config.keys :%s", self._dictstocks_config.keys())
195  if all_stocks == True:
196  return self._dictstocks_config.keys()
197  else:
198  return self._dictstocks.keys()
199 
200  ## @brief Return the static template from a stock symbol.
201  # If the symbol is not in the configuration file, the default Static template is returned
202  # @param symbol of the stock
203  def get_dict_static(self, symbol):
204  if symbol in self._dictstocks_config:
205  return self._dictstocks_config[symbol] #['Static'] after modifs in read_config
206  # return default
207  return StTmpl.get_template_stock('Static')
208 
209  ## @brief Print (a nice) output of the data.
210  # Can print more than one stock.\n
211  # If data have not been loaded or Stock is invalid, the format is: 'symbol ERROR error_message'
212  #
213  # @param action type of data/EAction to return
214  # @param input_stocks one stock(string) or list of stocks, default None to use with opt_all_stocks
215  # @param opt_all_stocks print data from all stocks read in the configuration file, not only loaded as Stocks (InvalidStock do not appear)
216  # @param opt_header add a header line to describe the data
217  # @return a string with '\n' as separator
218  def print_dict_stocks( self, action, input_stocks = None, opt_all_stocks = False, opt_header = False ):
219  self.logger.debug("Entry print_dict_stocks, action: %s" % action )
220  self.logger.debug("list stock: %s" % input_stocks)
221 
222  # all stocks in config file
223  if opt_all_stocks == True:
224  list_stock = self._dictstocks_config.keys()
225  # only the stocks already loaded
226  else :
227  # default argument print all stocks in dictionary, include InvalidStock
228  if input_stocks == None:
229  list_stock = self._dictstocks.keys()
230  # test if one element (string) or a list
231  else :
232  list_stock = Utils.to_list( input_stocks )
233 
234  # case of header, add a first line starting with '#'
235  stock_lines = str()
236  if opt_header:
237  stock_lines += StTmpl.make_header(action)
238 
239  # create the line(s)
240  for stock in list_stock:
241  # action argument specifies which template to use
242  stock_lines += self.get_stocks(stock).print_stock( action )
243 
244  return stock_lines
245 
246 # #### to re-implement
247 # # brief Send the instantaneous (maybe other later) data to the server_gui
248 # def send_to_server_gui(self, list_stock):
249 # if self._server_gui:
250 
251 # ############# private interface ###########
252 
253  ## @brief Create and initialise Stock objects.
254  # If stock_symbol = None, all stocks are loaded. Not good in public interface, ok if private.\n
255  # Only called by DictionaryStock (with load_all_stocks = True)\n
256  # If a symbol is wrong (not in the configuration file) an InvalidStock is created instead.
257  #
258  # @pre The stock(s) should be created only once(assert)
259  # @post Return a valid list of Stock objects, maybe InvalidStock
260  #
261  # @todo in case of reload, should recreate the Stock(problem with threads) ?
262  #
263  # @param stock_symbol list of symbols to create, if None create all from the configuration file
264  def _create_stocks(self, stock_symbol):
265  # dangerous, if already stock loaded, and then call stock_symbol = None
266  # problems later, if dictionary is reloaded
267  if stock_symbol == None :
268  self.logger.info("Load all Stocks from the configuration file")
269  assert len(self._dictstocks) == 0, \
270  "Option stock_symbol = None, but dictionary not empty"
271  list_symbol = self._dictstocks_config.keys()
272  else :
273  list_symbol = Utils.to_list( stock_symbol )
274 
275  for symbol in list_symbol:
276  # not already loaded
277  assert symbol not in self._dictstocks, \
278  "Try to create an already loaded Stock"
279  # check symbol exists, could overwrite symbol to Invalid but loose the wrong name
280  #if not self._dictstocks_config.has_key(symbol):
281  if symbol not in self._dictstocks_config:
282  stock = InvalidStock( symbol, StTmpl.get_tmpl_static_invalid() )
283  else :
284  # Stock creation, set with static data
285  stock = Stock( symbol, self.get_dict_static( symbol ) )
286  # insert the new stock in the dictionary, even if InvalidStock
287  self[symbol] = stock
288 
289  ## @brief Read static definition of the stocks from the configuration text file.
290  # It sets the static data key in the global StockTemplates.StTmpl from the header of dictstocks.txt\n
291  # Then fill _dictstocks_config with Static stock template (config and template)
292  # Use later for Stock creation\n
293  #
294  # @todo To implement: reload the file with new stocks or new parameters.
295  #
296  # @param file_stocks filename of the portfolio file, default "dictstocks.txt"
297  # @throw PortfolioError if the file cannot be opened
298  def _read_config_txt(self, file_stocks):
299  self.logger.debug("read_config_txt file: %s" % file_stocks)
300  # full path necessary when called from C++ with embedding of python code
301  file_stocks = GlobalDicts.PATH_DICT + os.sep + self._file_stocks
302  self.logger.debug("file_stocks: %s" % file_stocks)
303 
304  # check if global name_dict already setup, used for reload
305  #if name_dict != None :
306  #if self._file_stocks != None:
307  #file_stocks = name_dict
308  # self._file_stocks =
309  # self.logger.info("reload dictionary %s" % name_dict)
310  #print "reload dictionary %s" % name_dict
311 
312  # first time setup, use default if needed
313  #else :
314  # name_dict = file_stocks
315  # self.logger.info("load dictionary for the first time %s " % name_dict)
316  #print "load dictionary for the first time %s" % name_dict
317 
318  try:
319  self.logger.debug("open file %s" % file_stocks)
320  file=open(file_stocks,"r")
321  except :
322  self.logger.error("file: %s cannot be opened, throw PortfolioError" % file_stocks )
323  # throw a FileError would be cleaner, similar to C++ and can re-use
324  raise PortfolioError("Cannot open file: %s" % file_stocks)
325 
326  # read header, delete terminal \n
327  line = file.readline()[:-1]
328  # extract the key words from the header
329  list_key = line.split()
330  # delete first one : "##"
331  list_key.pop(0)
332  # This is the keys for static data
333  self.logger.debug("list_key in config %s" % list_key)
334 
335  # set model StTmpl._tmpl_static, should be done only once, assert in StTmpl
336  # pass the first element : symbol, will be the key
337  StTmpl.set_tmpl_static( list_key[1:] )
338 
339  # if dictionary is reloaded, send back the list of new stocks
340  list_new = []
341 
342  # with to be sure to close the file
343  for line in file.readlines():
344  # discard empty line, line.rsplit()
345  if line == '\n' : #if line in ['\n', '\r\n']
346  continue
347 
348  data=line.split()
349  # escape commented line starting with "#"
350  if data[0][0] == "#":
351  continue
352 
353  # check if a new symbol is found, important if reload of data which already exists
354  if data[0] not in self._dictstocks_config:
355 
356  # in case of reload only, add only the new symbol (not re-implemented)
357  list_new.append(data[0])
358  # get a copy of 'static','template'
359  tmpl_static = StTmpl.get_template_stock('Static')
360  # loop from 1, symbol will be the key
361  for i in range(1,len(data)):
362  # Need to modify the value of 'market' for OpeningMarket
363  if list_key[i]=='market':
364  #print "market ", data[i]
365  data[i] = GlobalDicts.MarketMySQL_to_DictStock(data[i])
366 
367  # previous code
368  #tmpl_static[ list_key[i] ] = data[i]
369  # new code, could be set_template('Static'), no...
370  tmpl_static['template'][ list_key[i] ] = data[i]
371 
372  # assign to dictstocks_config with symbol as keyword
373  self._dictstocks_config[ data[0] ] = tmpl_static
374 
375  file.close()
376  self.logger.debug("Dictionary Stocks configuration: %s" % self._dictstocks_config)
377  # never used, by reload ? written but not used at this point
378  return list_new
379 
380 # ############ Function related to the server, define a client #########
381 
382 ## Used by C++ to activate / desactivate the client socket.
383 #
384 # tcp_client was created at the initialisation of DictionaryStocks.
385 # flag active true/false determine if guistock server is running.
386 # coded here easier for the integration with C++
388 
389  print "Activate TCPClient"
390  # to check if necessary
391  # better get the tcp_client from singleton dictionary
392  global tcp_client
393 
394  #if ( tcp_client == None ):
395  if ( tcp_client.GetActivity() == False ):
396  self.logger.info("activate tcp_client")
397  #tcp_client = TCPClient()
398  tcp_client.active = True
399  else:
400  self.logger.info("tcp_client already activated")
401 
402 ## Used by C++ to desactivate the client.
404 
405  print "Desactivate TCPClient"
406  global tcp_client
407 
408  #if ( tcp_client != None ):
409  if ( tcp_client.GetActivity() == True ):
410  #tcp_client = None
411  tcp_client.active = False
412  self.logger.info("desactivate tcp_client")
413  else:
414  self.logger.info("tcp_client already desactivated")
415 
416 
417 # seems impossible with the singleton, or some very tricky one ??
418 # the version created here under the __main__Dictionnar..., works as soon it is in a different file
419 
420 # ############# Main only for standalone test ########
421 
422 # # problem because DictStock variable in __name__, cannot access from import module !
423 # if __name__ == "__main__":
Define a metaclass Singleton object.
def DesactivateTCPClient
Used by C++ to desactivate the client.
def __call__
Executed at the object creation, created only once.
def get_stocks
Return a Stock (or a list of Stock) object(s) from a symbol (or a list of symbols).
Base class of the custom exceptions.
def _create_stocks
Create and initialise Stock objects.
Make a connection to the root application server to send directly new data.
Definition: ModClientTCP.py:19
def __init__
Initialise the singleton dictionary and read the configuration text file.
def ActivateTCPClient
Used by C++ to activate / desactivate the client socket.
_dictstocks_config
Store the content of the static stocks definitions (dictstocks.txt).
Make a class for sending new data to the server of ROOT_application.
Definition: ModClientTCP.py:1
_dictstocks
Dictionary containing Stock objects with key/value = 'symbol' : Stock object.
def get_dict_static
Return the static template from a stock symbol.
Define the global variable StockTemplates.StTmpl and dictionary templates.
Class template for invalid stocks, if a stock symbol is not found in the configuration file...
Definition: Stock.py:1185
Define custom and specific exceptions for the complete package.
def get_stock_keys
Return the list of stock symbols loaded from the configuration file, do not create the Stock objects...
Store data and functions related to one stock.
Definition: Stock.py:63
_file_stocks
Store the name of the configuration file.
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 _read_config_txt
Read static definition of the stocks from the configuration text file.
_tcp_client
Access to the TCPClient for sending update to the GUIServer.
def print_dict_stocks
Print (a nice) output of the data.