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

Stop project generation if pre hook script fails #549

Merged

Conversation

eliasdorneles
Copy link
Contributor

Hey folks,

Would this be an adequate solution to address issue #464 ?

My use case is handling an invalid package name (see audreyfeldroy/cookiecutter-pypackage#6)

This proposal will stop the project generation if the pre hook script returns with an exit status different from 0 (indicating success).

With this proposal, we'd be able to handle that case with a pre_gen_project hook script like:

import re, sys

PACKAGE_REGEX = r'^[_a-zA-Z][_a-zA-Z0-9]+$'

repo_name = '{{ cookiecutter.repo_name }}'
if not re.match(PACKAGE_REGEX, repo_name):
    print('ERROR: %s is not a valid package name!' % repo_name)
    sys.exit(1)

Thoughts?

@codecov-io
Copy link

Current coverage is 98.39%

Merging #549 into master will decrease coverage by -0.78% as of 7141f6d

@@            master    #549   diff @@
======================================
  Files           12      12       
  Stmts          486     497    +11
  Branches         0       0       
  Methods          0       0       
======================================
+ Hit            482     489     +7
  Partial          0       0       
- Missed           4       8     +4

Review entire Coverage Diff as of 7141f6d


Uncovered Suggestions

  1. +0.80% via ...iecutter/generate.py#233...236
  2. +0.40% via ...okiecutter/config.py#19...20
  3. +0.40% via ...ookiecutter/utils.py#27...28

Powered by Codecov. Updated on successful CI builds.

@eliasdorneles
Copy link
Contributor Author

Okay, coverage bot, I'll add tests and improve coverage once I get some consensus that this is a good-enough approach. :)

@hackebrot
Copy link
Member

Hi @eliasdorneles, thank you for your contribution! 🙇

Tbh, I'd rather raise an error instead of returning 0. If I am not mistaken there is no error report outside of generate that the generation failed apart from a log entry.

@eliasdorneles
Copy link
Contributor Author

Hi @hackebrot :)
Right, my first try was raising a custom HookFailedException, but then I wasn't sure where it should be caught.
Also, it would raise for all hooks (not only for pre_gen_project), but maybe that'd be the desired behavior?

@eliasdorneles
Copy link
Contributor Author

Hey, sorry for the delay -- I updated the code to use a custom exception and added cleaning up the project dir when aborting the generation, and added a little test.
Does this look better?

@eliasdorneles
Copy link
Contributor Author

Note: tests are broken because of issue addressed in this PR: #555

@eliasdorneles eliasdorneles force-pushed the stop-build-if-pre-hook-fails branch 2 times, most recently from b17f77f to e19b739 Compare October 18, 2015 04:07
@eliasdorneles
Copy link
Contributor Author

Rebased to incorporate tests fix -- would appreciate some feedback. :)

I'm particularly not sure about what should be the handling the FailedHookException for the post_gen_project case -- which is why I didn't code anything for it.

@hackebrot
Copy link
Member

I'd say we handle errors for post_gen_project in the same fashion. 😶

More importantly, I feel we should definitely re-raise the exception after cleaning up the filesystem instead of returning None. We can catch the FailedHookException in the callers and sys.exit(1).

cc @michaeljoseph @audreyr @pydanny

with utils.work_in(self.repo_path):
with pytest.raises(exceptions.FailedHookException) as excinfo:
hooks.run_hook('pre_gen_project', tests_dir, {})
assert 'Hook script failed' in str(excinfo)
Copy link
Member

Choose a reason for hiding this comment

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

I think assert 'Hook script failed' in excinfo.value should work too.

@hackebrot hackebrot added enhancement This issue/PR relates to a feature request. needs-review PR Only: This PR require review from other developer labels Oct 18, 2015
@eliasdorneles
Copy link
Contributor Author

Thanks for reviewing @hackebrot -- I updated it with your suggestions. :)

Only thing, I think it's best not to clean up the filesystem for post_gen_project in the same fashion as for pre_gen_project. As it is a failure after the project has been generated, the user wouldn't expect it to go away, and removing the project might make harder for users debug the hook scripts for their templates.

@hackebrot
Copy link
Member

IMHO if a post_gen_project hook fails, the generated project is likely to corrupt regardless of the actual purpose of the hook. Even if it is just debug logging messages, I expect the hook to exit without an error.

For instance, if the rename_kv_file hook fails, the generated kivy app is corrupt and cannot be launched. 😟

So the question is why would you want to keep a broken project? I feel it is rather up to hook to use comprehensive logging messages to help users debug any error that might occur.

@eliasdorneles
Copy link
Contributor Author

Well, I don't have much experience using post_gen_project hooks, so if you say that when it fails it's likely the project to be broken, I trust you. :)

I think I was worrying unnecessarily, so lemme update this.

@eliasdorneles
Copy link
Contributor Author

Does this look better?

"""
with work_in(repo_dir):
try:
run_hook('pre_gen_project', project_dir, context)
Copy link
Member

Choose a reason for hiding this comment

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

I guess 'pre_gen_project' is supposed to be hook_name. 😶

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ooops, lemme fix that! :D

hook_path = os.path.join(self.hooks_path, 'pre_gen_project.py')
tests_dir = os.path.join(self.repo_path, 'input{{hooks}}')

with open(hook_path, 'w') as f:
Copy link
Member

Choose a reason for hiding this comment

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

Uhhm, are you changing the test files at runtime? This may break other tests unintentionally... 😟

Copy link
Contributor Author

Choose a reason for hiding this comment

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

self.repo_path is destroyed in teardown_method and recreated in setup_method for each test:

https://github.com/audreyr/cookiecutter/pull/549/files#diff-392f380cd08f2c7cc3ac39b10cf80d95R94

Copy link
Member

Choose a reason for hiding this comment

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

You are right 😪

@hackebrot
Copy link
Member

Great work @eliasdorneles 👍

Let's see what the others think 😸

@eliasdorneles
Copy link
Contributor Author

Cool, thanks for reviewing @hackebrot ! :)

@@ -104,4 +110,4 @@ def run_hook(hook_name, project_dir, context):
if script is None:
logging.debug('No hooks found')
return
return run_script_with_context(script, project_dir, context)
run_script_with_context(script, project_dir, context)
Copy link
Member

Choose a reason for hiding this comment

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

Why remove the ability to return a value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well, it was meant as a cleanup, run_script_with_context was never returning anything, so I removed the return to make clear this function is a command not a query.

But I can revert the change, if you have other plans or I missed something. :)

@pydanny
Copy link
Member

pydanny commented Oct 21, 2015

This is a really nice feature that deserves documentation ;)

@eliasdorneles
Copy link
Contributor Author

Agreed, lemme add some docs! :)

@eliasdorneles
Copy link
Contributor Author

I added a note under the hook section -- does it look good?

.. note::
Make sure your hook scripts work in a robust manner. If a hook script
fails (that is, if it finishes with an exit status that is not one of
success), the project generation will stop and the generated directory will
Copy link
Member

Choose a reason for hiding this comment

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

...with an exit status that is not 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.

right, I was unsure if mentioning 0 would be more clear or more confusing.
I guess 0 is more clear. :)

Copy link
Member

Choose a reason for hiding this comment

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

You could refer to the official sys.exit docs to be very explicit.

If it is an integer, zero is considered “successful termination” and any nonzero value is considered “abnormal termination” by shells and the like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, though the comment applies to shell script hooks too.
I'll link to it.

@hackebrot
Copy link
Member

I think the example that you provided in the description of the PR would be a great example for using this new feature. Let's add it to the docs 😉

(if @eliasdorneles and @pydanny agree 😁 )

@eliasdorneles
Copy link
Contributor Author

I've added an example and improved the wording -- looks good now?

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Here is an example of script that validates a template variable
before generating the project, to be used as ``hooks/pre_gen_project.py``::
Copy link
Member

Choose a reason for hiding this comment

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

I feel sorry for being this picky... 🙊

PEP8 Imports recommends the following:

import re
import sys

Also you could enable syntax highlighting in rst via:

to be used as ``hooks/pre_gen_project.py``:

.. code-block:: python

    import re

Copy link
Contributor Author

Choose a reason for hiding this comment

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

haha, I don't mind the nitpicking, keep them coming. :)

who doesn't enjoy some bikeshedding? 🚲 🎪 :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

although... python highlighting works for me without that markup. 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

PS: wow, vim highlights sample code inline with .. code-block:: python 😮 :notbad:

.. note::
Make sure your hook scripts work in a robust manner. If a hook script fails
(that is, `if it finishes with a nonzero exit status
<https://docs.python.org/2/library/sys.html#sys.exit>`_), the project
Copy link
Contributor

Choose a reason for hiding this comment

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

@eliasdorneles It would be nice to indicate the Python3 documentation instead of Legacy Python.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@hackebrot hackebrot added the needs-docs PR Only: This PR require additional documentation label Oct 22, 2015
@hackebrot
Copy link
Member

😸 👍

@eliasdorneles
Copy link
Contributor Author

Anything else, fellows?
Do you prefer I squash the commits?

@hackebrot
Copy link
Member

No, you're fine! 👍

We are just super busy atm. Apologies for the delay. 😟

Going to merge this guy once we get another upvote from a contributor.

@eliasdorneles
Copy link
Contributor Author

gotcha!

btw, in Scrapy we find useful to rename the PR prefixing with [MRG+1] for these cases (PRs that were already reviewed and are only waiting for another upvote). =)

@pydanny
Copy link
Member

pydanny commented Nov 5, 2015

LGTM! 👍

hackebrot added a commit that referenced this pull request Nov 5, 2015
Stop project generation if pre hook script fails
@hackebrot hackebrot merged commit 5b997e8 into cookiecutter:master Nov 5, 2015
@hackebrot
Copy link
Member

Great work! 👍 👍

hackebrot added a commit that referenced this pull request Nov 5, 2015
@eliasdorneles
Copy link
Contributor Author

Thank you! =)

@hackebrot
Copy link
Member

I just realized we missed sth 😢

We need coverage for cookiecutter.generate:

def _run_hook_from_repo_dir(repo_dir, hook_name, project_dir, context):
    """
    Run hook from repo directory, cleaning up project directory if hook fails
    """
    with work_in(repo_dir):
        try:
            run_hook(hook_name, project_dir, context)
        except FailedHookException:
            rmtree(project_dir)
            logging.error("Stopping generation because %s"
                          " hook script didn't exit sucessfully" % hook_name)
            raise

@hackebrot
Copy link
Member

Do you mind submitting a PR @eliasdorneles?

@eliasdorneles
Copy link
Contributor Author

oh, right.
sure, but it'll take me a few days, I'm on the road to Python Brasil. :)
I'll look at it after the conference.
On Nov 6, 2015 11:30 PM, "Raphael Pierzina" notifications@github.com
wrote:

Do you mind submitting a PR @eliasdorneles
https://github.com/eliasdorneles?


Reply to this email directly or view it on GitHub
#549 (comment).

@hackebrot
Copy link
Member

No worries! Have fun 😸

#580

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement This issue/PR relates to a feature request. needs-docs PR Only: This PR require additional documentation needs-review PR Only: This PR require review from other developer
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants