Code Monkey home page Code Monkey logo

microsoftgraph-python's Introduction

microsoftgraph-python

Microsoft graph API wrapper for Microsoft Graph written in Python.

Before start

To use Microsoft Graph to read and write resources on behalf of a user, your app must get an access token from the Microsoft identity platform and attach the token to requests that it sends to Microsoft Graph. The exact authentication flow that you will use to get access tokens will depend on the kind of app you are developing and whether you want to use OpenID Connect to sign the user in to your app. One common flow used by native and mobile apps and also by some Web apps is the OAuth 2.0 authorization code grant flow.

See Get access on behalf of a user

Breaking changes if you're upgrading prior 1.0.0

  • Added structure to library to match API documentation for e.g. client.get_me() => client.users.get_me().
  • Renamed several methods to match API documentation for e.g. client.get_me_events() => client.calendar.list_events().
  • Result from calling a method is not longer a dictionary but a Response object. To access the dict response as before then call .data attribute for e.g r = client.users.get_me() then r.data.
  • Previous API calls made through beta endpoints are now pointing to v1.0 by default. This can be changed to beta if needed with the parameter api_version in the client instantiation.
  • Removed Office 365 endpoints as they were merged with the Microsoft Graph API. See Office 365 APIs.

New in 1.0.0

  • You can access to Requests library's Response Object for e.g. r = client.users.get_me() then r.original or the response handled by the library r.data.
  • New Response properties r.status_code and r.throttling.
  • You can pass Requests library's Event Hooks with the parameter requests_hooks in the client instantiation. If you are using Django and want to log in database every request made through this library, see django-requests-logger.
  • Library can auto paginate responses. Set paginate parameter in client initialization. Defaults to True.
  • Better method docstrings and type hinting.
  • Better library structure.

Installing

pip install microsoftgraph-python

Usage

Client instantiation

from microsoftgraph.client import Client
client = Client('CLIENT_ID', 'CLIENT_SECRET', account_type='common') # by default common, thus account_type is optional parameter.

OAuth 2.0

Get authorization url

url = client.authorization_url(redirect_uri, scope, state=None)

Exchange the code for an access token

response = client.exchange_code(redirect_uri, code)

Refresh token

response = client.refresh_token(redirect_uri, refresh_token)

Set token

client.set_token(token)

Users

Get me

response = client.users.get_me()

Mail

List messages

response = client.mail.list_messages()

Get message

response = client.mail.get_message(message_id)

Send mail

data = {
    subject="Meet for lunch?",
    content="The new cafeteria is open.",
    content_type="text",
    to_recipients=["[email protected]"],
    cc_recipients=None,
    save_to_sent_items=True,
}
response = client.mail.send_mail(**data)

List mail folders

response = client.mail.list_mail_folders()

Create mail folder

response = client.mail.create_mail_folder(display_name)

Notes

List notebooks

response = client.notes.list_notebooks()

Get notebook

response = client.notes.get_notebook(notebook_id)

Get notebook sections

response = client.notes.list_sections(notebook_id)

List pages

response = client.notes.list_pages()

Create page

response = client.notes.create_page(section_id, files)

Calendar

Get events

response = client.calendar.list_events(calendar_id)

Get event

response = client.calendar.get_event(event_id)

Create calendar event

from datetime import datetime, timedelta

start_datetime = datetime.now() + timedelta(days=1) # tomorrow
end_datetime = datetime.now() + timedelta(days=1, hours=1) # tomorrow + one hour
timezone = "America/Bogota"

data = {
    "calendar_id": "CALENDAR_ID",
    "subject": "Let's go for lunch",
    "content": "Does noon work for you?",
    "content_type": "text",
    "start_datetime": start_datetime,
    "start_timezone": timezone,
    "end_datetime": end_datetime,
    "end_timezone": timezone,
    "location": "Harry's Bar",
}
response = client.calendar.create_event(**data)

Get calendars

response = client.calendar.list_calendars()

Create calendar

response = client.calendar.create_calendar(name)

Contacts

Get a contact

response = client.contacts.get_contact(contact_id)

Get contacts

response = client.contacts.list_contacts()

Create contact

data = {
    "given_name": "Pavel",
    "surname": "Bansky",
    "email_addresses": [
        {
            "address": "[email protected]",
            "name": "Pavel Bansky"
        }
    ],
    "business_phones": [
        "+1 732 555 0102"
    ],
    "folder_id": None,
}
response = client.contacts.create_contact(**data)

Get contact folders

response = client.contacts.list_contact_folders()

Create contact folders

response = client.contacts.create_contact_folder()

Files

Get root items

response = client.files.drive_root_items()

Get root children items

response = client.files.drive_root_children_items()

Get specific folder items

response = client.files.drive_specific_folder(folder_id)

Get item

response = client.files.drive_get_item(item_id)

Download the contents of a specific item

response = client.files.drive_download_contents(item_id)

Upload new file

# This example uploads the image in path to a file in the signed-in user's drive under Pictures named upload.jpg.
response = client.files.drive_upload_new_file("/Pictures/upload.jpg", "/mnt/c/Users/i/Downloads/image1.jpg")

Update existing file

# This example uploads the image in path to update an existing item id.
response = client.files.drive_update_existing_file(item_id, "/mnt/c/Users/i/Downloads/image2.jpg")

Search for files

query = ".xlsx, .xlsm"
response = client.files.search_items(query)

Workbooks

Create session for specific item

response = client.workbooks.create_session(workbook_id)

Refresh session for specific item

response = client.workbooks.refresh_session(workbook_id)

Close session for specific item

response = client.workbooks.close_session(workbook_id)

Get worksheets

response = client.workbooks.list_worksheets(workbook_id)

Get specific worksheet

response = client.workbooks.get_worksheet(workbook_id, worksheet_id)

Add worksheets

response = client.workbooks.add_worksheet(workbook_id)

Update worksheet

response = client.workbooks.update_worksheet(workbook_id, worksheet_id)

Get charts

response = client.workbooks.list_charts(workbook_id, worksheet_id)

Add chart

response = client.workbooks.add_chart(workbook_id, worksheet_id)

Get tables

response = client.workbooks.list_tables(workbook_id)

Add table

response = client.workbooks.add_table(workbook_id)

Add column to table

response = client.workbooks.create_table_column(workbook_id, worksheet_id, table_id)

Add row to table

response = client.workbooks.create_table_row(workbook_id, worksheet_id, table_id)

Get table rows

response = client.workbooks.list_table_rows(workbook_id, table_id)

Get range

response = client.workbooks.get_range(workbook_id, worksheet_id)

Get used range

response = client.workbooks.get_used_range(workbook_id, worksheet_id)

Update range

response1 = client.workbooks.create_session(workbook_id)
workbook_session_id = response1.data["id"]

client.set_workbook_session_id(workbook_session_id)

range_address = "A1:D2"
data = {
        "values": [
            ["John", "Doe", "+1 305 1234567", "Miami, FL"],
            ["Bill", "Gates", "+1 305 1234567", "St. Redmond, WA"],
        ]
}
response2 = client.workbooks.update_range(workbook_id, worksheet_id, range_address, json=data)

response3 = client.worbooks.close_session(workbook_id)

Webhooks

Create subscription

response = client.webhooks.create_subscription(change_type, notification_url, resource, expiration_datetime, client_state=None)

Renew subscription

response = client.webhooks.renew_subscription(subscription_id, expiration_datetime)

Delete subscription

response = client.webhooks.delete_subscription(subscription_id)

Requirements

  • requests

Tests

test/test.py

microsoftgraph-python's People

Contributors

austin1howard avatar cassidylaidlaw avatar damanrique avatar dcastano-gearplug avatar disruption avatar ingmferrer avatar n3rio avatar strictline avatar yagss avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

microsoftgraph-python's Issues

Unclear `account_type` in usage

In the Usage section, both Client constructors pass a dummy account_type value of "by defect common". This is potentially confusing to people who don't realize you meant "default" instead of "defect", such as in #5.

Since "common" fits the vast majority of use cases and is already the default value, I think the account_type parameter should just be removed from the examples.

If you feel like it should remain, please add a note about what the valid parameter values are or at least correct the typo.

KeyError in Client

Hi,

I'm getting this error after updating to 1.1.1. Not sure if this is a local issue or a bug? Noted some changes where made in nearby code in this PR: #29

Error in Microsoft login hook error Traceback (most recent call last):
  File "/workspaces/.../.../integrations/azure/aad/auth_hook.py", line 43, in handle_login
    user = client.users.get_me()
  File "/usr/src/.venv/lib/python3.8/site-packages/microsoftgraph/decorators.py", line 11, in helper
    return func(*args, **kwargs)
  File "/usr/src/.venv/lib/python3.8/site-packages/microsoftgraph/users.py", line 32, in get_me
    return self._client._get(self._client.base_url + "me", params=params)
  File "/usr/src/.venv/lib/python3.8/site-packages/microsoftgraph/client.py", line 227, in _get
    return self._paginate_response(response)
  File "/usr/src/.venv/lib/python3.8/site-packages/microsoftgraph/client.py", line 215, in _paginate_response
    data = list(response.data["value"])
KeyError: 'value'

Bad Request on paginated response when specifying query parameters, like "$top"

If you run a request specifying, for example, the $top parameter, it will fail if it needs to fetch a second page, with the following error:

microsoftgraph.exceptions.BadRequest: {'error': {'code': 'BadRequest', 'message': "Query option '$top' was specified more than
once, but it must be specified at most once.", 'innerError': {'date': '2022-02-18T23:21:56', 'request-id': 'ae804f84-62aa-4bb4-a521-8c5ad8be2ed7', 'client-request-id': 'ae804f84-62aa-4bb4-a521-8c5ad8be2ed7'}}}

The reason for this error is that, when we want to fetch the next page, we use "@odata.nextLink" as request url which already contains all query params from the original request but we still pass **kwargs to the requests.request constructor, so the values in the "params" dictionary get added again to the request, causing all request query params to be duplicated.

Flow for signing in user and getting "code"

Hey there, with this flow, what step do you recommend to pass "code" to exchange code for access token. I understand the user would login in the browser. Sorry if the question is low level, still learning more about Graph API

KeyError on 'content-type'

Some responses (like delete calendar event) don't include a Content-Type header.

Will send pull-request

Include update_event and delete_event into calendar.py.

Here's the simplistic code for a head start that works for me. Of course you can edit parts to make it conventional, but adding this in is a huge value add and makes it easier for people to pick up the API.

@token_required
    def update_event(
        self,
        start_datetime: datetime,
        start_timezone: str,
        end_datetime: datetime,
        end_timezone: str,
        id: str,
        **kwargs,
    ) -> Response:
        """Update an event in the user's default calendar or specified calendar.

        https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http

        Additional time zones: https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0

        Args:
            subject (str): The text of the event's subject line.
            content (str): The body of the message associated with the event.
            start_datetime (datetime): A single point of time in a combined date and time representation ({date}T{time};
            start_timezone (str): Represents a time zone, for example, "Pacific Standard Time".
            end_datetime (datetime): A single point of time in a combined date and time representation ({date}T{time}; for
            end_timezone (str): Represents a time zone, for example, "Pacific Standard Time".
            location (str): The location of the event.
            calendar_id (str, optional): Calendar ID. Defaults to None.
            content_type (str, optional): It can be in HTML or text format. Defaults to HTML.

        Returns:
            Response: Microsoft Graph Response.
        """
        if isinstance(start_datetime, datetime):
            start_datetime = format_time(start_datetime)
        if isinstance(end_datetime, datetime):
            end_datetime = format_time(end_datetime)

        body = {
            "start": {
                "dateTime": start_datetime,
                "timeZone": start_timezone,
            },
            "end": {
                "dateTime": end_datetime,
                "timeZone": end_timezone,
            },
        }
        body.update(kwargs)
        url = "me/events/{}".format(id)
        return self._client._patch(self._client.base_url + url, json=body)

    @token_required
    def delete_event(
        self,
        id: str
    ) -> Response:
        """Delete an event in the user's default calendar or specified calendar.

        https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-1.0&tabs=http

        Additional time zones: https://docs.microsoft.com/en-us/graph/api/resources/datetimetimezone?view=graph-rest-1.0

        Args:
            id (str): Event ID.

        Returns:
            Response: Microsoft Graph Response.
        """
        url = "me/events/{}".format(id)
        return self._client._delete(self._client.base_url + url)

Adding more attributes to webhooks call

Need to add attributes for the subscription webhook attributes like
lifecycleNotificationUrl(important to receive notification whenever user changes password of account subscription would be cancelled)
includeResourceData(Really important to avoid multiple api calls to retrieve resource)

Incorrect AUTHORITY_URL

For some reason AUTHORITY_URL and OFFICE365_AUTHORITY_URL are switched, so that the behavior of the office365 parameter to Client's constructor is the opposite of the expected behavior.

Release 1.1.1

Just wondering, since there were some fixes done, is 1.1.1 going to be released soon?

We could copy the library to our local project but I prefer to actually pull it from pypy and avoid having local changes to a library, but rather PR the fixes here as I did

'Response' object is not subscriptable and similar

I got

python3.9/site-packages/microsoftgraph/client.py", line 250, in _request
_headers["Authorization"] = "Bearer " + self.token["access_token"]
TypeError: 'Response' object is not subscriptable

when I call client.files.drive_root_items() after the authentication phase.

I saw that self.token is an object with attributes data, original, status_code, throttling and data contains the key access_token

So I solved by changing line 250 in client.py to _headers["Authentication"] = "Bearer " + self.token.data["access_token"]

But now in line 215 of client.py value is not a key of data

BG

Asynchronous Version

Would be nice if there was a version that utilised aiohttp instead of requests :) Might make a PR for it but I'd like to hear your thoughts

pip install results in FileNotFoundError

$ pip install microsoftgraph-python
Collecting microsoftgraph-python
  Using cached microsoftgraph-python-0.1.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-0iq72_j4/microsoftgraph-python/setup.py", line 12, in <module>
        long_description=read('README.md'),
      File "/tmp/pip-build-0iq72_j4/microsoftgraph-python/setup.py", line 6, in read
        return open(os.path.join(os.path.dirname(__file__), fname)).read()
    FileNotFoundError: [Errno 2] No such file or directory: '/tmp/pip-build-0iq72_j4/microsoftgraph-python/README.md'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-0iq72_j4/microsoftgraph-python/

No refresh_token in exchange_code response.

I've used exchange_token_result = client.exchange_code(REDIRECT_URL, code) to successfully exchange a code for a token, but there is no refresh_token in the response, which I anticipated from https://docs.microsoft.com/en-us/graph/auth-v2-user?context=graph%2Fapi%2Fbeta&view=graph-rest-beta#token-response

In the docs of this repo it is possible to call token = client.refresh_token(redirect_uri, refresh_token), but I don't know how I would obtain the refresh token to begin with. I tried using the access_token returned from exchange_code but that returned an Invalid Request exception.

Stack overflow in _paginate_response method

The _paginate_response method in the client performs pagination via recursion:

  • You call and endpoint, which calls _get, which calls _paginate_response, which on a while loop calls _get, which calls _paginate_response, etc

There are a few problems with this approach:

  • If the collection being requested is very big, and the page size is the default (10 items) this can lead to hundreds of stacked calls, and ultimately an exception will be raised
  • If we hit a faulty response (i.e. one where response.data is not a dict instance, which is one of the conditions for just returning response, that return will come inside the while loop, and we will do response.data["value"] += data, but if we reach through that path, response.data is not a dict, so response.data["value"] might fail if data is not a subscriptable object.

This could be solved by calling self._request directly on the while loop inside the _paginate_response method, rather than _get, which eliminates recursion. The check for response.data being a dict instance should be performed there as well to avoid the potential exception.

'list_messages()' and 'get_message()' functions only return response status code

Calling 'client.mail.list_messages()' and 'client.mail.get_message({message_id})' end up returning only the response status code and not what they're intended to return; i.e., a list of messages and the contents of a message.

Screenshot from 2022-10-23 16-36-40

Also tried fetching the json of the response object 'msgs', but it resulted in an attribute error which stated that the response object has no attribute 'json'.

Please provide a "general" get function

Please add a public "get" method in case I need some API access point that's not provided by the client class. All this function needs to do is paste the argument to base_url and send the request.

So you would use it like this:
item = client.get("/me/drive/root:/path/to/file", params=None)

And same for post and delete. Thanks for this module!

Working example of send_mail with multiple attachements

Can you include a working example of sending mail with multiple attachments?

Initially I thought that the library doesn't include mail functions as they were not documented in the readme. Just figured out which I was browsing the source code.

Thanks in advance!

microsoftgraph.exceptions.BadRequest

Currently getting the above exception with the attached message: 'message': '/me request is only valid with delegated authentication flow.'

Any suggestions on how to resolve?

token_required decorator doesn't check office365_token

When using the client with office365=True, the decorator says you require a token even if it is set.

this solved it for me:

def token_required(func):
    @wraps(func)
    def helper(*args, **kwargs):
        client = args[0]
        if not (client.token or client.office365_token):
                raise TokenRequired('You must set the Token.')
        return func(*args, **kwargs)

    return helper

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.