Repository: djrobstep/sqlakeyset (python)

Issue:
SQLAlchemy 2 wrong type for column desc
Using Postgres and getting this error when I call `select_page` against core (not ORM):

```
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/asyncio.py", line 84, in select_page
    return await core_get_page(s, selectable, per_page, place, backwards)
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/asyncio.py", line 30, in core_get_page
    sel = prepare_paging(
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/paging.py", line 196, in prepare_paging
    mapped_ocols = [find_order_key(ocol, column_descriptions) for ocol in order_cols]
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/paging.py", line 196, in <listcomp>
    mapped_ocols = [find_order_key(ocol, column_descriptions) for ocol in order_cols]
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/columns.py", line 429, in find_order_key
    ok = derive_order_key(ocol, desc, index)
  File "/Users/csantero/projects/example/.venv/lib/python3.10/site-packages/sqlakeyset/columns.py", line 370, in derive_order_key
    entity = desc["entity"]
KeyError: 'entity'
```

Python: 3.10.10
SQLAlchemy: 2.0.18
sqlakeyset: 2.0.1691149549
psycopg: 3.1.9

I've tried both AsyncEngine and Engine with psycopg 3. I also tried psycopg2 Engine and got the same result.

When I debug I can tell that `desc` is a dict, and not a `ColumnElement`:
```
"{'name': 'my_column', 'type': UUID(), 'expr': Column('my_column', UUID(), table=<my_table>)}"
```

If I stick this code at the start of `derive_order_key` I can extract the `ColumnElement` and then everything works as expected:

```py
    if isinstance(desc, dict):
        desc = desc.get("expr")
        if desc is None:
            return None
```

If this looks like the right approach then I'm happy to work up a PR, though I'm not quite sure how to produce a failing test case.

Pull request:
Fix errors handling nondeclarative tables (#95)
Fixes #95

Patch:
diff --git a/sqlakeyset/columns.py b/sqlakeyset/columns.py
index 4d1d1ac..e7ca7d7 100644
--- a/sqlakeyset/columns.py
+++ b/sqlakeyset/columns.py
@@ -367,7 +367,7 @@ def derive_order_key(ocol, desc, index):
         else:
             return None
 
-    entity = desc["entity"]
+    entity = desc.get("entity")
     expr = desc["expr"]
 
     if isinstance(expr, Bundle):
@@ -410,7 +410,7 @@ def derive_order_key(ocol, desc, index):
 
     # is an attribute with label
     try:
-        if ocol.quoted_full_name == OC(expr, enable_warnings=False).full_name:
+        if ocol.quoted_full_name == OC(expr, enable_warnings=False).quoted_full_name:
             return DirectColumn(ocol, index)
     except sqlalchemy.exc.ArgumentError:
         pass


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 2d5699d..8136337 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,7 +6,17 @@ from random import randrange
 import arrow
 import pytest
 from packaging import version
-from sqlalchemy import Column, Enum, ForeignKey, Integer, String, func, inspect
+from sqlalchemy import (
+    Column,
+    Enum,
+    ForeignKey,
+    Integer,
+    String,
+    Table,
+    func,
+    insert,
+    inspect,
+)
 from sqlalchemy import select as _select
 from sqlalchemy.ext.hybrid import hybrid_property
 from sqlalchemy.orm import column_property, relationship
@@ -153,6 +163,14 @@ class Author(Base):
         )
 
 
+Widget = Table(
+    "widget",
+    Base.metadata,
+    Column("id", Integer, primary_key=True),
+    Column("name", String(255)),
+)
+
+
 JoinedInheritanceBase = declarative_base()
 
 
@@ -240,10 +258,13 @@ def _dburl(request):
         Light(colour=Colour(i % 3), intensity=(i * 13) % 53, myint=i) for i in range(99)
     ]
 
+    widgets = [dict(name=f"widget {i}") for i in range(99)]
+
     with temporary_database(request.param, host="localhost") as dburl:
         with S(dburl) as s:
             Base.metadata.create_all(s.connection())
             s.add_all(data)
+            s.execute(insert(Widget).values(widgets))
         yield dburl
 
 
diff --git a/tests/test_paging.py b/tests/test_paging.py
index 0883ab9..dd07018 100644
--- a/tests/test_paging.py
+++ b/tests/test_paging.py
@@ -40,6 +40,7 @@ from conftest import (
     Vertebrate,
     Mammal,
     Light,
+    Widget,
     select,
     JoinedInheritanceBase,
     Base,
@@ -436,6 +437,12 @@ def test_core_order_by_result_processor(dburl):
         check_paging_core(selectable=selectable, s=s)
 
 
+def test_core_non_declarative(dburl):
+    with S(dburl, echo=ECHO) as s:
+        selectable = select(Widget).order_by(Widget.c.id)
+        check_paging_core(selectable=selectable, s=s)
+
+
 def test_orm_enum(dburl):
     with S(dburl, echo=ECHO) as s:
         q = s.query(Light.id, Light.colour).order_by(Light.intensity, Light.id)