Base64 encode binary uploaded data on the AppEngine - python

I've been trying to Base64 encode image data from the user (in this case a trusted admin) in order to skip as many calls to the BlobStore as I possibly can. Every time I attempt to encode it, I recieve an error saying:
Error uploading image: 'ascii' codec can't decode byte 0x89 in position 0: ordinal not in range(128)
I've googled the error (the less significant parts) and found that it may have something to do with Unicode (?). The template portion is just a basic upload form, while the handler contains the following code:
def post(self,id):
logging.info("ImagestoreHandler#post %s", self.request.path)
fileupload = self.request.POST.get("file",None)
if fileupload is None : return self.error(400)
content_type = fileupload.type or getContentType( fileupload.filename )
if content_type is None:
self.error(400)
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write( "Unsupported image type: " + fileupload.filename )
return
logging.debug( "File upload: %s, mime type: %s", fileupload.filename, content_type )
try:
(img_name, img_url) = self._store_image(
fileupload.filename, fileupload.file, content_type )
self.response.headers['Location'] = img_url
ex=None
except Exception, err:
logging.exception( "Error while storing image" )
self.error(400)
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write("Error uploading image: " + str(err))
return
#self.redirect(urlBase % img.key() ) #dummy redirect is acceptable for non-AJAX clients,
# location header should be acceptable for true REST clients, however AJAX requests
# might not be able to access the location header so we'll write a 200 response with
# the new URL in the response body:
acceptType = self.request.accept.best_match( listRenderers.keys() )
out = self.response.out
if acceptType == 'application/json':
self.response.headers['Content-Type'] = 'application/json'
out.write( '{"name":"%s","href":"%s"}' % ( img_name, img_url ) )
elif re.search( 'html|xml', acceptType ):
self.response.headers['Content-Type'] = 'text/html'
out.write( '%s' % ( img_url, img_name) )
def _store_image(self, name, file, content_type):
"""POST handler delegates to this method for actual image storage; as
a result, alternate implementation may easily override the storage
mechanism without rewriting the same content-type handling.
This method returns a tuple of file name and image URL."""
img_enc = base64.b64encode(file.read())
img_enc_struct = "data:%s;base64,%s" % (content_type, img_enc)
img = Image( name=name, data=img_enc_struct )
img.put()
logging.info("Saved image to key %s", img.key() )
return ( str(img.name), img.key() )
My image model:
from google.appengine.ext import db
class Image(db.Model):
name = db.StringProperty(required=True)
data = db.TextProperty(required=True)
created = db.DateTimeProperty(auto_now_add=True)
owner = db.UserProperty(auto_current_user_add=True)
Any help is greatly appreciated. This code, minus my image encoding in _store_image, comes from the blooger branch of gvdent here.

your store image code could be like this....
img = Image( name=name, data=file.read() )
img.put()
return ( str(img.name), img.key() )
doing base64encode of binary data may increase the size of data itself and increase the cpu encoding and decoding time.
and Blobstore uses the same storage sturcuture as datastore, so it is just making easier to
use file upload store download.

Related

Unable to create URI with whitespace in MarkLogic

I have created a Marklogic transform which tries to convert some URL encoded characters: [ ] and whitespace when ingesting data into database. This is the xquery code:
xquery version "1.0-ml";
module namespace space = "http://marklogic.com/rest-api/transform/space-to-space";
declare function space:transform(
$context as map:map,
$params as map:map,
$content as document-node()
) as document-node()
{
let $puts := (
xdmp:log($params),
xdmp:log($context),
map:put($context, "uri", fn:replace(map:get($context, "uri"), "%5B+", "[")),
map:put($context, "uri", fn:replace(map:get($context, "uri"), "%5D+", "]")),
map:put($context, "uri", fn:replace(map:get($context, "uri"), "%20+", " ")),
xdmp:log($context)
)
return $content
};
When I tried this with my python code below
def upload_document(self, inputContent, uri, fileType, database, collection):
if fileType == 'XML':
headers = {'Content-type': 'application/xml'}
fileBytes = str.encode(inputContent)
elif fileType == 'TXT':
headers = {'Content-type': 'text/*'}
fileBytes = str.encode(inputContent)
else:
headers = {'Content-type': 'application/octet-stream'}
fileBytes = inputContent
endpoint = ML_DOCUMENTS_ENDPOINT
params = {}
if uri is not None:
encodedUri = urllib.parse.quote(uri)
endpoint = endpoint + "?uri=" + encodedUri
if database is not None:
params['database'] = database
if collection is not None:
params['collection'] = collection
params['transform'] = 'space-to-space'
req = PreparedRequest()
req.prepare_url(endpoint, params)
response = requests.put(req.url, data=fileBytes, headers=headers, auth=HTTPDigestAuth(ML_USER_NAME, ML_PASSWORD))
print('upload_document result: ' + str(response.status_code))
if response.status_code == 400:
print(response.text)
The following lines are from the xquery logging:
2023-02-13 16:59:00.067 Info: {}
2023-02-13 16:59:00.067 Info:
{"input-type":"application/octet-stream",
"uri":"/Judgment/26856/supportingfiles/[TEST] 57_image1.PNG", "output-type":"application/octet-stream"}
2023-02-13 16:59:00.067 Info:
{"input-type":"application/octet-stream",
"uri":"/Judgment/26856/supportingfiles/[TEST] 57_image1.PNG", "output type":"application/octet-stream"}
2023-02-13 16:59:00.653 Info: Status 500: REST-INVALIDPARAM: (err:FOER0000)
Invalid parameter: invalid uri:
/Judgment/26856/supportingfiles/[TEST] 57_image1.PNG
The MarkLogic REST API is very opinionated about what a valid URI is, and it doesn't allow you to insert documents that have spaces in the URI. If you have an existing URI with a space in it, the REST API will retrieve or update it for you. However, it won't allow you to create a new document with such a URI.
If you need to create documents with spaces in the URI, then you will need to use lower-level APIs. xdmp:document-insert() will let you.

How to create a Docusign envelope with a custom pdf (Python, Django)

My goal is to create a pdf using WeasyPrint and add it the the payload sent to the Docusign Api when requesting an envelope to be created.
Here are my steps:
generate the a pdf with WeasyPrint and return a based64 string
def generate_envelope_document(document_name: str, context: dict):
content = render_to_string(f"insurance_contracts/{document_name}.html",
context=context)
css = find(f"insurance_contracts/{document_name}.css")
doc = HTML(string=content, media_type="screen").write_pdf(stylesheets=[css],
zoom=0.8)
return base64.b64encode(doc).decode("utf-8")
create my envelope definition:
def create_envelope_definition(envelope_data: dict, context: dict, custom_fields: dict = None):
mandate = Document(
document_base64=generate_envelope_document("name1", context),
name="name1",
file_extension="pdf",
document_id=1,
)
conditions = Document(
document_base64=generate_envelope_document("name2", context),
name="name2",
file_extension="pdf",
document_id=2,
)
signer = Signer(
email=envelope_data["signer_email"],
name=envelope_data["signer_name"],
recipient_id="1",
routing_order="1",
)
signer.tabs = Tabs(
sign_here_tabs=[
SignHere(
anchor_string="Sign",
anchor_units="pixels",
anchor_y_offset="50",
anchor_x_offset_metadata="50",
)
]
)
envelope_definition = EnvelopeDefinition(
status="sent", documents=[mandate, conditions], recipients=Recipients(signers=[signer])
)
if custom_fields:
envelope_definition.custom_fields = CustomFields(
text_custom_fields=[
TextCustomField(name=field_name, value=field_value, required=False)
for field_name, field_value in enumerate(custom_fields)
]
)
return envelope_definition
create a Docusign Api object:
def get_envelopes_api_client():
"""
Create the docusign api client object
Return EnvelopesApi object
"""
api_client = ApiClient()
api_client.host = settings.DOCUSIGN_BASE_PATH
api_client.set_default_header("Authorization", "Bearer " + get_access_token())
envelope_api = EnvelopesApi(api_client)
return envelope_api
create and send the Docusign envelope:
envelope_api = get_envelopes_api_client()
try:
envelope = envelope_api.create_envelope(
settings.DOCUSIGN_ACCOUNT_ID, envelope_definition=envelope_definition
)
except ApiException as e:
logger.error(e.body.decode())
return None
return envelope
at the moment I'm getting this error:
{"errorCode":"INVALID_REQUEST_BODY","message":"The request body is missing or improperly formatted. Could not cast or convert from System.String to API_REST.Models.v2_1.propertyMetadata."}
I don't understand what I could be doing wrong. Is my envelope definition not correct or is there something else I am missing. I can't seem to find official documentation on how to do this. All I have found is [https://developers.docusign.com/docs/esign-rest-api/how-to/send-binary/][1] which does not use the docusign SDK.
Any help would be welcome. Thanks!
email_subject needs to be added to envelope_definition and has some value. That's the subject of the email sent out by DocuSign.
document_id="2" instead of document_id=2
anchor_x_offset_metadata should not be used here and is probably the reason for your error.

How to create an image to unit test an API

I have a simple model representing a female that a user can view, edit and create:
class Female(models.Model):
firstName = models.CharField(max_length=255)
lastName = models.CharField(max_length=255)
profileImage = models.ImageField()
bio = models.TextField()
fantasticBio = models.TextField()
I am using a multi-part form to send the data for a create via an Angular service. This works fine. The django view that handles the creation is:
#api_view(['POST'])
def createFemale(request):
serializedFemale = FemaleSerializer(data=request.data)
if serializedFemale.is_valid():
serializedFemale.save()
return Response(serializedFemale.data)
else:
return Response(serializedFemale.errors, status=status.HTTP_400_BAD_REQUEST)
My problem is that I am unable to fully unit test this view. I am having trouble creating an image that I can use to test the view via the test client. My knowledge of image generation is limited so I may be generating the image wrong, however it seems to be accepted as a Django ImageField when I write it directly to the database in my set up. The relevant test code is as follows:
def createImageFile():
"""
Convinience function to create a test image
"""
image = Image.new('RGBA', size=(50, 50), color=(256, 0, 0))
image_file = BytesIO()
image.save(image_file, 'PNG')
img_str = base64.b64encode(image_file.getvalue())
img_str = str.encode("data:image/png;base64,") + img_str
return ImageFile(img_str)
def test_createFemale(self):
"""
A valid Female created with the API should be in the database
"""
profileImage = SimpleUploadedFile("test.png", createImageFile().file, content_type="image/png")
femaleToCreate = {}
femaleToCreate['firstName'] = "Test"
femaleToCreate['lastName'] = "Female"
femaleToCreate['profileImage'] = profileImage
femaleToCreate['bio'] = "Test bio"
femaleToCreate['fantasticBio'] = "Fantastic test bio"
response = self.client.post(url, femaleToCreate)
self.assertEquals(response.status_code, 200) # Make sure valid request returns success response
The response I receive from the server is:
{'profileImage': ['Upload a valid image. The file you uploaded was either not an image or a corrupted image.']}
How can I create an image that will be accepted by my API from within my unit test?
Please refrain from suggesting a file read of an existing image, I have considered this option and chosen not to pursue it as I believe it is a bad practice.
Thank you for any help.
Ok so I've got round this as follows:
def test_images_by_component_id_update(self):
image = PIL.Image.new('RGB', size=(1, 1))
file = tempfile.NamedTemporaryFile(suffix='.jpg')
image.save(file)
with open(file.name, 'rb') as f:
data = {'image': f}
response = self.client.patch(reverse('foo', args=(1, 1)), data=data, format='multipart')
self.assertEqual(200, response.status_code)
Notice I have to reopen the file, this took a while to debug. For whatever reason it doesn't like the NamedTemporaryFile.file attribute so I had to reopen.

SOAPpy WSDL client authentication

I'm trying to do simple SOAP call with WSDL and authentication, but don't know really how to pass credentials to the call. Is this possible with WSDL, or should I do this some other way?
from SOAPpy import WSDL
WSDLFILE = 'link/to/wsdl/file'
server = WSDL.Proxy(WSDLFILE)
result = server.methodCall()
Right now, I'm getting this error:
File "/usr/lib/pymodules/python2.7/SOAPpy/Client.py", line 263, in call
raise HTTPError(code, msg)
SOAPpy.Errors.HTTPError: <HTTPError 401 Unauthorized>
Quite an old question but since it shows up first when googling "SOAPpy authentication" - I will leave my findings so maybe you won't have to bang your head for 10 hours. Giving back to community.
Googling does not provide much help but this sample here (http://code.activestate.com/recipes/444758-how-to-add-cookiesheaders-to-soappy-calls/) made me to write my own helper (version 0.0 beta)
from SOAPpy import (
WSDL,
HTTPTransport,
Config,
SOAPAddress,
)
import urllib2
import base64
class AuthenticatedTransport(HTTPTransport):
_username = None
_password = None
def call(self, addr, data, namespace, soapaction = None, encoding = None, http_proxy = None, config = Config, timeout = 10):
if not isinstance(addr, SOAPAddress):
addr = SOAPAddress(addr, config)
content_type = 'text/xml'
if encoding != None:
content_type += '; charset="%s"' % encoding
encoded_auth = None
if ( isinstance(AuthenticatedTransport._username, str) != False ):
if ( isinstance(AuthenticatedTransport._password, str) == False ):
AuthenticatedTransport._password = ""
encoded_auth = base64.b64encode('%s:%s' % (self._username, self._password))
this_request = None
if ( encoded_auth is not None ):
this_request = urllib2.Request(
url=addr.proto + "://" + addr.host + addr.path,
data=data,
headers={
"Content-Type":content_type,
"SOAPAction":"%s" % soapaction,
"Authorization":"Basic %s" % encoded_auth
}
)
else:
this_request = urllib2.Request(
url=addr.proto + "://" + addr.host + addr.path,
data=data,
headers={
"Content-Type":content_type,
"SOAPAction":"%s" % soapaction,
}
)
response = urllib2.urlopen(this_request)
data = response.read()
# get the new namespace
if namespace is None:
new_ns = None
else:
new_ns = self.getNS(namespace, data)
# return response payload
return data, new_ns
WSDL.Config.strictNamespaces = 0
username = "your_username"
password = "your_password"
WSDLFile = "https://%s:%s#some_host/your_wsdl.php?wsdl" % (username, password)
namespace = "http://futureware.biz/mantisconnect"
proxy = WSDL.Proxy(WSDLFile, namespace=namespace, transport = AuthenticatedTransport(username,password))
print(proxy.mc_get_version()) # This is my WSDL call, your will be different
For whatever reason it is not enough to use AuthenticatedTransport class, the WSDL url itself also has to contain user name and password. Maybe it's because SOAP wrapper called by WSDL here is creating separate HTTP session (remember reading about it on SOAPpy mailing list).
Hope this helps somebody.

Adding attachments to TestCaseResults using pyral 0.9.3

I'm trying to add an attachment to a testcaseresult using pyral like this:
testCaseResult = rally.create('TestCaseResult', {'TestCase': tc.ref , 'Build': revision,
'Verdict': verdict[test.result], 'Date': resultdate, 'Notes': note,
'Tester': tester, 'Duration': runtime })
res = rally.addAttachment(testCaseResult.oid, file);
The TestaseResult is successfully created but res is False.
What am I doing wrong? Should I not be using the oid? I've tried passing testCaseResult, testCaseResult.oid and "TestCaseResult/" + testCaseResult.oid, and none seem to work...
UPDATED:
Based on Mark's answer below (pyral does not directly support adding attachments to testcaseresults), I wrote the following subroutine:
def addTestCaseResultAttachment(testCaseResult, filename, contentType='text/plain'):
if not os.path.exists(filename):
raise Exception('Named attachment filename: %s not found' % filename)
if not os.path.isfile(filename):
raise Exception('Named attachment filename: %s is not a regular file' % filename)
attachment_file_name = os.path.basename(filename)
attachment_file_size = os.path.getsize(filename)
if attachment_file_size == 0:
raise Exception('Cannot attach zero length file')
if attachment_file_size > 5000000:
raise Exception('Attachment file size too large, unable to attach to Rally Artifact')
contents = ''
with open(filename, 'r') as af:
contents = base64.encodestring(af.read())
# create an AttachmentContent item
ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
if not ac:
raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : contentType,
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % me.oid,
"TestCaseResult" : testCaseResult.ref
}
# and finally, create the Attachment
attachment = rally.create('Attachment', attachment_info, project=None)
if not attachment:
raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)
The issue is that the addAttachment method for pyral's restapi, sets a ref to the "Artifact" attribute of the Attachment object, as shown following (lines 1241 thru 1251 of restapi):
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : mime_type,
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % self.contextHelper.user_oid,
#"Artifact" : artifact.ref # (Artifact is an 'optional' field)
}
# While it's actually possible to have an Attachment not linked to an Artifact,
# in most cases, it'll be far more useful to have the linkage to an Artifact than not.
if artifact:
attachment_info["Artifact"] = artifact.ref
Thus, the addAttachment method will actually only work for objects that inherit from Artifact, i.e. Stories, Defects, Tasks, TestCases, etc. As seen in WSAPI Docs, to associate an Attachment to a TestCaseResult, the needed attribute is actually "TestCaseResult". This syntax was chosen since a TestCaseResult is actually a WorkspaceDomainObject, and not an Artifact.
Here's an example that creates a new TestCaseResult and adds an Attachment
#!/usr/bin/env python
#################################################################################################
#
# create_tcr_and_attachment.py -- Create a New TestCaseResult and attach a file to it
#
USAGE = """
Usage: py create_tcr_and_attachment.py <TestCaseFormatedID> <filename>
"""
#################################################################################################
import sys, os
import re
import string
import base64
from pyral import Rally, rallySettings
#################################################################################################
errout = sys.stderr.write
ATTACHMENT_ATTRIBUTES = ['oid', 'ObjectID', '_type', '_ref', '_CreatedAt', 'Name',
'CreationDate', 'Description',
'Content', 'ContentType', 'Size',
'Subscription',
'Workspace',
'Artifact',
'User'
]
ATTACHMENT_IMPORTANT_ATTRS = """
Subscription ref (supplied at creation)
Workspace ref (supplied at creation)
Name STRING Required (name of the file, like foo.txt or ThePlan.doc)
User ref to User Required Settable (User who added the object)
Content ref to AttachmentContent
Size INTEGER Required
ContentType STRING Required
Artifact ref to Artifact (optional field)
Description TEXT Optional
"""
#################################################################################################
def main(args):
options = [opt for opt in args if opt.startswith('--')]
args = [arg for arg in args if arg not in options]
server = "rally1.rallydev.com"
user = "user#company.com"
password = "topsecret"
workspace = "My Workspace"
project = "My Project"
print " ".join(["|%s|" % item for item in [server, user, '********', workspace, project]])
rally = Rally(server, user, password, workspace=workspace, version="1.43") # specify the Rally server and credentials
rally.enableLogging('rally.hist.create_tcr_and_attachment') # name of file you want logging to go to
if len(args) != 2:
errout('ERROR: You must supply a Test Case FormattedID and an attachment file name')
errout(USAGE)
sys.exit(1)
targetTCID, filename = args
me = rally.getUserInfo(username=user).pop(0)
print "%s user oid: %s" % (user, me.oid)
target_project = rally.getProject()
target_tc = rally.get('TestCase', query='FormattedID = %s' % targetTCID, instance=True)
datestring = "2014-05-01"
tcr_info = {
"TestCase" : target_tc.ref,
"Build" : "master-91321",
"Date" : datestring,
"Verdict" : "Pass",
"Notes" : "Honeycomb harvest project."
}
print "Creating Test Case Result ..."
tcr = rally.put('TestCaseResult', tcr_info)
print "Created TCR: %s" % (tcr.oid)
print "Creating AttachmentContent"
if not os.path.exists(filename):
raise Exception('Named attachment filename: %s not found' % filename)
if not os.path.isfile(filename):
raise Exception('Named attachment filename: %s is not a regular file' % filename)
attachment_file_name = os.path.basename(filename)
attachment_file_size = os.path.getsize(filename)
if attachment_file_size > 5000000:
raise Exception('Attachment file size too large, unable to attach to Rally Artifact')
contents = ''
with open(filename, 'r') as af:
contents = base64.encodestring(af.read())
# create an AttachmentContent item
ac = rally.create('AttachmentContent', {"Content" : contents}, project=None)
if not ac:
raise RallyRESTAPIError('Unable to create AttachmentContent for %s' % attachment_file_name)
attachment_info = { "Name" : attachment_file_name,
"Content" : ac.ref, # ref to AttachmentContent
"ContentType" : "image/jpeg",
"Size" : attachment_file_size, # must be size before encoding!!
"User" : 'user/%s' % me.oid,
"TestCaseResult" : tcr.ref
}
# and finally, create the Attachment
attachment = rally.create('Attachment', attachment_info, project=None)
if not attachment:
raise RallyRESTAPIError('Unable to create Attachment for %s' % attachment_file_name)
#################################################################################################
#################################################################################################
if __name__ == '__main__':
main(sys.argv[1:])
The function addAttachment in Rally API has been updated to support Test Case result:
# While it's actually possible to have an Attachment not linked to an Artifact,
# in most cases, it'll be far more useful to have the linkage to an Artifact than not.
# A special case is where the "Artifact" is actually a TestCaseResult, which is not a
# subclass of Artifact in the Rally data model, but the WSAPI has been adjusted to permit
# us to associate an Attachment with a TestCaseResult instance.
if artifact:
attachment_info["Artifact"] = artifact.ref
if artifact._type == 'TestCaseResult':
del attachment_info["Artifact"]
attachment_info["TestCaseResult"] = artifact.ref
The function call rally.addAttachment(testCaseResult.oid, file); should now work.

Categories