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

Clarify some utility functions (yt.funcs) with more_itertools #2893

Merged
merged 58 commits into from
Dec 16, 2020

Conversation

neutrinoceros
Copy link
Member

@neutrinoceros neutrinoceros commented Sep 4, 2020

PR Summary

Refactor some internals using more_itertools (which is already a transitive dependency).

remove yt.funcs.ensure_list

this function has two use cases

  • it is used in many places to iterate over one or more objects transparently. While ensure_list is bit clunky and requires maintenance on our part, the more powerful more_itertools.always_iterable does the job.
  • in places it is used as a sanitizer, it can be replaced by lambda x: list(always_iterable(x))

When we specifically need to iterate transparently over a arbitrary number of fields, always_iterable doesn't work out of the box since a single 2-tuple should be treated as a single field, but it's easy to configure, so I added a simple yt.funcs.iter_fields function:

def iter_fields(field_or_fields):
    return always_iterable(field_or_fields, base_type=(tuple, str, bytes))

rename yt.funcs.iterable

This function's name is confusing because it doesn't indicate that it's really a checker that returns a boolean (is_iterable would be clearer in that sense). On top of this it is also wrong because it actually only checks that len(obj) is valid, while some iterables have no length (e.g. infinite generators).
I renamed the existing function to is_sequence.

other associated renames

  • yt.funcs.validate_iterable -> validate_sequence
  • yt.geometry.coordinates.coordinate_handler.validate_sequence_width -> validate_sequence_width

rework yt.funcs.ensure_tuple and yt.funcs.just_one

Each can be simplified as combination of two functions from more_itertools.

Additionally, ensure_tuple is now only ever used to validate ds.periodicity, so there is some overlapping with #2868 and the functionality could be moved to the Dataset class directly.

This PR refactors the one place that ensure_tuple was used differently, namely yt.frontends.enzo_p.misc.nested_dict_get.
I also fixed a bug (and added a test for it) in this function while I was at it.

PR Checklist

  • rename iterable to is_sequence
  • add an iter_fields function
  • search and replace ensure_list calls with either iter_fields where an iterator is needed, list(iter_fields()) where we actually need a list of fields, and list(always_iterable()) elsewhere.
  • remove ensure_list
  • search and replace ensure_tuple calls with tuple(always_iterable()) (or update ensure_tuple to the same effect ?)
  • rework just_one
  • explicitly add more_itertools to deps
  • pass black --check yt/
  • pass isort . --check --diff
  • pass flake8 yt/
  • pass flynt yt/ --fail-on-change --dry-run -e yt/extern
  • New features are documented, with docstrings and narrative docs
  • Adds a test for any bugs fixed. Adds tests for new features.

@neutrinoceros neutrinoceros added the refactor improve readability, maintainability, modularity label Sep 4, 2020
@neutrinoceros neutrinoceros added the api-consistency naming conventions, code deduplication, informative error messages, code smells... label Sep 6, 2020
@neutrinoceros neutrinoceros marked this pull request as ready for review September 6, 2020 15:12
@neutrinoceros neutrinoceros changed the title [EXP]: simplify some utility functions with more_itertools Clarify some utility functions (yt.funcs) with more_itertools Sep 6, 2020
@matthewturk
Copy link
Member

In general I like this; I was initially opposed but this is indeed cleaner. Will review this.

Every time one of these refactors pops up it makes me long for the opportunity to deeply refactor the field system.

@neutrinoceros
Copy link
Member Author

Yeah I mean I started this as an exercise to learn about more_itertools but in the end I find it's a good (small) step forward towards the fields refactor. :)

@neutrinoceros
Copy link
Member Author

@yt-fido test this please

@neutrinoceros
Copy link
Member Author

Hm. The doc build seems to be idle (previous run timed out too). @Xarthisius, do you have any clue what's happening here ?

@Xarthisius
Copy link
Member

No idea, here's the error:

Exception occurred:
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/jinja2/loaders.py", line 429, in load
    raise TemplateNotFound(name)
jinja2.exceptions.TemplateNotFound: full
The full traceback has been saved in /tmp/sphinx-err-fuamjofs.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
Makefile:50: recipe for target 'html' failed
make: *** [html] Error 2

and

$ cat /tmp/sphinx-err-fuamjofs.log 
# Sphinx version: 3.1.2
# Python version: 3.8.3 (CPython)
# Docutils version: 0.16 release
# Jinja2 version: 2.11.2
# Last messages:
#   [autosummary] generating autosummary for: reference/api/api.rst
#   building [mo]: targets for 0 po files that are out of date
#   building [html]: targets for 574 source files that are out of date
#   updating environment:
#   [new config]
#   574 added, 0 changed, 0 removed
#   reading sources... [  0%] about/index
#   reading sources... [  0%] analyzing/astropy_integrations
#   reading sources... [  0%] analyzing/domain_analysis/clump_finding
#   reading sources... [  0%] analyzing/domain_analysis/cosmology_calculator
# Loaded extensions:
#   sphinx.ext.mathjax (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/mathjax.py
#   sphinxcontrib.applehelp (1.0.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinxcontrib/applehelp/__init__.py
#   sphinxcontrib.devhelp (1.0.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinxcontrib/devhelp/__init__.py
#   sphinxcontrib.htmlhelp (1.0.3) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinxcontrib/htmlhelp/__init__.py
#   sphinxcontrib.serializinghtml (1.1.4) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinxcontrib/serializinghtml/__init__.py
#   sphinxcontrib.qthelp (1.0.3) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinxcontrib/qthelp/__init__.py
#   alabaster (0.7.12) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/alabaster/__init__.py
#   sphinx.ext.autodoc.type_comment (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/autodoc/type_comment.py
#   sphinx.ext.autodoc (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/autodoc/__init__.py
#   sphinx.ext.intersphinx (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/intersphinx.py
#   sphinx.ext.viewcode (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/viewcode.py
#   sphinx.ext.napoleon (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/napoleon/__init__.py
#   yt_cookbook (0.1) from /var/jenkins_home/workspace/yt_docs_new/doc/extensions/yt_cookbook.py
#   yt_colormaps (0.1) from /var/jenkins_home/workspace/yt_docs_new/doc/extensions/yt_colormaps.py
#   config_help (1.0) from /var/jenkins_home/workspace/yt_docs_new/doc/extensions/config_help.py
#   yt_showfields (1.0) from /var/jenkins_home/workspace/yt_docs_new/doc/extensions/yt_showfields.py
#   sphinx.ext.autosummary (3.1.2) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/ext/autosummary/__init__.py
#   pythonscript_sphinxext (0.1) from /var/jenkins_home/workspace/yt_docs_new/doc/extensions/pythonscript_sphinxext.py
#   RunNotebook.notebook_sphinxext (0.1) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/RunNotebook/notebook_sphinxext.py
#   RunNotebook.notebookcell_sphinxext (0.1) from /var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/RunNotebook/notebookcell_sphinxext.py
Traceback (most recent call last):
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/cmd/build.py", line 280, in build_main
    app.build(args.force_all, filenames)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/application.py", line 348, in build
    self.builder.build_update()
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/builders/__init__.py", line 297, in build_update
    self.build(to_build,
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/builders/__init__.py", line 311, in build
    updated_docnames = set(self.read())
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/builders/__init__.py", line 418, in read
    self._read_serial(docnames)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/builders/__init__.py", line 439, in _read_serial
    self.read_doc(docname)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/builders/__init__.py", line 479, in read_doc
    doctree = read_doc(self.app, self.env, self.env.doc2path(docname))
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/io.py", line 221, in read_doc
    pub.publish()
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/core.py", line 217, in publish
    self.document = self.reader.read(self.source, self.parser,
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/io.py", line 126, in read
    self.parse()
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/readers/__init__.py", line 77, in parse
    self.parser.parse(self.input, document)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/sphinx/parsers.py", line 102, in parse
    self.statemachine.run(inputlines, document, inliner=self.inliner)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 170, in run
    results = StateMachineWS.run(self, input_lines, input_offset,
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/statemachine.py", line 241, in run
    context, next_state, result = self.check_line(
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/statemachine.py", line 459, in check_line
    return method(match, context, next_state)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 2769, in underline
    self.section(title, source, style, lineno - 1, messages)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 327, in section
    self.new_subsection(title, lineno, messages)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 393, in new_subsection
    newabsoffset = self.nested_parse(
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 281, in nested_parse
    state_machine.run(block, input_offset, memo=self.memo,
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 196, in run
    results = StateMachineWS.run(self, input_lines, input_offset)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/statemachine.py", line 241, in run
    context, next_state, result = self.check_line(
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/statemachine.py", line 459, in check_line
    return method(match, context, next_state)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 2342, in explicit_markup
    nodelist, blank_finish = self.explicit_construct(match)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 2354, in explicit_construct
    return method(self, expmatch)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 2096, in directive
    return self.run_directive(
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/docutils/parsers/rst/states.py", line 2146, in run_directive
    result = directive_instance.run()
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/RunNotebook/notebookcell_sphinxext.py", line 46, in run
    evaluated_text, resources = evaluate_notebook(
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/RunNotebook/notebook_sphinxext.py", line 190, in evaluate_notebook
    lines, resources, notebook = nb_to_html(nb_path, skip_exceptions)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/RunNotebook/notebook_sphinxext.py", line 146, in nb_to_html
    houtput, hresources = hexporter.from_notebook_node(eval_notebook)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/nbconvert/exporters/html.py", line 122, in from_notebook_node
    return super().from_notebook_node(nb, resources, **kw)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/nbconvert/exporters/templateexporter.py", line 382, in from_notebook_node
    output = self.template.render(nb=nb_copy, resources=resources)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/nbconvert/exporters/templateexporter.py", line 143, in template
    self._template_cached = self._load_template()
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/nbconvert/exporters/templateexporter.py", line 353, in _load_template
    return self.environment.get_template(template_file)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/jinja2/environment.py", line 883, in get_template
    return self._load_template(name, self.make_globals(globals))
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/jinja2/environment.py", line 857, in _load_template
    template = self.loader.load(self, name, globals)
  File "/var/jenkins_home/shiningpanda/jobs/c9e5cb26/virtualenvs/d41d8cd9/lib/python3.8/site-packages/jinja2/loaders.py", line 429, in load
    raise TemplateNotFound(name)
jinja2.exceptions.TemplateNotFound: full

@Xarthisius
Copy link
Member

I think it's due to nbconvert>6 will try to pin it and let's see if that helps

@Xarthisius
Copy link
Member

@yt-fido test this please

Comment on lines 99 to +105
def ensure_tuple(obj):
"""
This function ensures that *obj* is a tuple. Typically used to convert
This function ensures that *obj* is a tuple. Typically used to convert
scalar, list, or array arguments specified by a user in a context where
we assume a tuple internally
"""
if isinstance(obj, tuple):
return obj
elif isinstance(obj, (list, np.ndarray)):
return tuple(obj)
else:
return (obj,)
return tuple(always_iterable(obj))
Copy link
Member

Choose a reason for hiding this comment

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

since we have this update of ensure_tuple() that does return tuple(always_iterable(obj)), is it worth adding back the ensure_list() functionality that acts as a sanitizer and and use that in the code for consistency?

Copy link
Member Author

Choose a reason for hiding this comment

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

actually my plan was to get rid of both. As far as I recall, ensure_tuple is only ever used to sanitize Dataset.perdiodicity attributes within the code base, and what's more is that I think it's only ever done in the Boxlib frontend, so I would rather just get this one down that reintroduce its sibling. Does this sound fair to you ?

Copy link
Member Author

Choose a reason for hiding this comment

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

(sorry about not replying to your initial comment on this btw, it slipped my mind so it seems.)

Copy link
Member

Choose a reason for hiding this comment

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

(sorry about not replying to your initial comment on this btw, it slipped my mind so it seems.)

Not a problem! I just wanted to make sure it didn't get lost!

I would rather just get this one down that reintroduce its sibling. Does this sound fair to you ?

I think that's totally fine. 🙂 I was just curious for consistency reasons.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll remove ensure_tuple in a followup PR !

Copy link
Member

Choose a reason for hiding this comment

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

SGTM!

@munkm munkm merged commit b86eaea into yt-project:master Dec 16, 2020
@neutrinoceros neutrinoceros deleted the more-itt branch December 16, 2020 21:52
@jzuhone
Copy link
Contributor

jzuhone commented Jan 22, 2021

@neutrinoceros just a note--I should have been following this more closely, but ensure_list was being used in a few projects downstream of yt (pyXSIM, for instance), and so in the future make sure potentially big things like this get reported to the yt-dev list.

@neutrinoceros
Copy link
Member Author

Thanks for reporting back. I honestly had no idea the content of yt.funcs could be a dependence downstream. I am sorry about this breakage. It should be easier to manage transitional deprecations and removals in the future now (see https://github.com/yt-project/yt/pulls).

@munkm
Copy link
Member

munkm commented Feb 8, 2021

I should have been following this more closely, but ensure_list was being used in a few projects downstream of yt (pyXSIM, for instance), and so in the future make sure potentially big things like this get reported to the yt-dev list.

What do you think would be a good solution to remedy this @jzuhone ? Should we add it back in with a deprecation warning?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-consistency naming conventions, code deduplication, informative error messages, code smells... refactor improve readability, maintainability, modularity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants