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

RF: satisfies, diff and alike #273

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft

Conversation

yarikoptic
Copy link
Member

@yarikoptic yarikoptic commented Jul 26, 2018

The idea in this PR to make comparison (diff) and satisfaction check logic to be implemented in a generic way, so only that semantic or custom handling would be assigned to the corresponding subclasses of SpecObject or their attributes.

  • SpecObject.is_satisfied_by has replaced the custom DebianPackage.satisfies without breaking any tests
  • RF DebianDistribution.satisfies so it could be replaced by the generic is_satisfied_by, test that previous/tested functionality works
  • RF/ENH is_satisfied_by into a more generic compare method with a mode (or other name) kwarg with possible values of: satisfied_by, identical_to (anything else? better names? later on we could come up with e.g. upgraded_to to check if a given env is an upgraded version of an existing one)
  • ENH TypedList to also provide the compare, where satisfied_by would succeed if provided other is a subset where each entry satisfied_by, and then identical set with identical_to
  • RF files = attrib(default=attr.Factory(list)) into a dedicated files = FileList() which would probably be just an instance of a TypedList(str) to provide needed semantics for comparison, but better to have a dedicated FileList adapter happen we need to provide anything custom beyond TypedList
  • diff then could benefit from all the above to be generic:
    • RF diff to provide both identical_to and satisfied_by semantic
    • finish RFing output to be generic
    • RF to accept >2 specs, where for
      - identical_to it is a regular "all are identical to each other"
      - satisfied_by is an incremental satisfaction -- "each next one satisfies all previous ones"

Whatever is not done within this PR should be moved into separate issues/PRs

@yarikoptic yarikoptic added the WiP label Jul 26, 2018

@property
def _cmp_id(self):
if not self._cmp_fields:
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, but an empty tuple would go down this path. That seems to contradict above which says that an empty tuple means "all attribs establish identity".

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I like the semantics of an empty _cmp_fields meaning use all of the attributes. I'd rather require classes to define _cmp_fields explicitly and let the error be raised if they aren't. This will mean one less special case we have to keep track of ("nothing means everything") and one more defense against a forgetful coder not thinking about _cmp_fields in a new class.

Copy link
Contributor

Choose a reason for hiding this comment

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

@chaselgrove Sounds reasonable to me. I've updated the comment to resolve the comment/code mismatch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. Also removed the fallback to all attributes in the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, thanks, I hadn't spotted that. So there was originally a code/comment mismatch and a code/code mismatch (the fallback was inaccessible due to the not self._cmp_fields guard).

# Might need to be gone or some custom exception
raise RuntimeError(
"Cannot establish identity of %r since _cmp_fields "
"are not defined", self)
Copy link
Contributor

Choose a reason for hiding this comment

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

This message won't be formatted for you.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fixed.

"are not defined", self)
fields = self._cmp_fields \
if self._cmp_fields \
else self._attr_names
Copy link
Contributor

Choose a reason for hiding this comment

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

or fields = self._cmp_fields or self._attr_names

Copy link
Contributor

Choose a reason for hiding this comment

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

Removed (see above).


# TODO: code here is too generic and could be used for "is_identical_to"
# kind of check, just logic could be adjusted
def is_satisfied_by(self, other):
Copy link
Contributor

Choose a reason for hiding this comment

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

I like the name is_statisfied_by, though I think dropping the "is" (satisfied_by) would be better.

Copy link
Contributor

Choose a reason for hiding this comment

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

Changed.

# TODO: code here is too generic and could be used for "is_identical_to"
# kind of check, just logic could be adjusted
def is_satisfied_by(self, other):
"""does the other spec object satisfies the requirements of the current one
Copy link
Contributor

Choose a reason for hiding this comment

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

s/satisfies/satisfy/

Stylistically, I'd prefer

Does the spec object `other` satisfy the requirements of this one?

# # (to the caller) that identifier is not defined
# msg = "%s instance has no attribute 'identifier'" % self.__class__
# raise AttributeError(msg)
#
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO there's no reason to leave commented out code unless it comes with a TODO comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

A comment later in the code referred to the block. :)

Gone.

if s.endswith('repository'):
return s.replace('repository', 'repositories')
else:
return s + 's'
Copy link
Contributor

Choose a reason for hiding this comment

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

This is currently only used in one place. Do you plan to use it elsewhere? If not it doesn't seem worth having a separate function.


dist_1 = env_1.get_distribution(dist_type)
if dist_1:
pkgs_1 = { p._cmp_id: p for p in dist_1.packages }
pkgs_1 = {p._cmp_id: p for p in dist_1.packages}
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be better if you'd do purely cosmetic changes like this and the one below in a separate commit.

if pkgs_only_1 or pkgs_only_2:
print(pkg_type + 's:')
print(_make_plural(pkg_type) + ':')

Copy link
Contributor

Choose a reason for hiding this comment

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

When I run diff on a spec with Git repos, it fails because the code below assumes a version attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

There were a number of tests that were failing; I've fixed these.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks

# print('%s pkgsitory %s:' % (repo_type, repo_id))
# print('< %s (%s)' % (repo_1.commit, repo_1.path))
# print('---')
# print('> %s (%s)' % (repo_2.commit, repo_2.path))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason to leave this code commented out rather than deleting it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably left over to refer to during refactoring. Gone now.

@codecov
Copy link

codecov bot commented Sep 7, 2018

Codecov Report

Merging #273 into master will increase coverage by 0.01%.
The diff coverage is 96.68%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #273      +/-   ##
==========================================
+ Coverage   89.27%   89.28%   +0.01%     
==========================================
  Files         137      135       -2     
  Lines       10103     9853     -250     
==========================================
- Hits         9019     8797     -222     
+ Misses       1084     1056      -28
Impacted Files Coverage Δ
niceman/distributions/conda.py 94.16% <100%> (+0.02%) ⬆️
niceman/distributions/tests/test_debian.py 98.66% <100%> (+0.15%) ⬆️
niceman/distributions/tests/test_vcs.py 100% <100%> (ø) ⬆️
niceman/distributions/tests/test_redhat.py 100% <100%> (ø) ⬆️
niceman/distributions/vcs.py 95.84% <100%> (+0.85%) ⬆️
niceman/distributions/debian.py 95.23% <100%> (+1.67%) ⬆️
niceman/interface/tests/test_diff.py 100% <100%> (ø) ⬆️
niceman/distributions/redhat.py 95.15% <100%> (+3.17%) ⬆️
niceman/distributions/base.py 89.72% <87.75%> (-1.19%) ⬇️
niceman/interface/diff.py 96.8% <96.36%> (-0.26%) ⬇️
... and 19 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5aecd60...8639cba. Read the comment docs.

kyleam added a commit to yarikoptic/ReproNim that referenced this pull request Sep 7, 2018
The code raises an error if _cmp_fields is empty.

Re: ReproNim#273 (comment)
continue
if self_value != other_value:
return False
return True
Copy link
Member Author

Choose a reason for hiding this comment

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

snippet accompanying removed code if you decide to roll back

$> git diff
diff --git a/niceman/distributions/base.py b/niceman/distributions/base.py
index 13f6f9d..7dbb07c 100644
--- a/niceman/distributions/base.py
+++ b/niceman/distributions/base.py
@@ -156,6 +156,11 @@ class SpecObject(object):
             if isinstance(self_v, SpecObject):
                 if not self_v.satisfied_by(other_v):
                     return differ("the other does not satisfy self")
+            elif isinstance(self_v, TypedList):
+                return all(
+                    any(self_v_e.satisfied_by(o) for o in other_v)
+                    for self_v_e in self_v
+                )
             else:
                 # For now (and otherwise) - just a simple identity check
                 if self_v != other_v:

@kyleam kyleam mentioned this pull request Jan 8, 2019
5 tasks
@yarikoptic
Copy link
Member Author

seems, minor python3 issues, e.g.

  File "/home/travis/build/ReproNim/niceman/niceman/interface/diff.py", line 230
    print package.identity_string
                ^
SyntaxError: Missing parentheses in call to 'print'
making output directory...

and wonder what is your feel @chaselgrove about merging current state into master for the rename (#359)

@codecov-io
Copy link

Codecov Report

❗ No coverage uploaded for pull request base (master@3ce100a). Click here to learn what that means.
The diff coverage is 96.2%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master     #273   +/-   ##
=========================================
  Coverage          ?   88.79%           
=========================================
  Files             ?      137           
  Lines             ?    10217           
  Branches          ?        0           
=========================================
  Hits              ?     9072           
  Misses            ?     1145           
  Partials          ?        0
Impacted Files Coverage Δ
niceman/distributions/conda.py 94.16% <100%> (ø)
niceman/distributions/tests/test_debian.py 98.66% <100%> (ø)
niceman/distributions/tests/test_vcs.py 100% <100%> (ø)
niceman/distributions/tests/test_redhat.py 100% <100%> (ø)
niceman/distributions/vcs.py 95.84% <100%> (ø)
niceman/distributions/debian.py 95.23% <100%> (ø)
niceman/interface/tests/test_diff.py 100% <100%> (ø)
niceman/distributions/redhat.py 94.54% <100%> (ø)
niceman/distributions/base.py 89.47% <87.27%> (ø)
niceman/interface/diff.py 95.71% <94.94%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3ce100a...5eb8159. Read the comment docs.

@codecov-io
Copy link

codecov-io commented Jan 10, 2019

Codecov Report

Merging #273 into master will increase coverage by 0.01%.
The diff coverage is 96.09%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #273      +/-   ##
==========================================
+ Coverage   89.81%   89.83%   +0.01%     
==========================================
  Files         148      148              
  Lines       11669    11738      +69     
==========================================
+ Hits        10481    10545      +64     
- Misses       1188     1193       +5
Impacted Files Coverage Δ
reproman/distributions/debian.py 95.28% <ø> (-0.02%) ⬇️
reproman/distributions/vcs.py 95.86% <100%> (+0.02%) ⬆️
reproman/distributions/redhat.py 94.54% <100%> (+0.03%) ⬆️
reproman/distributions/venv.py 89.47% <100%> (+0.41%) ⬆️
reproman/interface/tests/test_diff.py 100% <100%> (ø) ⬆️
reproman/distributions/conda.py 94.19% <100%> (+0.02%) ⬆️
reproman/interface/diff.py 95.77% <100%> (+0.06%) ⬆️
reproman/distributions/base.py 89.34% <91.52%> (+0.15%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3cc4d53...fc4a848. Read the comment docs.

@chaselgrove
Copy link
Contributor

seems, minor python3 issues, e.g.

  File "/home/travis/build/ReproNim/niceman/niceman/interface/diff.py", line 230
    print package.identity_string
                ^
SyntaxError: Missing parentheses in call to 'print'
making output directory...

Fixed by a commit I hadn't pushed.

and wonder what is your feel @chaselgrove about merging current state into master for the rename (#359)

Sure, let's merge and reopen the other points as needed.

@yarikoptic
Copy link
Member Author

Please try the diff on these two traces http://www.onerussian.com/tmp/niceman-traces-1.tgz which were generated I believe from two installations of niceman, one was done with just '.' and another one '.[full]'

@yarikoptic
Copy link
Member Author

one of the development "use cases" is to use core functionality here to provide comparison/matching of the Distributions and Packages for the purpose of fixing those duplicates. chicken-and-egg problem of a kind

@yarikoptic
Copy link
Member Author

fresh issue happens with the absence of unassociated files

> /home/yoh/proj/repronim/reproman/reproman/distributions/base.py(475)__init__()
-> raise TypeError('objects cannot both be None')
(Pdb) l
470  	    def __init__(self, a, b):
471  	        if not isinstance(a, (SpecObject, list, type(None))) \
472  	            or not isinstance(b, (SpecObject, list, type(None))):
473  	            raise TypeError('objects must be SpecObjects or None')
474  	        if not a and not b:
475  ->	            raise TypeError('objects cannot both be None')
476  	        if a and b and type(a) != type(b):
477  	            raise TypeError('objects must be of the same type')
478  	        self.cls = type(a) if a is not None else type(b)
479  	        self.a = a
480  	        self.b = b
(Pdb) up
> /home/yoh/proj/repronim/reproman/reproman/interface/diff.py(110)diff()
-> diffs.append({'diff': SpecDiff(env_1.files, env_2.files),
(Pdb) p env_1.files
[]
(Pdb) 
(dev3) 1 13710 ->1.....................................:Thu 16 May 2019 02:42:30 PM EDT:.
(git)hopa:~/proj/repronim/reproman[rf-diff]git
$> cat /tmp/sample2.spec
# ReproMan Environment Configuration File
# This file was created by ReproMan 0.1.0 on 2019-05-16 14:40:24.032705
version: 0.0.1
distributions:
- name: debian
  apt_sources:
  - name: apt_Debian_testing_main_0
    component: main
    archive: testing
    architecture: amd64
    codename: buster
    origin: Debian
    label: Debian
    site: http.debian.net
    archive_uri: http://http.debian.net/debian
    date: '2019-05-15 03:19:32+00:00'
  - name: apt_Debian_unstable_main_0
    component: main
    archive: unstable
    architecture: amd64
    codename: sid
    origin: Debian
    label: Debian
    site: http.debian.net
    archive_uri: http://http.debian.net/debian
    date: '2019-05-15 03:19:32+00:00'
  - name: apt__now__0
    archive: now
    archive_uri: /var/lib/dpkg/status
  packages:
  - name: login
    version: 1:4.5-1.1
    architecture: amd64
    source_name: shadow
    size: '750700'
    md5: 866c75edc8c06c95a7f5c343e61d30b4
    sha256: fa2d9a8686e5a1d0fef5d7f4c3a1bbd9cb69ee0fa506eec8c6c5528ababe7905
    versions:
      1:4.6-1.1:
      - apt_Debian_testing_main_0
      - apt_Debian_unstable_main_0
      - apt__now__0
    install_date: '2018-12-21 21:43:35+00:00'
    files:
    - /usr/bin/lastlog
  version: buster/sid

@yarikoptic yarikoptic marked this pull request as draft February 15, 2023 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants