-
Couldn't load subscription status.
- Fork 5
feat: place examples in pack directories for cli #49
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
Conversation
|
@sbillinge ready for review |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #49 +/- ##
==========================================
+ Coverage 81.73% 82.16% +0.42%
==========================================
Files 11 12 +1
Lines 1210 1239 +29
==========================================
+ Hits 989 1018 +29
Misses 221 221
🚀 New features to boost your workflow:
|
Updated the examples section to include specific pack names and modified the directory structure for clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please see comments
src/diffpy/cmi/cli.py
Outdated
| return sorted([p.name for p in root.iterdir() if p.is_dir()]) | ||
| return {} | ||
|
|
||
| packs: dict[str, list[str]] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you are using chatgpt pleaes review the code very carefully. We are not using type-hints atm so remove all type hints. Also, this is a lot of code now. Is it all necessary or there is a more elegant way of accomplishing the same thing?
My life gets way harder when I have to review crappy code written by chatgpt. At least when I spend time reviewing crappy code written by students I feel that the student is learning and in the future, as a result, I will have to spend less time reviewing.....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there tests for this code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbillinge sorry about that... I do review any gpt generated code and try my best to use it as a tool and not to be lazy. I'll scrutinize it more in the future.
Here's my idea for how users can see and copy the examples. I want the examples to be listed under each pack like you see in the screenshot above where its something like this:
$ cmi example list
pack1:
- exampleA
- exampleB
pack2:
- exampleC
- exampleD
This code accomplishes this by creating a dict and storing the pack dir names as keys and a list of the examples as the value. Then, I want users to be able to copy the example to there cwd via cmi copy example pack1/exampleA. The upstream configuration copies examples by running cmi example copy exampleA, as more packs get added to cmi you could imagine how this would become a mess. You may have interpreted this from this PR already but I just want to be explicit in my objectives. Can you explain a bit more why you think this code is crappy?
Tieqiong wrote a CI test for this here that runs the commands but i don't see any tests for listing examples (cmi example list) or copying them (cmi example copy <ex name>)
|
Let me know when the is ready for review per our discussion @cadenmyers13 |
|
@sbillinge working on it rn. will do 👍 |
| """ | ||
| src = _installed_examples_dir() / example | ||
| if "/" not in pack_example: | ||
| raise ValueError("Example must be specified as <pack>/<exdir>") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforces new format of copying examples. This format is cmi example copy <pack>/<exdir>
| for pack, examples in get_examples_by_pack().items(): | ||
| print(f"{pack}:") | ||
| for ex in examples: | ||
| print(f" - {ex}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prints examples in the format of
$ cmi example list
pack1:
- exampleA
- exampleB
pack2:
- exampleC
- exampleD
instead of
$ cmi example list
exampleA
exampleB
exampleC
exampleD
| if pack_dir.is_dir(): | ||
| exdirs = sorted(p.name for p in pack_dir.iterdir() if p.is_dir()) | ||
| examples_by_pack[pack_dir.name] = exdirs | ||
| return examples_by_pack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returns a dict, mapping pack name (str) to its corresponding list of examples (list of strs). This replaces previous format were all the examples are stored in one list.
|
@sbillinge okay this is ready for review. i changed some function and variable names to reflect better what is happening. See inline for descriptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to review tests before I review code but i don't see tests for these new functions?
|
@sbillinge heard, on it |
|
super. fyi, this is conflicted now. i expect that this is where you deleted the junk then couldn't figure out why it reappeared :). You can just merge main into this branch after syncing main. |
|
@sbillinge Oh yeah, will do. Thanks for the heads up! |
|
@sbillinge Added tests, I had chatgpt help me with this but the logic makes sense to me. Basically, I'm just checking to see if the copied dir is copied, outputs are printed, etc.. I was able to get the tests to fail by changing the dirs. the "copy" test uses monkeypatch and the "list" test does not. I'm not sure which one would be preferred here. I assume probably not monkeypatch since we might want to test an actual existing directory. I ran into some bugs trying to do that. I have to head out right now, but if you let me know which one you prefer (if either) and i can make the changes tomorrow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of comments. The tests seem to be all testing main. Can you break out functionality into functions and test them separately?
Also, I want to standardize on using pytest.mock rather than monkypatch
|
here are kind of the steps:
I usually find that steps 1-4, my thinking evolves and changes so it can be a slightly iterative process
because you wrote the functions before the tests this will go a bit differently here, but don't write the tests so functions you worte pass, but write tests that give you the behavior you want, then write the function so the tests pass. Then you are sure you have the right behavior |
|
@sbillinge Thanks for the detailed instructions. Very helpful! I think I'm starting to get the hang of it a bit. I made a push testing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pls see comments. Just working on tests atm
tests/test_cli.py
Outdated
| @pytest.mark.parametrize( | ||
| "structure, expected", | ||
| [ | ||
| # case: one pack with one example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much much better. Even better would be
# case: one pack with one example. expect dict of "{pack-name: [example]}"
arguably I can see that from the code below, but it is easier and quicker for the reviewer if the intent is right there.
tests/test_cli.py
Outdated
| def test_map_pack_to_examples(tmp_path, mocker, structure, expected): | ||
| """Finds examples directory and returns a dictionary mapping packs | ||
| to examples.""" | ||
| # example input: build example structure |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove gratuitous unneeded comments. Rather, write the code in a way that it is directly readable. Comments can be added if the code is not clear for some reason that can't be changed.
tests/test_cli.py
Outdated
| assert rc == 0 | ||
| assert (cwd / "ex1").exists() | ||
| @pytest.mark.parametrize( | ||
| "structure, expected", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we change structure to inputs or sthg like that?
tests/test_cli.py
Outdated
| for ex in exdirs: | ||
| (packdir / ex).mkdir() | ||
| # patch _get_examples_dir to point to tmp_path | ||
| mocker.patch.object(cli, "_get_examples_dir", return_value=tmp_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only mock things in exceptional circumstances. If you have to mock it could mean you have a bad code design because things are not well separated. Here it is just not needed, not necessarily bad design.
Here we want to build examples directories in tmpdir and then have the function go get them, so the test will look like
examples_path = logic that builds the examples from the inputs
actual = build_examples_doc(examples_path)
assert actual == expected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we think the examples may be used by more than one test build them in conftest.py. We could use a structure like:
tmpdir / example-case-1 / docs / examples / pack_a / example_a / solutions / diffpy-cmi / solution1.py
/ example-case-2 / docs / examples / pack_a / example_a / solutions / diffpy-cmi / solution1.py
/ example_b / solutions / diffpy-cmi / solution1.py
and so on. The function would have to file_path as an input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only mock things in exceptional circumstances. If you have to mock it could mean you have a bad code design because things are not well separated. Here it is just not needed, not necessarily bad design.
@sbillinge _get_examples_dir() always returns the actual path to the examples and not the temp dir path (it returns diffpy.cmi/docs/examples). Since map_pack_to_examples() calls it, I mocked it so that the output path is the temp path rather than the actual path. I don't see a way of testing the behavior unless we change the function? Here's the function for reference:
def _get_examples_dir() -> Path:
"""Return the absolute path to the installed examples directory.
Returns
-------
pathlib.Path
Directory containing shipped examples.
Raises
------
FileNotFoundError
If the examples directory cannot be located in the installation.
"""
with get_package_dir() as pkgdir:
pkg = Path(pkgdir).resolve()
for c in (
pkg / "docs" / "examples",
pkg.parents[2] / "docs" / "examples",
):
if c.is_dir():
return c
raise FileNotFoundError(
"Could not locate requirements/packs. Check your installation."
)
```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could add an override feature to this function but that seems pretty much identical to mocking. plus is very ugly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Si the problem here may be a misunderstanding of how tests work. You seen to be writing the test to test the function, but we want to do it the other way around. We want to write the function after we write the test. So we write the test to capture the behavior we want.
If I were thinking about this I might say, "oh, I want this function to iterate over a file structure and collect all the examples it finds and load them in a dict that it returns. In that case I would expect it in general to take a path as input and return the dict. This allows the user to reuse the function in different ways. If the user is the test, it can then specify the tmpdir.
The tests then help to guide us to a better design.
|
@sbillinge Ready for review. I've added tests for |
|
@sbillinge ready for review. This is just one test function (working on the rest rn), but I went with a different approach that just checks the output data type |
|
@sbillinge Okay all tests are added. I think this looks much less hacky |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is pretty far from where it is supposed to be. We may want to spend an hour or two together working on this, so you can get the idea a bit better?
| "pdf/ch03NiModelling", | ||
| ], | ||
| ) | ||
| def test_copy_example_success(tmp_path, input_valid_str): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this test testing the test? We don't normally do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is testing the cmi example copy cli functionality, not the "run all scripts" test. Although they both use a temp directory.
| assert actual == expected | ||
|
|
||
|
|
||
| def test_copy_example_fnferror(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for bad tests we normally call them test_<function_name>_bad
| @pytest.mark.parametrize( | ||
| "input_bad_str", | ||
| [ | ||
| "", # empty string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here I see an empty string and then a comment saying # empty string which of course, is not needed. The comments need to be capturing behavior.
| """Test that map_pack_to_examples returns the right shape of | ||
| data.""" | ||
| actual = cli.map_pack_to_examples() | ||
| assert isinstance(actual, dict) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These isinstance tests do rather little. The right test is one that tests that the correct things are loaded in the dict. Let's start by just writing comments.
# single pack, single example. <expected output>
@sbillinge Im down to do that. Do you have time today/this evening? I see your calendar is blocked until 6pm. |
no, it will have to be Friday I think. Perhaps we can do it asynchronously. I could make a PR and push the steps one by one? |
|
Maybe I will merge this one and make a PR on to that |
|
@sbillinge We can start asynchronously, but I think it might be good for me to do this with you in real time. |
let's do both. Step 1 is to figure out at a very high level what the task is. this is the UC process. Here are some examples: Are there any I am missing? |
|
Next is a discussion of which UCs we want to implement. Roughly speaking let's assume we want them all. Next is a first cut at architecture. Good to discuss it a bit, but what structure do we want, data objects, etc. to cover as much of the UCs as possible? What is a list of tasks that the code would have to do to accomplish the tasks? e.g.,
and so on.... Next is discuss and finalize these things and we can start on the code..... |
|
@cadenmyers13 please do me a favor and move the last few discussion comments above to a new issue for safe keeping. I will merge this so that I can work on a clean PR. Please add any UCs you can think of or anything to the code design discussion. |
This makes the cli versatile for new pack additions. See screenshots for functionality and failure.