Repository: rasterio/rasterio

Issue:
Uncaught OverflowError when comparing a CRS to an object of some types
<!--

WELCOME ABOARD!

Hi and welcome to the Rasterio project. We appreciate bug reports, questions
about documentation, and suggestions for new features. This issue template
isn't intended to ward you off; only to intercept and redirect some particular
categories of reports, and to collect a few important facts that issue reporters
often omit.

The primary forum for questions about installation and usage of Rasterio is 
https://rasterio.groups.io/g/main. The authors and other users will answer 
questions when they have expertise to share and time to explain. Please take the
time to craft a clear question and be patient about responses. Please do not
bring these questions to Rasterio's issue tracker, which we want to reserve for
bug reports and other actionable issues.

Questions about development of Rasterio, brainstorming, requests for comment,
and not-yet-actionable proposals are welcome in the project's developers 
discussion group https://rasterio.groups.io/g/dev. Issues opened in Rasterio's
GitHub repo which haven't been socialized there may be perfunctorily closed.

Please note: Rasterio contains extension modules and is thus susceptible to
C library compatibility issues. If you are reporting an installation or module
import issue, please note that this project only accepts reports about problems
with packages downloaded from the Python Package Index. Conda users should take
issues to one of the following trackers:

- https://github.com/ContinuumIO/anaconda-issues/issues
- https://github.com/conda-forge/rasterio-feedstock

Also please note: we are currently working on 1.0 and pre-releases. Bugs found
in version 0.36 will not be fixed except in a 1.0 alpha or beta release. In 
some cases, 0.36 bugs have already been fixed in recent pre-releases.

You think you've found a bug? We believe you!
-->

This is a followup of https://github.com/rasterio/rasterio/issues/1439#issuecomment-1037447833.

## Expected behavior and actual behavior.

I expected this code:

```python
from rasterio.crs import CRS
print(CRS.from_epsg(4326) == 1111111111111111111)
```

...to print `False` (based on [this pull request](https://github.com/rasterio/rasterio/pull/1754)).

Instead, it raises an `OverflowError`:

```
% python3 crs_eq.py
Traceback (most recent call last):
  File "crs_eq.py", line 2, in <module>
    print(CRS.from_epsg(4326) == 1111111111111111111)
  File "/Users/arno/venv/all3.8/lib/python3.8/site-packages/rasterio/crs.py", line 89, in __eq__
    other = CRS.from_user_input(other)
  File "/Users/arno/venv/all3.8/lib/python3.8/site-packages/rasterio/crs.py", line 492, in from_user_input
    return cls.from_epsg(value)
  File "/Users/arno/venv/all3.8/lib/python3.8/site-packages/rasterio/crs.py", line 333, in from_epsg
    obj._crs = _CRS.from_epsg(code)
  File "rasterio/_crs.pyx", line 330, in rasterio._crs._CRS.from_epsg
OverflowError: value too large to convert to int
```

## Steps to reproduce the problem.

Run this script:

```python
from rasterio.crs import CRS
print(CRS.from_epsg(4326) == 1111111111111111111)
```

## Operating system

macOS Monterey 12.2.1 (ARM64) and Ubuntu 20.04 (AMD64).

## Rasterio version and provenance

rasterio 1.2.10 from PyPI using pip 22.0.2 and GDAL 3.4.1

## Suggestion

The [docs of from_user_input](https://github.com/rasterio/rasterio/blob/72a7e86be0b1c5f06ea4f07763bff450a8e91753/rasterio/crs.py#L466) say that it accepts "A Python int, dict, or str", but [`CRS.__eq__`](https://github.com/rasterio/rasterio/blob/72a7e86be0b1c5f06ea4f07763bff450a8e91753/rasterio/crs.py#L87) always passes its argument to `from_user_input`. Maybe `__eq__` could  check the argument's type first? Something like:

```python
    def __eq__(self, other):
        if (isinstance(other, (int, str, dict, CRS))):
            try:
                other = CRS.from_user_input(other)
            except CRSError:
                return False
            return (self._crs == other._crs)
        else:
            return False
```

Or it could just catch `OverflowError`, but I don't know if that's the only exception that can be raised by `CRS.from_user_input`.


Discussion:
I am thinking that it may make sense to catch the error here: https://github.com/rasterio/rasterio/blob/b0d795b835ee4234466927b37c24607d13d94618/rasterio/_crs.pyx#L383
@snowman2 You're right, that way is better. I guess that a `CRSError` should be raised, so `__eq__` has the correct behaviour(?)
> @snowman2 You're right, that way is better. I guess that a `CRSError` should be raised, so `__eq__` has the correct behaviour(?)

That makes sense to me.

Resolving patch:
diff --git a/rasterio/crs.py b/rasterio/crs.py
--- a/rasterio/crs.py
+++ b/rasterio/crs.py
@@ -346,7 +346,11 @@ def from_epsg(cls, code):
 
         """
         obj = cls()
-        obj._crs = _CRS.from_epsg(code)
+
+        try:
+            obj._crs = _CRS.from_epsg(code)
+        except OverflowError as err:
+            raise CRSError(f"Not in the range of valid EPSG codes: {code}") from err
         return obj
 
     @classmethod
@@ -571,7 +575,7 @@ def epsg_treats_as_northingeasting(input):
     > Important change of behavior since GDAL 3.0.
     In previous versions, projected CRS with northing, easting axis order
     imported with importFromEPSG() would cause this method to return FALSE
-    on them, whereas now it returns TRUE, since importFromEPSG() is now 
+    on them, whereas now it returns TRUE, since importFromEPSG() is now
     equivalent to importFromEPSGA().
 
     Parameters


Test patch:
diff --git a/tests/test_crs.py b/tests/test_crs.py
--- a/tests/test_crs.py
+++ b/tests/test_crs.py
@@ -162,6 +162,12 @@ def test_from_epsg_string():
         assert CRS.from_string('epsg:xyz')
 
 
+def test_from_epsg_overflow():
+    with pytest.raises(CRSError):
+        # the argument is large enough to cause an overflow in Cython
+        CRS.from_epsg(1111111111111111111111)
+
+
 def test_from_string():
     wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs')
     assert wgs84_crs.to_dict() == {'init': 'epsg:4326'}