Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

error-handling: implement exercise #798

Merged
merged 19 commits into from
Oct 24, 2017

Conversation

cmccandless
Copy link
Contributor

@cmccandless cmccandless commented Oct 13, 2017

Resolves #742

TODO:

  • update config.json
  • add README
  • create solution template
  • write test cases
  • write example solution

@cmccandless cmccandless changed the title (WIP) error-handling: implement exercise error-handling: implement exercise Oct 15, 2017
Copy link
Contributor

@ilya-khadykin ilya-khadykin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @cmccandless

Please read my comments

config.json Outdated
"unlocked_by": null,
"difficulty": 3,
"topics": [
"exception handling"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've switched to snake_case naming convention for topics to increase consistency among tracks, so could you please choose them from the following list - https://github.com/exercism/problem-specifications/blob/master/TOPICS.txt

import unittest

import error_handling as er

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please also leave a comment stating what version of canonical-data.json the tests were adopted as discussed in #784 if aplicable?
The format is:

# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this time, there is no canonical data for this exercise; these tests were adapted from the C# implemetation of this exercise. Is there a standard way of noting this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no tests in problem-specifications repo for this exercise, then it's Ok to leave it as is.

We agreed with @N-Parsons to use comment above to denote the version of canonical-data.json from which tests were adopted


## Source

Problem 6 at Project Euler [http://projecteuler.net/problem=6](http://projecteuler.net/problem=6)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This source isn't right - I presume that there's actually no source needed for this exercise?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct; I shall remove it. The included source section was in the README from another exercise from which I created this one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmccandless if it was based on canonical-data, it will be good to open PR there to fix it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was not. I copied the exercise-specific sections from the C# README and the track-specific sections (submitting, incomplete, etc) from another Python exercise. The source section was accidentally copied with the info from the other Python exercise.


def filelike_objects_are_closed_on_exception(filelike_object):
with filelike_object:
filelike_object.close()
Copy link
Contributor

@N-Parsons N-Parsons Oct 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't closing the filelike object on an exception, it's closing the filelike object and then raising an exception afterwards.

A better solution might be to have a was_open property and do_something method in the FileLike class, with the latter just raising some form of exception. Then it should be up to the user to call open, do_something, and close in that order. The tests can check that an exception was raised, that is_open is False, and that was_open is True.

It'd probably then be worth using the exercise's .meta/hints.md file to include something in the README about what methods exist in the filelike_object. The .meta/hints.md file is then included into the README when it's generated using the configlet - it's fine to just use copy-and-paste to put together the README, but also putting it in the hints file makes sure that it won't accidentally get lost in future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, as a learner I would expect that a context manager could be used here (eg. with open(filelike_object) as f:) - it's the Pythonic way to handle files. I'm not sure what would need to be implemented in the FileLike class to make this work though. If we can't get a context manager working, then I think that the README needs to tell learners that context managers would be the typical way to do it with files, but that they won't work with the custom class here, because otherwise there is a massive amount of room for confusion and frustration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The built-in open() method only takes a file name; you cannot implement a class to support open(filelike_object). However, I like the idea of do_something() raising an exception. See my latest changes.

self.was_open = True

def __enter__(self):
self.open()

Copy link
Contributor

@N-Parsons N-Parsons Oct 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add return self here (plus make the change to __exit__), then this class will work with a context manager with an assignment.

self.was_open = True

def __enter__(self):
self.open()

def __exit__(self):
Copy link
Contributor

@N-Parsons N-Parsons Oct 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__exit__ needs to accept 4 arguments, but we don't need to use them for anything here, so we can just use def __exit__(self, *args):

@@ -45,7 +53,12 @@ def test_filelike_objects_are_closed_on_exception(self):
filelike_object.open()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we should be passing an open object to the user's function. It seems strange to me that we'd pass something open to a function that (if we weren't just throwing an exception) may or may not close it - I would expect closing to be handled in the place that it's opened. I think it should be up to the user to open and close the filelike object however they see fit.

filelike_object.do_something()
except Exception:
filelike_object.close()
raise
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're actually mixing two error handling methods here - you have a context manager, but then you are explicitly closing an object using try ... except blocks to handle the exception behaviour.

Using try ... finally: (the finally block allows you to close the connection regardless of whether an exception is raised or not, and just doing except: raise is redundant)

filelike_object.open()
try:
    filelike_object.do_something()
finally:
    filelike_object.close()

Alternatively, a context manager can be used:

with filelike_object:
    filelike_object.do_something()

Or a context manager with an assignment:

with filelike_object as infile:
    infile.do_something()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had forgotten about finally; thanks.

raise
fobj.do_something()
finally:
fobj.close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I stated previously, you don't need both a context manager and a try block for this - they are two different methods of error handling. Either you use a context manager, which will call the __enter__ and __exit__ methods that will open and close the object, OR you handle opening and closing explicitly, using a try ... finally block to ensure that the object is closed once the try block has finished.

This function should either be:

def filelike_objects_are_closed_on_exception(filelike_object):
    filelike_object.open()
    try:
        filelike_object.do_something()
    finally:
        filelike_object.close()

Or:

def filelike_objects_are_closed_on_exception(filelike_object):
    with filelike_object as fobj:
        fobj.do_something()

Or:

def filelike_objects_are_closed_on_exception(filelike_object):
    with filelike_object:
        filelike_object.do_something()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, I had somehow managed to convince myself that when an exception is raised from within the context manager object itself, __exit__() would not necessarily be called correctly, thus the need for try... finally inside with. Fix forthcoming...

Copy link
Contributor

@N-Parsons N-Parsons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's better, thanks!

I just noticed a couple of minor issues in the test file regarding convention - once these are resolved, I will merge the PR. Typically we use assertIs(actual, True) and assertIs(actual, False) rather that assertTrue/assertFalse, and assertEqual should follow the pattern assertEqual(actual, expected).

All of the conventions should be listed in the README for this repo, but some were missing and I've just submitted a PR to add them in.

@N-Parsons N-Parsons self-assigned this Oct 24, 2017
@cmccandless
Copy link
Contributor Author

@N-Parsons Replaced assertTrue and assertFalse with assertIs.

Copy link
Contributor

@N-Parsons N-Parsons left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests need to follow the convention of assertEqual(actual, expected), currently you have assertEqual(expected, actual). The same is true for assertIs.

@cmccandless
Copy link
Contributor Author

@N-Parsons Sorry, missed that one. I should be covered now.

@N-Parsons N-Parsons dismissed ilya-khadykin’s stale review October 24, 2017 19:22

There is currently no canonical data for this exercise.

@N-Parsons N-Parsons merged commit 0d0d453 into exercism:master Oct 24, 2017
@N-Parsons
Copy link
Contributor

Thanks for your work on this, @cmccandless

@cmccandless cmccandless deleted the implement-error-handling branch October 24, 2017 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants