Skip to content

support psycopg3 #213

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

Merged
merged 18 commits into from
Jun 22, 2025
Merged

support psycopg3 #213

merged 18 commits into from
Jun 22, 2025

Conversation

davidszotten
Copy link
Contributor

this is a bit fiddly because all the supported use-cases but all the tests pass locally with either psycopg version

@palewire
Copy link
Owner

Thanks for the stab. Is there a way to wrap that compat into something a little more elegant than a big try and except? I'm not an expert on Python backwards compatibility, but I wonder if there's a common pattern from a big library like Django that we can borrow from. (Maybe something our friend Claude would know?)

@davidszotten
Copy link
Contributor Author

Thanks for the stab. Is there a way to wrap that compat into something a little more elegant than a big try and except? I'm not an expert on Python backwards compatibility, but I wonder if there's a common pattern from a big library like Django that we can borrow from. (Maybe something our friend Claude would know?)

i followed django https://github.com/django/django/blob/d6e0c710173531645bd3689bc03e9b809aa3d220/django/db/backends/postgresql/psycopg_any.py#L4

@palewire
Copy link
Owner

i followed django

TIL! ;)

I'm going to blitz through the repo with a machete and update a bunch of other stale stuff while I'm at it here.

@palewire
Copy link
Owner

Like the versions we're testing and such.

@davidszotten
Copy link
Contributor Author

Like the versions we're testing and such.

that would be helpful, thanks. see my latest error with distutils on py 3.12.

@davidszotten
Copy link
Contributor Author

maybe we should switch away from setup.py test (see eg https://packaging.python.org/en/latest/discussions/setup-py-deprecated/#python-setup-py-test ) and use eg pytest? happy to make a pr for this tomorrow

@palewire
Copy link
Owner

maybe we should switch away from setup.py test (see eg https://packaging.python.org/en/latest/discussions/setup-py-deprecated/#python-setup-py-test ) and use eg pytest? happy to make a pr for this tomorrow

I'd be up for this in a separate PR. The main trick is working in those different Django settings tricks. My standard approach for new libraries changed to uv and pyproject.toml a while ago (as seen here). I've been trying to avoid opening that can of worms thus far, but maybe we should.

@palewire
Copy link
Owner

Looks like I got tests passing, even with 3.13. That's good progress.

Comment on lines 16 to 26
if isinstance(destination, TextIOBase):
decoder = utf8_decoder_cls()
else:
decoder = NoopDecoder()
with cursor.copy(sql, params) as copy:
while data := copy.read():
data = decoder.decode(data)
destination.write(data)
# TODO: is this extra one needed?
if data := decoder.decode(b"", final=True):
destination.write(data)
Copy link
Owner

Choose a reason for hiding this comment

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

Can you explain this to me? I'm just ignorant of how the new version works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is all related to the comment on line 15 # psycopg2.copy_expert has special handling of encoding

psycopg2 (but not 3) detects the destination being an "instance" of TextIOBase (https://github.com/psycopg/psycopg2/blob/c2b6a8aaeae71b30a96403bf5d6e7eafc21afaef/psycopg/utils.c#L217) and decodes the data before writing

the entire interface of for copy has change significantly in psycopg3. this pr tries to maintain the previous interface with all these hacks. maybe a better idea would be design a new interface that more closely matches the new copy https://www.psycopg.org/psycopg3/docs/basic/copy.html#copying-block-by-block
but it seems strange for this single library to have different interfaces for different psycopg versions and that makes migration harder for users

Comment on lines 28 to 31
def copy_from(cursor, sql, source):
with cursor.copy(sql) as copy:
while data := source.read(BUFFER_SIZE):
copy.write(data)
Copy link
Owner

Choose a reason for hiding this comment

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

Why this buffer size? Can users expect any differences in speed based on how that is set?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

psycopg2 uses 8k by default https://github.com/psycopg/psycopg2/blob/c2b6a8aaeae71b30a96403bf5d6e7eafc21afaef/psycopg/cursor.h#L74 (configurable, though we don't) but i recently came across python/cpython#117151 which bumps the default buffer sizes in cpython so i thought i'd go for that instead.

it is worth noting that i don't have psycopg3 in production yet so i don't have any real benchmarks. if you (or any other reviewer) has has good test sets to benchmark with i'd be interested in feedback

@palewire
Copy link
Owner

@thorntonedgar and @wlorenzetti, do you have any comments or concerns about this implementation?

while data := copy.read():
data = decoder.decode(data)
destination.write(data)
# TODO: is this extra one needed?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is needed. was going to fixup and squash but maybe best to make more commits and fix history after the review?

Copy link
Owner

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 follow. If you want to make another patch yet before merge, let's get 'er in.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pushed, though should we wait a bit to give the others a chance to comment before we merge?

Copy link
Owner

Choose a reason for hiding this comment

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

I'd like to give them at least a beat, yeah. if we don't hear today we can revisit tomorrow.

@palewire
Copy link
Owner

I'm going to merge this in a few minutes, but before I do I'm going to experiment with asking Claude to drum up some additional unit tests. Mostly as an experiment for me. I'm not optimistic on how well it will do based on some early local tomfoolery.

@davidszotten
Copy link
Contributor Author

How come you want dedicated tests for the compt module? It is already tested by running the rest of the suite with each psycopg version

@palewire
Copy link
Owner

palewire commented Jun 22, 2025

I'm good with what you've got. It's strictly an experiment to see what AI comes up with. I'm just curious. I haven't got to play too much with these tools. I apologize for the indulgence.

@palewire
Copy link
Owner

palewire commented Jun 22, 2025

Alrighty. I'm good to merge this. I'm going to hammer in a couple long overdue modernizations before I ship a new version like uv for env management and some mypy static type hinting. I expect to get it shipped today. Thank you very much for this excellent pull request and I'll flag you when I get it out on pypi.

@palewire palewire merged commit fb4e4b6 into palewire:main Jun 22, 2025
29 checks passed
@palewire palewire mentioned this pull request Jun 22, 2025
@palewire
Copy link
Owner

Version 2.8.0 is released. Please give it a shot and let me know if you have any troubles.

https://pypi.org/project/django-postgres-copy/#history

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.

2 participants