Closed allaboutmikey closed 3 years ago
You should pass bytes
object instead of BytesIO
object. Try this cur.execute("insert into ppe_types (ppe_type, ppe_image) values (%s, %s)", (args.image_name, output.getvalue()))
So I made that change:
image=Image.open(args.image_path)
with io.BytesIO() as output:
image.save(output, format="jpeg")
with pytds.connect(dsn='removed', as_dict=True) as conn:
with conn.cursor() as cur:
cur.execute("insert into ppe_types (ppe_type, ppe_image) values (%s, %s)", (args.image_name, output.getvalue()))
The error is now:
Traceback (most recent call last):
File "/usr/lib/python3/dist-packages/pytds/tds_base.py", line 319, in force_unicode
return s.decode('utf8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
Still doesn't seem happy with the parameter. I'm not sure why it's decoding anything when the field is a bytes field.
I have brute forced the query to get my work done, but it is a bit ugly! Code is modified to:
with io.BytesIO() as output:
image.save(output, format="jpeg")
# removes the outer b and quotes from eg: b'1234' and makes it like 0x1234 which is a TSQL binary literal
hex = "0x" + str(binascii.hexlify(output.getvalue()))[2:-1]
with pytds.connect(dsn='removed', as_dict=True) as conn:
with conn.cursor() as cur:
cur.execute(f"insert into ppe_types (ppe_type, ppe_image) values (%s, {hex})", (args.image_name,))
which works, but is obviously not properly parameterised. I still haven't been able to figure out how to get plain bytes into a parameter. :(
Edit: Tracing through the code mentioned in the stack trace of the original version: in the _execute function (line 602 in init.py) the param_definition (line 635) has the field as an nvarchar(MAX) I assume this is the cause of my problem.
If I select the same field from the table and then call the description method on the cursor, I get the tuple:
('ppe_image', 165, None, 0, None, None, 1)
which is a varbinary.
From tds_base.py: BIGVARBINTYPE = XSYBVARBINARY = 165 # 0xA5
I'm starting to go cross-eyed (it's after 5), so I'll have to come back to figuring out how this happens next time I'm here.
You would also need to wrap your bytes value with pytds.Binary
, here is example: https://github.com/denisenkom/pytds/blob/master/tests/types_test.py#L124
Ah ha! The wrapping thing is what I was looking for. That's working perfectly now with all my ugly kludges removed. Thanks for your input. BTW I've said it before, but this is a great module! Well done on writing it. This would be a useful addition to the documentation IMHO. Is there a way I can help adding it?
Thanks, here is information about docs: Source for documentation is here https://github.com/denisenkom/pytds/tree/master/docs, rendered docs can be seen here https://github.com/denisenkom/pytds/tree/master/docs. You can start by adding that after Table Valued Parameters section.
With the following table structure, I am seemingly unable to insert image data into the ppe_image field:
The code looks like:
and the error is:
Fair enough that it can't infer a type from bytes, but can it not get type info from the database column directly? Failing the inference, I thought I'd just specifiy the type myself. The tds_types code seems to have types for varbinary and even Image, but I can't see anywhere that I can specify the type for my parameter. Do I have to construct my own column object? I read of someone doing that in another issue, but can't find any documentation for that object.
Edited to get the code markers right