Path: csiph.com!newsfeed.hal-mli.net!feeder3.hal-mli.net!newsfeed.hal-mli.net!feeder1.hal-mli.net!newsfeed.xs4all.nl!newsfeed2a.news.xs4all.nl!xs4all!newsgate.cistron.nl!newsgate.news.xs4all.nl!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; 'scripts': 0.03; 'languages.': 0.04; 'argument': 0.05; 'classes,': 0.05; 'output': 0.05; 'url:pipermail': 0.05; 'debugging': 0.07; 'element': 0.07; 'failing': 0.07; 'method.': 0.07; '(self,': 0.09; 'abstraction': 0.09; 'defines': 0.09; 'input,': 0.09; 'output,': 0.09; 'run,': 0.09; 'steps:': 0.09; 'subject:script': 0.09; 'testing,': 0.09; 'trailing': 0.09; 'python': 0.11; 'def': 0.12; 'mostly': 0.14; "wouldn't": 0.14; 'posted': 0.15; '(),': 0.16; "(it's": 0.16; '0.01': 0.16; '1.01': 0.16; 'argument,': 0.16; 'autonomous': 0.16; 'better:': 0.16; 'callable,': 0.16; 'dict': 0.16; 'element,': 0.16; 'nest:': 0.16; 'nesting': 0.16; 'parameters,': 0.16; 'reasonably': 0.16; 'received:195.186': 0.16; 'received:bluewin.ch': 0.16; 'retains': 0.16; 'splits': 0.16; 'ssh': 0.16; 'subject:between': 0.16; 'url:html)': 0.16; 'elements': 0.16; 'all.': 0.16; 'wrote:': 0.18; 'module': 0.19; 'any,': 0.19; 'deployment': 0.19; "skip:' 30": 0.19; 'input': 0.22; 'programming': 0.22; 'handles': 0.22; 'header:User-Agent:1': 0.23; 'convenient': 0.24; 'fine': 0.24; 'equivalent': 0.26; 'this:': 0.26; 'second': 0.26; 'code:': 0.26; 'certain': 0.27; 'developing': 0.27; 'header:In-Reply-To:1': 0.27; 'testing': 0.29; 'raise': 0.29; "doesn't": 0.30; 'forgot': 0.30; 'skip:( 20': 0.30; 'along': 0.30; 'asked': 0.31; 'correctly.': 0.31; 'fixing': 0.31; 'keywords,': 0.31; 'quotes': 0.31; 'repair': 0.31; 'file': 0.32; 'class': 0.32; 'this.': 0.32; 'summary': 0.32; 'run': 0.32; 'worked': 0.33; 'url:python': 0.33; 'running': 0.33; 'ago': 0.33; 'framework': 0.33; 'reader': 0.33; 'subject:the': 0.34; 'problem': 0.35; 'display': 0.35; 'classes': 0.35; 'common': 0.35; 'something': 0.35; 'done.': 0.35; 'but': 0.35; 'add': 0.35; 'there': 0.35; 'really': 0.36; 'adjust': 0.36; 'data,': 0.36; 'returning': 0.36; "didn't": 0.36; 'method': 0.36; 'url:org': 0.36; 'too': 0.37; 'list': 0.37; 'step': 0.37; 'architecture': 0.38; 'growing': 0.38; 'implement': 0.38; 'sometimes': 0.38; 'displays': 0.38; 'question,': 0.38; 'writes': 0.38; 'to:addr :python-list': 0.38; 'pm,': 0.38; 'resource': 0.38; 'previous': 0.38; 'does': 0.39; 'simply': 0.61; 'simple': 0.61; 'first': 0.61; 'name': 0.63; 'developed': 0.63; 'such': 0.63; 'design,': 0.64; 'interest': 0.64; 'more': 0.64; 'taking': 0.65; 'great': 0.65; 'management': 0.65; 'relatively': 0.65; 'design.': 0.68; 'results': 0.69; 'containing': 0.69; 'records,': 0.69; 'construction': 0.72; 'alongside': 0.84; 'chain:': 0.84; 'complexity': 0.84; 'coupled': 0.84; 'fails,': 0.84; 'improvement': 0.84; 'steps.': 0.91; 'thing,': 0.91; 'trend': 0.91; 'whereas': 0.91; 'remember,': 0.93 Date: Sat, 22 Feb 2014 15:56:41 +0100 From: "F.R." User-Agent: Mozilla/5.0 (X11; Linux i686; rv:24.0) Gecko/20100101 Thunderbird/24.2.0 MIME-Version: 1.0 To: python-list@python.org Subject: Re: Storing the state of script between steps References: In-Reply-To: Content-Type: text/plain; charset=ISO-8859-1; format=flowed Content-Transfer-Encoding: 7bit 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: 150 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1393081015 news.xs4all.nl 2869 [2001:888:2000:d::a6]:48393 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:66898 On 02/21/2014 09:59 PM, Denis Usanov wrote: > Good evening. > > First of all I would like to apologize for the name of topic. I really didn't know how to name it more correctly. > > I mostly develop on Python some automation scripts such as deployment (it's not about fabric and may be not ssh at all), testing something, etc. In this terms I have such abstraction as "step". > > Some code: > > class IStep(object): > def run(): > raise NotImplementedError() > > And the certain steps: > > class DeployStep: ... > class ValidateUSBFlash: ... > class SwitchVersionS: ... > > Where I implement run method. > Then I use some "builder" class which can add steps to internal list and has a method "start" running all step one by one. > > And I like this. It's loosely coupled system. It works fine in simple cases. But sometimes some steps have to use the results from previous steps. And now I have problems. Before now I had internal dict in "builder" and named it as "world" and passed it to each run() methods of steps. It worked but I disliked this. > > How would you solve this problem and how would you do it? I understant that it's more architecture specific question, not a python one. > > I bet I wouldn't have asked this if I had worked with some of functional programming languages. A few months ago I posted a summary of a data transformation framework inviting commentary. (https://mail.python.org/pipermail/python-list/2013-August/654226.html). It didn't meet with much interest and I forgot about it. Now that someone is looking for something along the line as I understand his post, there might be some interest after all. My module is called TX. A base class "Transformer" handles the flow of data. A custom Transformer defines a method "T.transform (self)" which transforms input to output. Transformers are callable, taking input as an argument and returning the output: transformed_input = T (some_input) A Transformer object retains both input and output after a run. If it is called a second time without input, it simply returns its output, without needlessly repeating its job: same_transformed_input = T () Because of this IO design, Transformers nest: csv_text = CSV_Maker (Data_Line_Picker (Line_Splitter (File_Reader ('1st-quarter-2013.statement')))) A better alternative to nesting is to build a Chain: Statement_To_CSV = TX.Chain (File_Reader, Line_Splitter, Data_Line_Picker, CSV_Maker) A Chain is functionally equivalent to a Transformer: csv_text = Statement_To_CSV ('1st-quarter-2013.statement') Since Transformers retain their data, developing or debugging a Chain is a relatively simple affair. If a Chain fails, the method "show ()" displays the innards of its elements one by one. The failing element is the first one that has no output. It also displays such messages as the method "transform (self)" would have logged. (self.log (message)). While fixing the failing element, the element preceding keeps providing the original input for testing, until the repair is done. Since a Chain is functionally equivalent to a Transformer, a Chain can be placed into a containing Chain alongside Transformers: Table_Maker = TX.Chain (TX.File_Reader (), TX.Line_Splitter (), TX.Table_Maker ()) Table_Writer = TX.Chain (Table_Maker, Table_Formatter, TX.File_Writer (file_name = '/home/xy/office/addresses-4214')) DB_Writer = TX.Chain (Table_Maker, DB_Formatter, TX.DB_Writer (table_name = 'contacts')) Better: Splitter = TX.Splitter (TX.Table_Writer (), TX.DB_Writer ()) Table_Handler = TX.Chain (Table_Maker, Splitter) Table_Handler ('home/xy/Downloads/report-4214') # Writes to both file and to DB If a structure builds up too complex to remember, the method "show_tree ()" would display something like this: Chain Chain[0] - Chain Chain[0][0] - Quotes Chain[0][1] - Adjust Splits Chain[1] - Splitter Chain[1][0] - Chain Chain[1][0][0] - High_Low_Range Chain[1][0][1] - Splitter Chain[1][0][1][0] - Trailing_High_Low_Ratio Chain[1][0][1][1] - Standard Deviations Chain[1][1] - Chain Chain[1][1][0] - Trailing Trend Chain[1][1][1] - Pegs Following a run, all intermediary formats are accessible: standard_deviations = C[1][0][1][1]() TM = TX.Table_Maker () TM (standard_deviations).write () 0 | 1 | 2 | 116.49 | 132.93 | 11.53 | 115.15 | 128.70 | 11.34 | 1.01 | 0.00 | 0.01 | A Transformer takes parameters, either at construction time or by means of the method "T.set (key = parameter)". Whereas a File Reader doesn't get payload passed and may take a file name as input argument, as a convenient alternative, a File Writer does take payload and the file name must be set by keyword: File_Writer = TX.File_Writer (file_name = '/tmp/memos-with-dates-1') File_Writer (input) # Writes file File_Writer.set ('/tmp/memos-with-dates-2') File_Writer () # Writes the same thing to the second file That's about it. I am very pleased with the design. I developed it to wrap a growing jungle of existing modules and classes having no interconnectability and no common input-output specifications. The improvement in terms of work time and resource management is enormous. I would share the base class and a few custom classes, reasonably autonomous to not require surgical extraction from the jungle. Writing a custom class requires no more than defining private keywords, if any, and writing the method "transform (self)", or "process_record (self, record)" if the input is a list of records, which it often is. The modular design encourages to have a Transformer do just one simple thing, easy to write and easy to debug. Complexity comes from assembling simple Transformers in a great variety of configurations. Frederic