Path: csiph.com!usenet.pasdenom.info!aioe.org!news.stack.nl!newsfeed.xs4all.nl!newsfeed4.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'else:': 0.03; 'essentially': 0.04; 'parameters': 0.04; 'argument': 0.05; 'configure': 0.05; 'insert': 0.05; 'output': 0.05; 'class,': 0.07; 'column': 0.07; 'debugging': 0.07; 'failing': 0.07; 'float': 0.07; 'none,': 0.07; 'reject': 0.07; 'removes': 0.07; 'table.': 0.07; 'transform': 0.07; '(self,': 0.09; '__init__': 0.09; 'classes.': 0.09; 'decorator': 0.09; 'derived': 0.09; 'historic': 0.09; 'insertion': 0.09; 'inserts': 0.09; 'key.': 0.09; 'maker': 0.09; 'none)': 0.09; 'okay': 0.09; 'output,': 0.09; 'parameter': 0.09; 'prevents': 0.09; 'runtime': 0.09; 'subject:skip:t 10': 0.09; 'wrapper': 0.09; 'runs': 0.10; 'def': 0.12; "'''": 0.16; "'to": 0.16; '(),': 0.16; '(1,': 0.16; '(high': 0.16; '(int': 0.16; '(self):': 0.16; '(tm)': 0.16; '16),': 0.16; 'assembled': 0.16; 'between.': 0.16; 'columns': 0.16; 'csv': 0.16; 'fly.': 0.16; 'give.': 0.16; 'keyed': 0.16; 'link,': 0.16; 'mean,': 0.16; 'modularity': 0.16; 'needless': 0.16; 'nesting': 0.16; 'presume': 0.16; 'received:195.186': 0.16; 'received:bluewin.ch': 0.16; 'retains': 0.16; 'self.get': 0.16; 'sequence.': 0.16; 'think.': 0.16; 'towns': 0.16; 'track.': 0.16; 'true),': 0.16; 'urllib': 0.16; 'index': 0.16; 'filename:fname piece:txt': 0.16; 'library': 0.18; 'all,': 0.19; 'module': 0.19; 'file,': 0.19; 'passing': 0.19; 'meant': 0.20; 'work,': 0.20; 'written': 0.21; '>>>': 0.22; 'input': 0.22; 'example': 0.22; 'import': 0.22; 'separate': 0.22; 'print': 0.22; 'header:User-Agent:1': 0.23; 'adds': 0.24; 'comparing': 0.24; 'example.': 0.24; 'instance,': 0.24; 'replace': 0.24; 'text.': 0.24; 'header': 0.24; '2.0': 0.26; 'equivalent': 0.26; 'task': 0.26; 'post': 0.26; 'values': 0.27; 'developing': 0.27; 'record': 0.27; 'fixed': 0.29; 'feature': 0.29; 'quickly': 0.29; "doesn't": 0.30; 'sets': 0.30; 'skip:( 20': 0.30; "skip:' 10": 0.31; '(since': 0.31; 'consequence': 0.31; 'keys': 0.31; 'quotes': 0.31; 'skip:- 100': 0.31; 'allows': 0.31; 'file': 0.32; 'class': 0.32; 'handled': 0.32; 'run': 0.32; 'another': 0.32; 'open': 0.33; 'running': 0.33; 'table': 0.34; 'sense': 0.34; 'basic': 0.35; 'bridge': 0.65; 'relatively': 0.65; 'temporary': 0.65; 'town': 0.65; 'subject:. ': 0.67; 'close': 0.67; 'date,': 0.68; 'methodology': 0.68; 'percent': 0.68; 'savings.': 0.68; 'finance': 0.70; 'risk': 0.72; 'repeat': 0.74; 'low': 0.83; 'blown': 0.84; 'fails,': 0.84; 'finance.': 0.84; 'gap': 0.84; 'high,': 0.84; 'subject:inviting': 0.84; 'tx:': 0.84; 'composite': 0.91; 'mean.': 0.91; 'ratio': 0.91; 'trend': 0.91; 'try.': 0.91; 'serious': 0.97 Date: Wed, 21 Aug 2013 18:29:59 +0200 From: "F.R." User-Agent: Mozilla/5.0 (X11; Linux i686; rv:17.0) Gecko/20130510 Thunderbird/17.0.6 MIME-Version: 1.0 To: python-list@python.org Subject: A data transformation framework. A presentation inviting commentary. Content-Type: multipart/mixed; boundary="------------090204020502050708050302" X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 565 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1377102672 news.xs4all.nl 15897 [2001:888:2000:d::a6]:53814 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:52772 This is a multi-part message in MIME format. --------------090204020502050708050302 Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit Hi all, In an effort to do some serious cleaning up of a hopelessly cluttered working environment, I developed a modular data transformation system that pretty much stands. I am very pleased with it. I expect huge time savings. I would share it, if had a sense that there is an interest out there and would appreciate comments. Here's a description. I named the module TX: The nucleus of the TX system is a Transformer class, a wrapper for any kind of transformation functionality. The Transformer takes input as calling argument and returns it transformed. This design allows the assembly of transformation chains, either nesting calls or better, using the class Chain, derived from 'Transformer' and 'list'. A Chain consists of a sequence of Transformers and is functionally equivalent to an individual Transformer. A high degree of modularity results: Chains nest. Another consequence is that many transformation tasks can be handled with a relatively modest library of a few basic prefabricated Transformers from which many different Chains can be assembled on the fly. A custom Transformer to bridge an eventual gap is quickly written and tested, because the task likely is trivial. A good analogy of the TX methodology is a road map with towns scattered all over it and highways connecting them. To get from any town to any other one is a simple matter of hopping the towns in between. The TX equivalent of the towns are data formats, the equivalent of the highways are TX Transformers. They are not so much thought of in terms of what they do than in terms of the formats they take and give. Designing a library of Transformers is essentially a matter of establishing a collection of standard data formats. First the towns, then the highways. A feature of the TX Transformer is that it retains both its input and output. This makes a Chain a breeze to build progressively, link by link, and also makes debugging easy: If a Chain doesn't work, Chain.show () reveals the failing link as the first one that has no output. It can be replaced with a corrected instance, as one would replace a blown fuse. Running the Chain again without input makes it have another try. Parameter passing runs on a track that is completely separate from the payload track. Parameters can be set in order to configure a Chain prior to running it, or can be sent at runtime by individual Transformers to its siblings and their progeny. Parameters are keyed and get picked up by those Chain links whose set of pre-defined keys includes the parameter's key. Unintended pick-ups with coincidentally shared keys for unrelated parameters can be prevented by addressing parameters to individual Translators. Below an application example. Five custom classes at the end exemplify the pattern. I join the post also as attachment, in case some auto-line-wrap messes up this text. Commentary welcome Frederic ---------------------------------------------------------------------------------------------------- An example of use: Download historic stock quotes from Yahoo Finance for a given range of dates and a list of symbols, delete a column and add three, insert the data in a MySQL table. Also write them to temporary files in tabular form for verification. "make_quotes_writer ()" returns a custom transformation tree. "run_quotes ()" makes such a tree, sets it on a given time range and runs it on a list of symbols. (Since Yahoo publishes the data for downloading, I presume it's okay to do it this way. This is a demo of TX, however, and should not be misconstrued as an encouragement to violate any publisher's terms of service.) import TX, yahoo_historic_quotes as yhq def make_quotes_writer (): Visualizer = TX.Chain ( yhq.percent (), TX.Table_Maker (has_header = True), TX.Table_Writer (), name = 'Visualizer' ) To_DB = TX.Chain (yhq.header_stripper(), TX.DB_Writer(table_name = 'quotes'), name = 'To DB') To_File = TX.Chain (Visualizer, TX.File_Writer (), name = 'To File') Splitter = TX.Splitter (To_DB, To_File, name = 'Splitter') Quotes = TX.Chain ( yhq.yahoo_quotes (), TX.CSV_To_List (delimiter = ','), TX.Numerizer (), yhq.wiggle_and_trend (), yhq.symbol (), Splitter, name = 'Quotes' ) return Quotes >>> Quotes = make_quotes_writer () >>> Quotes.show_tree() Quotes Quotes[0] - Yahoo Quotes Quotes[1] - CSV To List Quotes[2] - Numerizer Quotes[3] - Wiggle and Trend Quotes[4] - Symbol Quotes[5] - Splitter Quotes[5][0] - To DB Quotes[5][0][0] - Header Stripper Quotes[5][0][1] - DB Writer Quotes[5][1] - To File Quotes[5][1][0] - Visualizer Quotes[5][1][0][0] - Percent Quotes[5][1][0][1] - Table Maker Quotes[5][1][0][2] - Table Writer Quotes[5][1][1] - File Writer def run_quotes (symbols, from_date = '1970-01-01', to_date = '2099-12-31'): '''Downloads historic stock quotes from Yahoo Finance. Inserts data into MySQL table "quotes" relying on index to reject repeat insertions. Also writes the data in a table format to a temporary file, one for each symbol. ''' Quotes = make_quotes_writer () Quotes.set (from_date = from_date, to_date = to_date) FW = Quotes [5][1][1] # File Writer for symbol in symbols: outfilename = '/tmp/%s' % symbol FW.set (terminal = outfilename) Quotes.set (symbol = symbol) Quotes () >>> run_quotes (('MO', 'SU'), '2013-08-10') Checking: >>> DBRC = TX.DB_Run_Command () >>> for record in DBRC ("select * from quotes where symbol in ('MO','SU') and date >= 20130810;"): print record (datetime.date(2013, 8, 16), 'MO', 34.51, 34.63, 34.25, 34.29, 8364900L, 0.0110337, -0.00638792) (datetime.date(2013, 8, 15), 'MO', 34.78, 34.93, 34.5, 34.57, 15144400L, 0.0123866, -0.00604926) (datetime.date(2013, 8, 14), 'MO', 35.23, 35.25, 34.81, 35.06, 5871900L, 0.0125607, -0.00485298) (datetime.date(2013, 8, 13), 'MO', 35.17, 35.27, 35.01, 35.22, 5919700L, 0.00739898, 0.00142288) (datetime.date(2013, 8, 12), 'MO', 35.25, 35.28, 35.05, 35.16, 4884300L, 0.00654059, -0.00255936) (datetime.date(2013, 8, 16), 'SU', 33.84, 34.63, 33.81, 34.18, 7396600L, 0.0239626, 0.00993571) (datetime.date(2013, 8, 15), 'SU', 32.84, 33.99, 32.7, 33.94, 7747100L, 0.0386865, 0.0329885) (datetime.date(2013, 8, 14), 'SU', 32.06, 32.91, 32.02, 32.72, 4690200L, 0.0274141, 0.0203296) (datetime.date(2013, 8, 13), 'SU', 32.04, 32.18, 31.94, 32.01, 2051900L, 0.00748596, -0.000935745) (datetime.date(2013, 8, 12), 'SU', 32.15, 32.35, 31.99, 32.15, 2870500L, 0.0111906, 0.0) >>> FR = TX.File_Reader () >>> print FR ('/tmp/SU') Date | Symbol | Open | High | Low | Close | Volume | Wiggle | Trend | 2013-08-16 | SU | 33.84 | 34.63 | 33.81 | 34.18 | 7396600.00 | 2.40% | +0.99% | 2013-08-15 | SU | 32.84 | 33.99 | 32.70 | 33.94 | 7747100.00 | 3.87% | +3.30% | 2013-08-14 | SU | 32.06 | 32.91 | 32.02 | 32.72 | 4690200.00 | 2.74% | +2.03% | 2013-08-13 | SU | 32.04 | 32.18 | 31.94 | 32.01 | 2051900.00 | 0.75% | -0.09% | 2013-08-12 | SU | 32.15 | 32.35 | 31.99 | 32.15 | 2870500.00 | 1.12% | +0.00% | ---------------------------------------------------------------------------------------------------- Each Transformer retains input and output, freezing the state of a composite after each run. This makes developing step by step and debugging a breeze. If a Chain fails, the failing link is the first one to have no output. Chain.show () displays them all in sequence. The deficient Transformer can be replaced with a fixed instance and the Chain run again without input for another try. >>> Quotes.replace (Fixed_Numerizer, 2) >>> catch = Quotes () # catch removes the risk of flooding the display ---------------------------------------------------------------------------------------------------- These are the five custom classes. The decorator "setup" updates parameters and prevents needless reruns comparing time stamps. class wiggle_and_trend (TX.Transformer): '''Deletes column "Adj Close" and adds columns "Wiggle" and "Trend". The values are ratios: (day's high - day's low) / mean, and (day's close - day's open) / mean. ''' name = 'Wiggle and Trend' @TX.setup def transform (self): input = self.Input.data output = [] output.append (tuple (input[0][:-1]) + ('Wiggle', 'Trend')) for i in range (1, len (input)): date, open, high, low, close, vol, adj = input [i] wiggle = high - low mean = (high + low) / 2.0 wiggle_ratio = wiggle / mean trend = close - open trend_ratio = trend / mean output.append ((date, open, high, low, close, vol, wiggle_ratio, trend_ratio)) self.Output.take (output) class symbol (TX.Transformer): 'Adds a column Symbol' name = 'Symbol' def __init__ (self): TX.Transformer.__init__ (self, symbol = None) @TX.setup def transform (self): symbol = self.get ('symbol') if not symbol: self.log ('No symbol!') else: input = self.Input.data output = [] output.append ((input[0][0], 'Symbol') + input [0][1:]) for i in range (1, len (input)): output.append ((input[i][0], symbol) + input [i][1:]) self.Output.take (output) class percent (TX.Transformer): 'Converts float ratio to percent for better legibility' name = 'Percent' @TX.setup def transform (self): input = self.Input.data output = [input [0]] for i in range (1, len (input)): wiggle = '%5.2f%%' % (input [i][7] * 100.0) trend = '%+5.2f%%' % (input [i][8] * 100.0) output.append (input [i][:7] + (wiggle, trend)) self.Output.take (output) class header_stripper (TX.Transformer): 'Header names are not meant for insertion into data base table' name = 'Header Stripper' @TX.setup def transform (self): self.Output.take (self.Input.data [1:]) class yahoo_quotes (TX.WWW_Reader): 'Gets historic stock quotes from Yahoo Finance' import urllib name = 'Yahoo Quotes' URL_TRUNK = 'http://ichart.finance.yahoo.com/table.csv' def __init__ (self, from_date = '1970-01-01', to_date = '2099-12-31', **keywords): private_keys = { 'symbol' : None, 'from_date' : from_date, 'to_date' : to_date, 'url_trunk' : self.URL_TRUNK, } private_keys.update (keywords) TX.WWW_Reader.__init__ (self, **private_keys) def make_url (self): fy, fm, fd = self.get ('from_date').split ('-') ty, tm, td = self.get ('to_date').split ('-') fm = '%0d' % (int (fm) - 1) tm = '%0d' % (int (tm) - 1) symbol = self.get ('symbol') qs = ( ('s',symbol), ('a',fm),('b',fd),('c',fy), ('d',tm),('e',td),('f',ty), ('g','d'), ('ignore', '.csv'), ) self.set (url_parameters = qs) TX.WWW_Reader.make_url (self) ---------------------------------------------------------------------------------------------------- Let me know what you think. --------------090204020502050708050302 Content-Type: text/plain; charset=UTF-8; name="tx-presentation-post-d821.txt" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="tx-presentation-post-d821.txt" Hi all, In an effort to do some serious cleaning up of a hopelessly cluttered working environment, I developed a modular data transformation system that pretty much stands. I am very pleased with it. I expect huge time savings. I would share it, if had a sense that there is an interest out there and would appreciate comments. Here's a description. I named the module TX: The nucleus of the TX system is a Transformer class, a wrapper for any kind of transformation functionality. The Transformer takes input as calling argument and returns it transformed. This design allows the assembly of transformation chains, either nesting calls or better, using the class Chain, derived from 'Transformer' and 'list'. A Chain consists of a sequence of Transformers and is functionally equivalent to an individual Transformer. A high degree of modularity results: Chains nest. Another consequence is that many transformation tasks can be handled with a relatively modest library of a few basic prefabricated Transformers from which many different Chains can be assembled on the fly. A custom Transformer to bridge an eventual gap is quickly written and tested, because the task likely is trivial. A good analogy of the TX methodology is a road map with towns scattered all over it and highways connecting them. To get from any town to any other one is a simple matter of hopping the towns in between. The TX equivalent of the towns are data formats, the equivalent of the highways are TX Transformers. They are not so much thought of in terms of what they do than in terms of the formats they take and give. Designing a library of Transformers is essentially a matter of establishing a collection of standard data formats. First the towns, then the highways. A feature of the TX Transformer is that it retains both its input and output. This makes a Chain a breeze to build progressively, link by link, and also makes debugging easy: If a Chain doesn't work, Chain.show () reveals the failing link as the first one that has no output. It can be replaced with a corrected instance, as one would replace a blown fuse. Running the Chain again without input makes it have another try. Parameter passing runs on a track that is completely separate from the payload track. Parameters can be set in order to configure a Chain prior to running it, or can be sent at runtime by individual Transformers to its siblings and their progeny. Parameters are keyed and get picked up by those Chain links whose set of pre-defined keys includes the parameter's key. Unintended pick-ups with coincidentally shared keys for unrelated parameters can be prevented by addressing parameters to individual Translators. Below an application example. Five custom classes at the end exemplify the pattern. I join the post also as attachment, in case some auto-line-wrap messes up this text. Commentary welcome Frederic ---------------------------------------------------------------------------------------------------- An example of use: Download historic stock quotes from Yahoo Finance for a given range of dates and a list of symbols, delete a column and add three, insert the data in a MySQL table. Also write them to temporary files in tabular form for verification. "make_quotes_writer ()" returns a custom transformation tree. "run_quotes ()" makes such a tree, sets it on a given time range and runs it on a list of symbols. (Since Yahoo publishes the data for downloading, I presume it's okay to do it this way. This is a demo of TX, however, and should not be misconstrued as an encouragement to violate any publisher's terms of service.) import TX, yahoo_historic_quotes as yhq def make_quotes_writer (): Visualizer = TX.Chain ( yhq.percent (), TX.Table_Maker (has_header = True), TX.Table_Writer (), name = 'Visualizer' ) To_DB = TX.Chain (yhq.header_stripper(), TX.DB_Writer(table_name = 'quotes'), name = 'To DB') To_File = TX.Chain (Visualizer, TX.File_Writer (), name = 'To File') Splitter = TX.Splitter (To_DB, To_File, name = 'Splitter') Quotes = TX.Chain ( yhq.yahoo_quotes (), TX.CSV_To_List (delimiter = ','), TX.Numerizer (), yhq.wiggle_and_trend (), yhq.symbol (), Splitter, name = 'Quotes' ) return Quotes >>> Quotes = make_quotes_writer () >>> Quotes.show_tree() Quotes Quotes[0] - Yahoo Quotes Quotes[1] - CSV To List Quotes[2] - Numerizer Quotes[3] - Wiggle and Trend Quotes[4] - Symbol Quotes[5] - Splitter Quotes[5][0] - To DB Quotes[5][0][0] - Header Stripper Quotes[5][0][1] - DB Writer Quotes[5][1] - To File Quotes[5][1][0] - Visualizer Quotes[5][1][0][0] - Percent Quotes[5][1][0][1] - Table Maker Quotes[5][1][0][2] - Table Writer Quotes[5][1][1] - File Writer def run_quotes (symbols, from_date = '1970-01-01', to_date = '2099-12-31'): '''Downloads historic stock quotes from Yahoo Finance. Inserts data into MySQL table "quotes" relying on index to reject repeat insertions. Also writes the data in a table format to a temporary file, one for each symbol. ''' Quotes = make_quotes_writer () Quotes.set (from_date = from_date, to_date = to_date) FW = Quotes [5][1][1] # File Writer for symbol in symbols: outfilename = '/tmp/%s' % symbol FW.set (terminal = outfilename) Quotes.set (symbol = symbol) Quotes () >>> run_quotes (('MO', 'SU'), '2013-08-10') Checking: >>> DBRC = TX.DB_Run_Command () >>> for record in DBRC ("select * from quotes where symbol in ('MO','SU') and date >= 20130810;"): print record (datetime.date(2013, 8, 16), 'MO', 34.51, 34.63, 34.25, 34.29, 8364900L, 0.0110337, -0.00638792) (datetime.date(2013, 8, 15), 'MO', 34.78, 34.93, 34.5, 34.57, 15144400L, 0.0123866, -0.00604926) (datetime.date(2013, 8, 14), 'MO', 35.23, 35.25, 34.81, 35.06, 5871900L, 0.0125607, -0.00485298) (datetime.date(2013, 8, 13), 'MO', 35.17, 35.27, 35.01, 35.22, 5919700L, 0.00739898, 0.00142288) (datetime.date(2013, 8, 12), 'MO', 35.25, 35.28, 35.05, 35.16, 4884300L, 0.00654059, -0.00255936) (datetime.date(2013, 8, 16), 'SU', 33.84, 34.63, 33.81, 34.18, 7396600L, 0.0239626, 0.00993571) (datetime.date(2013, 8, 15), 'SU', 32.84, 33.99, 32.7, 33.94, 7747100L, 0.0386865, 0.0329885) (datetime.date(2013, 8, 14), 'SU', 32.06, 32.91, 32.02, 32.72, 4690200L, 0.0274141, 0.0203296) (datetime.date(2013, 8, 13), 'SU', 32.04, 32.18, 31.94, 32.01, 2051900L, 0.00748596, -0.000935745) (datetime.date(2013, 8, 12), 'SU', 32.15, 32.35, 31.99, 32.15, 2870500L, 0.0111906, 0.0) >>> FR = TX.File_Reader () >>> print FR ('/tmp/SU') Date | Symbol | Open | High | Low | Close | Volume | Wiggle | Trend | 2013-08-16 | SU | 33.84 | 34.63 | 33.81 | 34.18 | 7396600.00 | 2.40% | +0.99% | 2013-08-15 | SU | 32.84 | 33.99 | 32.70 | 33.94 | 7747100.00 | 3.87% | +3.30% | 2013-08-14 | SU | 32.06 | 32.91 | 32.02 | 32.72 | 4690200.00 | 2.74% | +2.03% | 2013-08-13 | SU | 32.04 | 32.18 | 31.94 | 32.01 | 2051900.00 | 0.75% | -0.09% | 2013-08-12 | SU | 32.15 | 32.35 | 31.99 | 32.15 | 2870500.00 | 1.12% | +0.00% | ---------------------------------------------------------------------------------------------------- Each Transformer retains input and output, freezing the state of a composite after each run. This makes developing step by step and debugging a breeze. If a Chain fails, the failing link is the first one to have no output. Chain.show () displays them all in sequence. The deficient Transformer can be replaced with a fixed instance and the Chain run again without input for another try. >>> Quotes.replace (Fixed_Numerizer, 2) >>> catch = Quotes () # catch removes the risk of flooding the display ---------------------------------------------------------------------------------------------------- These are the five custom classes. The decorator "setup" updates parameters and prevents needless reruns comparing time stamps. class wiggle_and_trend (TX.Transformer): '''Deletes column "Adj Close" and adds columns "Wiggle" and "Trend". The values are ratios: (day's high - day's low) / mean, and (day's close - day's open) / mean. ''' name = 'Wiggle and Trend' @TX.setup def transform (self): input = self.Input.data output = [] output.append (tuple (input[0][:-1]) + ('Wiggle', 'Trend')) for i in range (1, len (input)): date, open, high, low, close, vol, adj = input [i] wiggle = high - low mean = (high + low) / 2.0 wiggle_ratio = wiggle / mean trend = close - open trend_ratio = trend / mean output.append ((date, open, high, low, close, vol, wiggle_ratio, trend_ratio)) self.Output.take (output) class symbol (TX.Transformer): 'Adds a column Symbol' name = 'Symbol' def __init__ (self): TX.Transformer.__init__ (self, symbol = None) @TX.setup def transform (self): symbol = self.get ('symbol') if not symbol: self.log ('No symbol!') else: input = self.Input.data output = [] output.append ((input[0][0], 'Symbol') + input [0][1:]) for i in range (1, len (input)): output.append ((input[i][0], symbol) + input [i][1:]) self.Output.take (output) class percent (TX.Transformer): 'Converts float ratio to percent for better legibility' name = 'Percent' @TX.setup def transform (self): input = self.Input.data output = [input [0]] for i in range (1, len (input)): wiggle = '%5.2f%%' % (input [i][7] * 100.0) trend = '%+5.2f%%' % (input [i][8] * 100.0) output.append (input [i][:7] + (wiggle, trend)) self.Output.take (output) class header_stripper (TX.Transformer): 'Header names are not meant for insertion into data base table' name = 'Header Stripper' @TX.setup def transform (self): self.Output.take (self.Input.data [1:]) class yahoo_quotes (TX.WWW_Reader): 'Gets historic stock quotes from Yahoo Finance' import urllib name = 'Yahoo Quotes' URL_TRUNK = 'http://ichart.finance.yahoo.com/table.csv' def __init__ (self, from_date = '1970-01-01', to_date = '2099-12-31', **keywords): private_keys = { 'symbol' : None, 'from_date' : from_date, 'to_date' : to_date, 'url_trunk' : self.URL_TRUNK, } private_keys.update (keywords) TX.WWW_Reader.__init__ (self, **private_keys) def make_url (self): fy, fm, fd = self.get ('from_date').split ('-') ty, tm, td = self.get ('to_date').split ('-') fm = '%0d' % (int (fm) - 1) tm = '%0d' % (int (tm) - 1) symbol = self.get ('symbol') qs = ( ('s',symbol), ('a',fm),('b',fd),('c',fy), ('d',tm),('e',td),('f',ty), ('g','d'), ('ignore', '.csv'), ) self.set (url_parameters = qs) TX.WWW_Reader.make_url (self) ---------------------------------------------------------------------------------------------------- Let me know what you think. --------------090204020502050708050302--