Sprint 6.4 will begin with Emily and I taking an estimated 1-pointer ticket, UW-200. The team Is starting to look ahead to the end of this Program Increment(PI), where they'll be onboarding at least 2-5 new Interns from CU-Boulder within the Post-Baccalaureate Program. The Unified Workflow Team also has a presentation with UFS Steering Committee that will be on what the team has accomplished In the year It has been active, where It Is currently and the roadmap to where It Is headed.
What Is the task? And the description of the task
Task: Add logging to Config class to print the configure contents
Description:
It will be useful to know more about the content of the Configuration Object used by a whole variety of our tools. Add a method to the Configure base class to print the dictionary contents in a nice readable format using our logger.py tool
What does this mean?
From reading the description and reviewing the acceptance criteria I had several follow-up questions:
We wanted to confirm the files we would be editing for this task. The description mentioned adding a method to the Configure base class and we wanted to confirm the config.py was the correct file. We assumed we would be adding the new method under the base config class, "class Config(collections.UserDict)" within the config.py file.
We also wanted to confirm the logger tool that we would be logger.py. Additonally the class we would be calling within the logger.py file would be under the class "Logger"
For example, we could call any of these LOG_LEVELS using the logger tool, ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
Breakdown:
We had a couple different files:
config.py
logger.py
test_config.py
test_logger.py
In our Tagup with Christina we were able to explore a bit more on exactly what we needed to accomplish for UW-200. We were easily about to confirm with Christina that the files we would be focusing on for the story would be the config.py file and the new method would be added under the base config class. With that confirmed we had more questions on how to use the logger tool to accomplish UW-200.
After speaking with Christina we decided to move In a different direction for this ticket. Instead of using logging to print that the contents of the dictionary contents, we would be using JSON to print. With a simple google search and the help of stack overflow, we were able to find a simple solution(Image 1).
Image 1 - Printing dictionary contents using JSON
Using JSON made UW-200 an even easier task, now we had to create a method and add a single line 'print(json.dumps(self.data)).' The class Config had already been passed the dictionary and to return It we just had to call self.data. This overall method looked like:
def config_dump(self):
print(json.dumps(self.data))
Additionally during our meeting Christina asked us to add a __str__() or __repr__(). Now Emily and I had more questions:
What Is a __repr__()?
What Is a __str__()?
What Is the difference between them?
And what are they used for?
Finding answers:
A __repr__() Is a function that returns the object representation In string format. The method is called when use rep() on an object.
A __str__() returns the string representation of the object. The method Is called when we use print() or str() on an object.
The difference between these two Is subtle, but It's In the wording we can distinguish the two. __str__ returns the object In the form of a string vs. __repr__ that returns the object representation just In string format.
Either way both are used to return a string. If there Is no __str__ method Implemented than the __repr__ Is used as the default option (would only need to Implement the __repr__
With a quick Google search, I was able to find some context on how to code up a __repr__ method.
Implementing our Ideas:
After we had context on the __repr__ method and knew how to print our dictionary contents we were able to begin. Our config_dump and __repr__ methods were both one line In length to produce the desired results. Later, we would get comments on our PR to drop the config_dump method altogether and condense our code down to just the __repr__. We would find updating test_config.py to pass the tests would be the most complex part of UW-200.
The test cases were the most difficult because we had our outcome that was not matching up to our result. This was due to how JSON creates a dictionary vs. how our tester dictionary was setup. JSON using double quotes "" around keys, whereas the desired outcome dictionary was Initialized was single quotes ''. This made It difficult to work around, but we ended up using the builtin .replace() method In Python. We switched out our double quotes "" returned from our JSON dictionary for single quotes '' to match our desired outcome dictionary.
At first we weren't sure If this was the optimal solution because we saw It more as 'crafty' than 'elegant.' However, the team liked our approach to this Issue and with their approval we were able to create a Pull Request(PR) on UW-200 and close It out with one week left In the sprint.
Resources referenced to complete UW-200:
https://stackoverflow.com/questions/3229419/how-to-pretty-print-nested-dictionaries
https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr
https://stackoverflow.com/questions/32569732/correct-way-of-unit-testing-repr-with-dict
https://flexiple.com/python/python-sort-dictionary-by-value/#section7
https://stackoverflow.com/questions/4162642/single-vs-double-quotes-in-json
https://www.digitalocean.com/community/tutorials/python-pretty-print-json
Starting week two of Sprint 6.4, Emily and I spoke with the team about pulling forward UW-201. The team agreed that this would be the best ticket for us to begin working on and It would most likely carry over to Sprint 6.5 since It was estimated a 2-pointers.
Image 2 - Snippet from another tool
What Is the task? And the description of the task
Task: Add logging to templater.py to pretty print command line arguments.
Description:
When running the tool, it will be nice to have some logged information about the initial arguments provided. Use our logger tool to set up a pre-amble to the standalone templater.py script that prints the parsed args. Here's an example from another tool that I found helpful (Image 2).
What does this mean?
From reading the description and reviewing the acceptance criteria I had several follow-up questions:
Do we need test cases?
Should we create a separate method or include it in the main function?
Can this block of code from the URWS(Short-Range Weather Repository) be used for UW-201 (Image 3)?
Do we need logging to be incorporated in templater.py?
Image 3 - URWS reusable code block
After some research, we were able to find the code that generated the output seen In Image 2. The code was found In the URWS(Short-Range Weather Repository) and can be seen In Image 3. Christina confirmed for us that we would not need additional test cases, and we could reuse the code found In the URWS repository.
Inspecting the code:
The code In Image 3(lines 735 to 739) printed each key value pair by Iterating over a dictionary. We were able to directly copy and edit this code slightly meet the requirements of UW-201. A comparison Is shown of the code(Images 4 & 5).
Image 4 - URWS retrieve_data.py pretty printing
print("Running script retrieve_data.py with args:\n", f"{('-' * 80)}\n{('-' * 80)}")
for name, val in cla.__dict__.items():
if name not in ["config"]:
print(f"{name:>15s}: {val}")
print(f"{('-' * 80)}\n{('-' * 80)}")
Image 5 - workflow-tools templater.py pretty printing
print("Running script templater.py with args:n", f"{('-' * 80)}\n{('-' * 80)}")
for name, val in user_args.__dict__.items():
if name not in ["config"]:
print(f"{name:>15s}: {val}")
print(f"{('-' * 80)}\n{('-' * 80)}")
Between working on UW-201, we were still awaiting a peer review on our PR for UW-200. Christina provided us with feedback that I mentioned above to drop the config_dump method altogether and condense our code down to just the __repr__. Emily and I began working on UW-200 and UW-201 simultaneously, which with past Internship and projects I hadn't had the chance to experience before. I found It quite difficult at first to remember to switch before different branches In git when working on UW-200 and UW-201. The main Idea Is when you are working on one ticket you want to keep all your changes to a single branch. This allows you 'to request a brand new working directory, staging area, and project history' for that Individual ticket. I think being able to switch between these tasks was frustrating at first, but by the end of the week It become rewarding to have accomplished a technique I knew I'd be using In Industry In the future.
Again, the test cases were became the largest part of the ticket for the week. We had almost an identical situation to UW-200, where our expected result was not matching up with our outcome. Due to the addition of printing our parsed arguments In the command line, we began to fail 3 tests In test_templater.py. These three test cases directly compared the standard output In the command line to a specific outcome variables. Meaning to pass the test cases we needed to change the outcome in the test case to match the new result. Our current Idea for doing this was to have the pretty print in a three-part string, with an input_file variable in the middle to account for the input_template path (in order to avoid hard coding; Image 6).
Image 6 - Our desired outcome to pass one of the three failing test cases
input_file = os.path.join(uwtools_file_base, "fixtures/nml.IN")
outcome=\
"""Running script templater.py with args:n
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
outfile: None
input_template: """ + input_file + """
config_file: None
config_items: []
dry_run: True
values_needed: False
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
&salad
base = 'kale'
fruit = 'banana'
vegetable = 'tomato'
how_many = 22
dressing = 'balsamic'
/
"""
However, we weren't sure again If the solution we had was the most ideal. Once confirming with the team in our Friday DSM we were given the the approval for Implementing this solution. We decided this was a good stopping point on UW-201 to come back to for starting the next sprint.
Emily and I will continue to work on UW-201 In Sprint 6.5. If time permits we will also be able to collaborate on another ticket within our last sprint with the Unified Workflow Team.
In Fridays DSM we were able to merge UW-200 Into the develop branch and confirm with the team that UW-201 would be carried over to Sprint 6.5.
Image 7 - Sprint 6.4 Retrospective
Sprint 6.5 will begin Monday, November 7th and we will begin the sprint with finishing up UW-201 from this week. Emily and I are hoping to finish UW-201 early In one week of Sprint 6.5, so we are able to tackle another ticket to close out Program Increment 6!