def insert_entity(self, entity): if entity.exists: raise edm.EntityExists(str(entity.get_location())) if self.is_medialink_collection(): # insert a blank stream and then update mle = self.new_stream(src=StringIO()) entity.set_key(mle.key()) # 2-way merge mle.merge(entity) entity.merge(mle) entity.exists = True self.update_entity(entity) else: doc = core.Document(root=core.Entry(None, entity)) data = str(doc) request = http.ClientRequest(str(self.baseURI), 'POST', entity_body=data) request.set_content_type( params.MediaType.from_str(core.ODATA_RELATED_ENTRY_TYPE)) self.client.process_request(request) if request.status == 201: # success, read the entity back from the response doc = core.Document() doc.Read(request.res_body) entity.exists = True doc.root.GetValue(entity) # so which bindings got handled? Assume all of them for k, dv in entity.NavigationItems(): dv.bindings = [] else: self.RaiseError(request)
def __setitem__(self, key, entity): if not isinstance( entity, edm.Entity) or entity.entity_set is not self.entity_set: raise TypeError if key != entity.key(): raise ValueError if not entity.exists: raise edm.NonExistentEntity(str(entity.get_location())) if not self.isCollection: request = http.ClientRequest(str(self.baseURI), 'GET') self.client.process_request(request) if request.status == 200: # this collection is not empty, which will be an error # unless it already contains entity, in which case it's # a no-op existingEntity = self.new_entity() doc = core.Document() doc.Read(request.res_body) existingEntity.exists = True doc.root.GetValue(existingEntity) if existingEntity.key() == entity.key(): return else: raise edm.NavigationError( "Navigation property %s already points to an entity (use replace to update it)" % self.name) elif request.status != 404: # some type of error self.RaiseError(request) doc = core.Document(root=core.URI) doc.root.SetValue(str(entity.get_location())) data = str(doc) request = http.ClientRequest(str(self.linksURI), 'PUT', entity_body=data) request.set_content_type( params.MediaType.from_str('application/xml')) self.client.process_request(request) if request.status == 204: return else: self.RaiseError(request) else: doc = core.Document(root=core.URI) doc.root.SetValue(str(entity.get_location())) data = str(doc) request = http.ClientRequest(str(self.linksURI), 'POST', entity_body=data) request.set_content_type( params.MediaType.from_str('application/xml')) self.client.process_request(request) if request.status == 204: return else: self.RaiseError(request)
def __len__(self): if self.isCollection: return super(NavigationCollection, self).__len__() else: # This is clumsy as we grab the entity itself entityURL = str(self.baseURI) sysQueryOptions = {} if self.filter is not None: sysQueryOptions[core.SystemQueryOption.filter] = unicode( self.filter) if sysQueryOptions: entityURL = uri.URI.from_octets( entityURL + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) request = http.ClientRequest(str(entityURL)) request.set_header('Accept', 'application/atom+xml;type=entry') self.client.process_request(request) if request.status == 404: # if we got a 404 from the underlying system we're done return 0 elif request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=entityURL) doc.Read(request.res_body) if isinstance(doc.root, atom.Entry): entity = core.Entity(self.entity_set) entity.exists = True doc.root.GetValue(entity) return 1 else: raise core.InvalidEntryDocument(str(entityURL))
def update_entity(self, entity): if not entity.exists: raise edm.NonExistentEntity(str(entity.get_location())) doc = core.Document(root=core.Entry) doc.root.SetValue(entity, True) data = str(doc) request = http.ClientRequest(str(entity.get_location()), 'PUT', entity_body=data) request.set_content_type( params.MediaType.from_str(core.ODATA_RELATED_ENTRY_TYPE)) self.client.process_request(request) if request.status == 204: # success, nothing to read back but we're not done # we've only updated links to existing entities on properties with # single cardinality for k, dv in entity.NavigationItems(): if not dv.bindings or dv.isCollection: continue # we need to know the location of the target entity set binding = dv.bindings[-1] if isinstance(binding, edm.Entity) and binding.exists: dv.bindings = [] # now use the default method to finish the job self.update_bindings(entity) return else: self.RaiseError(request)
def new_stream(self, src, sinfo=None, key=None): """Creates a media resource""" if not self.is_medialink_collection(): raise ExpectedMediaLinkCollection if sinfo is None: sinfo = core.StreamInfo() request = http.ClientRequest(str(self.baseURI), 'POST', entity_body=src) request.set_content_type(sinfo.type) if sinfo.size is not None: request.set_content_length(sinfo.size) if sinfo.modified is not None: request.set_last_modified(params.FullDate(src=sinfo.modified)) if key: request.set_header("Slug", str(app.Slug(unicode(key)))) self.client.process_request(request) if request.status == 201: # success, read the entity back from the response doc = core.Document() doc.Read(request.res_body) entity = self.new_entity() entity.exists = True doc.root.GetValue(entity) return entity else: self.RaiseError(request)
def __getitem__(self, key): sysQueryOptions = {} if self.filter is not None: sysQueryOptions[core.SystemQueryOption.filter] = "%s and %s" % ( core.ODataURI.key_dict_to_query( self.entity_set.key_dict(key)), unicode(self.filter)) entityURL = str(self.baseURI) else: entityURL = ( str(self.baseURI) + core.ODataURI.FormatKeyDict(self.entity_set.GetKeyDict(key))) if self.expand is not None: sysQueryOptions[core.SystemQueryOption.expand] = core.FormatExpand( self.expand) if self.select is not None: sysQueryOptions[core.SystemQueryOption.select] = core.FormatSelect( self.select) if sysQueryOptions: entityURL = uri.URI.from_octets( entityURL + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) request = http.ClientRequest(str(entityURL)) if self.filter: request.set_header('Accept', 'application/atom+xml') else: request.set_header('Accept', 'application/atom+xml;type=entry') self.client.process_request(request) if request.status == 404: raise KeyError(key) elif request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=entityURL) doc.Read(request.res_body) if isinstance(doc.root, atom.Entry): entity = core.Entity(self.entity_set) entity.exists = True doc.root.GetValue(entity) return entity elif isinstance(doc.root, atom.Feed): nresults = len(doc.root.Entry) if nresults == 0: raise KeyError(key) elif nresults == 1: e = doc.root.Entry[0] entity = core.Entity(self.entity_set) entity.exists = True e.GetValue(entity) return entity else: raise UnexpectedHTTPResponse( "%i entities returned from %s" % nresults, entityURL) elif isinstance(doc.root, core.Error): raise KeyError(key) else: raise core.InvalidEntryDocument(str(entityURL))
def LoadService(self, serviceRoot): """Configures this client to use the service at *serviceRoot* *serviceRoot* is a string or :py:class:`pyslet.rfc2396.URI` instance.""" if isinstance(serviceRoot, uri.URI): self.serviceRoot = serviceRoot else: self.serviceRoot = uri.URIFactory.URI(serviceRoot) request = http.HTTPRequest(str(self.serviceRoot)) request.SetHeader('Accept', 'application/atomsvc+xml') self.ProcessRequest(request) if request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=self.serviceRoot) doc.Read(request.resBody) if isinstance(doc.root, app.Service): self.service = doc.root self.serviceRoot = uri.URIFactory.URI(doc.root.ResolveBase()) self.feeds = {} self.model = None for w in self.service.Workspace: for f in w.Collection: url = f.GetFeedURL() if f.Title: self.feeds[f.Title.GetValue()] = url else: raise InvalidServiceDocument(str(serviceRoot)) self.pathPrefix = self.serviceRoot.absPath if self.pathPrefix[-1] == u"/": self.pathPrefix = self.pathPrefix[:-1] metadata = uri.URIFactory.Resolve(serviceRoot, '$metadata') doc = edmx.Document(baseURI=metadata, reqManager=self) defaultContainer = None try: doc.Read() if isinstance(doc.root, edmx.Edmx): self.model = doc.root for s in self.model.DataServices.Schema: for container in s.EntityContainer: if container.IsDefaultEntityContainer(): prefix = "" defaultContainer = container else: prefix = container.name + "." for es in container.EntitySet: fTitle = prefix + es.name if fTitle in self.feeds: if self.feeds[fTitle] == es.GetLocation(): self.feeds[fTitle] = es else: raise DataFormatError(str(metadata)) except xml.XMLError, e: # Failed to read the metadata document, there may not be one of course raise DataFormatError(str(e))
def InsertEntity(self, entity): if entity.exists: raise edm.EntityExists(str(entity.GetLocation())) doc = core.Document(root=core.Entry(None, entity)) data = str(doc) request = http.HTTPRequest(str(self.baseURI), 'POST', reqBody=data) request.SetContentType( http.MediaType.FromString(core.ODATA_RELATED_ENTRY_TYPE)) self.client.ProcessRequest(request) if request.status == 201: # success, read the entity back from the response doc = core.Document() doc.Read(request.resBody) entity.exists = True doc.root.GetValue(entity) # so which bindings got handled? Assume all of them for k, dv in entity.NavigationItems(): dv.bindings = [] else: self.RaiseError(request)
def entity_generator(self): feedURL = self.baseURI sysQueryOptions = {} if self.filter is not None: sysQueryOptions[ core.SystemQueryOption.filter] = unicode(self.filter) if self.expand is not None: sysQueryOptions[ core.SystemQueryOption.expand] = core.FormatExpand(self.expand) if self.select is not None: sysQueryOptions[ core.SystemQueryOption.select] = core.FormatSelect(self.select) if self.orderby is not None: sysQueryOptions[ core.SystemQueryOption.orderby] = core.CommonExpression.OrderByToString( self.orderby) if sysQueryOptions: feedURL = uri.URIFactory.URI( str(feedURL) + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) while True: request = http.HTTPRequest(str(feedURL)) request.SetHeader('Accept', 'application/atom+xml') self.client.ProcessRequest(request) if request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=feedURL) doc.Read(request.resBody) if isinstance(doc.root, atom.Feed): if len(doc.root.Entry): for e in doc.root.Entry: entity = core.Entity(self.entity_set) entity.exists = True e.GetValue(entity) yield entity else: break else: raise core.InvalidFeedDocument(str(feedURL)) feedURL = None for link in doc.root.Link: if link.rel == "next": feedURL = link.ResolveURI(link.href) break if feedURL is None: break
def __getitem__(self, key): if self.isCollection: return super(NavigationCollection, self).__getitem__(key) else: # The baseURI points to a single entity already, we must not add # the key entityURL = str(self.baseURI) sysQueryOptions = {} if self.filter is not None: sysQueryOptions[ core.SystemQueryOption.filter] = unicode(self.filter) if self.expand is not None: sysQueryOptions[ core.SystemQueryOption.expand] = core.FormatExpand( self.expand) if self.select is not None: sysQueryOptions[ core.SystemQueryOption.select] = core.FormatSelect( self.select) if sysQueryOptions: entityURL = uri.URIFactory.URI( entityURL + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) request = http.HTTPRequest(str(entityURL)) request.SetHeader('Accept', 'application/atom+xml;type=entry') self.client.ProcessRequest(request) if request.status == 404: raise KeyError(key) elif request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=entityURL) doc.Read(request.resBody) if isinstance(doc.root, atom.Entry): entity = core.Entity(self.entity_set) entity.exists = True doc.root.GetValue(entity) if entity.Key() == key: return entity else: raise KeyError(key) elif isinstance(doc.root, core.Error): raise KeyError(key) else: raise core.InvalidEntryDocument(str(entityURL))
def replace(self, entity): if not entity.exists: raise edm.NonExistentEntity(str(entity.GetLocation())) if self.isCollection: # inherit the implementation super(NavigationCollection, self).replace(entity) else: if not isinstance(entity, edm.Entity) or entity.entity_set is not self.entity_set: raise TypeError doc = core.Document(root=core.URI) doc.root.SetValue(str(entity.GetLocation())) data = str(doc) request = http.HTTPRequest(str(self.linksURI), 'PUT', reqBody=data) request.SetContentType( http.MediaType.FromString('application/xml')) self.client.ProcessRequest(request) if request.status == 204: return else: self.RaiseError(request)
def entity_generator(self): if self.isCollection: for entity in super(NavigationCollection, self).entity_generator(): yield entity else: # The baseURI points to a single entity already, we must not add # the key entityURL = str(self.baseURI) sysQueryOptions = {} if self.filter is not None: sysQueryOptions[core.SystemQueryOption.filter] = unicode( self.filter) if self.expand is not None: sysQueryOptions[ core.SystemQueryOption.expand] = core.FormatExpand( self.expand) if self.select is not None: sysQueryOptions[ core.SystemQueryOption.select] = core.FormatSelect( self.select) if sysQueryOptions: entityURL = uri.URI.from_octets( entityURL + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) request = http.ClientRequest(str(entityURL)) request.set_header('Accept', 'application/atom+xml;type=entry') self.client.process_request(request) if request.status == 404: return elif request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=entityURL) doc.Read(request.res_body) if isinstance(doc.root, atom.Entry): entity = core.Entity(self.entity_set) entity.exists = True doc.root.GetValue(entity) yield entity else: raise core.InvalidEntryDocument(str(entityURL))
def RaiseError(self, request): """Given a :py:class:`pyslet.http.messages.Message` object containing an unexpected status in the response, parses an error response and raises an error accordingly.""" if request.status == 404: # translates in to a key error etype = KeyError elif request.status == 405: # indicates the URL doesn't support the operation, for example # an attempt to POST to a navigation property that the server # doesn't support perhaps etype = NotImplementedError elif request.status == 401: etype = AuthorizationRequired elif request.status >= 400 and request.status < 500: etype = edm.ConstraintError else: etype = UnexpectedHTTPResponse debugMsg = None if request.res_body: doc = core.Document() doc.Read(src=request.res_body) if isinstance(doc.root, core.Error): errorMsg = "%s: %s" % (doc.root.Code.GetValue(), doc.root.Message.GetValue()) if doc.root.InnerError is not None: debugMsg = doc.root.InnerError.GetValue() else: errorMsg = request.response.reason else: errorMsg = request.response.reason if etype == KeyError: logging.info("404: %s", errorMsg) else: logging.info("%i: %s", request.status, errorMsg) if debugMsg: logging.debug(debugMsg) raise etype(errorMsg)
def replace(self, entity): if not entity.exists: raise edm.NonExistentEntity(str(entity.get_location())) if self.isCollection: # inherit the implementation super(NavigationCollection, self).replace(entity) else: if not isinstance( entity, edm.Entity) or entity.entity_set is not self.entity_set: raise TypeError doc = core.Document(root=core.URI) doc.root.SetValue(str(entity.get_location())) data = str(doc) request = http.ClientRequest(str(self.linksURI), 'PUT', entity_body=data) request.set_content_type( params.MediaType.from_str('application/xml')) self.client.process_request(request) if request.status == 204: return else: self.RaiseError(request)
def LoadService(self, serviceRoot): """Configures this client to use the service at *serviceRoot* *serviceRoot* is a string or :py:class:`pyslet.rfc2396.URI` instance.""" if isinstance(serviceRoot, uri.URI): self.serviceRoot = serviceRoot else: self.serviceRoot = uri.URIFactory.URI(serviceRoot) request = http.HTTPRequest(str(self.serviceRoot)) request.SetHeader('Accept', 'application/atomsvc+xml') self.ProcessRequest(request) if request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=self.serviceRoot) doc.Read(request.resBody) if isinstance(doc.root, app.Service): self.service = doc.root self.serviceRoot = uri.URIFactory.URI(doc.root.ResolveBase()) self.feeds = {} self.model = None for w in self.service.Workspace: for f in w.Collection: url = f.GetFeedURL() if f.Title: self.feeds[f.Title.GetValue()] = url else: raise InvalidServiceDocument(str(serviceRoot)) self.pathPrefix = self.serviceRoot.absPath if self.pathPrefix[-1] == u"/": self.pathPrefix = self.pathPrefix[:-1] metadata = uri.URIFactory.Resolve(serviceRoot, '$metadata') doc = edmx.Document(baseURI=metadata, reqManager=self) defaultContainer = None try: doc.Read() if isinstance(doc.root, edmx.Edmx): self.model = doc.root for s in self.model.DataServices.Schema: for container in s.EntityContainer: if container.IsDefaultEntityContainer(): prefix = "" defaultContainer = container else: prefix = container.name + "." for es in container.EntitySet: fTitle = prefix + es.name if fTitle in self.feeds: if self.feeds[fTitle] == es.GetLocation(): self.feeds[fTitle] = es else: raise DataFormatError(str(metadata)) except xml.XMLError as e: # Failed to read the metadata document, there may not be one of # course raise DataFormatError(str(e)) # Missing feeds are pruned from the list, perhaps the service advertises them # but if we don't have a model of them we can't use of them for f in self.feeds.keys(): if isinstance(self.feeds[f], uri.URI): logging.info( "Can't find metadata definition of feed: %s", str( self.feeds[f])) del self.feeds[f] else: # Bind our EntityCollection class entity_set = self.feeds[f] entity_set.Bind(EntityCollection, client=self) for np in entity_set.entityType.NavigationProperty: entity_set.BindNavigation( np.name, NavigationCollection, client=self) logging.debug( "Registering feed: %s", str(self.feeds[f].GetLocation()))
def LoadService(self, serviceRoot, metadata=None): """Configures this client to use the service at *serviceRoot* serviceRoot A string or :py:class:`pyslet.rfc2396.URI` instance. The URI may now point to a local file though this must have an xml:base attribute to point to the true location of the service as calls to the feeds themselves require the use of http(s). metadata (None) A :py:class:`pyslet.rfc2396.URI` instance pointing to the metadata file. This is usually derived automatically by adding $metadata to the service root but some services have inconsistent metadata models. You can download a copy, modify the model and use a local copy this way instead, e.g., by passing something like:: URI.from_path('metadata.xml') If you use a local copy you must add an xml:base attribute to the root element indicating the true location of the $metadata file as the client uses this information to match feeds with the metadata model.""" if isinstance(serviceRoot, uri.URI): self.serviceRoot = serviceRoot else: self.serviceRoot = uri.URI.from_octets(serviceRoot) doc = core.Document(baseURI=self.serviceRoot) if isinstance(self.serviceRoot, uri.FileURL): # load the service root from a file instead doc.Read() else: request = http.ClientRequest(str(self.serviceRoot)) request.set_header('Accept', 'application/atomsvc+xml') self.process_request(request) if request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc.Read(request.res_body) if isinstance(doc.root, app.Service): self.service = doc.root self.serviceRoot = uri.URI.from_octets(doc.root.ResolveBase()) self.feeds = {} self.model = None for w in self.service.Workspace: for f in w.Collection: url = f.GetFeedURL() if f.Title: self.feeds[f.Title.GetValue()] = url else: raise InvalidServiceDocument(str(serviceRoot)) self.pathPrefix = self.serviceRoot.abs_path if self.pathPrefix[-1] == u"/": self.pathPrefix = self.pathPrefix[:-1] if metadata is None: metadata = uri.URI.from_octets('$metadata').resolve( self.serviceRoot) doc = edmx.Document(baseURI=metadata, reqManager=self) defaultContainer = None try: doc.Read() if isinstance(doc.root, edmx.Edmx): self.model = doc.root for s in self.model.DataServices.Schema: for container in s.EntityContainer: if container.is_default_entity_container(): prefix = "" defaultContainer = container else: prefix = container.name + "." for es in container.EntitySet: fTitle = prefix + es.name if fTitle in self.feeds: if self.feeds[fTitle] == es.get_location(): self.feeds[fTitle] = es else: raise DataFormatError(str(metadata)) except xml.XMLError as e: # Failed to read the metadata document, there may not be one of # course raise DataFormatError(str(e)) # Missing feeds are pruned from the list, perhaps the service advertises them # but if we don't have a model of them we can't use of them for f in self.feeds.keys(): if isinstance(self.feeds[f], uri.URI): logging.info("Can't find metadata definition of feed: %s", str(self.feeds[f])) del self.feeds[f] else: # bind our EntityCollection class entity_set = self.feeds[f] entity_set.bind(EntityCollection, client=self) for np in entity_set.entityType.NavigationProperty: entity_set.BindNavigation(np.name, NavigationCollection, client=self) logging.debug("Registering feed: %s", str(self.feeds[f].get_location()))
def iterpage(self, set_next=False): feedURL = self.baseURI sysQueryOptions = {} if self.filter is not None: sysQueryOptions[core.SystemQueryOption.filter] = unicode( self.filter) if self.expand is not None: sysQueryOptions[core.SystemQueryOption.expand] = core.FormatExpand( self.expand) if self.select is not None: sysQueryOptions[core.SystemQueryOption.select] = core.FormatSelect( self.select) if self.orderby is not None: sysQueryOptions[core.SystemQueryOption. orderby] = core.CommonExpression.OrderByToString( self.orderby) if self.top is not None: sysQueryOptions[core.SystemQueryOption.top] = unicode(self.top) if self.skip is not None: sysQueryOptions[core.SystemQueryOption.skip] = unicode(self.skip) if self.skiptoken is not None: sysQueryOptions[core.SystemQueryOption.skiptoken] = self.skiptoken if sysQueryOptions: feedURL = uri.URI.from_octets( str(feedURL) + "?" + core.ODataURI.FormatSysQueryOptions(sysQueryOptions)) request = http.ClientRequest(str(feedURL)) request.set_header('Accept', 'application/atom+xml') self.client.process_request(request) if request.status != 200: raise UnexpectedHTTPResponse( "%i %s" % (request.status, request.response.reason)) doc = core.Document(baseURI=feedURL) doc.Read(request.res_body) if isinstance(doc.root, atom.Feed): if len(doc.root.Entry): for e in doc.root.Entry: entity = core.Entity(self.entity_set) entity.exists = True e.GetValue(entity) yield entity feedURL = self.nextSkiptoken = None for link in doc.root.Link: if link.rel == "next": feedURL = link.ResolveURI(link.href) break if feedURL is not None: # extract the skiptoken from this link feedURL = core.ODataURI(feedURL, self.client.pathPrefix) self.nextSkiptoken = feedURL.sysQueryOptions.get( core.SystemQueryOption.skiptoken, None) if set_next: if self.nextSkiptoken is not None: self.skiptoken = self.nextSkiptoken self.skip = None elif self.skip is not None: self.skip += len(doc.root.Entry) else: self.skip = len(doc.root.Entry) else: raise core.InvalidFeedDocument(str(feedURL))