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

Add Typecheck Utils and mark all type errors #20

Merged
merged 19 commits into from
Nov 15, 2024
Merged

Add Typecheck Utils and mark all type errors #20

merged 19 commits into from
Nov 15, 2024

Conversation

willsheffler
Copy link
Contributor

@willsheffler willsheffler commented Nov 15, 2024

Summary by Sourcery

Enhancements:

  • Add type ignore comments to suppress type checking errors in various files.

Copy link
Contributor

sourcery-ai bot commented Nov 15, 2024

Reviewer's Guide by Sourcery

This PR adds type checking annotations and fixes type errors throughout the codebase. The changes primarily involve adding type ignore comments and explicit type annotations to resolve mypy type checking issues.

Class diagram for Vec class with type annotations

classDiagram
    class Vec {
        +float x
        +float y
        +float z
        +to_rosetta() xyzVector_double_t
        +dot(Vec v) float
        +normdot(Vec v) float
        +angle(Vec v) float
        +angle_degrees(Vec v) float
        +lineangle(Vec v) float
        +lineangle_degrees(Vec v) float
        +length() float
        +length_squared() float
        +distance(Vec v) float
        +distance_squared(Vec v) float
        +cross(Vec v) Vec
        +normalize() void
        +normalized() Vec
        +outer(Vec v) Mat
        +unit() Vec
        +abs() Vec
        +tuple() tuple
        +key() tuple
        +round0() void
    }
Loading

Class diagram for Mat class with type annotations

classDiagram
    class Mat {
        +float xx
        +float xy
        +float xz
        +float yx
        +float yy
        +float yz
        +float zx
        +float zy
        +float zz
        +to_rosetta() xyzMatrix_double_t
        +row(int i) Vec
        +col(int i) Vec
        +rowx() Vec
        +rowy() Vec
        +rowz() Vec
        +colx() Vec
        +coly() Vec
        +colz() Vec
        +transpose() void
        +transposed() Mat
        +det() float
        +trace() float
        +add_diagonal(Vec v) Mat
        +is_rotation() bool
    }
Loading

Class diagram for Xform class with type annotations

classDiagram
    class Xform {
        +Mat R
        +Vec t
        +from_four_points(Vec cen, Vec a, Vec b, Vec c) Xform
        +from_two_vecs(Vec a, Vec b) Xform
        +tolocal(Vec x) Vec
        +toglobal(Vec x) Vec
        +inverse() Xform
        +rotation_axis() Vec
        +rotation_axis_center() Vec
    }
Loading

File-Level Changes

Change Details Files
Add type ignore comments to suppress mypy errors
  • Add # type: ignore comments to suppress type checking errors where fixing the underlying type issue would be too complex
  • Add type ignore comments for third-party imports like pymol, torch, and scipy
  • Add type ignore comments for dynamic attribute access and optional fields
ipd/sym/old/pymol_symgen.py
ipd/sym/old/pymol_xyzmath.py
ipd/viz/pymol_cgo.py
ipd/tests/sym/xtal/test_spacegroup_symelems.py
Add explicit type annotations and fix type errors
  • Add return type annotations to functions
  • Fix incorrect type annotations on class attributes and methods
  • Add type annotations to function parameters
  • Fix type errors in numpy array operations and tensor manipulations
ipd/sym/sym_manager.py
ipd/sym/sym_adapt.py
ipd/crud/frontend.py
ipd/crud/backend.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @willsheffler - I've reviewed your changes and found some issues that need to be addressed.

Blocking issues:

  • Using eval() is a security risk - consider using a safer alternative or restoring safe_eval (link)

Overall Comments:

  • Consider gradually replacing some of the type: ignore comments with proper type annotations in follow-up PRs to improve type safety while keeping the code working
Here's what I looked at during the review
  • 🟡 General issues: 2 issues found
  • 🔴 Security: 1 blocking issue
  • 🟡 Testing: 2 issues found
  • 🟡 Complexity: 4 issues found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -257,14 +294,18 @@ def apply_symmetry_index(self, idx: T, val, isidx, **kw) -> T:
assert th.allclose(newidx, idx)
return new

def apply_symmetry_scalar(self, shapekind: ShapeKind, contig: 'th.Tensor', **kw) -> 'th.Tensor':
N = len(contig) // self.nsub
def apply_symmetry_scalar(self, shapekind: ShapeKind, contig: 'th.Tensor', **kw) -> 'th.Tensor': # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Potential division by zero error if self.nsub is 0

Add validation to ensure self.nsub is non-zero before performing integer division with //. This could cause runtime crashes.


def get_all_annotations(cls):
annotations = {}
for base in cls.__mro__[::-1]:
annotations |= getattr(base, '__annotations__', {})
return annotations

def fstr(template):
return safe_eval(f'f"""{template}"""')
def eval_fstring(template, namespace):
Copy link
Contributor

Choose a reason for hiding this comment

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

🚨 issue (security): Using eval() is a security risk - consider using a safer alternative or restoring safe_eval

The eval() function can execute arbitrary Python code which could be malicious. Even with a restricted namespace, it can still access builtins unless explicitly prevented.

raise Valuerror(f'cant make hypothesis strat for {T} {type(T)} {orig} {args}')
if (strat := self.type_mapping.get(T, None)) is not None: return strat # type: ignore

raise Valuerror(f'cant make hypothesis strat for {T} {type(T)} {orig} {args}') # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (typo): Typo in error name 'Valuerror' - should be 'ValueError'

SymElem(2, axis=[1, 0, 0], cen=[0.25, 0.5, 0.0], hel=0.5, label='C21'), # type: ignore

SymElem(2, axis=[1, 0, 0], cen=[0.25, 0.5, 0.5], hel=0.5, label='C21'), # type: ignore

],
)
helper_test_symelem('P622', val, debug, **kw)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Test data validation could be improved

The test data for space group symmetry elements contains many hardcoded values without clear validation of their correctness. Consider adding assertions or documentation explaining how these reference values were determined and why they are correct.

    expected_result = get_reference_symelems('P622')
    assert_symelems_match(expected_result, val, 'P622')
    helper_test_symelem('P622', val, debug, **kw)

Comment on lines 233 to 240
assert b3.d == "dee"

foo = Namespace(a=1, b="c")
b = Bunch(foo, _strict=False)
b = Bunch(foo, _strict=False) # type: ignore

assert b.a == 1
assert b.b == "c"
assert b.missing is None
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Missing edge case tests for Bunch initialization

The test_bunch_init() function could be expanded to cover more edge cases like empty dictionaries, None values, nested bunches, and invalid input types. This would help ensure the Bunch class handles all scenarios correctly.

def test_bunch_init():
    b = Bunch(dict(a=2, b="bee"), _strict=False)
    b2 = Bunch(b, _strict=False)
    b3 = Bunch(c=3, d="dee", _strict=False, **b)
    b4 = Bunch({}, _strict=False)
    b5 = Bunch(dict(x=None), _strict=False)
    b6 = Bunch(dict(y={"z": 1}), _strict=False)

    assert b.a == 2 and b.b == "bee"
    assert b3.d == "dee" and b3.a == 2
    assert b4.missing is None
    assert b5.x is None
    assert b6.y.z == 1


sg_symelem_frame444_compids_dict = sgdata.get('sg_symelem_frame444_compids_dict', dict()) # type: ignore

sg_symelem_frame444_opcompids_dict = sgdata.get('sg_symelem_frame444_opcompids_dict', dict()) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Replace dict() with {} (dict-literal)

Suggested change
sg_symelem_frame444_opcompids_dict = sgdata.get('sg_symelem_frame444_opcompids_dict', dict()) # type: ignore
sg_symelem_frame444_opcompids_dict = sgdata.get('sg_symelem_frame444_opcompids_dict', {}) # type: ignore


ExplanationThe most concise and Pythonic way to create a dictionary is to use the {}
notation.

This fits in with the way we create dictionaries with items, saving a bit of
mental energy that might be taken up with thinking about two different ways of
creating dicts.

x = {"first": "thing"}

Doing things this way has the added advantage of being a nice little performance
improvement.

Here are the timings before and after the change:

$ python3 -m timeit "x = dict()"
5000000 loops, best of 5: 69.8 nsec per loop
$ python3 -m timeit "x = {}"
20000000 loops, best of 5: 29.4 nsec per loop

Similar reasoning and performance results hold for replacing list() with [].

Comment on lines +409 to +413
if len(cperr.match) >= len(bestbadiframes): # type: ignore

if np.max(cperr.match) < np.max(bestbadiframes): # type: ignore

bestbadiframes, bestbadelem = cperr.match, movedelem # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (code-quality): Merge nested if conditions (merge-nested-ifs)

Suggested change
if len(cperr.match) >= len(bestbadiframes): # type: ignore
if np.max(cperr.match) < np.max(bestbadiframes): # type: ignore
bestbadiframes, bestbadelem = cperr.match, movedelem # type: ignore
if len(cperr.match) >= len(bestbadiframes) and np.max(cperr.match) < np.max(bestbadiframes):
bestbadiframes, bestbadelem = cperr.match, movedelem # type: ignore


ExplanationToo much nesting can make code difficult to understand, and this is especially
true in Python, where there are no brackets to help out with the delineation of
different nesting levels.

Reading deeply nested code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two if conditions can be combined using
and is an easy win.

Comment on lines 68 to +69
for i in range(9):
assert np.allclose(SS(diffuse=i).linear1, i + 1)
assert np.allclose(SS(diffuse=i).linear1, i + 1) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 72 to +73
for i in range(9):
assert np.allclose(SS(rfold=i).square, i**2)
assert np.allclose(SS(rfold=i).square, i**2) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

Comment on lines 76 to +77
for i in range(9):
assert np.allclose(SS(design=i).cube, i**3)
assert np.allclose(SS(design=i).cube, i**3) # type: ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests

@willsheffler willsheffler merged commit 8b5699c into main Nov 15, 2024
1 check passed
@willsheffler willsheffler deleted the typecheck branch November 15, 2024 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant