-
Notifications
You must be signed in to change notification settings - Fork 5
feat: build examples dict for cli commands #53
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
Warning! No news item is found for this PR. If this is a user-facing |
@sbillinge test |
pretty print test function is also ready for review |
src/diffpy/cmi/cli.py
Outdated
packs: str | List[str], | ||
target_dir: Optional[Path] = None, | ||
) -> List[Path]: | ||
"""Copy all examples from one or more packs to a target directory. |
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.
What do you think about this and the above function? This could be broken out into 4 functions for each UC (copy one ex, one pack, multiple exs, or multiple packs). Or, it could be combined into one function that handles all 4 of these use cases. Right now, its at the middle ground with two functions which felt like a safe in-between place.
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 don't need this function because we have (or will have) a copy_examples
function which copies examples, right? I think we can handle all the copy UCs in one function, no problem.
Also, we may want to decide if we want this to be a method in the PacksManager
class or standalone here. It can probably be argued both ways, and we can look in main to see how these are handled there by Tieqiong,
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.
As it stands, copy_examples
is not a method in PacksManager
. I think its okay to leave it as a standalone function.
src/diffpy/cmi/cli.py
Outdated
If the examples directory cannot be located in the installation. | ||
""" | ||
with get_package_dir() as pkgdir: | ||
with get_package_dir(root_path) as pkgdir: |
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 don't need this (yet). Here the function is being called, not defined, and we are still working on the API and tests.
src/diffpy/cmi/cli.py
Outdated
Parameters | ||
---------- | ||
packs : str or list of 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.
do you want this structure? Maybe better to just take a list?
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.
yeah, true. just take a list
src/diffpy/cmi/cli.py
Outdated
return | ||
|
||
|
||
def copy_packs( |
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.
why are we copying packs? I am not sure what this means. Don't we want to copy examples?
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 meant this as it copies examples given paths, but since were using a dictionary to hold everything this is unnecessary. We can just have one function copy_examples
tests/test_packsmanager.py
Outdated
for pack in expected_pack: | ||
assert pack in returned_pack, f"{pack} not found in returned packs." | ||
expected_examples = expected_dict[pack] | ||
returned_examples = returned_dict.get(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.
we normally would call this actual
tests/test_packsmanager.py
Outdated
returned_pack = list(returned_dict.keys()) | ||
for pack in expected_pack: | ||
assert pack in returned_pack, f"{pack} not found in returned packs." | ||
expected_examples = expected_dict[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.
call this expected
tests/test_packsmanager.py
Outdated
returned_dict = pkmg.available_examples() | ||
expected_pack = list(expected_dict.keys()) | ||
returned_pack = list(returned_dict.keys()) | ||
for pack in expected_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.
this is too specific I think because some cases won't have packs, right?
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.
right
tests/test_packsmanager.py
Outdated
|
||
|
||
def test_print_info(capsys): | ||
"""Test that print_info prints expected information to stdout.""" |
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.
see above
tests/test_packsmanager.py
Outdated
], | ||
) | ||
def test_available_examples(expected_dict): | ||
"""Test that available_examples returns a 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.
no docstring, but please write all the cases you want to test, then we can write the code to do that.
@sbillinge responded to comments and made changes. still need to address comments on |
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.
good progress. Please see my comments.
src/diffpy/cmi/cli.py
Outdated
Returns | ||
------- | ||
list of 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.
I am not sure we need to return this. How will it be used?
src/diffpy/cmi/cli.py
Outdated
Raises | ||
------ | ||
ValueError |
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 don't think we want to raise this. We just copy all versions that we find. This will be a case we test.
We often don't do this Raises section, the interesting raises are caught in the tests. Maybe we should but our current style is not to. It can be tmi
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.
Okay I'll leave it empty for now
src/diffpy/cmi/cli.py
Outdated
examples: List[str], | ||
target_dir: Optional[Path] = None, | ||
) -> List[Path]: | ||
"""Copy one or more examples to a target directory. |
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.
how about:
"""Copy one or more examples from the installed package to a target directory.
tests/test_packsmanager.py
Outdated
"expected", | ||
[ | ||
{ | ||
# test with pack that has examples |
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.
How about this?
# pack with no examples. Expect {'empty_pack': []}
# pack with multiple examples. Expect {'full_pack': [('example1`, path_to_1'), 'example2', path_to_2)]
# multiple packs. Expect dict with multiple pack:tuple pairs
# no pack found. Expect {}
tests/test_packsmanager.py
Outdated
], | ||
) | ||
def test_available_examples(temp_path, expected): | ||
for pack, examples in expected.items(): |
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 all wrong, but we can revisit this when we have the cases sorted out. Basically, we will need to build a different set of folders for each case and the paramertrize will have to hand the path to each case as an input. Paramatrize usually has a kind of [('input','expected')] feel to it which it doesn't atm here.
tests/test_packsmanager.py
Outdated
ex_dir = pack_dir / ex | ||
ex_dir.mkdir(parents=True, exist_ok=True) | ||
pkmg = PacksManager(temp_path) | ||
actual = pkmg.available_examples(temp_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.
you seem to be handing temp_path to this twice. Think about how you want it to work (the behavior you want) and then do it that way.
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've made the change. the methods are taking temp_path
as input
we need a news |
…e being found. the keys of actual and expected are asserting True
@sbillinge I was able to move |
Also added test for the stored examples and packs. There are some sporadic comments that can be ignored for now until we have a complete version |
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.
great progress. Please see comments. Mostly on style.
tests/test_packsmanager.py
Outdated
], | ||
) | ||
def test_available_examples(input, expected, example_cases): | ||
root_path = example_cases / 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.
give this a more readable name than root_path
Give it a name that makes logical sense.
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.
called it tmp_ex_dir
since it terminates at examples/caseX
. This also pairs well with the assert case below
tests/test_packsmanager.py
Outdated
def test_available_examples(input, expected, example_cases): | ||
root_path = example_cases / input | ||
pkmg = PacksManager(root_path) | ||
print() |
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 all debugging print statements and comments.
In general, replace with a comment like the cases above about what behavior is being tested. But see below.
tests/test_packsmanager.py
Outdated
# print("packsmananger_dir:", pkmg.examples_dir) | ||
# print("root_path:", root_path) | ||
for path in root_path.rglob("*"): | ||
print(" -", path.relative_to(example_cases)) |
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 debugging prints
tests/test_packsmanager.py
Outdated
print() | ||
# print("packsmananger_dir:", pkmg.examples_dir) | ||
# print("root_path:", root_path) | ||
for path in root_path.rglob("*"): |
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.
move this out of this test. I like that we have these tests, but they should be run in a separate function, something like test_tmp_filesystem()
or sthg, so they are just run once.
tests/test_packsmanager.py
Outdated
# print(" +", path.relative_to(pkmg.examples_dir.parent)) | ||
|
||
actual = pkmg.available_examples() | ||
print("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.
remove remove remove etc.
tests/test_packsmanager.py
Outdated
|
||
assert ( | ||
actual.keys() == expected.keys() | ||
), f"Keys differ: expected {expected.keys()}, got {actual.keys()}" |
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 don't need printed outputs for test fails.
tests/test_packsmanager.py
Outdated
assert ( | ||
actual.keys() == expected.keys() | ||
), f"Keys differ: expected {expected.keys()}, got {actual.keys()}" | ||
# if values of excepted are in actual assert true |
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.
ummm why is this here?
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.
no reason, I didn't clean comments before pushing. its been removed
tests/test_packsmanager.py
Outdated
# if values of excepted are in actual assert true | ||
# assert actual == expected | ||
for expected_pack, expected_tuple in expected.items(): | ||
assert expected_pack in actual, f"Missing pack: {expected_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.
this assert could be stricter... ==
remove comment
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.
On second thought, we can probably just remove this assert because about with have
assert actual.keys() == expected.keys()
which does the same thing.
tests/test_packsmanager.py
Outdated
for expected_pack, expected_tuple in expected.items(): | ||
assert expected_pack in actual, f"Missing pack: {expected_pack}" | ||
for expected_name, expected_rel_path in expected_tuple: | ||
matches = [ |
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 seems to be a rather unintuitive and unreadable way of doing this, is there a reason you chose this approach?
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 agree its unreadable. I've made the change to make it more readable.
@sbillinge I made a push to this branch addressing your comments but for some reason its not showing up here. Its showing up on my origin though... https://github.com/cadenmyers13/diffpy.cmi/blob/making-cli/tests/test_packsmanager.py. Maybe github is getting overworked? edit: it was just lagging, commits are coming through 👍 |
@sbillinge Also, I wrote the function for this tests as well so that is ready for review under |
# 6a) user wants to copy all examples from all packs to cwd | ||
# 6b) user wants to copy all examples from all packs to a target dir | ||
(), | ||
], |
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 ready for review here^^^^. Some use cases I jotted down. Let me know what you think.
@sbillinge jotted down some UCs for the copy_examples on this PR. on second thought however, maybe we want to merge the other test first and do the copy_exmaples test on a separate PR (if so, I will clean up this branch a bit). |
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.
next round
src/diffpy/cmi/packsmanager.py
Outdated
# root_path option provided for testing | ||
else: | ||
self.packs_dir = Path(root_path).resolve() | ||
self.examples_dir = Path(root_path).resolve() |
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 think this would be easier to read if we just move self.examples_dir = self._get_examples_dir()
outside of the if/else
clause.
""" | ||
examples_dir = tmp_path_factory.mktemp("examples") | ||
|
||
# case 1: pack with no examples |
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 think we need a requirements/packs
dir that we we will use as root_dir
in the test
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.
See the tree structure here: #53 (comment). This is what the temp dir looks like. let me know if this looks okay. I believe everything is being correctly and there is no magic
from diffpy.cmi.packsmanager import PacksManager | ||
|
||
|
||
def test_print_info(temp_path, capsys): |
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.
heads up, I am, not reviewing these till we have the dict building done.
# 2) pack with multiple examples. | ||
# Expect {'full_pack': [('example1`, path_to_1'), 'example2', path_to_2)] | ||
# 3) multiple packs. Expect dict with multiple pack:tuple pairs | ||
# 4) no pack found. Expect {} |
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 leave these here, we need to add case 5 here also.
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.
done
tests/test_packsmanager.py
Outdated
"case3", | ||
{ | ||
"packA": [ | ||
("my_ex1", "case3/packA/my_ex1"), |
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.
not a big deal, but to make the intent cleearer let's call the examples exampleA1
and so on and use the same pattern for all the cases.
tests/test_packsmanager.py
Outdated
# Ensure the example directory is correctly set | ||
assert pkmg.examples_dir == tmp_ex_dir | ||
actual = pkmg.available_examples() | ||
# Verify that the keys (pack names) are correct |
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 delete gratuitous comments
tests/test_packsmanager.py
Outdated
actual = pkmg.available_examples() | ||
# Verify that the keys (pack names) are correct | ||
assert set(actual.keys()) == set(expected.keys()) | ||
# Verify that each expected example exists in actual |
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 comment is also gratuitous, but a more informational comment would be helpful here. Something like # unpack the actual dict to test the values individually. global identity will fail because of the temp path
tests/test_packsmanager.py
Outdated
# Verify that the keys (pack names) are correct | ||
assert set(actual.keys()) == set(expected.keys()) | ||
# Verify that each expected example exists in actual | ||
for expected_pack, expected_list in expected.items(): |
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 asked chatGPT for a more pythonic way of doing this and got the following, which I think I prefer:
def values_match(list_a, list_b, tol=1e-9):
if len(list_a) != len(list_b):
return False
for (num_a, str_a), (num_b, str_b) in zip(list_a, list_b):
if abs(num_a - num_b) > tol:
return False
if not (str_a in str_b or str_b in str_a):
return False
return True
all(values_match(a[k], b[k]) for k in a)
function definition would be outside the test function, which makes the test function very readable. Of course variable names etc. should be modified.
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.
implemented something similar
tests/test_packsmanager.py
Outdated
|
||
|
||
@pytest.mark.parametrize("input,expected", example_params) | ||
def test_tmp_(input, expected, example_cases): |
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 good
tests/test_packsmanager.py
Outdated
example_path = example_cases / input | ||
for path in example_path.rglob("*"): | ||
if path.suffix: | ||
# Checks temp files are files and not dirs |
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 remove
@sbillinge ready for review. Heres the temp tree structure.
|
This directory structure looks great, thanks. This is getting a bit hard to review. Please could we make a few separate PRs? Let's make one PR that creates the directory structure fixture. It will need a news and should pass tests (it may fail codecov, that is ok). THen make one PR per test and we work to get everything green. Makes these PRs from a clean PR, though where necessary you can copy over code from here. This needs a news. |
@sbillinge good idea yeah i can do that right now. Do you want to keep this PR or should we just close it so the commit history is clean? |
This can be closed. Let's only reuse code judiciously from it but treat it more as a learning/design exercise. I think we came up with a good design now, though the code and the commit history itself could be cleaner. Working of off clean PRs from main can fix that. Please make sure PRs are passing everything before reesting a review, but have one function tested per PR, sthg like that. |
@sbillinge okay I've made two PRs, one with the temp dir fixture and the other with the function and test temp dir fixture: #54 (ready for review) function and test: #55 |
closing to keep PR list clean |
cli commands