diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 00000000..14a4ad03 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,19 @@ +# Hacking + +This document provides guides on how to develop and maintain the pyodata Python +module. + +## Tips & tricks + +If you want to avoid creating pull requests that fail on lint errors but you +always forgot to run `make check`, create the pre-commit file in the director +.git/hooks with the following content: + +```bash +#!/bin/sh + +make check +``` + +Do not forget to run `chmod +x .git/hooks/pre-commit` to make the hook script +executable. diff --git a/LICENSE b/LICENSE index 185d6423..8b893a31 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2018 SAPĀ® + Copyright 2019 SAPĀ® Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..2c848d0b --- /dev/null +++ b/NOTICE @@ -0,0 +1 @@ +Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. diff --git a/README.md b/README.md index b96f2fd9..95b9eeb2 100644 --- a/README.md +++ b/README.md @@ -1,212 +1,58 @@ -# python-pyodata +# Python OData Client - pyodata -Enterprise-ready Python OData client +Python OData client which provides comfortable Python agnostic +way for communication with OData services. -## Requirements - -- Python 3.6 - -## Usage - -### Get the service - -```python -import requests -import pyodata +The goal of this Python module is to hide all OData protocol implementation +details. -SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/' - -# Create instance of OData client -client = pyodata.Client(SERVICE_URL, requests.Session()) -``` - -### Get one entity identified by its key value - -```python -# Get employee identified by 1 and print employee first name -employee1 = client.entity_sets.Employees.get_entity(1).execute() -print employee1.FirstName -``` +## Requirements -### Get one entity identified by its key value which is not scalar +- [Python >= 3.6](https://www.python.org/downloads/release/python-368/) -```python -# Get number of orderd units in the order identified by ProductID 42 and OrderID 10248. -order = client.entity_sets.Order_Details.get_entity(OrderID=10248, ProductID=42).execute() -print(order.Quantity) -``` +## Download and Installation -### Get all entities of an entity set - -```python +You can make use of [pip](https://packaging.python.org/tutorials/installing-packages/#installing-from-vcs) +to install the pyodata module automatically: -# Print unique identification (Id) and last name of all cemployees -employees = client.entity_sets.Employees.get_entities().select('EmployeeID,LasttName').execute() -for employee in employees: - print(employee.EmployeeID, employee.LastName) +```bash +pip install -e git+https://github.com/SAP/python-pyodata.git ``` -### Get entities matching a filter +## Configuration -```python -# Print unique identification (Id) of all employees with name John Smith -smith_employees_request = client.entity_sets.Employees.get_entities() -smith_employees_request = smith_employees_request.filter("FirstName eq 'John' and LastName eq 'Smith'") -for smith in smith_employees.execute(): - print(smith.EmployeeID) -``` +You can start building your OData projects straight away after installing the +Python module without any additional configuration steps needed. -### Get entities matching a filter in more Pythonic way +## Limitations -```python -from pyodata.v2.service import GetEntitySetFilter +There have been no limitations discovered yet. -# Print unique identification (Id) of all employees with name John Smith -smith_employees_request = client.entity_sets.Employees.get_entities() -smith_employees_request = smith_employees_request.filter(GetEntitySetFilter.and_( - smith_employees_request.FirstName == 'Jonh', - smith_employees_request.LastName == 'Smith')) -for smith in smith_employees_request.execute(): - print(smith.EmployeeID) -``` +## Known Issues -### Creating entity +There are no known issues at this time. -You need to use the method set which accepts key value parameters: +## How to obtain support -```python -employee_data = { - 'FirstName': 'Mark', - 'LastName': 'Goody', - 'Address': { - 'HouseNumber': 42, - 'Street': 'Paradise', - 'City': 'Heaven' - } -} +We accept bug reports, feature requests, questions and comments via [GitHub issues](https://github.com/SAP/python-pyodata/issues) -create_request = client.entity_sets.Employees.create_entity() -create_request.set(**employee_data) - -new_employee_entity = create_request.execute() -``` +## Usage -or you can do it explicitly: +The only thing you need to do is to import the _pyodata_ Python module. ```python -create_request = client.entity_sets.Employees.create_entity() -create_request.set( - FirstName='Mark', - LastName='Goody', - Address={ - 'HouseNumber': 42, - 'Street': 'Paradise', - 'City': 'Heaven' - } -) - -new_employee_entity = request.execute() -``` - - -### Batch requests - -Example of batch request that contains 3 simple entity queries -``` -client = pyodata.Client(SERVICE_URL, requests.Session()) - -batch = client.create_batch() - -batch.add_request(client.entity_sets.Employees.get_entity(108)) -batch.add_request(client.entity_sets.Employees.get_entity(234)) -batch.add_request(client.entity_sets.Employees.get_entity(23)) - -response = batch.execute() +import requests +import pyodata -print(response[0].EmployeeID, response[0].LastName) -print(response[1].EmployeeID, response[1].LastName) -print(response[1].EmployeeID, response[1].LastName) -``` +SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/' -Example of batch request that contains simple entity query as well -as changest consisting of two requests for entity modification -``` +# Create instance of OData client client = pyodata.Client(SERVICE_URL, requests.Session()) - -batch = client.create_batch() - -batch.add_request(client.entity_sets.Employees.get_entity(108)) - -changeset = client.create_changeset() - -changeset.add_request(client.entity_sets.Employees.update_entity(45).set(LastName='Douglas')) - -batch.add_request(changeset) - -response = batch.execute() - -print(response[0].EmployeeID, response[0].LastName) ``` -### Calling a function import +Find more sophisticated examples in the [USAGE](USAGE.md) section. -```python -products = client.functions.GetProductsByRating.parameter('rating', 16).execute() -for prod in products: - print(prod) -``` - -## Error handling - -PyOData returns HttpError when the response code does not match the expected -code. - -In the case you know the implementation of back-end part and you want to show -accurate errors reported by your service, you can replace HttpError by your -sub-class HttpError by assigning your type into the class member VendorType of -the class HttpError. - -```python -from pyodata.exceptions import HttpError - - -class MyHttpError(HttpError): - - def __init__(self, message, response): - super(MyHttpError, self).__init__('Better message', response) - - -HttpError.VendorType = MyHttpError -``` - -The class ```pyodata.vendor.SAP.BusinessGatewayError``` is an example of such -an HTTP error handling. - -## Metadata tests - -By default, the client makes sure that references to properties, entities and -entity sets are pointing to existing elements. - -The most often problem that we had to deal with was an invalid ValueList annotion -pointing to a non-existing property. - - -### Property has this label - -```python - -assert client.schema.entity_type('Customer').proprty('CustomerID').label == 'Identifier' -``` - -### Property has a value helper - -```python - -assert client.schema.entity_type('Customer').proprty('City').value_helper is not None -``` - - -# Contributing +## Contributing Before contributing, please, make yourself familiar with git. You can [try git online](https://try.github.io/). Things would be easier for all of us if you do @@ -222,7 +68,7 @@ negative decisions - i.e. why you decided to not do particular things. Please bare in mind that other developers might not understand what the original problem was. -## Full example +### Full example Here's an example workflow for a project `PyOData` hosted on Github Your username is `yourname` and you're submitting a basic bugfix or feature. @@ -243,17 +89,8 @@ Your username is `yourname` and you're submitting a basic bugfix or feature. * Hit 'submit'! And please be patient - the maintainers will get to you when they can. -## Tips & tricks - -If you want to avoid creating pull requests that fail on lint errors but you -always forgot to run `make check`, create the pre-commit file in the director -.git/hooks with the following content: - -```bash -#!/bin/sh - -make check -``` +## License -Do not forget to run `chmod +x .git/hooks/pre-commit` to make the hook script -executable. +Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. +This file is licensed under the Apache Software License, v. 2 except as noted +otherwise in [the LICENSE file](LICENSE) diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 00000000..fec4dbaa --- /dev/null +++ b/USAGE.md @@ -0,0 +1,199 @@ +# Usage examples + +### Get the service + +```python +import requests +import pyodata + +SERVICE_URL = 'http://services.odata.org/V2/Northwind/Northwind.svc/' + +# Create instance of OData client +client = pyodata.Client(SERVICE_URL, requests.Session()) +``` + +### Get one entity identified by its key value + +```python +# Get employee identified by 1 and print employee first name +employee1 = client.entity_sets.Employees.get_entity(1).execute() +print employee1.FirstName +``` + +### Get one entity identified by its key value which is not scalar + +```python +# Get number of orderd units in the order identified by ProductID 42 and OrderID 10248. +order = client.entity_sets.Order_Details.get_entity(OrderID=10248, ProductID=42).execute() +print(order.Quantity) +``` + +### Get all entities of an entity set + +```python + +# Print unique identification (Id) and last name of all cemployees +employees = client.entity_sets.Employees.get_entities().select('EmployeeID,LasttName').execute() +for employee in employees: + print(employee.EmployeeID, employee.LastName) +``` + +### Get entities matching a filter + +```python +# Print unique identification (Id) of all employees with name John Smith +smith_employees_request = client.entity_sets.Employees.get_entities() +smith_employees_request = smith_employees_request.filter("FirstName eq 'John' and LastName eq 'Smith'") +for smith in smith_employees.execute(): + print(smith.EmployeeID) +``` + +### Get entities matching a filter in more Pythonic way + +```python +from pyodata.v2.service import GetEntitySetFilter + +# Print unique identification (Id) of all employees with name John Smith +smith_employees_request = client.entity_sets.Employees.get_entities() +smith_employees_request = smith_employees_request.filter(GetEntitySetFilter.and_( + smith_employees_request.FirstName == 'Jonh', + smith_employees_request.LastName == 'Smith')) +for smith in smith_employees_request.execute(): + print(smith.EmployeeID) +``` + +### Creating entity + +You need to use the method set which accepts key value parameters: + +```python +employee_data = { + 'FirstName': 'Mark', + 'LastName': 'Goody', + 'Address': { + 'HouseNumber': 42, + 'Street': 'Paradise', + 'City': 'Heaven' + } +} + +create_request = client.entity_sets.Employees.create_entity() +create_request.set(**employee_data) + +new_employee_entity = create_request.execute() +``` + +or you can do it explicitly: + +```python +create_request = client.entity_sets.Employees.create_entity() +create_request.set( + FirstName='Mark', + LastName='Goody', + Address={ + 'HouseNumber': 42, + 'Street': 'Paradise', + 'City': 'Heaven' + } +) + +new_employee_entity = request.execute() +``` + + +### Batch requests + +Example of batch request that contains 3 simple entity queries +``` +client = pyodata.Client(SERVICE_URL, requests.Session()) + +batch = client.create_batch() + +batch.add_request(client.entity_sets.Employees.get_entity(108)) +batch.add_request(client.entity_sets.Employees.get_entity(234)) +batch.add_request(client.entity_sets.Employees.get_entity(23)) + +response = batch.execute() + +print(response[0].EmployeeID, response[0].LastName) +print(response[1].EmployeeID, response[1].LastName) +print(response[1].EmployeeID, response[1].LastName) +``` + +Example of batch request that contains simple entity query as well +as changest consisting of two requests for entity modification +``` +client = pyodata.Client(SERVICE_URL, requests.Session()) + +batch = client.create_batch() + +batch.add_request(client.entity_sets.Employees.get_entity(108)) + +changeset = client.create_changeset() + +changeset.add_request(client.entity_sets.Employees.update_entity(45).set(LastName='Douglas')) + +batch.add_request(changeset) + +response = batch.execute() + +print(response[0].EmployeeID, response[0].LastName) +``` + +### Calling a function import + +```python +products = client.functions.GetProductsByRating.parameter('rating', 16).execute() +for prod in products: + print(prod) +``` + +## Error handling + +PyOData returns HttpError when the response code does not match the expected +code. + +In the case you know the implementation of back-end part and you want to show +accurate errors reported by your service, you can replace HttpError by your +sub-class HttpError by assigning your type into the class member VendorType of +the class HttpError. + +```python +from pyodata.exceptions import HttpError + + +class MyHttpError(HttpError): + + def __init__(self, message, response): + super(MyHttpError, self).__init__('Better message', response) + + +HttpError.VendorType = MyHttpError +``` + +The class ```pyodata.vendor.SAP.BusinessGatewayError``` is an example of such +an HTTP error handling. + +## Metadata tests + +By default, the client makes sure that references to properties, entities and +entity sets are pointing to existing elements. + +The most often problem that we had to deal with was an invalid ValueList annotion +pointing to a non-existing property. + + +### Property has this label + +```python + +assert client.schema.entity_type('Customer').proprty('CustomerID').label == 'Identifier' +``` + +### Property has a value helper + +```python + +assert client.schema.entity_type('Customer').proprty('City').value_helper is not None +``` +