internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: ibis-project/ibis

Issue:
bug: repr for ibis.literal is a bit wonky
### What happened?

I don't _think_ this was a deliberate change?  

```python
[ins] In [1]: import ibis

[ins] In [2]: ibis.literal(42)
Out[2]: 42: 42
```

### What version of ibis are you using?

master

### What backend(s) are you using, if any?

_No response_

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
We're hitting this dispatch rule: https://github.com/ibis-project/ibis/blob/master/ibis/expr/format.py#L118-L122
and ending up with a repr formatted as 

`f"{name}:{value}"` but `name == str(value)`

Resolving patch:
diff --git a/ibis/expr/format.py b/ibis/expr/format.py
--- a/ibis/expr/format.py
+++ b/ibis/expr/format.py
@@ -122,6 +122,14 @@ def _fmt_root_value_op(op: ops.Value, *, name: str, aliases: Aliases, **_: Any)
     return f"{prefix}{value}{type_info(op.to_expr().type())}"
 
 
+@fmt_root.register
+def _fmt_root_literal_op(
+    op: ops.Literal, *, name: str, aliases: Aliases, **_: Any
+) -> str:
+    value = fmt_value(op, aliases=aliases)
+    return f"{value}{type_info(op.to_expr().type())}"
+
+
 @fmt_root.register(ops.SortKey)
 def _fmt_root_sort_key(op: ops.SortKey, *, aliases: Aliases, **_: Any) -> str:
     return fmt_value(op, aliases=aliases)


Test patch:
diff --git a/ibis/tests/expr/test_format.py b/ibis/tests/expr/test_format.py
--- a/ibis/tests/expr/test_format.py
+++ b/ibis/tests/expr/test_format.py
@@ -361,3 +361,11 @@ def multi_output_udf(v):
 
     assert "sum:  StructField(ReductionVectorizedUDF" in result
     assert "mean: StructField(ReductionVectorizedUDF" in result
+
+
+@pytest.mark.parametrize(
+    "literal, typ, output",
+    [(42, None, '42'), ('42', None, "'42'"), (42, "double", '42.0')],
+)
+def test_format_literal(literal, typ, output):
+    assert repr(ibis.literal(literal, type=typ)) == output
Repository: ibis-project/ibis

Issue:
feat: add py.typed marker to allow packages using ibis to benefit from ibis's type annotations
### Is your feature request related to a problem?

I'm trying to run mypy against a package that uses Ibis, but I get some errors. For example:

```
bigframes/series.py:8: error: Skipping analyzing "ibis.expr.types": module is installed, but missing library stubs or py.typed marker  [import]
bigframes/dataframe.py:5: error: Skipping analyzing "ibis.expr.types": module is installed, but missing library stubs or py.typed marker  [import]
bigframes/dataframe.py:5: note: See https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
bigframes/engine.py:7: error: Skipping analyzing "ibis": module is installed, but missing library stubs or py.typed marker  [import]
```

### Describe the solution you'd like

Since Ibis already type checks itself, I think it may be sufficient to add a `py.typed` file as a marker. See: https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-library-stubs-or-py-typed-marker

### What version of ibis are you running?

4.0.0

### What backend(s) are you using, if any?

BigQuery

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
@tswast This seems reasonable to me. Would you mind putting a PR up for that?

Resolving patch:
diff --git a/ibis/backends/base/__init__.py b/ibis/backends/base/__init__.py
--- a/ibis/backends/base/__init__.py
+++ b/ibis/backends/base/__init__.py
@@ -510,6 +510,23 @@ def list_tables(
             The list of the table names that match the pattern `like`.
         """
 
+    @abc.abstractmethod
+    def table(self, name: str, database: str | None = None) -> ir.Table:
+        """Construct a table expression.
+
+        Parameters
+        ----------
+        name
+            Table name
+        database
+            Database name
+
+        Returns
+        -------
+        Table
+            Table expression
+        """
+
     @functools.cached_property
     def tables(self):
         """An accessor for tables in the database.


Test patch:
Repository: ibis-project/ibis

Issue:
docs: remove docs versioning
It's not clear if versioning is adding a ton of value right now and does create some annoying problems. Let's remove the versioning!


Discussion:

Resolving patch:
diff --git a/gen_redirects.py b/gen_redirects.py
new file mode 100644
--- /dev/null
+++ b/gen_redirects.py
@@ -0,0 +1,53 @@
+import pathlib
+
+import mkdocs_gen_files
+
+HTML_TEMPLATE = """
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="utf-8">
+    <title>Redirecting...</title>
+    <link rel="canonical" href="{url}">
+    <meta name="robots" content="noindex">
+    <script>var anchor=window.location.hash.substr(1);location.href="{url}"+(anchor?"#"+anchor:"")</script>
+    <meta http-equiv="refresh" content="0; url={url}">
+</head>
+<body>
+Redirecting...
+</body>
+</html>
+"""
+
+# Versions for templated redirects
+VERSIONS = ["latest", "dev", "4.1.0", "4.0.0", "3.2.0", "3.1.0"]
+
+# Templated redirects
+TEMPLATED_REDIRECTS = {
+    "/docs/{version}/": "/",
+    "/docs/{version}/install/": "/install/",
+    "/docs/{version}/docs/": "/docs/",
+    "/docs/{version}/backends/": "/backends/",
+}
+
+# Untemplated redirects
+REDIRECTS = {}
+
+# Fill in templates
+REDIRECTS.update(
+    {
+        old.format(version=version): new
+        for version in VERSIONS
+        for old, new in TEMPLATED_REDIRECTS.items()
+    }
+)
+
+# Write all redirect files
+for old, new in REDIRECTS.items():
+    if old.endswith("/"):
+        old = old + "index.html"
+
+    html = HTML_TEMPLATE.format(url=new)
+
+    with mkdocs_gen_files.open(pathlib.Path(old.lstrip("/")), "w") as f:
+        f.write(html)


Test patch:
Repository: ibis-project/ibis

Issue:
refactor(common): move the `ibis.common.geospatial` module to the base SQL backend
Since it contains routines for compiling geospatial values. 


Discussion:

Resolving patch:
diff --git a/ibis/common/geospatial.py b/ibis/backends/base/sql/registry/geospatial.py
similarity index 100%
rename from ibis/common/geospatial.py
rename to ibis/backends/base/sql/registry/geospatial.py
diff --git a/ibis/backends/postgres/registry.py b/ibis/backends/postgres/registry.py
--- a/ibis/backends/postgres/registry.py
+++ b/ibis/backends/postgres/registry.py
@@ -12,8 +12,8 @@
 import sqlalchemy as sa
 from sqlalchemy.dialects import postgresql as pg
 
+import ibis.backends.base.sql.registry.geospatial as geo
 import ibis.common.exceptions as com
-import ibis.common.geospatial as geo
 import ibis.expr.datatypes as dt
 import ibis.expr.operations as ops
 
@@ -383,7 +383,6 @@ def _literal(t, op):
         return sa.literal_column(f"INTERVAL '{value} {dtype.resolution}'")
     elif dtype.is_set():
         return list(map(sa.literal, value))
-    # geo spatial data type
     elif dtype.is_geospatial():
         # inline_metadata ex: 'SRID=4326;POINT( ... )'
         return sa.literal_column(geo.translate_literal(op, inline_metadata=True))


Test patch:
Repository: ibis-project/ibis

Issue:
bug: read_in_memory does not register the table
### What happened?

```python
import ibis
import pyarrow as pa


df_arrow = pa.table({"a": ["a"], "b": [1]})
con = ibis.duckdb.connect() # defaults to in-memory
df_ibis = con.read_in_memory(df_arrow, table_name="df_arrow")

con.list_tables()
```
Expected output: `["df_arrow"]`
Actual output: `[]`

### What version of ibis are you using?

4.1.0

### What backend(s) are you using, if any?

DuckDB

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Confirmed -- thanks for the report @lorentzenchr !
Ahh, ok, so Arrow tables are registered as views (as expected), but where parquet files and similar are registered with `main` as the attribute in `information_schema.tables.schema`, Arrow tables are set as `temp`.

@Mause -- is there any reason not to include all views in `get_view_names` if `schema=None`?

Resolving patch:
diff --git a/ibis/backends/duckdb/__init__.py b/ibis/backends/duckdb/__init__.py
--- a/ibis/backends/duckdb/__init__.py
+++ b/ibis/backends/duckdb/__init__.py
@@ -406,6 +406,15 @@ def read_in_memory(
 
         return self.table(table_name)
 
+    def list_tables(self, like=None, database=None):
+        tables = self.inspector.get_table_names(schema=database)
+        views = self.inspector.get_view_names(schema=database)
+        # workaround for GH5503
+        temp_views = self.inspector.get_view_names(
+            schema="temp" if database is None else database
+        )
+        return self._filter_with_like(tables + views + temp_views, like)
+
     def read_postgres(self, uri, table_name: str | None = None, schema: str = "public"):
         """Register a table from a postgres instance into a DuckDB table.
 


Test patch:
diff --git a/ibis/backends/duckdb/tests/test_register.py b/ibis/backends/duckdb/tests/test_register.py
--- a/ibis/backends/duckdb/tests/test_register.py
+++ b/ibis/backends/duckdb/tests/test_register.py
@@ -3,6 +3,7 @@
 from pathlib import Path
 
 import pandas as pd
+import pyarrow as pa
 import pytest
 import sqlalchemy as sa
 
@@ -111,6 +112,18 @@ def test_register_sqlite(data_directory):
     assert ft.count().execute()
 
 
+def test_read_in_memory():
+    con = ibis.duckdb.connect()
+
+    df_arrow = pa.table({"a": ["a"], "b": [1]})
+    df_pandas = pd.DataFrame({"a": ["a"], "b": [1]})
+    con.read_in_memory(df_arrow, table_name="df_arrow")
+    con.read_in_memory(df_pandas, table_name="df_pandas")
+
+    assert "df_arrow" in con.list_tables()
+    assert "df_pandas" in con.list_tables()
+
+
 def test_memtable_with_nullable_dtypes():
     data = pd.DataFrame(
         {
Repository: ibis-project/ibis

Issue:
bug: re_replace() doesn't fill params right?
### What happened?

Thanks for looking at this.

```python
import ibis
# this errors:
ibis.literal("hi").re_replace("x", "y").substitute({"d": "b"}, else_="k")
# This works fine:
# ibis.literal("hi").re_replace("x", "y")
# this works fine:
# ibis.literal("hi").substitute({"d": "b"}, else_="k")
```

It generates:
```
SELECT CASE regexp_replace(:param_1, :param_2, :param_3, :regexp_replace_1) WHEN :param_4 THEN :param_5 ELSE :param_6 END AS "SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')"
```
which results in
```
Error: Invalid Input Error: Unrecognized Regex option d
[SQL: SELECT CASE regexp_replace(?, ?, ?, ?) WHEN ? THEN ? ELSE ? END AS "SimpleCase(RegexReplace('hi', 'x', 'y'), 'k')"]
[parameters: ('hi', 'x', 'y', 'g', 'd', 'b', 'k')]
```

So something about how ibis doesn't correctly fill in `:regexp_replace_1` with the constant "g" flag as it should, but the value I passed in, "d" from the substitute(), is used instead.

Lemme know if you think of a workaround, I could write my own extension function for re_replace() until this is fixed, but maybe there's something easier.

### What version of ibis are you using?

4.1.0

### What backend(s) are you using, if any?

duckdb

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
This is an unfortunate artifact of using the `numeric` style for bind parameters and perhaps a bug in `duckdb-engine` (though that would be surprising).

In any event we can fix this by using `sa.text`. Fix incoming.

Resolving patch:
diff --git a/ibis/backends/duckdb/registry.py b/ibis/backends/duckdb/registry.py
--- a/ibis/backends/duckdb/registry.py
+++ b/ibis/backends/duckdb/registry.py
@@ -249,7 +249,7 @@ def _struct_column(t, op):
         ),
         ops.RegexExtract: fixed_arity(_regex_extract, 3),
         ops.RegexReplace: fixed_arity(
-            lambda *args: sa.func.regexp_replace(*args, "g"), 3
+            lambda *args: sa.func.regexp_replace(*args, sa.text("'g'")), 3
         ),
         ops.StringContains: fixed_arity(sa.func.contains, 2),
         ops.ApproxMedian: reduction(


Test patch:
diff --git a/ibis/backends/tests/test_string.py b/ibis/backends/tests/test_string.py
--- a/ibis/backends/tests/test_string.py
+++ b/ibis/backends/tests/test_string.py
@@ -554,3 +554,10 @@ def test_array_string_join(con):
 
     expr = s.join(",")
     assert con.execute(expr) == expected
+
+
+@pytest.mark.notimpl(["dask", "datafusion", "mssql", "mysql", "pandas", "pyspark"])
+def test_subs_with_re_replace(con):
+    expr = ibis.literal("hi").re_replace("i", "a").substitute({"d": "b"}, else_="k")
+    result = con.execute(expr)
+    assert result == "k"
Repository: ibis-project/ibis

Issue:
feat(ux): unlimited columns in interactive repr in jupyter
### Is your feature request related to a problem?

Originally from https://app.reviewnb.com/ibis-project/ibis/pull/5727/discussion/#comment-1133062663

```python
import ibis

ibis.options.interactive = True

cars = ibis.examples.mtcars.fetch() # 11 columns
big = cars.join(t, "mpg")  #21 columns
big
```

In a notebook, where I can scroll sideways, I would like to be able to see all columns by default. Currently, it looks like it's capped at 20.

<img width="210" alt="image" src="https://user-images.githubusercontent.com/10820686/227621492-edc81bfe-db7a-4a42-ad3b-df30a235eeb0.png">


### Describe the solution you'd like

all columns when there is a scrollbar. I'm not sure how to detect if there's a scrollbar. In a vanilla terminal, it would be nice if we continued to cap the width to the terminal width.

### What version of ibis are you running?

master

### What backend(s) are you using, if any?

NA

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:

Resolving patch:
diff --git a/ibis/expr/types/pretty.py b/ibis/expr/types/pretty.py
--- a/ibis/expr/types/pretty.py
+++ b/ibis/expr/types/pretty.py
@@ -198,11 +198,9 @@ def to_rich_table(table, console_width=None):
 
     max_columns = ibis.options.repr.interactive.max_columns
     if console_width == float("inf"):
-        if max_columns == 0:
-            # Show up to 20 columns by default, mirroring pandas's behavior
-            if orig_ncols >= 20:
-                table = table.select(*table.columns[:20])
-        elif max_columns is not None and max_columns < orig_ncols:
+        # When there's infinite display space, only subset columns
+        # if an explicit limit has been set.
+        if max_columns and max_columns < orig_ncols:
             table = table.select(*table.columns[:max_columns])
     else:
         # Determine the maximum subset of columns that *might* fit in the


Test patch:
diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py
--- a/ibis/backends/tests/test_client.py
+++ b/ibis/backends/tests/test_client.py
@@ -917,8 +917,8 @@ def test_interactive_repr_max_columns(alltypes, is_jupyter, monkeypatch):
     text = "".join(s.text for s in console.render(expr, options))
     assert " c_0 " in text
     if is_jupyter:
-        # Default of 20 columns are written
-        assert " c_19 " in text
+        # All columns are written
+        assert " c_49 " in text
     else:
         # width calculations truncates well before 20 columns
         assert " c_19 " not in text
Repository: ibis-project/ibis

Issue:
bug: filtering with `_` erroneously "works" with non-existent columns
### What happened?

I think this is probably related to some existing discussions on the semantics of the `_` API, but I have an example that I think is particularly tricky. The following should throw an error:

```python
from ibis import _, examples, show_sql

t = examples.aids.fetch() # PS I love how the examples make these repro steps so easy!
# the column "bogus" does not exist
result = t.filter([_.year != _.bogus])
show_sql(result)
```
shows that no filtering is happening:
```sql
SELECT
  t0.year,
  t0.quarter,
  t0.delay,
  t0.dud,
  t0.time,
  t0.y
FROM ibis_read_csv_29 AS t0
```

If I reference two columns that actually exist, then the filter behaves as expected.

### What version of ibis are you using?

5.0.0

### What backend(s) are you using, if any?

I think this would be relevant for all backends

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
This looks ... interesting 😁 

Thanks for the report!
Well, we're being just a bit too aggressive about catching `AttributeError`!

Resolving patch:
diff --git a/ibis/expr/deferred.py b/ibis/expr/deferred.py
--- a/ibis/expr/deferred.py
+++ b/ibis/expr/deferred.py
@@ -137,7 +137,6 @@ def __neg__(self) -> Deferred:
 
 
 def _resolve(arg: Any, *, expr: ir.Expr) -> Any:
-    try:
-        return arg.resolve(expr)
-    except AttributeError:
-        return arg
+    if (func := getattr(arg, "resolve", None)) is not None:
+        return func(expr)
+    return arg


Test patch:
diff --git a/ibis/expr/tests/test_deferred.py b/ibis/expr/tests/test_deferred.py
new file mode 100644
--- /dev/null
+++ b/ibis/expr/tests/test_deferred.py
@@ -0,0 +1,12 @@
+import pytest
+
+import ibis
+
+
+def test_invalid_attribute():
+    from ibis import _
+
+    t = ibis.table(dict(a="int"), name="t")
+    d = _.a + _.b
+    with pytest.raises(AttributeError):
+        d.resolve(t)
Repository: ibis-project/ibis

Issue:
bug: Impala Documentation is out of date
### What happened?

I was following https://ibis-project.org/backends/Impala/ and most of the things described there are not working anymore. The docstrings seem to be out of date.
For example in the first connection example:
```
>>> hdfs = ibis.impala.hdfs_connect(host=hdfs_host, port=hdfs_port)
```
returns an error: `AttributeError: module 'ibis' has no attribute 'impala'`



### What version of ibis are you using?

5.1.0

### What backend(s) are you using, if any?

Impala

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Thanks for letting us know, @matthias-Q !  

We recently restricted the available methods in the `ibis.<backend>` namespace to avoid running commands on unconnected backends.

For a quick workaround, you can run `ibis.backends.impala.hdfs_connect` instead.

@jcrist -- do we have a policy on how many (if any) special cased methods we want to make available in the ibis backend proxy objects?
I would err on the side of allowing `ibis.impala.hdfs_connect` because it gets passed to a subsequent `ibis.impala.connect` call.

Resolving patch:
diff --git a/ibis/__init__.py b/ibis/__init__.py
--- a/ibis/__init__.py
+++ b/ibis/__init__.py
@@ -111,5 +111,8 @@ def connect(*args, **kwargs):
     proxy._to_sql = backend._to_sql
     if hasattr(backend, "_sqlglot_dialect"):
         proxy._sqlglot_dialect = backend._sqlglot_dialect
+    # Add any additional methods that should be exposed at the top level
+    for name in getattr(backend, "_top_level_methods", ()):
+        setattr(proxy, name, getattr(backend, name))
 
     return proxy
diff --git a/ibis/backends/impala/__init__.py b/ibis/backends/impala/__init__.py
--- a/ibis/backends/impala/__init__.py
+++ b/ibis/backends/impala/__init__.py
@@ -182,6 +182,7 @@ class Backend(BaseSQLBackend):
     name = 'impala'
     # not 100% accurate, but very close
     _sqlglot_dialect = "hive"
+    _top_level_methods = ("hdfs_connect",)
     database_class = ImpalaDatabase
     table_expr_class = ImpalaTable
     compiler = ImpalaCompiler


Test patch:
diff --git a/ibis/backends/impala/tests/test_client.py b/ibis/backends/impala/tests/test_client.py
--- a/ibis/backends/impala/tests/test_client.py
+++ b/ibis/backends/impala/tests/test_client.py
@@ -21,6 +21,10 @@ def db(con, test_data_db):
     return con.database(test_data_db)
 
 
+def test_hdfs_connect_function_is_public():
+    assert hasattr(ibis.impala, "hdfs_connect")
+
+
 def test_cursor_garbage_collection(con):
     for _ in range(5):
         con.raw_sql('select 1').fetchall()
Repository: ibis-project/ibis

Issue:
bug: UTC issue with MySQL backend
### What happened?

when trying to connect to ```ibis.mysql.connect(host='...', user='...',password='...', database='...')```, I get this error: 
```
pymysql.err.OperationalError: (1298, "Unknown or incorrect time zone: 'UTC'")
...
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1298, "Unknown or incorrect time zone: 'UTC'")
```

coming from here:
```
File "...\envs\ml310\lib\site-packages\ibis\backends\mysql\__init__.py", line 107, in connect
cur.execute("SET @@session.time_zone = 'UTC'")
```

when I change it to SYSTEM, it works:

```
cur.execute("SET @@session.time_zone = 'SYSTEM'")
```

### What version of ibis are you using?

5.1

### What backend(s) are you using, if any?

mysql

### Relevant log output

_No response_

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion:
Hi @ededovic 👋🏻!

Thanks for the issue.

To help us debug, are you using MariaDB or MySQL, or something else entirely?

Our CI tests against MariaDB so we might have some unintentional assumptions that only apply to MariaDB.
I'm using Percona 8.0.25-15, I think it is some kind of variant of MySQL. 
Oddly, it fails to connect even when using MariaDB 5.5. It only takes in SYSTEM not UTC. 
> I'm using Percona 8.0.25-15, I think it is some kind of variant of MySQL. Oddly, it fails to connect even when using MariaDB 5.5. It only takes in SYSTEM not UTC.

It's almost certainly unrelated to the version or even implentation of MySQL in this case and more likely that time zone tables in the database aren't populated.

There's not much we can do about a timezone-impoverished setup, except to avoid failing when setting the session time zone. I can put up a PR to catch the exception and show a warning when this happens.

Resolving patch:
diff --git a/ibis/backends/mysql/__init__.py b/ibis/backends/mysql/__init__.py
--- a/ibis/backends/mysql/__init__.py
+++ b/ibis/backends/mysql/__init__.py
@@ -3,6 +3,7 @@
 from __future__ import annotations
 
 import re
+import warnings
 from typing import Iterable, Literal
 
 import sqlalchemy as sa
@@ -104,7 +105,10 @@ def do_connect(
         @sa.event.listens_for(engine, "connect")
         def connect(dbapi_connection, connection_record):
             with dbapi_connection.cursor() as cur:
-                cur.execute("SET @@session.time_zone = 'UTC'")
+                try:
+                    cur.execute("SET @@session.time_zone = 'UTC'")
+                except sa.exc.OperationalError:
+                    warnings.warn("Unable to set session timezone to UTC.")
 
         super().do_connect(engine)
 


Test patch:
Repository: ibis-project/ibis

Issue:
refactor(common): use an enum as a sentinel value instead of NoMatch class
This pattern plays better with type checkers, and makes it clearer that `NoMatch` is a singleton *value* rather than a singleton type (i.e. consumers should check for `is NoMatch` rather than `isinstance(x, NoMatch)`). 

```python
class NoMatchType(enum.Enum):
    NoMatch = "NoMatch"

NoMatch = NoMatchType.NoMatch
```

This way if we ever wanted to type annotate the `match` methods below, their signature could be:

```python
def match(self, value: Value) -> Value | NoMatchType:
    ...
```

See https://peps.python.org/pep-0484/#support-for-singleton-types-in-unions for reference.

_Originally posted by @jcrist in https://github.com/ibis-project/ibis/pull/6041#discussion_r1172829530_
            


Discussion:

Resolving patch:
diff --git a/ibis/common/patterns.py b/ibis/common/patterns.py
--- a/ibis/common/patterns.py
+++ b/ibis/common/patterns.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import enum
 import inspect
 import math
 import numbers
@@ -70,8 +71,11 @@ class MatchError(Exception):
     ...
 
 
-class NoMatch:
-    """Sentinel value for when a pattern doesn't match."""
+class NoMatchType(enum.Enum):
+    NoMatch = "NoMatch"
+
+
+NoMatch = NoMatchType.NoMatch  # Sentinel value for when a pattern doesn't match.
 
 
 # TODO(kszucs): have an As[int] or Coerced[int] type in ibis.common.typing which


Test patch: