├── .github └── workflows │ └── build_pages.yml ├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── O365 ├── __init__.py ├── __version__.py ├── account.py ├── address_book.py ├── calendar.py ├── category.py ├── connection.py ├── directory.py ├── drive.py ├── excel.py ├── groups.py ├── mailbox.py ├── message.py ├── planner.py ├── sharepoint.py ├── tasks.py ├── teams.py └── utils │ ├── __init__.py │ ├── attachment.py │ ├── casing.py │ ├── consent.py │ ├── decorators.py │ ├── query.py │ ├── token.py │ ├── utils.py │ └── windows_tz.py ├── README.md ├── build_docs.sh ├── docs ├── .nojekyll ├── Makefile ├── index.html ├── latest │ ├── .buildinfo │ ├── .buildinfo.bak │ ├── .nojekyll │ ├── _modules │ │ ├── O365 │ │ │ ├── account.html │ │ │ ├── address_book.html │ │ │ ├── calendar.html │ │ │ ├── category.html │ │ │ ├── connection.html │ │ │ ├── directory.html │ │ │ ├── drive.html │ │ │ ├── excel.html │ │ │ ├── groups.html │ │ │ ├── mailbox.html │ │ │ ├── message.html │ │ │ ├── planner.html │ │ │ ├── sharepoint.html │ │ │ ├── tasks.html │ │ │ ├── tasks_graph.html │ │ │ ├── teams.html │ │ │ └── utils │ │ │ │ ├── attachment.html │ │ │ │ ├── query.html │ │ │ │ ├── token.html │ │ │ │ └── utils.html │ │ └── index.html │ ├── _sources │ │ ├── api.rst.txt │ │ ├── api │ │ │ ├── account.rst.txt │ │ │ ├── address_book.rst.txt │ │ │ ├── attachment.rst.txt │ │ │ ├── calendar.rst.txt │ │ │ ├── category.rst.txt │ │ │ ├── connection.rst.txt │ │ │ ├── directory.rst.txt │ │ │ ├── drive.rst.txt │ │ │ ├── excel.rst.txt │ │ │ ├── global.rst.txt │ │ │ ├── group.rst.txt │ │ │ ├── mailbox.rst.txt │ │ │ ├── message.rst.txt │ │ │ ├── onedrive.rst.txt │ │ │ ├── planner.rst.txt │ │ │ ├── sharepoint.rst.txt │ │ │ ├── tasks.rst.txt │ │ │ ├── teams.rst.txt │ │ │ ├── utils.rst.txt │ │ │ └── utils │ │ │ │ ├── attachment.rst.txt │ │ │ │ ├── query.rst.txt │ │ │ │ ├── token.rst.txt │ │ │ │ └── utils.rst.txt │ │ ├── getting_started.rst.txt │ │ ├── index.rst.txt │ │ ├── overview.rst.txt │ │ ├── usage.rst.txt │ │ └── usage │ │ │ ├── account.rst.txt │ │ │ ├── addressbook.rst.txt │ │ │ ├── calendar.rst.txt │ │ │ ├── connection.rst.txt │ │ │ ├── directory.rst.txt │ │ │ ├── excel.rst.txt │ │ │ ├── group.rst.txt │ │ │ ├── mailbox.rst.txt │ │ │ ├── onedrive.rst.txt │ │ │ ├── planner.rst.txt │ │ │ ├── query.rst.txt │ │ │ ├── sharepoint.rst.txt │ │ │ ├── tasks.rst.txt │ │ │ ├── teams.rst.txt │ │ │ ├── utils.rst.txt │ │ │ └── utils │ │ │ ├── query.rst.txt │ │ │ ├── token.rst.txt │ │ │ └── utils.rst.txt │ ├── _static │ │ ├── _sphinx_javascript_frameworks_compat.js │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ ├── style.css │ │ │ └── theme.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ ├── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── jquery-3.4.1.js │ │ ├── jquery-3.5.1.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── modernizr.min.js │ │ │ ├── theme.js │ │ │ └── versions.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sphinx_highlight.js │ │ ├── underscore-1.3.1.js │ │ └── underscore.js │ ├── api.html │ ├── api │ │ ├── account.html │ │ ├── address_book.html │ │ ├── attachment.html │ │ ├── calendar.html │ │ ├── category.html │ │ ├── connection.html │ │ ├── directory.html │ │ ├── drive.html │ │ ├── excel.html │ │ ├── global.html │ │ ├── group.html │ │ ├── mailbox.html │ │ ├── message.html │ │ ├── onedrive.html │ │ ├── planner.html │ │ ├── sharepoint.html │ │ ├── tasks.html │ │ ├── teams.html │ │ ├── utils.html │ │ └── utils │ │ │ ├── attachment.html │ │ │ ├── query.html │ │ │ ├── token.html │ │ │ └── utils.html │ ├── genindex.html │ ├── getting_started.html │ ├── index.html │ ├── objects.inv │ ├── overview.html │ ├── py-modindex.html │ ├── search.html │ ├── searchindex.js │ ├── usage.html │ └── usage │ │ ├── account.html │ │ ├── addressbook.html │ │ ├── calendar.html │ │ ├── connection.html │ │ ├── directory.html │ │ ├── excel.html │ │ ├── group.html │ │ ├── mailbox.html │ │ ├── onedrive.html │ │ ├── planner.html │ │ ├── query.html │ │ ├── sharepoint.html │ │ ├── tasks.html │ │ ├── teams.html │ │ ├── utils.html │ │ └── utils │ │ ├── query.html │ │ ├── token.html │ │ └── utils.html ├── make.bat └── source │ ├── _static │ └── css │ │ └── style.css │ ├── _templates │ └── layout.html │ ├── api.rst │ ├── api │ ├── account.rst │ ├── address_book.rst │ ├── calendar.rst │ ├── category.rst │ ├── connection.rst │ ├── directory.rst │ ├── excel.rst │ ├── global.rst │ ├── group.rst │ ├── mailbox.rst │ ├── message.rst │ ├── onedrive.rst │ ├── planner.rst │ ├── sharepoint.rst │ ├── tasks.rst │ ├── teams.rst │ ├── utils.rst │ └── utils │ │ ├── attachment.rst │ │ ├── query.rst │ │ ├── token.rst │ │ └── utils.rst │ ├── conf.py │ ├── getting_started.rst │ ├── index.rst │ ├── overview.rst │ ├── usage.rst │ └── usage │ ├── account.rst │ ├── addressbook.rst │ ├── calendar.rst │ ├── connection.rst │ ├── directory.rst │ ├── excel.rst │ ├── group.rst │ ├── mailbox.rst │ ├── onedrive.rst │ ├── planner.rst │ ├── sharepoint.rst │ ├── tasks.rst │ ├── teams.rst │ ├── utils.rst │ └── utils │ ├── query.rst │ ├── token.rst │ └── utils.rst ├── examples ├── automatic_response_example.py ├── before version 1 │ ├── calendarCookbook.py │ └── search_subfolders.py ├── jwt_assertion.py ├── onedrive_example.py ├── storageDownloadFile └── token_backends.py ├── pyproject.toml ├── release.py ├── requirements-dev.txt ├── requirements-pages.txt ├── requirements.txt ├── setup.py └── tests ├── run_tests_notes.txt ├── test_account.py ├── test_connection.py ├── test_mailbox.py ├── test_message.py ├── test_planner.py ├── test_protocol.py ├── test_recipient.py └── test_teams.py /.github/workflows/build_pages.yml: -------------------------------------------------------------------------------- 1 | name: Pages Build 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | jobs: 7 | pages_build: 8 | name: Build Pages 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - name: "Checkout the repository" 12 | uses: actions/checkout@v4 13 | 14 | - name: "Set up Python" 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: "3.12" 18 | cache: "pip" 19 | 20 | - name: "Install requirements" 21 | run: python3 -m pip install -r requirements-pages.txt 22 | 23 | - name: "Build pages" 24 | run: sphinx-build -b html -c ./docs/source/ ./docs/source/ ./docs/latest/ 25 | 26 | - name: "Pull any updates" 27 | shell: bash 28 | run: git pull 29 | 30 | - name: "Check for changes" 31 | shell: bash 32 | run: git status 33 | 34 | - name: "Stage changed files" 35 | shell: bash 36 | run: git add ./docs/latest 37 | 38 | - name: "Commit changed files" 39 | shell: bash 40 | run: | 41 | git config --local user.email "action@github.com" 42 | git config --local user.name "GitHub Action" 43 | git commit -m "Update the docs" || true 44 | 45 | - name: Push changes 46 | uses: ad-m/github-push-action@master 47 | with: 48 | github_token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | .doctrees/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # Passwords and other things I care to publish 58 | *.pw 59 | *bookings.json 60 | */pid 61 | 62 | .idea/ 63 | 64 | trail.py 65 | sample_run.py 66 | 67 | # Dev tooling 68 | .python-version 69 | Pipfile 70 | Pipfile.lock 71 | .vscode/* 72 | 73 | # config.py file that contains secrets 74 | config.py 75 | 76 | # virtualenvironments 77 | venv/ 78 | 79 | # O365 specific 80 | o365_token\.txt 81 | local_tests/ 82 | 83 | # Mac Specifoc 84 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '3.5' 4 | - '3.6' 5 | install: 6 | - pip install -r requirements-dev.txt 7 | - python setup.py install 8 | script: pytest 9 | -------------------------------------------------------------------------------- /O365/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | A simple python library to interact with Microsoft Graph and other MS api 3 | """ 4 | 5 | import warnings 6 | import sys 7 | 8 | from .__version__ import __version__ 9 | 10 | from .account import Account 11 | from .connection import Connection, Protocol, MSGraphProtocol 12 | from .utils import FileSystemTokenBackend, EnvTokenBackend 13 | from .message import Message 14 | 15 | 16 | if sys.warnoptions: 17 | # allow Deprecation warnings to appear 18 | warnings.simplefilter("always", DeprecationWarning) 19 | -------------------------------------------------------------------------------- /O365/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.1.4' 2 | -------------------------------------------------------------------------------- /O365/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .attachment import BaseAttachments, BaseAttachment, AttachableMixin 2 | from .utils import ApiComponent, OutlookWellKnowFolderNames 3 | from .utils import CaseEnum, ImportanceLevel, TrackerSet 4 | from .utils import Recipient, Recipients, HandleRecipientsMixin 5 | from .utils import NEXT_LINK_KEYWORD, ME_RESOURCE, USERS_RESOURCE 6 | from .utils import OneDriveWellKnowFolderNames, Pagination, Query 7 | from .token import BaseTokenBackend, FileSystemTokenBackend, FirestoreBackend, AWSS3Backend, AWSSecretsBackend, EnvTokenBackend, BitwardenSecretsManagerBackend, DjangoTokenBackend 8 | from .windows_tz import get_iana_tz, get_windows_tz 9 | from .consent import consent_input_token 10 | from .casing import to_snake_case, to_pascal_case, to_camel_case 11 | 12 | from .query import QueryBuilder as ExperimentalQuery, CompositeFilter 13 | -------------------------------------------------------------------------------- /O365/utils/casing.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def to_snake_case(value: str) -> str: 5 | """Convert string into snake case""" 6 | pass 7 | value = re.sub(r"[\-.\s]", '_', str(value)) 8 | if not value: 9 | return value 10 | return str(value[0]).lower() + re.sub( 11 | r"[A-Z]", 12 | lambda matched: '_' + str(matched.group(0)).lower(), 13 | value[1:] 14 | ) 15 | 16 | 17 | def to_upper_lower_case(value: str, upper: bool = True) -> str: 18 | """Convert string into upper or lower case""" 19 | 20 | value = re.sub(r"\w[\s\W]+\w", '', str(value)) 21 | if not value: 22 | return value 23 | 24 | first_letter = str(value[0]) 25 | if upper: 26 | first_letter = first_letter.upper() 27 | else: 28 | first_letter = first_letter.lower() 29 | 30 | return first_letter + re.sub( 31 | r"[\-_.\s]([a-z])", 32 | lambda matched: str(matched.group(1)).upper(), 33 | value[1:] 34 | ) 35 | 36 | 37 | def to_camel_case(value: str) -> str: 38 | """Convert string into camel case""" 39 | 40 | return to_upper_lower_case(value, upper=False) 41 | 42 | 43 | def to_pascal_case(value: str) -> str: 44 | """Convert string into pascal case""" 45 | 46 | return to_upper_lower_case(value, upper=True) 47 | -------------------------------------------------------------------------------- /O365/utils/consent.py: -------------------------------------------------------------------------------- 1 | def consent_input_token(consent_url): 2 | print('Visit the following url to give consent:') 3 | print(consent_url) 4 | 5 | return input('Paste the authenticated url here:\n') 6 | -------------------------------------------------------------------------------- /O365/utils/decorators.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from functools import wraps 3 | 4 | log = logging.getLogger(__name__) 5 | 6 | 7 | def deprecated(version, *replacement): 8 | """ Decorator to mark a specified function as deprecated 9 | 10 | :param version: version in which it is deprecated 11 | :param replacement: replacement functions to use 12 | """ 13 | 14 | def deprecated_wrapper(func): 15 | replacement_message = 'Use {} instead'.format(', '.join( 16 | ["'{}'".format(_get_func_fq_name(x)) 17 | for x in replacement])) 18 | log_message = ("'{}' is deprecated, {}" 19 | "".format(_get_func_fq_name(func), replacement_message)) 20 | # func.__doc__ = replacement[0].__doc__ 21 | 22 | func_path = _get_func_path(func) 23 | doc_replacement = [] 24 | for x in replacement: 25 | if func_path == _get_func_path(x): 26 | doc_replacement.append(':func:`{}`'.format(_func_name(x))) 27 | else: 28 | doc_replacement.append( 29 | ':func:`{}`'.format(_get_func_fq_name(x))) 30 | 31 | func.__doc__ = """ 32 | .. deprecated:: {} 33 | Use {} instead 34 | 35 | {} 36 | """.format(version, 37 | ', '.join(doc_replacement), 38 | func.__doc__ if func.__doc__ else '') 39 | 40 | @wraps(func) 41 | def wrapper(*args, **kwargs): 42 | log.warning(log_message) 43 | return func(*args, **kwargs) 44 | 45 | return wrapper 46 | 47 | return deprecated_wrapper 48 | 49 | 50 | def _func_name(func): 51 | if isinstance(func, property): 52 | func = func.fget 53 | return func.__name__ 54 | 55 | 56 | def _get_func_path(func): 57 | if isinstance(func, property): 58 | func = func.fget 59 | full_path = "{}.".format(func.__module__) 60 | if callable(func): 61 | try: 62 | temp = func.__qualname__.split('.', 1)[0].rsplit('.', 1)[0] 63 | full_path += "{}.".format(temp) 64 | except AttributeError as _: 65 | try: 66 | # noinspection PyUnresolvedReferences 67 | temp = func.im_class 68 | full_path += "{}.".format(temp) 69 | except AttributeError as _: 70 | pass 71 | 72 | return full_path 73 | 74 | 75 | def _get_func_fq_name(func): 76 | if isinstance(func, property): 77 | func = func.fget 78 | full_path = _get_func_path(func) 79 | full_path += func.__name__ 80 | return full_path 81 | 82 | 83 | def fluent(func): 84 | func.__doc__ = """{} 85 | .. note:: This method is part of fluent api and can be chained 86 | """.format(func.__doc__ if func.__doc__ else '') 87 | 88 | @wraps(func) 89 | def inner(self, *args, **kwargs): 90 | return func(self, *args, **kwargs) 91 | 92 | return inner 93 | 94 | 95 | def action(func): 96 | func.__doc__ = """{} 97 | .. note:: The success/failure of this action can be obtained 98 | from **success** and **error_message** attributes after 99 | executing this function 100 | 101 | Example: 102 | .. code-block:: python 103 | 104 | my_obj.one().two().finish() 105 | if not my_obj.is_success: 106 | print(my_obj.error_message) 107 | 108 | this will return success/failure of **finish** action 109 | """.format(func.__doc__ if func.__doc__ else '') 110 | 111 | @wraps(func) 112 | def inner(self, *args, **kwargs): 113 | obj = self.__class__.__new__(self.__class__) 114 | obj.__dict__ = self.__dict__.copy() 115 | func(obj, *args, **kwargs) 116 | return obj 117 | 118 | return inner 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Downloads](https://pepy.tech/badge/O365)](https://pepy.tech/project/O365) 2 | [![PyPI](https://img.shields.io/pypi/v/O365.svg)](https://pypi.python.org/pypi/O365) 3 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/O365.svg)](https://pypi.python.org/pypi/O365/) 4 | 5 | # O365 - Microsoft Graph and related APIs made easy 6 | 7 | This project aims to make interacting with the Microsoft api, and related apis, easy to do in a Pythonic way. 8 | Access to Email, Calendar, Contacts, OneDrive, Sharepoint, etc. Are easy to do in a way that feel easy and straight forward to beginners and feels just right to seasoned python programmer. 9 | 10 | The project is currently developed and maintained by [alejcas](https://github.com/alejcas). 11 | 12 | #### Core developers 13 | - [Alejcas](https://github.com/alejcas) 14 | - [Toben Archer](https://github.com/Narcolapser) 15 | - [Geethanadh](https://github.com/GeethanadhP) 16 | 17 | **We are always open to new pull requests!** 18 | 19 | ## Detailed docs and api reference on [O365 Docs site](https://o365.github.io/python-o365/latest/index.html) 20 | 21 | ### Quick example on sending a message: 22 | 23 | ```python 24 | from O365 import Account 25 | 26 | credentials = ('client_id', 'client_secret') 27 | 28 | account = Account(credentials) 29 | m = account.new_message() 30 | m.to.add('to_example@example.com') 31 | m.subject = 'Testing!' 32 | m.body = "George Best quote: I've stopped drinking, but only while I'm asleep." 33 | m.send() 34 | ``` 35 | 36 | 37 | ### Why choose O365? 38 | - Almost Full Support for MsGraph Rest Api. 39 | - Good Abstraction layer for the Api. 40 | - Full oauth support with automatic handling of refresh tokens. 41 | - Automatic handling between local datetimes and server datetimes. Work with your local datetime and let this library do the rest. 42 | - Change between different resource with ease: access shared mailboxes, other users resources, SharePoint resources, etc. 43 | - Pagination support through a custom iterator that handles future requests automatically. Request Infinite items! 44 | - A query helper to help you build custom OData queries (filter, order, select and search). 45 | - Modular ApiComponents can be created and built to achieve further functionality. 46 | 47 | ___ 48 | 49 | This project was also a learning resource for us. This is a list of not so common python idioms used in this project: 50 | - New unpacking technics: `def method(argument, *, with_name=None, **other_params):` 51 | - Enums: `from enum import Enum` 52 | - Factory paradigm 53 | - Package organization 54 | - Timezone conversion and timezone aware datetimes 55 | - Etc. ([see the code!](https://github.com/O365/python-o365/tree/master/O365)) 56 | -------------------------------------------------------------------------------- /build_docs.sh: -------------------------------------------------------------------------------- 1 | sphinx-build -b html -c ./docs/source/ ./docs/source/ ./docs/latest/ 2 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/.nojekyll -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = latest 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/latest/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 7c4370ffb66904ca9b2ae0e7eb0059ce 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/latest/.buildinfo.bak: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file records the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 2a8b3f04da91464cc27722debcd1b3b1 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/latest/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/.nojekyll -------------------------------------------------------------------------------- /docs/latest/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Overview: module code — O365 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 55 | 56 |
60 | 61 |
62 |
63 |
64 |
    65 |
  • 66 | 67 |
  • 68 |
  • 69 |
70 |
71 |
72 |
73 |
74 | 75 |

All modules for which code is available

76 | 96 | 97 |
98 |
99 |
100 | 101 |
102 | 103 |
104 |

© Copyright 2025, alejcas.

105 |
106 | 107 | Built with Sphinx using a 108 | theme 109 | provided by Read the Docs. 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /docs/latest/_sources/api.rst.txt: -------------------------------------------------------------------------------- 1 | ======== 2 | O365 API 3 | ======== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | api/account 10 | api/address_book 11 | api/calendar 12 | api/category 13 | api/connection 14 | api/directory 15 | api/excel 16 | api/group 17 | api/mailbox 18 | api/message 19 | api/onedrive 20 | api/planner 21 | api/sharepoint 22 | api/tasks 23 | api/teams 24 | api/utils 25 | -------------------------------------------------------------------------------- /docs/latest/_sources/api/account.rst.txt: -------------------------------------------------------------------------------- 1 | Account 2 | ----------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.account 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/address_book.rst.txt: -------------------------------------------------------------------------------- 1 | Address Book 2 | ------------ 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.address_book 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/attachment.rst.txt: -------------------------------------------------------------------------------- 1 | Attachment 2 | ---------- 3 | 4 | .. automodule:: O365.utils.attachment 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/latest/_sources/api/calendar.rst.txt: -------------------------------------------------------------------------------- 1 | Calendar 2 | -------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.calendar 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/category.rst.txt: -------------------------------------------------------------------------------- 1 | Category 2 | -------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.category 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/connection.rst.txt: -------------------------------------------------------------------------------- 1 | Connection 2 | ---------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.connection 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/directory.rst.txt: -------------------------------------------------------------------------------- 1 | Directory 2 | --------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.directory 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/drive.rst.txt: -------------------------------------------------------------------------------- 1 | One Drive 2 | --------- 3 | 4 | .. automodule:: O365.drive 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/latest/_sources/api/excel.rst.txt: -------------------------------------------------------------------------------- 1 | Excel 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.excel 7 | :members: 8 | :undoc-members: 9 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/global.rst.txt: -------------------------------------------------------------------------------- 1 | .. |br| raw:: html 2 | 3 |
   -------------------------------------------------------------------------------- /docs/latest/_sources/api/group.rst.txt: -------------------------------------------------------------------------------- 1 | Group 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.groups 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise 11 | -------------------------------------------------------------------------------- /docs/latest/_sources/api/mailbox.rst.txt: -------------------------------------------------------------------------------- 1 | Mailbox 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.mailbox 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/message.rst.txt: -------------------------------------------------------------------------------- 1 | Message 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.message 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/onedrive.rst.txt: -------------------------------------------------------------------------------- 1 | One Drive 2 | --------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.drive 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/planner.rst.txt: -------------------------------------------------------------------------------- 1 | Planner 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.planner 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/sharepoint.rst.txt: -------------------------------------------------------------------------------- 1 | Sharepoint 2 | ---------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.sharepoint 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/tasks.rst.txt: -------------------------------------------------------------------------------- 1 | Tasks 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.tasks 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/teams.rst.txt: -------------------------------------------------------------------------------- 1 | Teams 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.teams 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/utils.rst.txt: -------------------------------------------------------------------------------- 1 | ===== 2 | Utils 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | utils/attachment 10 | utils/query 11 | utils/token 12 | utils/utils 13 | -------------------------------------------------------------------------------- /docs/latest/_sources/api/utils/attachment.rst.txt: -------------------------------------------------------------------------------- 1 | Attachment 2 | ---------- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.attachment 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/utils/query.rst.txt: -------------------------------------------------------------------------------- 1 | Query 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.query 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/utils/token.rst.txt: -------------------------------------------------------------------------------- 1 | Token 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.token 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/api/utils/utils.rst.txt: -------------------------------------------------------------------------------- 1 | Utils 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.utils 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/latest/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | Welcome to O365's documentation! 2 | ================================ 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | :caption: Contents: 7 | 8 | overview 9 | getting_started 10 | usage 11 | api 12 | 13 | Indices and tables 14 | ================== 15 | 16 | * :ref:`genindex` 17 | * :ref:`modindex` 18 | * :ref:`search` 19 | -------------------------------------------------------------------------------- /docs/latest/_sources/overview.rst.txt: -------------------------------------------------------------------------------- 1 | ######## 2 | Overview 3 | ######## 4 | 5 | **O365 - Microsoft Graph API made easy** 6 | 7 | .. important:: 8 | 9 | With version 2.1 old access tokens will not work, and the library will require a new authentication flow to get new access and refresh tokens. 10 | 11 | This project aims to make interacting with Microsoft Graph easy to do in a Pythonic way. Access to Email, Calendar, Contacts, OneDrive, etc. Are easy to do in a way that feel easy and straight forward to beginners and feels just right to seasoned python programmer. 12 | 13 | The project is currently developed and maintained by `alejcas `_. 14 | 15 | Core developers 16 | --------------- 17 | * `Alejcas `_ 18 | * `Toben Archer `_ 19 | * `Geethanadh `_ 20 | 21 | We are always open to new pull requests! 22 | 23 | Quick example 24 | ------------- 25 | Here is a simple example showing how to send an email using python-o365. 26 | Create a Python file and add the following code: 27 | 28 | .. code-block:: python 29 | 30 | from O365 import Account 31 | 32 | credentials = ('client_id', 'client_secret') 33 | account = Account(credentials) 34 | 35 | m = account.new_message() 36 | m.to.add('to_example@example.com') 37 | m.subject = 'Testing!' 38 | m.body = "George Best quote: I've stopped drinking, but only while I'm asleep." 39 | m.send() 40 | 41 | 42 | Why choose O365? 43 | ---------------- 44 | * Almost Full Support for MsGraph Rest Api. 45 | * Full OAuth support with automatic handling of refresh tokens. 46 | * Automatic handling between local datetimes and server datetimes. Work with your local datetime and let this library do the rest. 47 | * Change between different resource with ease: access shared mailboxes, other users resources, SharePoint resources, etc. 48 | * Pagination support through a custom iterator that handles future requests automatically. Request Infinite items! 49 | * A query helper to help you build custom OData queries (filter, order, select and search). 50 | * Modular ApiComponents can be created and built to achieve further functionality. 51 | 52 | ---- 53 | 54 | This project was also a learning resource for us. This is a list of not so common python idioms used in this project: 55 | 56 | * New unpacking technics: ``def method(argument, *, with_name=None, **other_params)``: 57 | * Enums: from enum import Enum 58 | * Factory paradigm 59 | * Package organization 60 | * Timezone conversion and timezone aware datetimes 61 | * Etc. (see the code!) 62 | 63 | Rebuilding HTML Docs 64 | -------------------- 65 | * Install ``sphinx`` python library: 66 | 67 | .. code-block:: console 68 | 69 | pip install sphinx 70 | 71 | * Run the shell script ``build_docs.sh``, or copy the command from the file when using on Windows -------------------------------------------------------------------------------- /docs/latest/_sources/usage.rst.txt: -------------------------------------------------------------------------------- 1 | ============== 2 | Detailed Usage 3 | ============== 4 | 5 | .. toctree:: 6 | :maxdepth: 3 7 | :caption: Contents: 8 | 9 | usage/connection 10 | usage/account 11 | usage/addressbook 12 | usage/calendar 13 | usage/directory 14 | usage/excel 15 | usage/group 16 | usage/mailbox 17 | usage/onedrive 18 | usage/planner 19 | usage/sharepoint 20 | usage/tasks 21 | usage/teams 22 | usage/utils 23 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/addressbook.rst.txt: -------------------------------------------------------------------------------- 1 | Address Book 2 | ============ 3 | AddressBook groups the functionality of both the Contact Folders and Contacts. Outlook Distribution Groups are not supported (By the Microsoft API's). 4 | 5 | These are the scopes needed to work with the ``AddressBook`` and ``Contact`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Contacts.Read address_book To only read my personal contacts 11 | Contacts.Read.Shared address_book_shared To only read another user / shared mailbox contacts 12 | Contacts.ReadWrite address_book_all To read and save personal contacts 13 | Contacts.ReadWrite.Shared address_book_all_shared To read and save contacts from another user / shared mailbox 14 | User.ReadBasic.All users To only read basic properties from users of my organization (User.Read.All requires administrator consent). 15 | ========================= ======================================= ====================================== 16 | 17 | Contact Folders 18 | --------------- 19 | Represents a Folder within your Contacts Section in Office 365. AddressBook class represents the parent folder (it's a folder itself). 20 | 21 | You can get any folder in your address book by requesting child folders or filtering by name. 22 | 23 | .. code-block:: python 24 | 25 | address_book = account.address_book() 26 | 27 | contacts = address_book.get_contacts(limit=None) # get all the contacts in the Personal Contacts root folder 28 | 29 | work_contacts_folder = address_book.get_folder(folder_name='Work Contacts') # get a folder with 'Work Contacts' name 30 | 31 | message_to_all_contats_in_folder = work_contacts_folder.new_message() # creates a draft message with all the contacts as recipients 32 | 33 | message_to_all_contats_in_folder.subject = 'Hallo!' 34 | message_to_all_contats_in_folder.body = """ 35 | George Best quote: 36 | 37 | If you'd given me the choice of going out and beating four men and smashing a goal in 38 | from thirty yards against Liverpool or going to bed with Miss World, 39 | it would have been a difficult choice. Luckily, I had both. 40 | """ 41 | message_to_all_contats_in_folder.send() 42 | 43 | # querying folders is easy: 44 | child_folders = address_book.get_folders(25) # get at most 25 child folders 45 | 46 | for folder in child_folders: 47 | print(folder.name, folder.parent_id) 48 | 49 | # creating a contact folder: 50 | address_book.create_child_folder('new folder') 51 | 52 | .. _global_address_list: 53 | 54 | Global Address List 55 | ------------------- 56 | MS Graph API has no concept such as the Outlook Global Address List. 57 | However you can use the `Users API `_ to access all the users within your organization. 58 | 59 | Without admin consent you can only access a few properties of each user such as name and email and little more. You can search by name or retrieve a contact specifying the complete email. 60 | 61 | * Basic Permission needed is Users.ReadBasic.All (limit info) 62 | * Full Permission is Users.Read.All but needs admin consent. 63 | 64 | To search the Global Address List (Users API): 65 | 66 | .. code-block:: python 67 | 68 | global_address_list = account.directory() 69 | 70 | # for backwards compatibility only this also works and returns a Directory object: 71 | # global_address_list = account.address_book(address_book='gal') 72 | 73 | # start a new query: 74 | q = global_address_list.new_query('display_name') 75 | q.startswith('George Best') 76 | 77 | for user in global_address_list.get_users(query=q): 78 | print(user) 79 | 80 | To retrieve a contact by their email: 81 | 82 | .. code-block:: python 83 | 84 | contact = global_address_list.get_user('example@example.com') 85 | Contacts 86 | 87 | Everything returned from an AddressBook instance is a Contact instance. Contacts have all the information stored as attributes 88 | 89 | Creating a contact from an AddressBook: 90 | 91 | new_contact = address_book.new_contact() 92 | 93 | new_contact.name = 'George Best' 94 | new_contact.job_title = 'football player' 95 | new_contact.emails.add('george@best.com') 96 | 97 | new_contact.save() # saved on the cloud 98 | 99 | message = new_contact.new_message() # Bonus: send a message to this contact 100 | 101 | # ... 102 | 103 | new_contact.delete() # Bonus: deleted from the cloud -------------------------------------------------------------------------------- /docs/latest/_sources/usage/calendar.rst.txt: -------------------------------------------------------------------------------- 1 | Calendar 2 | ======== 3 | The calendar and events functionality is group in a Schedule object. 4 | 5 | A ``Schedule`` instance can list and create calendars. It can also list or create events on the default user calendar. To use other calendars use a ``Calendar`` instance. 6 | 7 | These are the scopes needed to work with the ``Schedule``, ``Calendar`` and ``Event`` classes. 8 | 9 | ========================== ======================================= ====================================== 10 | Raw Scope Included in Scope Helper Description 11 | ========================== ======================================= ====================================== 12 | Calendars.Read calendar To only read my personal calendars 13 | Calendars.Read.Shared calendar_shared To only read another user / shared mailbox calendars 14 | Calendars.ReadWrite calendar_all To read and save personal calendars 15 | Calendars.ReadWrite.Shared calendar_shared_all To read and save calendars from another user / shared mailbox 16 | ========================== ======================================= ====================================== 17 | 18 | Working with the ``Schedule`` instance: 19 | 20 | .. code-block:: python 21 | 22 | import datetime as dt 23 | 24 | # ... 25 | schedule = account.schedule() 26 | 27 | calendar = schedule.get_default_calendar() 28 | new_event = calendar.new_event() # creates a new unsaved event 29 | new_event.subject = 'Recruit George Best!' 30 | new_event.location = 'England' 31 | 32 | # naive datetimes will automatically be converted to timezone aware datetime 33 | # objects using the local timezone detected or the protocol provided timezone 34 | 35 | new_event.start = dt.datetime(2019, 9, 5, 19, 45) 36 | # so new_event.start becomes: datetime.datetime(2018, 9, 5, 19, 45, tzinfo=) 37 | 38 | new_event.recurrence.set_daily(1, end=dt.datetime(2019, 9, 10)) 39 | new_event.remind_before_minutes = 45 40 | 41 | new_event.save() 42 | 43 | Working with Calendar instances: 44 | 45 | .. code-block:: python 46 | 47 | calendar = schedule.get_calendar(calendar_name='Birthdays') 48 | 49 | calendar.name = 'Football players birthdays' 50 | calendar.update() 51 | 52 | 53 | start_q = calendar.new_query('start').greater_equal(dt.datetime(2018, 5, 20)) 54 | end_q = calendar.new_query('start').less_equal(dt.datetime(2018, 5, 24)) 55 | 56 | birthdays = calendar.get_events( 57 | include_recurring=True, # include_recurring=True will include repeated events on the result set. 58 | start_recurring=start_q, 59 | end_recurring=end_q, 60 | ) 61 | 62 | for event in birthdays: 63 | if event.subject == 'George Best Birthday': 64 | # He died in 2005... but we celebrate anyway! 65 | event.accept("I'll attend!") # send a response accepting 66 | else: 67 | event.decline("No way I'm coming, I'll be in Spain", send_response=False) # decline the event but don't send a response to the organizer 68 | 69 | **Notes regarding Calendars and Events**: 70 | 71 | 1. Include_recurring=True: 72 | 73 | It's important to know that when querying events with include_recurring=True (which is the default), 74 | it is required that you must provide start and end parameters, these may be simple date strings, python dates or individual queries. 75 | Unlike when using include_recurring=False those attributes will NOT filter the data based on the operations you set on the query (greater_equal, less, etc.) 76 | but just filter the events start datetime between the provided start and end datetimes. 77 | 78 | 2. Shared Calendars: 79 | 80 | There are some known issues when working with `shared calendars `_ in Microsoft Graph. 81 | 82 | 3. Event attachments: 83 | 84 | For some unknown reason, Microsoft does not allow to upload an attachment at the event creation time (as opposed with message attachments). 85 | See `this `_. So, to upload attachments to Events, first save the event, then attach the message and save again. -------------------------------------------------------------------------------- /docs/latest/_sources/usage/connection.rst.txt: -------------------------------------------------------------------------------- 1 | Protocols 2 | ========= 3 | Protocols handles the aspects of communications between different APIs. This project uses the Microsoft Graph APIs. But, you can use many other Microsoft APIs as long as you implement the protocol needed. 4 | 5 | You can use: 6 | 7 | * MSGraphProtocol to use the `Microsoft Graph API `_ 8 | 9 | .. code-block:: python 10 | 11 | from O365 import Account 12 | 13 | credentials = ('client_id', 'client_secret') 14 | 15 | account = Account(credentials, auth_flow_type='credentials', tenant_id='my_tenant_id') 16 | if account.authenticate(): 17 | print('Authenticated!') 18 | mailbox = account.mailbox('sender_email@my_domain.com') 19 | m = mailbox.new_message() 20 | m.to.add('to_example@example.com') 21 | m.subject = 'Testing!' 22 | m.body = "George Best quote: I've stopped drinking, but only while I'm asleep." 23 | m.save_message() 24 | m.attachment.add = 'filename.txt' 25 | m.send() 26 | 27 | The default protocol used by the ``Account`` Class is ``MSGraphProtocol``. 28 | 29 | You can implement your own protocols by inheriting from Protocol to communicate with other Microsoft APIs. 30 | 31 | You can instantiate and use protocols like this: 32 | 33 | .. code-block:: python 34 | 35 | from O365 import Account, MSGraphProtocol # same as from O365.connection import MSGraphProtocol 36 | 37 | # ... 38 | 39 | # try the api version beta of the Microsoft Graph endpoint. 40 | protocol = MSGraphProtocol(api_version='beta') # MSGraphProtocol defaults to v1.0 api version 41 | account = Account(credentials, protocol=protocol) 42 | 43 | 44 | Resources 45 | ========= 46 | Each API endpoint requires a resource. This usually defines the owner of the data. Every protocol defaults to resource 'ME'. 'ME' is the user which has given consent, but you can change this behaviour by providing a different default resource to the protocol constructor. 47 | 48 | .. note:: 49 | 50 | When using the "with your own identity" authentication method the resource 'ME' is overwritten to be blank as the authentication method already states that you are login with your own identity. 51 | 52 | For example when accessing a shared mailbox: 53 | 54 | .. code-block:: python 55 | 56 | # ... 57 | account = Account(credentials=my_credentials, main_resource='shared_mailbox@example.com') 58 | # Any instance created using account will inherit the resource defined for account. 59 | 60 | This can be done however at any point. For example at the protocol level: 61 | 62 | .. code-block:: python 63 | 64 | # ... 65 | protocol = MSGraphProtocol(default_resource='shared_mailbox@example.com') 66 | 67 | account = Account(credentials=my_credentials, protocol=protocol) 68 | 69 | # now account is accessing the shared_mailbox@example.com in every api call. 70 | shared_mailbox_messages = account.mailbox().get_messages() 71 | 72 | Instead of defining the resource used at the account or protocol level, you can provide it per use case as follows: 73 | 74 | .. code-block:: python 75 | 76 | # ... 77 | account = Account(credentials=my_credentials) # account defaults to 'ME' resource 78 | 79 | mailbox = account.mailbox('shared_mailbox@example.com') # mailbox is using 'shared_mailbox@example.com' resource instead of 'ME' 80 | 81 | # or: 82 | 83 | message = Message(parent=account, main_resource='shared_mailbox@example.com') # message is using 'shared_mailbox@example.com' resource 84 | 85 | Usually you will work with the default 'ME' resource, but you can also use one of the following: 86 | 87 | * 'me': the user which has given consent. The default for every protocol. Overwritten when using "with your own identity" authentication method (Only available on the authorization auth_flow_type). 88 | * 'user:user@domain.com': a shared mailbox or a user account for which you have permissions. If you don't provide 'user:' it will be inferred anyway. 89 | * 'site:sharepoint-site-id': a Sharepoint site id. 90 | * 'group:group-site-id': an Microsoft 365 group id. 91 | 92 | By setting the resource prefix (such as 'user:' or 'group:') you help the library understand the type of resource. You can also pass it like 'users/example@exampl.com'. The same applies to the other resource prefixes. -------------------------------------------------------------------------------- /docs/latest/_sources/usage/directory.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Directory and Users 3 | =================== 4 | The Directory object can retrieve users. 5 | 6 | A User instance contains by default the `basic properties of the user `_. If you want to include more, you will have to select the desired properties manually. 7 | 8 | Check :ref:`global_address_list` for further information. 9 | 10 | These are the scopes needed to work with the Directory class. 11 | 12 | ========================= ======================================= ====================================== 13 | Raw Scope Included in Scope Helper Description 14 | ========================= ======================================= ====================================== 15 | User.ReadBasic.All users To read a basic set of profile properties of other users in your organization on behalf of the signed-in user. This includes display name, first and last name, email address, open extensions and photo. Also allows the app to read the full profile of the signed-in user. 16 | User.Read.All — To read the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. 17 | User.ReadWrite.All — To read and write the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. Also allows the app to create and delete users as well as reset user passwords on behalf of the signed-in user. 18 | Directory.Read.All — To read data in your organization's directory, such as users, groups and apps, without a signed-in user. 19 | Directory.ReadWrite.All — To read and write data in your organization's directory, such as users, and groups, without a signed-in user. Does not allow user or group deletion. 20 | ========================= ======================================= ====================================== 21 | 22 | .. note:: 23 | 24 | To get authorized with the above scopes you need a work or school account, it doesn't work with personal account. 25 | 26 | Working with the ``Directory`` instance to read the active directory users: 27 | 28 | .. code-block:: python 29 | 30 | directory = account.directory() 31 | for user in directory.get_users(): 32 | print(user) 33 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/excel.rst.txt: -------------------------------------------------------------------------------- 1 | Excel 2 | ===== 3 | You can interact with new Excel files (.xlsx) stored in OneDrive or a SharePoint Document Library. You can retrieve workbooks, worksheets, tables, and even cell data. You can also write to any excel online. 4 | 5 | To work with Excel files, first you have to retrieve a ``File`` instance using the OneDrive or SharePoint functionality. 6 | 7 | The scopes needed to work with the ``WorkBook`` and Excel related classes are the same used by OneDrive. 8 | 9 | This is how you update a cell value: 10 | 11 | .. code-block:: python 12 | 13 | from O365.excel import WorkBook 14 | 15 | # given a File instance that is a xlsx file ... 16 | excel_file = WorkBook(my_file_instance) # my_file_instance should be an instance of File. 17 | 18 | ws = excel_file.get_worksheet('my_worksheet') 19 | cella1 = ws.get_range('A1') 20 | cella1.values = 35 21 | cella1.update() 22 | 23 | Workbook Sessions 24 | ----------------- 25 | 26 | When interacting with Excel, you can use a workbook session to efficiently make changes in a persistent or nonpersistent way. These sessions become usefull if you perform numerous changes to the Excel file. 27 | 28 | The default is to use a session in a persistent way. Sessions expire after some time of inactivity. When working with persistent sessions, new sessions will automatically be created when old ones expire. 29 | 30 | You can however change this when creating the ``Workbook`` instance: 31 | 32 | .. code-block:: python 33 | 34 | excel_file = WorkBook(my_file_instance, use_session=False, persist=False) 35 | 36 | Available Objects 37 | ----------------- 38 | 39 | After creating the ``WorkBook`` instance you will have access to the following objects: 40 | 41 | * WorkSheet 42 | * Range and NamedRange 43 | * Table, TableColumn and TableRow 44 | * RangeFormat (to format ranges) 45 | * Charts (not available for now) 46 | 47 | Some examples: 48 | 49 | Set format for a given range 50 | 51 | .. code-block:: python 52 | 53 | # ... 54 | my_range = ws.get_range('B2:C10') 55 | fmt = myrange.get_format() 56 | fmt.font.bold = True 57 | fmt.update() 58 | 59 | Autofit Columns: 60 | 61 | .. code-block:: python 62 | 63 | ws.get_range('B2:C10').get_format().auto_fit_columns() 64 | 65 | Get values from Table: 66 | 67 | .. code-block:: python 68 | 69 | table = ws.get_table('my_table') 70 | column = table.get_column_at_index(1) 71 | values = column.values[0] # values returns a two-dimensional array. 72 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/group.rst.txt: -------------------------------------------------------------------------------- 1 | Group 2 | ===== 3 | Groups enables viewing of groups 4 | 5 | These are the scopes needed to work with the ``Group`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Group.Read.All — To read groups 11 | ========================= ======================================= ====================================== 12 | 13 | Assuming an authenticated account and a previously created group, create a Plan instance. 14 | 15 | .. code-block:: python 16 | 17 | #Create a plan instance 18 | from O365 import Account 19 | account = Account(('app_id', 'app_pw')) 20 | groups = account.groups() 21 | 22 | # To retrieve the list of groups 23 | group_list = groups.list_groups() 24 | 25 | # Or to retrieve a list of groups for a given user 26 | user_groups = groups.get_user_groups(user_id="object_id") 27 | 28 | # To retrieve a group by an identifier 29 | group = groups.get_group_by_id(group_id="object_id") 30 | group = groups.get_group_by_mail(group_mail="john@doe.com") 31 | 32 | 33 | # To retrieve the owners and members of a group 34 | owners = group.get_group_owners() 35 | members = group.get_group_members() 36 | 37 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/onedrive.rst.txt: -------------------------------------------------------------------------------- 1 | OneDrive 2 | ======== 3 | The ``Storage`` class handles all functionality around One Drive and Document Library Storage in SharePoint. 4 | 5 | The ``Storage`` instance allows retrieval of ``Drive`` instances which handles all the Files 6 | and Folders from within the selected ``Storage``. Usually you will only need to work with the 7 | default drive. But the ``Storage`` instances can handle multiple drives. 8 | 9 | A ``Drive`` will allow you to work with Folders and Files. 10 | 11 | These are the scopes needed to work with the ``Storage``, ``Drive`` and ``DriveItem`` classes. 12 | 13 | ========================= ======================================= ====================================== 14 | Raw Scope Included in Scope Helper Description 15 | ========================= ======================================= ====================================== 16 | Files.Read — To only read my files 17 | Files.Read.All onedrive To only read all the files the user has access 18 | Files.ReadWrite — To read and save my files 19 | Files.ReadWrite.All onedrive_all To read and save all the files the user has access 20 | ========================= ======================================= ====================================== 21 | 22 | .. code-block:: python 23 | 24 | account = Account(credentials=my_credentials) 25 | 26 | storage = account.storage() # here we get the storage instance that handles all the storage options. 27 | 28 | # list all the drives: 29 | drives = storage.get_drives() 30 | 31 | # get the default drive 32 | my_drive = storage.get_default_drive() # or get_drive('drive-id') 33 | 34 | # get some folders: 35 | root_folder = my_drive.get_root_folder() 36 | attachments_folder = my_drive.get_special_folder('attachments') 37 | 38 | # iterate over the first 25 items on the root folder 39 | for item in root_folder.get_items(limit=25): 40 | if item.is_folder: 41 | print(list(item.get_items(2))) # print the first to element on this folder. 42 | elif item.is_file: 43 | if item.is_photo: 44 | print(item.camera_model) # print some metadata of this photo 45 | elif item.is_image: 46 | print(item.dimensions) # print the image dimensions 47 | else: 48 | # regular file: 49 | print(item.mime_type) # print the mime type 50 | 51 | Both Files and Folders are DriveItems. Both Image and Photo are Files, but Photo is also an Image. All have some different methods and properties. Take care when using 'is_xxxx'. 52 | 53 | When copying a DriveItem the api can return a direct copy of the item or a pointer to a resource that will inform on the progress of the copy operation. 54 | 55 | .. code-block:: python 56 | 57 | # copy a file to the documents special folder 58 | 59 | documents_folder = my_drive.get_special_folder('documents') 60 | 61 | files = my_drive.search('george best quotes', limit=1) 62 | 63 | if files: 64 | george_best_quotes = files[0] 65 | operation = george_best_quotes.copy(target=documents_folder) # operation here is an instance of CopyOperation 66 | 67 | # to check for the result just loop over check_status. 68 | # check_status is a generator that will yield a new status and progress until the file is finally copied 69 | for status, progress in operation.check_status(): # if it's an async operations, this will request to the api for the status in every loop 70 | print(f"{status} - {progress}") # prints 'in progress - 77.3' until finally completed: 'completed - 100.0' 71 | copied_item = operation.get_item() # the copy operation is completed so you can get the item. 72 | if copied_item: 73 | copied_item.delete() # ... oops! 74 | 75 | You can also work with share permissions: 76 | 77 | .. code-block:: python 78 | 79 | current_permisions = file.get_permissions() # get all the current permissions on this drive_item (some may be inherited) 80 | 81 | # share with link 82 | permission = file.share_with_link(share_type='edit') 83 | if permission: 84 | print(permission.share_link) # the link you can use to share this drive item 85 | # share with invite 86 | permission = file.share_with_invite(recipients='george_best@best.com', send_email=True, message='Greetings!!', share_type='edit') 87 | if permission: 88 | print(permission.granted_to) # the person you share this item with 89 | 90 | You can also: 91 | 92 | .. code-block:: python 93 | 94 | # download files: 95 | file.download(to_path='/quotes/') 96 | 97 | # upload files: 98 | 99 | # if the uploaded file is bigger than 4MB the file will be uploaded in chunks of 5 MB until completed. 100 | # this can take several requests and can be time consuming. 101 | uploaded_file = folder.upload_file(item='path_to_my_local_file') 102 | 103 | # restore versions: 104 | versions = file.get_versions() 105 | for version in versions: 106 | if version.name == '2.0': 107 | version.restore() # restore the version 2.0 of this file 108 | 109 | # ... and much more ... -------------------------------------------------------------------------------- /docs/latest/_sources/usage/planner.rst.txt: -------------------------------------------------------------------------------- 1 | Planner 2 | ======= 3 | Planner enables the creation and maintenance of plans, buckets and tasks 4 | 5 | These are the scopes needed to work with the ``Planner`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Group.Read.All — To only read plans 11 | Group.ReadWrite.All — To create and maintain a plan 12 | ========================= ======================================= ====================================== 13 | 14 | Assuming an authenticated account and a previously created group, create a Plan instance. 15 | 16 | .. code-block:: python 17 | 18 | #Create a plan instance 19 | from O365 import Account 20 | account = Account(('app_id', 'app_pw')) 21 | planner = account.planner() 22 | plan = planner.create_plan( 23 | owner="group_object_id", title="Test Plan" 24 | ) 25 | 26 | | Common commands for :code:`planner` include :code:`.create_plan()`, :code:`.get_bucket_by_id()`, :code:`.get_my_tasks()`, :code:`.list_group_plans()`, :code:`.list_group_tasks()` and :code:`.delete()`. 27 | | Common commands for :code:`plan` include :code:`.create_bucket()`, :code:`.get_details()`, :code:`.list_buckets()`, :code:`.list_tasks()` and :code:`.delete()`. 28 | 29 | Then to create a bucket within a plan. 30 | 31 | .. code-block:: python 32 | 33 | #Create a bucket instance in a plan 34 | bucket = plan.create_bucket(name="Test Bucket") 35 | 36 | Common commands for :code:`bucket` include :code:`.list_tasks()` and :code:`.delete()`. 37 | 38 | Then to create a task, assign it to a user, set it to 50% completed and add a description. 39 | 40 | .. code-block:: python 41 | 42 | #Create a task in a bucket 43 | assignments = { 44 | "user_object_id: { 45 | "@odata.type": "microsoft.graph.plannerAssignment", 46 | "orderHint": "1 !", 47 | } 48 | } 49 | task = bucket.create_task(title="Test Task", assignments=assignments) 50 | 51 | task.update(percent_complete=50) 52 | 53 | task_details = task.get_details() 54 | task_details.update(description="Test Description") 55 | 56 | Common commands for :code:`task` include :code:`.get_details()`, :code:`.update()` and :code:`.delete()`. -------------------------------------------------------------------------------- /docs/latest/_sources/usage/query.rst.txt: -------------------------------------------------------------------------------- 1 | Query 2 | ===== 3 | **Query** class helps with creating filters for the results (It can be either filtering event or email messages or any function that accepts query attribute) 4 | 5 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/sharepoint.rst.txt: -------------------------------------------------------------------------------- 1 | Sharepoint 2 | ========== 3 | 4 | These are the scopes needed to work with the SharePoint and Site classes. 5 | 6 | ========================= ======================================= ======================================= 7 | Raw Scope Included in Scope Helper Description 8 | ========================= ======================================= ======================================= 9 | Sites.Read.All sharepoint To only read sites, lists and items 10 | Sites.ReadWrite.All sharepoint_dl To read and save sites, lists and items 11 | ========================= ======================================= ======================================= 12 | 13 | Assuming an authenticated account, create a Sharepoint instance, and connect 14 | to a Sharepoint site. 15 | 16 | .. code-block:: python 17 | 18 | #Create Sharepoint instance and connect to a site 19 | from O365 import Account 20 | acct = Account(('app_id', 'app_pw')) 21 | sp_site = acct.sharepoint().get_site('root', 'path/tosite') 22 | 23 | Common commands for :code:`sp_site` include :code:`.display_name`, 24 | :code:`.get_document_library()`, :code:`.get_subsites()`, :code:`.get_lists()`, 25 | and :code:`.get_list_by_name('list_name')`. 26 | 27 | **Accessing Subsites** 28 | 29 | If a Sharepoint site contains subsites they can be returned as a list of 30 | Sharepoint sites by the :code:`.get_subsites()` function. 31 | 32 | .. code-block:: python 33 | 34 | #Return a List of subsites 35 | sp_site_subsites = sp_site.get_subsites() 36 | print(sp_sites_subsites) 37 | [Site: subsitename1, Site: subsitename2] 38 | 39 | #Make another Site object from a desired subsite 40 | new_sp_site = sp_site_subsites[0] #return the first subsite 41 | 42 | Sharepoint Lists 43 | ^^^^^^^^^^^^^^^^ 44 | 45 | Sharepoint Lists are accessible from their Sharepoint site using :code:`.get_lists()` which 46 | returns a Python list of Sharepoint list objects. A known list can be accessed 47 | by providing a :code:`list_name` to :code:`.get_list_by_name('list_name')` which will return 48 | the requested list as a :code:`sharepointlist` object. 49 | 50 | .. code-block:: python 51 | 52 | #Return a list of sharepoint lists 53 | sp_site_lists = sp_site.get_lists() 54 | 55 | #Return a specific list by name 56 | sp_list = sp_site.get_list_by_name('list_name') 57 | 58 | 59 | Commmon functions on a Sharepoint list include :code:`.get_list_columns()`, 60 | :code:`.get_items()`, :code:`.get_item_by_id()`, :code:`.create_list_item()`, 61 | :code:`.delete_list_item()`. 62 | 63 | 64 | Sharepoint List Items 65 | """"""""""""""""""""" 66 | 67 | Accessing a list item from a Sharepoint list is done by utilizing :code:`.get_items()`, 68 | or :code:`.get_item_by_id(item_id)`. 69 | 70 | .. code-block:: python 71 | 72 | #Return a list of sharepoint list Items 73 | sp_list_items = sp_list.get_items() 74 | 75 | #Return a specific sharepoint list item by its object ID 76 | sp_list_item = sp_list.get_item_by_id(item_id) 77 | 78 | 79 | **Creating & Deleting Sharepoint Items** 80 | 81 | A Sharepoint list item can be created by passing the new data in a dictionary 82 | consisting of :code:`{'column_name': 'new_data'}`. Not all columns in the Sharepoint list have to 83 | be accounted for in the dictionary, any Sharepoint List column not in the dictionary 84 | will be filled with a blank. The `column_name` must be the internal column name 85 | of the sharepoint list. :code:`.column_name_cw` of a sharepoint list will provide a 86 | dictionary of :code:`{'Display Name': 'Internal Name'}` if needed. 87 | 88 | .. code-block:: python 89 | 90 | #Create a new sharepoint list item 91 | new_item = sp_list.create_list_item({'col1': 'New Data Col 1', 92 | 'col2': 'New Data Col 2'}) 93 | 94 | #Delete the item just created 95 | sp_list.delete_list_item(new_item.object_id) #Pass the item ID to be deleted 96 | 97 | **Updating a Sharepoint List Item** 98 | 99 | Sharepoint list items can be updated by passing a dictionary of 100 | :code:`{'column_name': 'Updated Data'}` to the :code:`.update_fields()` function of a 101 | Sharepoint list item. The `column_name` keys of the dictionary must again refer 102 | to the internal column name, otherwise an error will occur. 103 | 104 | .. code-block:: python 105 | 106 | #Update a Sharepoint List item 107 | new_item.update_fields({'col1': 'Updated Data Col1', 108 | 'col2': 'Updated Data Col2'}) 109 | 110 | #Once done updating a sharepoint item save changes to the cloud 111 | new_item.save_updates() #Returns True if successful 112 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/tasks.rst.txt: -------------------------------------------------------------------------------- 1 | Tasks 2 | ===== 3 | The tasks functionality is grouped in a ToDo object. 4 | 5 | A ToDo instance can list and create task folders. It can also list or create tasks on the default user folder. To use other folders use a Folder instance. 6 | 7 | These are the scopes needed to work with the ToDo, Folder and Task classes. 8 | 9 | ========================= ======================================= ====================================== 10 | Raw Scope Included in Scope Helper Description 11 | ========================= ======================================= ====================================== 12 | Tasks.Read tasks To only read my personal tasks 13 | Tasks.ReadWrite tasks_all To read and save personal calendars 14 | ========================= ======================================= ====================================== 15 | 16 | Working with the `ToDo`` instance: 17 | 18 | .. code-block:: python 19 | 20 | import datetime as dt 21 | 22 | # ... 23 | todo = account.tasks() 24 | 25 | #list current tasks 26 | folder = todo.get_default_folder() 27 | new_task = folder.new_task() # creates a new unsaved task 28 | new_task.subject = 'Send contract to George Best' 29 | new_task.due = dt.datetime(2020, 9, 25, 18, 30) 30 | new_task.save() 31 | 32 | #some time later.... 33 | 34 | new_task.mark_completed() 35 | new_task.save() 36 | 37 | # naive datetimes will automatically be converted to timezone aware datetime 38 | # objects using the local timezone detected or the protocol provided timezone 39 | # as with the Calendar functionality 40 | 41 | Working with Folder instances: 42 | 43 | .. code-block:: python 44 | 45 | #create a new folder 46 | new_folder = todo.new_folder('Defenders') 47 | 48 | #rename a folder 49 | folder = todo.get_folder(folder_name='Strikers') 50 | folder.name = 'Forwards' 51 | folder.update() 52 | 53 | #list current tasks 54 | task_list = folder.get_tasks() 55 | for task in task_list: 56 | print(task) 57 | print('') -------------------------------------------------------------------------------- /docs/latest/_sources/usage/teams.rst.txt: -------------------------------------------------------------------------------- 1 | Teams 2 | ===== 3 | Teams enables the communications via Teams Chat, plus Presence management 4 | 5 | These are the scopes needed to work with the ``Teams`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Channel.ReadBasic.All — To read basic channel information 11 | ChannelMessage.Read.All — To read channel messages 12 | ChannelMessage.Send — To send messages to a channel 13 | Chat.Read — To read users chat 14 | Chat.ReadWrite — To read users chat and send chat messages 15 | Presence.Read presence To read users presence status 16 | Presence.Read.All — To read any users presence status 17 | Presence.ReadWrite — To update users presence status 18 | Team.ReadBasic.All — To read only the basic properties for all my teams 19 | User.ReadBasic.All users To only read basic properties from users of my organization (User.Read.All requires administrator consent) 20 | ========================= ======================================= ====================================== 21 | 22 | Presence 23 | -------- 24 | Assuming an authenticated account. 25 | 26 | .. code-block:: python 27 | 28 | # Retrieve logged-in user's presence 29 | from O365 import Account 30 | account = Account(('app_id', 'app_pw')) 31 | teams = account.teams() 32 | presence = teams.get_my_presence() 33 | 34 | # Retrieve another user's presence 35 | user = account.directory().get_user("john@doe.com") 36 | presence2 = teams.get_user_presence(user.object_id) 37 | 38 | To set a users status or preferred status: 39 | 40 | .. code-block:: python 41 | 42 | # Set user's presence 43 | from O365.teams import Activity, Availability, PreferredActivity, PreferredAvailability 44 | 45 | status = teams.set_my_presence(CLIENT_ID, Availability.BUSY, Activity.INACALL, "1H") 46 | 47 | # or set User's preferred presence (which is more likely the one you want) 48 | 49 | status = teams.set_my_user_preferred_presence(PreferredAvailability.OFFLINE, PreferredActivity.OFFWORK, "1H") 50 | 51 | 52 | Chat 53 | ---- 54 | Assuming an authenticated account. 55 | 56 | .. code-block:: python 57 | 58 | # Retrieve logged-in user's chats 59 | from O365 import Account 60 | account = Account(('app_id', 'app_pw')) 61 | teams = account.teams() 62 | chats = teams.get_my_chats() 63 | 64 | # Then to retrieve chat messages and chat members 65 | for chat in chats: 66 | if chat.chat_type != "unknownFutureValue": 67 | message = chat.get_messages(limit=10) 68 | memberlist = chat.get_members() 69 | 70 | 71 | # And to send a chat message 72 | 73 | chat.send_message(content="Hello team!", content_type="text") 74 | 75 | | Common commands for :code:`Chat` include :code:`.get_member()` and :code:`.get_message()` 76 | 77 | 78 | Team 79 | ---- 80 | Assuming an authenticated account. 81 | 82 | .. code-block:: python 83 | 84 | # Retrieve logged-in user's teams 85 | from O365 import Account 86 | account = Account(('app_id', 'app_pw')) 87 | teams = account.teams() 88 | my_teams = teams.get_my_teams() 89 | 90 | # Then to retrieve team channels and messages 91 | for team in my_teams: 92 | channels = team.get_channels() 93 | for channel in channels: 94 | messages = channel.get_messages(limit=10) 95 | for channelmessage in messages: 96 | print(channelmessage) 97 | 98 | 99 | # To send a message to a team channel 100 | channel.send_message("Hello team") 101 | 102 | # To send a reply to a message 103 | channelmessage.send_message("Hello team leader") 104 | 105 | | Common commands for :code:`Teams` include :code:`.create_channel()`, :code:`.get_apps_in_channel()` and :code:`.get_channel()` 106 | | Common commands for :code:`Team` include :code:`.get_channel()` 107 | | Common commands for :code:`Channel` include :code:`.get_message()` 108 | | Common commands for :code:`ChannelMessage` include :code:`.get_replies()` and :code:`.get_reply()` 109 | 110 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/utils.rst.txt: -------------------------------------------------------------------------------- 1 | ===== 2 | Utils 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | utils/query 10 | utils/token 11 | utils/utils 12 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/utils/query.rst.txt: -------------------------------------------------------------------------------- 1 | Query 2 | ===== 3 | 4 | Query Builder 5 | ------------- 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/latest/_sources/usage/utils/token.rst.txt: -------------------------------------------------------------------------------- 1 | Token 2 | ===== 3 | 4 | When initiating the account connection you may wish to store the token for ongoing usage, removing the need to re-authenticate every time. There are a variety of storage mechanisms available which are shown in the detailed api. 5 | 6 | FileSystemTokenBackend 7 | ---------------------- 8 | To store the token in your local file system, you can use the ``FileSystemTokenBackend``. This takes a path and a file name as parameters. 9 | 10 | For example: 11 | 12 | .. code-block:: python 13 | 14 | from O365 import Account, FileSystemTokenBackend 15 | 16 | token_backend = FileSystemTokenBackend(token_path=token_path, token_filename=token_filename) 17 | 18 | account = Account(credentials=('my_client_id', 'my_client_secret'), token_backend=token_backend) 19 | 20 | The methods are similar for the other token backends. 21 | 22 | You can also pass in a cryptography manager to the token backend so encrypt the token in the store, and to decrypt on retrieval. The cryptography manager must support the ``encrypt`` and ``decrypt`` methods. 23 | 24 | .. code-block:: python 25 | 26 | from O365 import Account, FileSystemTokenBackend 27 | from xxx import CryptoManager 28 | 29 | key = "my really secret key" 30 | mycryptomanager = CryptoManager(key) 31 | 32 | token_backend = FileSystemTokenBackend(token_path=token_path, token_filename=token_filename, cryptography_manager=mycryptomanager) 33 | 34 | account = Account(credentials=('my_client_id', 'my_client_secret'), token_backend=token_backend) -------------------------------------------------------------------------------- /docs/latest/_sources/usage/utils/utils.rst.txt: -------------------------------------------------------------------------------- 1 | Utils 2 | ===== 3 | Pagination 4 | ---------- 5 | When using certain methods, it is possible that you request more items than the api can return in a single api call. In this case the Api, returns a "next link" url where you can pull more data. 6 | 7 | When this is the case, the methods in this library will return a ``Pagination`` object which abstracts all this into a single iterator. The pagination object will request "next links" as soon as they are needed. 8 | 9 | For example: 10 | 11 | .. code-block:: python 12 | 13 | mailbox = account.mailbox() 14 | 15 | messages = mailbox.get_messages(limit=1500) # the MS Graph API have a 999 items limit returned per api call. 16 | 17 | # Here messages is a Pagination instance. It's an Iterator so you can iterate over. 18 | 19 | # The first 999 iterations will be normal list iterations, returning one item at a time. 20 | # When the iterator reaches the 1000 item, the Pagination instance will call the api again requesting exactly 500 items 21 | # or the items specified in the batch parameter (see later). 22 | 23 | for message in messages: 24 | print(message.subject) 25 | 26 | When using certain methods you will have the option to specify not only a limit option (the number of items to be returned) but a batch option. This option will indicate the method to request data to the api in batches until the limit is reached or the data consumed. This is useful when you want to optimize memory or network latency. 27 | 28 | For example: 29 | 30 | .. code-block:: python 31 | 32 | messages = mailbox.get_messages(limit=100, batch=25) 33 | 34 | # messages here is a Pagination instance 35 | # when iterating over it will call the api 4 times (each requesting 25 items). 36 | 37 | for message in messages: # 100 loops with 4 requests to the api server 38 | print(message.subject) 39 | 40 | Query helper 41 | ------------ 42 | Every ``ApiComponent`` (such as ``MailBox``) implements a new_query method that will return a ``Query`` instance. This ``Query`` instance can handle the filtering, sorting, selecting, expanding and search very easily. 43 | 44 | For example: 45 | 46 | .. code-block:: python 47 | 48 | query = mailbox.new_query() # you can use the shorthand: mailbox.q() 49 | 50 | query = query.on_attribute('subject').contains('george best').chain('or').startswith('quotes') 51 | 52 | # 'created_date_time' will automatically be converted to the protocol casing. 53 | # For example when using MS Graph this will become 'createdDateTime'. 54 | 55 | query = query.chain('and').on_attribute('created_date_time').greater(datetime(2018, 3, 21)) 56 | 57 | print(query) 58 | 59 | # contains(subject, 'george best') or startswith(subject, 'quotes') and createdDateTime gt '2018-03-21T00:00:00Z' 60 | # note you can pass naive datetimes and those will be converted to you local timezone and then send to the api as UTC in iso8601 format 61 | 62 | # To use Query objetcs just pass it to the query parameter: 63 | filtered_messages = mailbox.get_messages(query=query) 64 | 65 | You can also specify specific data to be retrieved with "select": 66 | 67 | .. code-block:: python 68 | 69 | # select only some properties for the retrieved messages: 70 | query = mailbox.new_query().select('subject', 'to_recipients', 'created_date_time') 71 | 72 | messages_with_selected_properties = mailbox.get_messages(query=query) 73 | 74 | You can also search content. As said in the graph docs: 75 | 76 | You can currently search only message and person collections. A $search request returns up to 250 results. You cannot use $filter or $orderby in a search request. 77 | 78 | If you do a search on messages and specify only a value without specific message properties, the search is carried out on the default search properties of from, subject, and body. 79 | 80 | .. code-block:: python 81 | 82 | # searching is the easy part ;) 83 | query = mailbox.q().search('george best is da boss') 84 | messages = mailbox.get_messages(query=query) 85 | 86 | Request Error Handling 87 | ---------------------- 88 | Whenever a Request error raises, the connection object will raise an exception. Then the exception will be captured and logged it to the stdout with its message, and return Falsy (None, False, [], etc...) 89 | 90 | HttpErrors 4xx (Bad Request) and 5xx (Internal Server Error) are considered exceptions and 91 | raised also by the connection. You can tell the ``Connection`` to not raise http errors by passing ``raise_http_errors=False`` (defaults to True). -------------------------------------------------------------------------------- /docs/latest/_static/_sphinx_javascript_frameworks_compat.js: -------------------------------------------------------------------------------- 1 | /* Compatability shim for jQuery and underscores.js. 2 | * 3 | * Copyright Sphinx contributors 4 | * Released under the two clause BSD licence 5 | */ 6 | 7 | /** 8 | * small helper function to urldecode strings 9 | * 10 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 11 | */ 12 | jQuery.urldecode = function(x) { 13 | if (!x) { 14 | return x 15 | } 16 | return decodeURIComponent(x.replace(/\+/g, ' ')); 17 | }; 18 | 19 | /** 20 | * small helper function to urlencode strings 21 | */ 22 | jQuery.urlencode = encodeURIComponent; 23 | 24 | /** 25 | * This function returns the parsed url parameters of the 26 | * current request. Multiple values per key are supported, 27 | * it will always return arrays of strings for the value parts. 28 | */ 29 | jQuery.getQueryParameters = function(s) { 30 | if (typeof s === 'undefined') 31 | s = document.location.search; 32 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 33 | var result = {}; 34 | for (var i = 0; i < parts.length; i++) { 35 | var tmp = parts[i].split('=', 2); 36 | var key = jQuery.urldecode(tmp[0]); 37 | var value = jQuery.urldecode(tmp[1]); 38 | if (key in result) 39 | result[key].push(value); 40 | else 41 | result[key] = [value]; 42 | } 43 | return result; 44 | }; 45 | 46 | /** 47 | * highlight a given string on a jquery object by wrapping it in 48 | * span elements with the given class name. 49 | */ 50 | jQuery.fn.highlightText = function(text, className) { 51 | function highlight(node, addItems) { 52 | if (node.nodeType === 3) { 53 | var val = node.nodeValue; 54 | var pos = val.toLowerCase().indexOf(text); 55 | if (pos >= 0 && 56 | !jQuery(node.parentNode).hasClass(className) && 57 | !jQuery(node.parentNode).hasClass("nohighlight")) { 58 | var span; 59 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 60 | if (isInSVG) { 61 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 62 | } else { 63 | span = document.createElement("span"); 64 | span.className = className; 65 | } 66 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 67 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 68 | document.createTextNode(val.substr(pos + text.length)), 69 | node.nextSibling)); 70 | node.nodeValue = val.substr(0, pos); 71 | if (isInSVG) { 72 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 73 | var bbox = node.parentElement.getBBox(); 74 | rect.x.baseVal.value = bbox.x; 75 | rect.y.baseVal.value = bbox.y; 76 | rect.width.baseVal.value = bbox.width; 77 | rect.height.baseVal.value = bbox.height; 78 | rect.setAttribute('class', className); 79 | addItems.push({ 80 | "parent": node.parentNode, 81 | "target": rect}); 82 | } 83 | } 84 | } 85 | else if (!jQuery(node).is("button, select, textarea")) { 86 | jQuery.each(node.childNodes, function() { 87 | highlight(this, addItems); 88 | }); 89 | } 90 | } 91 | var addItems = []; 92 | var result = this.each(function() { 93 | highlight(this, addItems); 94 | }); 95 | for (var i = 0; i < addItems.length; ++i) { 96 | jQuery(addItems[i].parent).before(addItems[i].target); 97 | } 98 | return result; 99 | }; 100 | 101 | /* 102 | * backward compatibility for jQuery.browser 103 | * This will be supported until firefox bug is fixed. 104 | */ 105 | if (!jQuery.browser) { 106 | jQuery.uaMatch = function(ua) { 107 | ua = ua.toLowerCase(); 108 | 109 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 110 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 111 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 112 | /(msie) ([\w.]+)/.exec(ua) || 113 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 114 | []; 115 | 116 | return { 117 | browser: match[ 1 ] || "", 118 | version: match[ 2 ] || "0" 119 | }; 120 | }; 121 | jQuery.browser = {}; 122 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 123 | } 124 | -------------------------------------------------------------------------------- /docs/latest/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/latest/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/css/style.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: none; 3 | } 4 | /* override table no-wrap */ 5 | .wy-table-responsive table td, .wy-table-responsive table th { 6 | white-space: normal; 7 | } 8 | 9 | /* 10 | Fix for horizontal stacking weirdness in the RTD theme with Python properties: 11 | https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 12 | */ 13 | .py.property { 14 | display: block !important; 15 | } -------------------------------------------------------------------------------- /docs/latest/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Base JavaScript utilities for all Sphinx HTML documentation. 3 | */ 4 | "use strict"; 5 | 6 | const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ 7 | "TEXTAREA", 8 | "INPUT", 9 | "SELECT", 10 | "BUTTON", 11 | ]); 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * Small JavaScript module for the documentation. 23 | */ 24 | const Documentation = { 25 | init: () => { 26 | Documentation.initDomainIndexTable(); 27 | Documentation.initOnKeyListeners(); 28 | }, 29 | 30 | /** 31 | * i18n support 32 | */ 33 | TRANSLATIONS: {}, 34 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 35 | LOCALE: "unknown", 36 | 37 | // gettext and ngettext don't access this so that the functions 38 | // can safely bound to a different name (_ = Documentation.gettext) 39 | gettext: (string) => { 40 | const translated = Documentation.TRANSLATIONS[string]; 41 | switch (typeof translated) { 42 | case "undefined": 43 | return string; // no translation 44 | case "string": 45 | return translated; // translation exists 46 | default: 47 | return translated[0]; // (singular, plural) translation tuple exists 48 | } 49 | }, 50 | 51 | ngettext: (singular, plural, n) => { 52 | const translated = Documentation.TRANSLATIONS[singular]; 53 | if (typeof translated !== "undefined") 54 | return translated[Documentation.PLURAL_EXPR(n)]; 55 | return n === 1 ? singular : plural; 56 | }, 57 | 58 | addTranslations: (catalog) => { 59 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 60 | Documentation.PLURAL_EXPR = new Function( 61 | "n", 62 | `return (${catalog.plural_expr})` 63 | ); 64 | Documentation.LOCALE = catalog.locale; 65 | }, 66 | 67 | /** 68 | * helper function to focus on search bar 69 | */ 70 | focusSearchBar: () => { 71 | document.querySelectorAll("input[name=q]")[0]?.focus(); 72 | }, 73 | 74 | /** 75 | * Initialise the domain index toggle buttons 76 | */ 77 | initDomainIndexTable: () => { 78 | const toggler = (el) => { 79 | const idNumber = el.id.substr(7); 80 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 81 | if (el.src.substr(-9) === "minus.png") { 82 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 83 | toggledRows.forEach((el) => (el.style.display = "none")); 84 | } else { 85 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 86 | toggledRows.forEach((el) => (el.style.display = "")); 87 | } 88 | }; 89 | 90 | const togglerElements = document.querySelectorAll("img.toggler"); 91 | togglerElements.forEach((el) => 92 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 93 | ); 94 | togglerElements.forEach((el) => (el.style.display = "")); 95 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 96 | }, 97 | 98 | initOnKeyListeners: () => { 99 | // only install a listener if it is really needed 100 | if ( 101 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 102 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 103 | ) 104 | return; 105 | 106 | document.addEventListener("keydown", (event) => { 107 | // bail for input elements 108 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 109 | // bail with special keys 110 | if (event.altKey || event.ctrlKey || event.metaKey) return; 111 | 112 | if (!event.shiftKey) { 113 | switch (event.key) { 114 | case "ArrowLeft": 115 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 116 | 117 | const prevLink = document.querySelector('link[rel="prev"]'); 118 | if (prevLink && prevLink.href) { 119 | window.location.href = prevLink.href; 120 | event.preventDefault(); 121 | } 122 | break; 123 | case "ArrowRight": 124 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 125 | 126 | const nextLink = document.querySelector('link[rel="next"]'); 127 | if (nextLink && nextLink.href) { 128 | window.location.href = nextLink.href; 129 | event.preventDefault(); 130 | } 131 | break; 132 | } 133 | } 134 | 135 | // some keyboard layouts may need Shift to get / 136 | switch (event.key) { 137 | case "/": 138 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 139 | Documentation.focusSearchBar(); 140 | event.preventDefault(); 141 | } 142 | }); 143 | }, 144 | }; 145 | 146 | // quick alias for translations 147 | const _ = Documentation.gettext; 148 | 149 | _ready(Documentation.init); 150 | -------------------------------------------------------------------------------- /docs/latest/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | const DOCUMENTATION_OPTIONS = { 2 | VERSION: '', 3 | LANGUAGE: 'en', 4 | COLLAPSE_INDEX: false, 5 | BUILDER: 'html', 6 | FILE_SUFFIX: '.html', 7 | LINK_SUFFIX: '.html', 8 | HAS_SOURCE: true, 9 | SOURCELINK_SUFFIX: '.txt', 10 | NAVIGATION_WITH_KEYS: false, 11 | SHOW_SEARCH_SUMMARY: true, 12 | ENABLE_SEARCH_SHORTCUTS: true, 13 | }; -------------------------------------------------------------------------------- /docs/latest/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/file.png -------------------------------------------------------------------------------- /docs/latest/_static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/latest/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/latest/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/latest/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/latest/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/latest/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 56 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 57 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 58 | var s_v = "^(" + C + ")?" + v; // vowel in stem 59 | 60 | this.stemWord = function (w) { 61 | var stem; 62 | var suffix; 63 | var firstch; 64 | var origword = w; 65 | 66 | if (w.length < 3) 67 | return w; 68 | 69 | var re; 70 | var re2; 71 | var re3; 72 | var re4; 73 | 74 | firstch = w.substr(0,1); 75 | if (firstch == "y") 76 | w = firstch.toUpperCase() + w.substr(1); 77 | 78 | // Step 1a 79 | re = /^(.+?)(ss|i)es$/; 80 | re2 = /^(.+?)([^s])s$/; 81 | 82 | if (re.test(w)) 83 | w = w.replace(re,"$1$2"); 84 | else if (re2.test(w)) 85 | w = w.replace(re2,"$1$2"); 86 | 87 | // Step 1b 88 | re = /^(.+?)eed$/; 89 | re2 = /^(.+?)(ed|ing)$/; 90 | if (re.test(w)) { 91 | var fp = re.exec(w); 92 | re = new RegExp(mgr0); 93 | if (re.test(fp[1])) { 94 | re = /.$/; 95 | w = w.replace(re,""); 96 | } 97 | } 98 | else if (re2.test(w)) { 99 | var fp = re2.exec(w); 100 | stem = fp[1]; 101 | re2 = new RegExp(s_v); 102 | if (re2.test(stem)) { 103 | w = stem; 104 | re2 = /(at|bl|iz)$/; 105 | re3 = new RegExp("([^aeiouylsz])\\1$"); 106 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 107 | if (re2.test(w)) 108 | w = w + "e"; 109 | else if (re3.test(w)) { 110 | re = /.$/; 111 | w = w.replace(re,""); 112 | } 113 | else if (re4.test(w)) 114 | w = w + "e"; 115 | } 116 | } 117 | 118 | // Step 1c 119 | re = /^(.+?)y$/; 120 | if (re.test(w)) { 121 | var fp = re.exec(w); 122 | stem = fp[1]; 123 | re = new RegExp(s_v); 124 | if (re.test(stem)) 125 | w = stem + "i"; 126 | } 127 | 128 | // Step 2 129 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 130 | if (re.test(w)) { 131 | var fp = re.exec(w); 132 | stem = fp[1]; 133 | suffix = fp[2]; 134 | re = new RegExp(mgr0); 135 | if (re.test(stem)) 136 | w = stem + step2list[suffix]; 137 | } 138 | 139 | // Step 3 140 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 141 | if (re.test(w)) { 142 | var fp = re.exec(w); 143 | stem = fp[1]; 144 | suffix = fp[2]; 145 | re = new RegExp(mgr0); 146 | if (re.test(stem)) 147 | w = stem + step3list[suffix]; 148 | } 149 | 150 | // Step 4 151 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 152 | re2 = /^(.+?)(s|t)(ion)$/; 153 | if (re.test(w)) { 154 | var fp = re.exec(w); 155 | stem = fp[1]; 156 | re = new RegExp(mgr1); 157 | if (re.test(stem)) 158 | w = stem; 159 | } 160 | else if (re2.test(w)) { 161 | var fp = re2.exec(w); 162 | stem = fp[1] + fp[2]; 163 | re2 = new RegExp(mgr1); 164 | if (re2.test(stem)) 165 | w = stem; 166 | } 167 | 168 | // Step 5 169 | re = /^(.+?)e$/; 170 | if (re.test(w)) { 171 | var fp = re.exec(w); 172 | stem = fp[1]; 173 | re = new RegExp(mgr1); 174 | re2 = new RegExp(meq1); 175 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 176 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 177 | w = stem; 178 | } 179 | re = /ll$/; 180 | re2 = new RegExp(mgr1); 181 | if (re.test(w) && re2.test(w)) { 182 | re = /.$/; 183 | w = w.replace(re,""); 184 | } 185 | 186 | // and turn initial Y back to y 187 | if (firstch == "y") 188 | w = firstch.toLowerCase() + w.substr(1); 189 | return w; 190 | } 191 | } 192 | 193 | -------------------------------------------------------------------------------- /docs/latest/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/minus.png -------------------------------------------------------------------------------- /docs/latest/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/_static/plus.png -------------------------------------------------------------------------------- /docs/latest/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #F00 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #04D } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #800 } /* Name.Constant */ 41 | .highlight .nd { color: #A2F } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #00F } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #BBB } /* Text.Whitespace */ 51 | .highlight .mb { color: #666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #00F } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/latest/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | const rest = document.createTextNode(val.substr(pos + text.length)); 33 | parent.insertBefore( 34 | span, 35 | parent.insertBefore( 36 | rest, 37 | node.nextSibling 38 | ) 39 | ); 40 | node.nodeValue = val.substr(0, pos); 41 | /* There may be more occurrences of search term in this node. So call this 42 | * function recursively on the remaining fragment. 43 | */ 44 | _highlight(rest, addItems, text, className); 45 | 46 | if (isInSVG) { 47 | const rect = document.createElementNS( 48 | "http://www.w3.org/2000/svg", 49 | "rect" 50 | ); 51 | const bbox = parent.getBBox(); 52 | rect.x.baseVal.value = bbox.x; 53 | rect.y.baseVal.value = bbox.y; 54 | rect.width.baseVal.value = bbox.width; 55 | rect.height.baseVal.value = bbox.height; 56 | rect.setAttribute("class", className); 57 | addItems.push({ parent: parent, target: rect }); 58 | } 59 | } 60 | } else if (node.matches && !node.matches("button, select, textarea")) { 61 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 62 | } 63 | }; 64 | const _highlightText = (thisNode, text, className) => { 65 | let addItems = []; 66 | _highlight(thisNode, addItems, text, className); 67 | addItems.forEach((obj) => 68 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 69 | ); 70 | }; 71 | 72 | /** 73 | * Small JavaScript module for the documentation. 74 | */ 75 | const SphinxHighlight = { 76 | 77 | /** 78 | * highlight the search words provided in localstorage in the text 79 | */ 80 | highlightSearchWords: () => { 81 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 82 | 83 | // get and clear terms from localstorage 84 | const url = new URL(window.location); 85 | const highlight = 86 | localStorage.getItem("sphinx_highlight_terms") 87 | || url.searchParams.get("highlight") 88 | || ""; 89 | localStorage.removeItem("sphinx_highlight_terms") 90 | url.searchParams.delete("highlight"); 91 | window.history.replaceState({}, "", url); 92 | 93 | // get individual terms from highlight string 94 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 95 | if (terms.length === 0) return; // nothing to do 96 | 97 | // There should never be more than one element matching "div.body" 98 | const divBody = document.querySelectorAll("div.body"); 99 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 100 | window.setTimeout(() => { 101 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 102 | }, 10); 103 | 104 | const searchBox = document.getElementById("searchbox"); 105 | if (searchBox === null) return; 106 | searchBox.appendChild( 107 | document 108 | .createRange() 109 | .createContextualFragment( 110 | '" 114 | ) 115 | ); 116 | }, 117 | 118 | /** 119 | * helper function to hide the search marks again 120 | */ 121 | hideSearchWords: () => { 122 | document 123 | .querySelectorAll("#searchbox .highlight-link") 124 | .forEach((el) => el.remove()); 125 | document 126 | .querySelectorAll("span.highlighted") 127 | .forEach((el) => el.classList.remove("highlighted")); 128 | localStorage.removeItem("sphinx_highlight_terms") 129 | }, 130 | 131 | initEscapeListener: () => { 132 | // only install a listener if it is really needed 133 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 134 | 135 | document.addEventListener("keydown", (event) => { 136 | // bail for input elements 137 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 138 | // bail with special keys 139 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 140 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 141 | SphinxHighlight.hideSearchWords(); 142 | event.preventDefault(); 143 | } 144 | }); 145 | }, 146 | }; 147 | 148 | _ready(() => { 149 | /* Do not call highlightSearchWords() when we are on the search page. 150 | * It will highlight words from the *previous* search query. 151 | */ 152 | if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); 153 | SphinxHighlight.initEscapeListener(); 154 | }); 155 | -------------------------------------------------------------------------------- /docs/latest/api/global.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <no title> — O365 documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 56 | 57 |
61 | 62 |
63 |
64 |
65 | 72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 |
80 |
81 |
82 | 83 |
84 | 85 |
86 |

© Copyright 2025, alejcas.

87 |
88 | 89 | Built with Sphinx using a 90 | theme 91 | provided by Read the Docs. 92 | 93 | 94 |
95 |
96 |
97 |
98 |
99 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/latest/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/O365/python-o365/442af954097ff3ecb96f935f82749e2cc5eeac72/docs/latest/objects.inv -------------------------------------------------------------------------------- /docs/latest/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search — O365 documentation 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 58 | 59 |
63 | 64 |
65 |
66 |
67 |
    68 |
  • 69 | 70 |
  • 71 |
  • 72 |
73 |
74 |
75 |
76 |
77 | 78 | 85 | 86 | 87 |
88 | 89 |
90 | 91 |
92 |
93 |
94 | 95 |
96 | 97 |
98 |

© Copyright 2025, alejcas.

99 |
100 | 101 | Built with Sphinx using a 102 | theme 103 | provided by Read the Docs. 104 | 105 | 106 |
107 |
108 |
109 |
110 |
111 | 116 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_static/css/style.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: none; 3 | } 4 | /* override table no-wrap */ 5 | .wy-table-responsive table td, .wy-table-responsive table th { 6 | white-space: normal; 7 | } 8 | 9 | /* 10 | Fix for horizontal stacking weirdness in the RTD theme with Python properties: 11 | https://github.com/readthedocs/sphinx_rtd_theme/issues/1301 12 | */ 13 | .py.property { 14 | display: block !important; 15 | } -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | O365 API 3 | ======== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | api/account 10 | api/address_book 11 | api/calendar 12 | api/category 13 | api/connection 14 | api/directory 15 | api/excel 16 | api/group 17 | api/mailbox 18 | api/message 19 | api/onedrive 20 | api/planner 21 | api/sharepoint 22 | api/tasks 23 | api/teams 24 | api/utils 25 | -------------------------------------------------------------------------------- /docs/source/api/account.rst: -------------------------------------------------------------------------------- 1 | Account 2 | ----------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.account 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/address_book.rst: -------------------------------------------------------------------------------- 1 | Address Book 2 | ------------ 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.address_book 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/calendar.rst: -------------------------------------------------------------------------------- 1 | Calendar 2 | -------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.calendar 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/category.rst: -------------------------------------------------------------------------------- 1 | Category 2 | -------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.category 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/connection.rst: -------------------------------------------------------------------------------- 1 | Connection 2 | ---------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.connection 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/directory.rst: -------------------------------------------------------------------------------- 1 | Directory 2 | --------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.directory 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/excel.rst: -------------------------------------------------------------------------------- 1 | Excel 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.excel 7 | :members: 8 | :undoc-members: 9 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/global.rst: -------------------------------------------------------------------------------- 1 | .. |br| raw:: html 2 | 3 |
   -------------------------------------------------------------------------------- /docs/source/api/group.rst: -------------------------------------------------------------------------------- 1 | Group 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.groups 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise 11 | -------------------------------------------------------------------------------- /docs/source/api/mailbox.rst: -------------------------------------------------------------------------------- 1 | Mailbox 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.mailbox 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/message.rst: -------------------------------------------------------------------------------- 1 | Message 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.message 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/onedrive.rst: -------------------------------------------------------------------------------- 1 | One Drive 2 | --------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.drive 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/planner.rst: -------------------------------------------------------------------------------- 1 | Planner 2 | ------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.planner 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/sharepoint.rst: -------------------------------------------------------------------------------- 1 | Sharepoint 2 | ---------- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.sharepoint 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/tasks.rst: -------------------------------------------------------------------------------- 1 | Tasks 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.tasks 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/teams.rst: -------------------------------------------------------------------------------- 1 | Teams 2 | ----- 3 | 4 | .. include:: global.rst 5 | 6 | .. automodule:: O365.teams 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/utils.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Utils 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | utils/attachment 10 | utils/query 11 | utils/token 12 | utils/utils 13 | -------------------------------------------------------------------------------- /docs/source/api/utils/attachment.rst: -------------------------------------------------------------------------------- 1 | Attachment 2 | ---------- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.attachment 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/utils/query.rst: -------------------------------------------------------------------------------- 1 | Query 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.query 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/utils/token.rst: -------------------------------------------------------------------------------- 1 | Token 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.token 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/api/utils/utils.rst: -------------------------------------------------------------------------------- 1 | Utils 2 | ----- 3 | 4 | .. include:: ../global.rst 5 | 6 | .. automodule:: O365.utils.utils 7 | :members: 8 | :undoc-members: 9 | :show-inheritance: 10 | :member-order: groupwise -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to O365's documentation! 2 | ================================ 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | :caption: Contents: 7 | 8 | overview 9 | getting_started 10 | usage 11 | api 12 | 13 | Indices and tables 14 | ================== 15 | 16 | * :ref:`genindex` 17 | * :ref:`modindex` 18 | * :ref:`search` 19 | -------------------------------------------------------------------------------- /docs/source/overview.rst: -------------------------------------------------------------------------------- 1 | ######## 2 | Overview 3 | ######## 4 | 5 | **O365 - Microsoft Graph API made easy** 6 | 7 | .. important:: 8 | 9 | With version 2.1 old access tokens will not work, and the library will require a new authentication flow to get new access and refresh tokens. 10 | 11 | This project aims to make interacting with Microsoft Graph easy to do in a Pythonic way. Access to Email, Calendar, Contacts, OneDrive, etc. Are easy to do in a way that feel easy and straight forward to beginners and feels just right to seasoned python programmer. 12 | 13 | The project is currently developed and maintained by `alejcas `_. 14 | 15 | Core developers 16 | --------------- 17 | * `Alejcas `_ 18 | * `Toben Archer `_ 19 | * `Geethanadh `_ 20 | 21 | We are always open to new pull requests! 22 | 23 | Quick example 24 | ------------- 25 | Here is a simple example showing how to send an email using python-o365. 26 | Create a Python file and add the following code: 27 | 28 | .. code-block:: python 29 | 30 | from O365 import Account 31 | 32 | credentials = ('client_id', 'client_secret') 33 | account = Account(credentials) 34 | 35 | m = account.new_message() 36 | m.to.add('to_example@example.com') 37 | m.subject = 'Testing!' 38 | m.body = "George Best quote: I've stopped drinking, but only while I'm asleep." 39 | m.send() 40 | 41 | 42 | Why choose O365? 43 | ---------------- 44 | * Almost Full Support for MsGraph Rest Api. 45 | * Full OAuth support with automatic handling of refresh tokens. 46 | * Automatic handling between local datetimes and server datetimes. Work with your local datetime and let this library do the rest. 47 | * Change between different resource with ease: access shared mailboxes, other users resources, SharePoint resources, etc. 48 | * Pagination support through a custom iterator that handles future requests automatically. Request Infinite items! 49 | * A query helper to help you build custom OData queries (filter, order, select and search). 50 | * Modular ApiComponents can be created and built to achieve further functionality. 51 | 52 | ---- 53 | 54 | This project was also a learning resource for us. This is a list of not so common python idioms used in this project: 55 | 56 | * New unpacking technics: ``def method(argument, *, with_name=None, **other_params)``: 57 | * Enums: from enum import Enum 58 | * Factory paradigm 59 | * Package organization 60 | * Timezone conversion and timezone aware datetimes 61 | * Etc. (see the code!) 62 | 63 | Rebuilding HTML Docs 64 | -------------------- 65 | * Install ``sphinx`` python library: 66 | 67 | .. code-block:: console 68 | 69 | pip install sphinx 70 | 71 | * Run the shell script ``build_docs.sh``, or copy the command from the file when using on Windows -------------------------------------------------------------------------------- /docs/source/usage.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Detailed Usage 3 | ============== 4 | 5 | .. toctree:: 6 | :maxdepth: 3 7 | :caption: Contents: 8 | 9 | usage/connection 10 | usage/account 11 | usage/addressbook 12 | usage/calendar 13 | usage/directory 14 | usage/excel 15 | usage/group 16 | usage/mailbox 17 | usage/onedrive 18 | usage/planner 19 | usage/sharepoint 20 | usage/tasks 21 | usage/teams 22 | usage/utils 23 | -------------------------------------------------------------------------------- /docs/source/usage/addressbook.rst: -------------------------------------------------------------------------------- 1 | Address Book 2 | ============ 3 | AddressBook groups the functionality of both the Contact Folders and Contacts. Outlook Distribution Groups are not supported (By the Microsoft API's). 4 | 5 | These are the scopes needed to work with the ``AddressBook`` and ``Contact`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Contacts.Read address_book To only read my personal contacts 11 | Contacts.Read.Shared address_book_shared To only read another user / shared mailbox contacts 12 | Contacts.ReadWrite address_book_all To read and save personal contacts 13 | Contacts.ReadWrite.Shared address_book_all_shared To read and save contacts from another user / shared mailbox 14 | User.ReadBasic.All users To only read basic properties from users of my organization (User.Read.All requires administrator consent). 15 | ========================= ======================================= ====================================== 16 | 17 | Contact Folders 18 | --------------- 19 | Represents a Folder within your Contacts Section in Office 365. AddressBook class represents the parent folder (it's a folder itself). 20 | 21 | You can get any folder in your address book by requesting child folders or filtering by name. 22 | 23 | .. code-block:: python 24 | 25 | address_book = account.address_book() 26 | 27 | contacts = address_book.get_contacts(limit=None) # get all the contacts in the Personal Contacts root folder 28 | 29 | work_contacts_folder = address_book.get_folder(folder_name='Work Contacts') # get a folder with 'Work Contacts' name 30 | 31 | message_to_all_contats_in_folder = work_contacts_folder.new_message() # creates a draft message with all the contacts as recipients 32 | 33 | message_to_all_contats_in_folder.subject = 'Hallo!' 34 | message_to_all_contats_in_folder.body = """ 35 | George Best quote: 36 | 37 | If you'd given me the choice of going out and beating four men and smashing a goal in 38 | from thirty yards against Liverpool or going to bed with Miss World, 39 | it would have been a difficult choice. Luckily, I had both. 40 | """ 41 | message_to_all_contats_in_folder.send() 42 | 43 | # querying folders is easy: 44 | child_folders = address_book.get_folders(25) # get at most 25 child folders 45 | 46 | for folder in child_folders: 47 | print(folder.name, folder.parent_id) 48 | 49 | # creating a contact folder: 50 | address_book.create_child_folder('new folder') 51 | 52 | .. _global_address_list: 53 | 54 | Global Address List 55 | ------------------- 56 | MS Graph API has no concept such as the Outlook Global Address List. 57 | However you can use the `Users API `_ to access all the users within your organization. 58 | 59 | Without admin consent you can only access a few properties of each user such as name and email and little more. You can search by name or retrieve a contact specifying the complete email. 60 | 61 | * Basic Permission needed is Users.ReadBasic.All (limit info) 62 | * Full Permission is Users.Read.All but needs admin consent. 63 | 64 | To search the Global Address List (Users API): 65 | 66 | .. code-block:: python 67 | 68 | global_address_list = account.directory() 69 | 70 | # for backwards compatibility only this also works and returns a Directory object: 71 | # global_address_list = account.address_book(address_book='gal') 72 | 73 | # start a new query: 74 | q = global_address_list.new_query('display_name') 75 | q.startswith('George Best') 76 | 77 | for user in global_address_list.get_users(query=q): 78 | print(user) 79 | 80 | To retrieve a contact by their email: 81 | 82 | .. code-block:: python 83 | 84 | contact = global_address_list.get_user('example@example.com') 85 | Contacts 86 | 87 | Everything returned from an AddressBook instance is a Contact instance. Contacts have all the information stored as attributes 88 | 89 | Creating a contact from an AddressBook: 90 | 91 | new_contact = address_book.new_contact() 92 | 93 | new_contact.name = 'George Best' 94 | new_contact.job_title = 'football player' 95 | new_contact.emails.add('george@best.com') 96 | 97 | new_contact.save() # saved on the cloud 98 | 99 | message = new_contact.new_message() # Bonus: send a message to this contact 100 | 101 | # ... 102 | 103 | new_contact.delete() # Bonus: deleted from the cloud -------------------------------------------------------------------------------- /docs/source/usage/calendar.rst: -------------------------------------------------------------------------------- 1 | Calendar 2 | ======== 3 | The calendar and events functionality is group in a Schedule object. 4 | 5 | A ``Schedule`` instance can list and create calendars. It can also list or create events on the default user calendar. To use other calendars use a ``Calendar`` instance. 6 | 7 | These are the scopes needed to work with the ``Schedule``, ``Calendar`` and ``Event`` classes. 8 | 9 | ========================== ======================================= ====================================== 10 | Raw Scope Included in Scope Helper Description 11 | ========================== ======================================= ====================================== 12 | Calendars.Read calendar To only read my personal calendars 13 | Calendars.Read.Shared calendar_shared To only read another user / shared mailbox calendars 14 | Calendars.ReadWrite calendar_all To read and save personal calendars 15 | Calendars.ReadWrite.Shared calendar_shared_all To read and save calendars from another user / shared mailbox 16 | ========================== ======================================= ====================================== 17 | 18 | Working with the ``Schedule`` instance: 19 | 20 | .. code-block:: python 21 | 22 | import datetime as dt 23 | 24 | # ... 25 | schedule = account.schedule() 26 | 27 | calendar = schedule.get_default_calendar() 28 | new_event = calendar.new_event() # creates a new unsaved event 29 | new_event.subject = 'Recruit George Best!' 30 | new_event.location = 'England' 31 | 32 | # naive datetimes will automatically be converted to timezone aware datetime 33 | # objects using the local timezone detected or the protocol provided timezone 34 | 35 | new_event.start = dt.datetime(2019, 9, 5, 19, 45) 36 | # so new_event.start becomes: datetime.datetime(2018, 9, 5, 19, 45, tzinfo=) 37 | 38 | new_event.recurrence.set_daily(1, end=dt.datetime(2019, 9, 10)) 39 | new_event.remind_before_minutes = 45 40 | 41 | new_event.save() 42 | 43 | Working with Calendar instances: 44 | 45 | .. code-block:: python 46 | 47 | calendar = schedule.get_calendar(calendar_name='Birthdays') 48 | 49 | calendar.name = 'Football players birthdays' 50 | calendar.update() 51 | 52 | 53 | start_q = calendar.new_query('start').greater_equal(dt.datetime(2018, 5, 20)) 54 | end_q = calendar.new_query('start').less_equal(dt.datetime(2018, 5, 24)) 55 | 56 | birthdays = calendar.get_events( 57 | include_recurring=True, # include_recurring=True will include repeated events on the result set. 58 | start_recurring=start_q, 59 | end_recurring=end_q, 60 | ) 61 | 62 | for event in birthdays: 63 | if event.subject == 'George Best Birthday': 64 | # He died in 2005... but we celebrate anyway! 65 | event.accept("I'll attend!") # send a response accepting 66 | else: 67 | event.decline("No way I'm coming, I'll be in Spain", send_response=False) # decline the event but don't send a response to the organizer 68 | 69 | **Notes regarding Calendars and Events**: 70 | 71 | 1. Include_recurring=True: 72 | 73 | It's important to know that when querying events with include_recurring=True (which is the default), 74 | it is required that you must provide start and end parameters, these may be simple date strings, python dates or individual queries. 75 | Unlike when using include_recurring=False those attributes will NOT filter the data based on the operations you set on the query (greater_equal, less, etc.) 76 | but just filter the events start datetime between the provided start and end datetimes. 77 | 78 | 2. Shared Calendars: 79 | 80 | There are some known issues when working with `shared calendars `_ in Microsoft Graph. 81 | 82 | 3. Event attachments: 83 | 84 | For some unknown reason, Microsoft does not allow to upload an attachment at the event creation time (as opposed with message attachments). 85 | See `this `_. So, to upload attachments to Events, first save the event, then attach the message and save again. -------------------------------------------------------------------------------- /docs/source/usage/connection.rst: -------------------------------------------------------------------------------- 1 | Protocols 2 | ========= 3 | Protocols handles the aspects of communications between different APIs. This project uses the Microsoft Graph APIs. But, you can use many other Microsoft APIs as long as you implement the protocol needed. 4 | 5 | You can use: 6 | 7 | * MSGraphProtocol to use the `Microsoft Graph API `_ 8 | 9 | .. code-block:: python 10 | 11 | from O365 import Account 12 | 13 | credentials = ('client_id', 'client_secret') 14 | 15 | account = Account(credentials, auth_flow_type='credentials', tenant_id='my_tenant_id') 16 | if account.authenticate(): 17 | print('Authenticated!') 18 | mailbox = account.mailbox('sender_email@my_domain.com') 19 | m = mailbox.new_message() 20 | m.to.add('to_example@example.com') 21 | m.subject = 'Testing!' 22 | m.body = "George Best quote: I've stopped drinking, but only while I'm asleep." 23 | m.save_message() 24 | m.attachment.add = 'filename.txt' 25 | m.send() 26 | 27 | The default protocol used by the ``Account`` Class is ``MSGraphProtocol``. 28 | 29 | You can implement your own protocols by inheriting from Protocol to communicate with other Microsoft APIs. 30 | 31 | You can instantiate and use protocols like this: 32 | 33 | .. code-block:: python 34 | 35 | from O365 import Account, MSGraphProtocol # same as from O365.connection import MSGraphProtocol 36 | 37 | # ... 38 | 39 | # try the api version beta of the Microsoft Graph endpoint. 40 | protocol = MSGraphProtocol(api_version='beta') # MSGraphProtocol defaults to v1.0 api version 41 | account = Account(credentials, protocol=protocol) 42 | 43 | 44 | Resources 45 | ========= 46 | Each API endpoint requires a resource. This usually defines the owner of the data. Every protocol defaults to resource 'ME'. 'ME' is the user which has given consent, but you can change this behaviour by providing a different default resource to the protocol constructor. 47 | 48 | .. note:: 49 | 50 | When using the "with your own identity" authentication method the resource 'ME' is overwritten to be blank as the authentication method already states that you are login with your own identity. 51 | 52 | For example when accessing a shared mailbox: 53 | 54 | .. code-block:: python 55 | 56 | # ... 57 | account = Account(credentials=my_credentials, main_resource='shared_mailbox@example.com') 58 | # Any instance created using account will inherit the resource defined for account. 59 | 60 | This can be done however at any point. For example at the protocol level: 61 | 62 | .. code-block:: python 63 | 64 | # ... 65 | protocol = MSGraphProtocol(default_resource='shared_mailbox@example.com') 66 | 67 | account = Account(credentials=my_credentials, protocol=protocol) 68 | 69 | # now account is accessing the shared_mailbox@example.com in every api call. 70 | shared_mailbox_messages = account.mailbox().get_messages() 71 | 72 | Instead of defining the resource used at the account or protocol level, you can provide it per use case as follows: 73 | 74 | .. code-block:: python 75 | 76 | # ... 77 | account = Account(credentials=my_credentials) # account defaults to 'ME' resource 78 | 79 | mailbox = account.mailbox('shared_mailbox@example.com') # mailbox is using 'shared_mailbox@example.com' resource instead of 'ME' 80 | 81 | # or: 82 | 83 | message = Message(parent=account, main_resource='shared_mailbox@example.com') # message is using 'shared_mailbox@example.com' resource 84 | 85 | Usually you will work with the default 'ME' resource, but you can also use one of the following: 86 | 87 | * 'me': the user which has given consent. The default for every protocol. Overwritten when using "with your own identity" authentication method (Only available on the authorization auth_flow_type). 88 | * 'user:user@domain.com': a shared mailbox or a user account for which you have permissions. If you don't provide 'user:' it will be inferred anyway. 89 | * 'site:sharepoint-site-id': a Sharepoint site id. 90 | * 'group:group-site-id': an Microsoft 365 group id. 91 | 92 | By setting the resource prefix (such as 'user:' or 'group:') you help the library understand the type of resource. You can also pass it like 'users/example@exampl.com'. The same applies to the other resource prefixes. -------------------------------------------------------------------------------- /docs/source/usage/directory.rst: -------------------------------------------------------------------------------- 1 | 2 | Directory and Users 3 | =================== 4 | The Directory object can retrieve users. 5 | 6 | A User instance contains by default the `basic properties of the user `_. If you want to include more, you will have to select the desired properties manually. 7 | 8 | Check :ref:`global_address_list` for further information. 9 | 10 | These are the scopes needed to work with the Directory class. 11 | 12 | ========================= ======================================= ====================================== 13 | Raw Scope Included in Scope Helper Description 14 | ========================= ======================================= ====================================== 15 | User.ReadBasic.All users To read a basic set of profile properties of other users in your organization on behalf of the signed-in user. This includes display name, first and last name, email address, open extensions and photo. Also allows the app to read the full profile of the signed-in user. 16 | User.Read.All — To read the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. 17 | User.ReadWrite.All — To read and write the full set of profile properties, reports, and managers of other users in your organization, on behalf of the signed-in user. Also allows the app to create and delete users as well as reset user passwords on behalf of the signed-in user. 18 | Directory.Read.All — To read data in your organization's directory, such as users, groups and apps, without a signed-in user. 19 | Directory.ReadWrite.All — To read and write data in your organization's directory, such as users, and groups, without a signed-in user. Does not allow user or group deletion. 20 | ========================= ======================================= ====================================== 21 | 22 | .. note:: 23 | 24 | To get authorized with the above scopes you need a work or school account, it doesn't work with personal account. 25 | 26 | Working with the ``Directory`` instance to read the active directory users: 27 | 28 | .. code-block:: python 29 | 30 | directory = account.directory() 31 | for user in directory.get_users(): 32 | print(user) 33 | -------------------------------------------------------------------------------- /docs/source/usage/excel.rst: -------------------------------------------------------------------------------- 1 | Excel 2 | ===== 3 | You can interact with new Excel files (.xlsx) stored in OneDrive or a SharePoint Document Library. You can retrieve workbooks, worksheets, tables, and even cell data. You can also write to any excel online. 4 | 5 | To work with Excel files, first you have to retrieve a ``File`` instance using the OneDrive or SharePoint functionality. 6 | 7 | The scopes needed to work with the ``WorkBook`` and Excel related classes are the same used by OneDrive. 8 | 9 | This is how you update a cell value: 10 | 11 | .. code-block:: python 12 | 13 | from O365.excel import WorkBook 14 | 15 | # given a File instance that is a xlsx file ... 16 | excel_file = WorkBook(my_file_instance) # my_file_instance should be an instance of File. 17 | 18 | ws = excel_file.get_worksheet('my_worksheet') 19 | cella1 = ws.get_range('A1') 20 | cella1.values = 35 21 | cella1.update() 22 | 23 | Workbook Sessions 24 | ----------------- 25 | 26 | When interacting with Excel, you can use a workbook session to efficiently make changes in a persistent or nonpersistent way. These sessions become usefull if you perform numerous changes to the Excel file. 27 | 28 | The default is to use a session in a persistent way. Sessions expire after some time of inactivity. When working with persistent sessions, new sessions will automatically be created when old ones expire. 29 | 30 | You can however change this when creating the ``Workbook`` instance: 31 | 32 | .. code-block:: python 33 | 34 | excel_file = WorkBook(my_file_instance, use_session=False, persist=False) 35 | 36 | Available Objects 37 | ----------------- 38 | 39 | After creating the ``WorkBook`` instance you will have access to the following objects: 40 | 41 | * WorkSheet 42 | * Range and NamedRange 43 | * Table, TableColumn and TableRow 44 | * RangeFormat (to format ranges) 45 | * Charts (not available for now) 46 | 47 | Some examples: 48 | 49 | Set format for a given range 50 | 51 | .. code-block:: python 52 | 53 | # ... 54 | my_range = ws.get_range('B2:C10') 55 | fmt = myrange.get_format() 56 | fmt.font.bold = True 57 | fmt.update() 58 | 59 | Autofit Columns: 60 | 61 | .. code-block:: python 62 | 63 | ws.get_range('B2:C10').get_format().auto_fit_columns() 64 | 65 | Get values from Table: 66 | 67 | .. code-block:: python 68 | 69 | table = ws.get_table('my_table') 70 | column = table.get_column_at_index(1) 71 | values = column.values[0] # values returns a two-dimensional array. 72 | -------------------------------------------------------------------------------- /docs/source/usage/group.rst: -------------------------------------------------------------------------------- 1 | Group 2 | ===== 3 | Groups enables viewing of groups 4 | 5 | These are the scopes needed to work with the ``Group`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Group.Read.All — To read groups 11 | ========================= ======================================= ====================================== 12 | 13 | Assuming an authenticated account and a previously created group, create a Plan instance. 14 | 15 | .. code-block:: python 16 | 17 | #Create a plan instance 18 | from O365 import Account 19 | account = Account(('app_id', 'app_pw')) 20 | groups = account.groups() 21 | 22 | # To retrieve the list of groups 23 | group_list = groups.list_groups() 24 | 25 | # Or to retrieve a list of groups for a given user 26 | user_groups = groups.get_user_groups(user_id="object_id") 27 | 28 | # To retrieve a group by an identifier 29 | group = groups.get_group_by_id(group_id="object_id") 30 | group = groups.get_group_by_mail(group_mail="john@doe.com") 31 | 32 | 33 | # To retrieve the owners and members of a group 34 | owners = group.get_group_owners() 35 | members = group.get_group_members() 36 | 37 | -------------------------------------------------------------------------------- /docs/source/usage/onedrive.rst: -------------------------------------------------------------------------------- 1 | OneDrive 2 | ======== 3 | The ``Storage`` class handles all functionality around One Drive and Document Library Storage in SharePoint. 4 | 5 | The ``Storage`` instance allows retrieval of ``Drive`` instances which handles all the Files 6 | and Folders from within the selected ``Storage``. Usually you will only need to work with the 7 | default drive. But the ``Storage`` instances can handle multiple drives. 8 | 9 | A ``Drive`` will allow you to work with Folders and Files. 10 | 11 | These are the scopes needed to work with the ``Storage``, ``Drive`` and ``DriveItem`` classes. 12 | 13 | ========================= ======================================= ====================================== 14 | Raw Scope Included in Scope Helper Description 15 | ========================= ======================================= ====================================== 16 | Files.Read — To only read my files 17 | Files.Read.All onedrive To only read all the files the user has access 18 | Files.ReadWrite — To read and save my files 19 | Files.ReadWrite.All onedrive_all To read and save all the files the user has access 20 | ========================= ======================================= ====================================== 21 | 22 | .. code-block:: python 23 | 24 | account = Account(credentials=my_credentials) 25 | 26 | storage = account.storage() # here we get the storage instance that handles all the storage options. 27 | 28 | # list all the drives: 29 | drives = storage.get_drives() 30 | 31 | # get the default drive 32 | my_drive = storage.get_default_drive() # or get_drive('drive-id') 33 | 34 | # get some folders: 35 | root_folder = my_drive.get_root_folder() 36 | attachments_folder = my_drive.get_special_folder('attachments') 37 | 38 | # iterate over the first 25 items on the root folder 39 | for item in root_folder.get_items(limit=25): 40 | if item.is_folder: 41 | print(list(item.get_items(2))) # print the first to element on this folder. 42 | elif item.is_file: 43 | if item.is_photo: 44 | print(item.camera_model) # print some metadata of this photo 45 | elif item.is_image: 46 | print(item.dimensions) # print the image dimensions 47 | else: 48 | # regular file: 49 | print(item.mime_type) # print the mime type 50 | 51 | Both Files and Folders are DriveItems. Both Image and Photo are Files, but Photo is also an Image. All have some different methods and properties. Take care when using 'is_xxxx'. 52 | 53 | When copying a DriveItem the api can return a direct copy of the item or a pointer to a resource that will inform on the progress of the copy operation. 54 | 55 | .. code-block:: python 56 | 57 | # copy a file to the documents special folder 58 | 59 | documents_folder = my_drive.get_special_folder('documents') 60 | 61 | files = my_drive.search('george best quotes', limit=1) 62 | 63 | if files: 64 | george_best_quotes = files[0] 65 | operation = george_best_quotes.copy(target=documents_folder) # operation here is an instance of CopyOperation 66 | 67 | # to check for the result just loop over check_status. 68 | # check_status is a generator that will yield a new status and progress until the file is finally copied 69 | for status, progress in operation.check_status(): # if it's an async operations, this will request to the api for the status in every loop 70 | print(f"{status} - {progress}") # prints 'in progress - 77.3' until finally completed: 'completed - 100.0' 71 | copied_item = operation.get_item() # the copy operation is completed so you can get the item. 72 | if copied_item: 73 | copied_item.delete() # ... oops! 74 | 75 | You can also work with share permissions: 76 | 77 | .. code-block:: python 78 | 79 | current_permisions = file.get_permissions() # get all the current permissions on this drive_item (some may be inherited) 80 | 81 | # share with link 82 | permission = file.share_with_link(share_type='edit') 83 | if permission: 84 | print(permission.share_link) # the link you can use to share this drive item 85 | # share with invite 86 | permission = file.share_with_invite(recipients='george_best@best.com', send_email=True, message='Greetings!!', share_type='edit') 87 | if permission: 88 | print(permission.granted_to) # the person you share this item with 89 | 90 | You can also: 91 | 92 | .. code-block:: python 93 | 94 | # download files: 95 | file.download(to_path='/quotes/') 96 | 97 | # upload files: 98 | 99 | # if the uploaded file is bigger than 4MB the file will be uploaded in chunks of 5 MB until completed. 100 | # this can take several requests and can be time consuming. 101 | uploaded_file = folder.upload_file(item='path_to_my_local_file') 102 | 103 | # restore versions: 104 | versions = file.get_versions() 105 | for version in versions: 106 | if version.name == '2.0': 107 | version.restore() # restore the version 2.0 of this file 108 | 109 | # ... and much more ... -------------------------------------------------------------------------------- /docs/source/usage/planner.rst: -------------------------------------------------------------------------------- 1 | Planner 2 | ======= 3 | Planner enables the creation and maintenance of plans, buckets and tasks 4 | 5 | These are the scopes needed to work with the ``Planner`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Group.Read.All — To only read plans 11 | Group.ReadWrite.All — To create and maintain a plan 12 | ========================= ======================================= ====================================== 13 | 14 | Assuming an authenticated account and a previously created group, create a Plan instance. 15 | 16 | .. code-block:: python 17 | 18 | #Create a plan instance 19 | from O365 import Account 20 | account = Account(('app_id', 'app_pw')) 21 | planner = account.planner() 22 | plan = planner.create_plan( 23 | owner="group_object_id", title="Test Plan" 24 | ) 25 | 26 | | Common commands for :code:`planner` include :code:`.create_plan()`, :code:`.get_bucket_by_id()`, :code:`.get_my_tasks()`, :code:`.list_group_plans()`, :code:`.list_group_tasks()` and :code:`.delete()`. 27 | | Common commands for :code:`plan` include :code:`.create_bucket()`, :code:`.get_details()`, :code:`.list_buckets()`, :code:`.list_tasks()` and :code:`.delete()`. 28 | 29 | Then to create a bucket within a plan. 30 | 31 | .. code-block:: python 32 | 33 | #Create a bucket instance in a plan 34 | bucket = plan.create_bucket(name="Test Bucket") 35 | 36 | Common commands for :code:`bucket` include :code:`.list_tasks()` and :code:`.delete()`. 37 | 38 | Then to create a task, assign it to a user, set it to 50% completed and add a description. 39 | 40 | .. code-block:: python 41 | 42 | #Create a task in a bucket 43 | assignments = { 44 | "user_object_id: { 45 | "@odata.type": "microsoft.graph.plannerAssignment", 46 | "orderHint": "1 !", 47 | } 48 | } 49 | task = bucket.create_task(title="Test Task", assignments=assignments) 50 | 51 | task.update(percent_complete=50) 52 | 53 | task_details = task.get_details() 54 | task_details.update(description="Test Description") 55 | 56 | Common commands for :code:`task` include :code:`.get_details()`, :code:`.update()` and :code:`.delete()`. -------------------------------------------------------------------------------- /docs/source/usage/sharepoint.rst: -------------------------------------------------------------------------------- 1 | Sharepoint 2 | ========== 3 | 4 | These are the scopes needed to work with the SharePoint and Site classes. 5 | 6 | ========================= ======================================= ======================================= 7 | Raw Scope Included in Scope Helper Description 8 | ========================= ======================================= ======================================= 9 | Sites.Read.All sharepoint To only read sites, lists and items 10 | Sites.ReadWrite.All sharepoint_dl To read and save sites, lists and items 11 | ========================= ======================================= ======================================= 12 | 13 | Assuming an authenticated account, create a Sharepoint instance, and connect 14 | to a Sharepoint site. 15 | 16 | .. code-block:: python 17 | 18 | #Create Sharepoint instance and connect to a site 19 | from O365 import Account 20 | acct = Account(('app_id', 'app_pw')) 21 | sp_site = acct.sharepoint().get_site('root', 'path/tosite') 22 | 23 | Common commands for :code:`sp_site` include :code:`.display_name`, 24 | :code:`.get_document_library()`, :code:`.get_subsites()`, :code:`.get_lists()`, 25 | and :code:`.get_list_by_name('list_name')`. 26 | 27 | **Accessing Subsites** 28 | 29 | If a Sharepoint site contains subsites they can be returned as a list of 30 | Sharepoint sites by the :code:`.get_subsites()` function. 31 | 32 | .. code-block:: python 33 | 34 | #Return a List of subsites 35 | sp_site_subsites = sp_site.get_subsites() 36 | print(sp_sites_subsites) 37 | [Site: subsitename1, Site: subsitename2] 38 | 39 | #Make another Site object from a desired subsite 40 | new_sp_site = sp_site_subsites[0] #return the first subsite 41 | 42 | Sharepoint Lists 43 | ^^^^^^^^^^^^^^^^ 44 | 45 | Sharepoint Lists are accessible from their Sharepoint site using :code:`.get_lists()` which 46 | returns a Python list of Sharepoint list objects. A known list can be accessed 47 | by providing a :code:`list_name` to :code:`.get_list_by_name('list_name')` which will return 48 | the requested list as a :code:`sharepointlist` object. 49 | 50 | .. code-block:: python 51 | 52 | #Return a list of sharepoint lists 53 | sp_site_lists = sp_site.get_lists() 54 | 55 | #Return a specific list by name 56 | sp_list = sp_site.get_list_by_name('list_name') 57 | 58 | 59 | Commmon functions on a Sharepoint list include :code:`.get_list_columns()`, 60 | :code:`.get_items()`, :code:`.get_item_by_id()`, :code:`.create_list_item()`, 61 | :code:`.delete_list_item()`. 62 | 63 | 64 | Sharepoint List Items 65 | """"""""""""""""""""" 66 | 67 | Accessing a list item from a Sharepoint list is done by utilizing :code:`.get_items()`, 68 | or :code:`.get_item_by_id(item_id)`. 69 | 70 | .. code-block:: python 71 | 72 | #Return a list of sharepoint list Items 73 | sp_list_items = sp_list.get_items() 74 | 75 | #Return a specific sharepoint list item by its object ID 76 | sp_list_item = sp_list.get_item_by_id(item_id) 77 | 78 | 79 | **Creating & Deleting Sharepoint Items** 80 | 81 | A Sharepoint list item can be created by passing the new data in a dictionary 82 | consisting of :code:`{'column_name': 'new_data'}`. Not all columns in the Sharepoint list have to 83 | be accounted for in the dictionary, any Sharepoint List column not in the dictionary 84 | will be filled with a blank. The `column_name` must be the internal column name 85 | of the sharepoint list. :code:`.column_name_cw` of a sharepoint list will provide a 86 | dictionary of :code:`{'Display Name': 'Internal Name'}` if needed. 87 | 88 | .. code-block:: python 89 | 90 | #Create a new sharepoint list item 91 | new_item = sp_list.create_list_item({'col1': 'New Data Col 1', 92 | 'col2': 'New Data Col 2'}) 93 | 94 | #Delete the item just created 95 | sp_list.delete_list_item(new_item.object_id) #Pass the item ID to be deleted 96 | 97 | **Updating a Sharepoint List Item** 98 | 99 | Sharepoint list items can be updated by passing a dictionary of 100 | :code:`{'column_name': 'Updated Data'}` to the :code:`.update_fields()` function of a 101 | Sharepoint list item. The `column_name` keys of the dictionary must again refer 102 | to the internal column name, otherwise an error will occur. 103 | 104 | .. code-block:: python 105 | 106 | #Update a Sharepoint List item 107 | new_item.update_fields({'col1': 'Updated Data Col1', 108 | 'col2': 'Updated Data Col2'}) 109 | 110 | #Once done updating a sharepoint item save changes to the cloud 111 | new_item.save_updates() #Returns True if successful 112 | -------------------------------------------------------------------------------- /docs/source/usage/tasks.rst: -------------------------------------------------------------------------------- 1 | Tasks 2 | ===== 3 | The tasks functionality is grouped in a ToDo object. 4 | 5 | A ToDo instance can list and create task folders. It can also list or create tasks on the default user folder. To use other folders use a Folder instance. 6 | 7 | These are the scopes needed to work with the ToDo, Folder and Task classes. 8 | 9 | ========================= ======================================= ====================================== 10 | Raw Scope Included in Scope Helper Description 11 | ========================= ======================================= ====================================== 12 | Tasks.Read tasks To only read my personal tasks 13 | Tasks.ReadWrite tasks_all To read and save personal calendars 14 | ========================= ======================================= ====================================== 15 | 16 | Working with the `ToDo`` instance: 17 | 18 | .. code-block:: python 19 | 20 | import datetime as dt 21 | 22 | # ... 23 | todo = account.tasks() 24 | 25 | #list current tasks 26 | folder = todo.get_default_folder() 27 | new_task = folder.new_task() # creates a new unsaved task 28 | new_task.subject = 'Send contract to George Best' 29 | new_task.due = dt.datetime(2020, 9, 25, 18, 30) 30 | new_task.save() 31 | 32 | #some time later.... 33 | 34 | new_task.mark_completed() 35 | new_task.save() 36 | 37 | # naive datetimes will automatically be converted to timezone aware datetime 38 | # objects using the local timezone detected or the protocol provided timezone 39 | # as with the Calendar functionality 40 | 41 | Working with Folder instances: 42 | 43 | .. code-block:: python 44 | 45 | #create a new folder 46 | new_folder = todo.new_folder('Defenders') 47 | 48 | #rename a folder 49 | folder = todo.get_folder(folder_name='Strikers') 50 | folder.name = 'Forwards' 51 | folder.update() 52 | 53 | #list current tasks 54 | task_list = folder.get_tasks() 55 | for task in task_list: 56 | print(task) 57 | print('') -------------------------------------------------------------------------------- /docs/source/usage/teams.rst: -------------------------------------------------------------------------------- 1 | Teams 2 | ===== 3 | Teams enables the communications via Teams Chat, plus Presence management 4 | 5 | These are the scopes needed to work with the ``Teams`` classes. 6 | 7 | ========================= ======================================= ====================================== 8 | Raw Scope Included in Scope Helper Description 9 | ========================= ======================================= ====================================== 10 | Channel.ReadBasic.All — To read basic channel information 11 | ChannelMessage.Read.All — To read channel messages 12 | ChannelMessage.Send — To send messages to a channel 13 | Chat.Read — To read users chat 14 | Chat.ReadWrite — To read users chat and send chat messages 15 | Presence.Read presence To read users presence status 16 | Presence.Read.All — To read any users presence status 17 | Presence.ReadWrite — To update users presence status 18 | Team.ReadBasic.All — To read only the basic properties for all my teams 19 | User.ReadBasic.All users To only read basic properties from users of my organization (User.Read.All requires administrator consent) 20 | ========================= ======================================= ====================================== 21 | 22 | Presence 23 | -------- 24 | Assuming an authenticated account. 25 | 26 | .. code-block:: python 27 | 28 | # Retrieve logged-in user's presence 29 | from O365 import Account 30 | account = Account(('app_id', 'app_pw')) 31 | teams = account.teams() 32 | presence = teams.get_my_presence() 33 | 34 | # Retrieve another user's presence 35 | user = account.directory().get_user("john@doe.com") 36 | presence2 = teams.get_user_presence(user.object_id) 37 | 38 | To set a users status or preferred status: 39 | 40 | .. code-block:: python 41 | 42 | # Set user's presence 43 | from O365.teams import Activity, Availability, PreferredActivity, PreferredAvailability 44 | 45 | status = teams.set_my_presence(CLIENT_ID, Availability.BUSY, Activity.INACALL, "1H") 46 | 47 | # or set User's preferred presence (which is more likely the one you want) 48 | 49 | status = teams.set_my_user_preferred_presence(PreferredAvailability.OFFLINE, PreferredActivity.OFFWORK, "1H") 50 | 51 | 52 | Chat 53 | ---- 54 | Assuming an authenticated account. 55 | 56 | .. code-block:: python 57 | 58 | # Retrieve logged-in user's chats 59 | from O365 import Account 60 | account = Account(('app_id', 'app_pw')) 61 | teams = account.teams() 62 | chats = teams.get_my_chats() 63 | 64 | # Then to retrieve chat messages and chat members 65 | for chat in chats: 66 | if chat.chat_type != "unknownFutureValue": 67 | message = chat.get_messages(limit=10) 68 | memberlist = chat.get_members() 69 | 70 | 71 | # And to send a chat message 72 | 73 | chat.send_message(content="Hello team!", content_type="text") 74 | 75 | | Common commands for :code:`Chat` include :code:`.get_member()` and :code:`.get_message()` 76 | 77 | 78 | Team 79 | ---- 80 | Assuming an authenticated account. 81 | 82 | .. code-block:: python 83 | 84 | # Retrieve logged-in user's teams 85 | from O365 import Account 86 | account = Account(('app_id', 'app_pw')) 87 | teams = account.teams() 88 | my_teams = teams.get_my_teams() 89 | 90 | # Then to retrieve team channels and messages 91 | for team in my_teams: 92 | channels = team.get_channels() 93 | for channel in channels: 94 | messages = channel.get_messages(limit=10) 95 | for channelmessage in messages: 96 | print(channelmessage) 97 | 98 | 99 | # To send a message to a team channel 100 | channel.send_message("Hello team") 101 | 102 | # To send a reply to a message 103 | channelmessage.send_message("Hello team leader") 104 | 105 | | Common commands for :code:`Teams` include :code:`.create_channel()`, :code:`.get_apps_in_channel()` and :code:`.get_channel()` 106 | | Common commands for :code:`Team` include :code:`.get_channel()` 107 | | Common commands for :code:`Channel` include :code:`.get_message()` 108 | | Common commands for :code:`ChannelMessage` include :code:`.get_replies()` and :code:`.get_reply()` 109 | 110 | -------------------------------------------------------------------------------- /docs/source/usage/utils.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Utils 3 | ===== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | utils/query 10 | utils/token 11 | utils/utils 12 | -------------------------------------------------------------------------------- /docs/source/usage/utils/query.rst: -------------------------------------------------------------------------------- 1 | Query 2 | ===== 3 | 4 | Query Builder 5 | ------------- 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/source/usage/utils/token.rst: -------------------------------------------------------------------------------- 1 | Token 2 | ===== 3 | 4 | When initiating the account connection you may wish to store the token for ongoing usage, removing the need to re-authenticate every time. There are a variety of storage mechanisms available which are shown in the detailed api. 5 | 6 | FileSystemTokenBackend 7 | ---------------------- 8 | To store the token in your local file system, you can use the ``FileSystemTokenBackend``. This takes a path and a file name as parameters. 9 | 10 | For example: 11 | 12 | .. code-block:: python 13 | 14 | from O365 import Account, FileSystemTokenBackend 15 | 16 | token_backend = FileSystemTokenBackend(token_path=token_path, token_filename=token_filename) 17 | 18 | account = Account(credentials=('my_client_id', 'my_client_secret'), token_backend=token_backend) 19 | 20 | The methods are similar for the other token backends. 21 | 22 | You can also pass in a cryptography manager to the token backend so encrypt the token in the store, and to decrypt on retrieval. The cryptography manager must support the ``encrypt`` and ``decrypt`` methods. 23 | 24 | .. code-block:: python 25 | 26 | from O365 import Account, FileSystemTokenBackend 27 | from xxx import CryptoManager 28 | 29 | key = "my really secret key" 30 | mycryptomanager = CryptoManager(key) 31 | 32 | token_backend = FileSystemTokenBackend(token_path=token_path, token_filename=token_filename, cryptography_manager=mycryptomanager) 33 | 34 | account = Account(credentials=('my_client_id', 'my_client_secret'), token_backend=token_backend) -------------------------------------------------------------------------------- /docs/source/usage/utils/utils.rst: -------------------------------------------------------------------------------- 1 | Utils 2 | ===== 3 | Pagination 4 | ---------- 5 | When using certain methods, it is possible that you request more items than the api can return in a single api call. In this case the Api, returns a "next link" url where you can pull more data. 6 | 7 | When this is the case, the methods in this library will return a ``Pagination`` object which abstracts all this into a single iterator. The pagination object will request "next links" as soon as they are needed. 8 | 9 | For example: 10 | 11 | .. code-block:: python 12 | 13 | mailbox = account.mailbox() 14 | 15 | messages = mailbox.get_messages(limit=1500) # the MS Graph API have a 999 items limit returned per api call. 16 | 17 | # Here messages is a Pagination instance. It's an Iterator so you can iterate over. 18 | 19 | # The first 999 iterations will be normal list iterations, returning one item at a time. 20 | # When the iterator reaches the 1000 item, the Pagination instance will call the api again requesting exactly 500 items 21 | # or the items specified in the batch parameter (see later). 22 | 23 | for message in messages: 24 | print(message.subject) 25 | 26 | When using certain methods you will have the option to specify not only a limit option (the number of items to be returned) but a batch option. This option will indicate the method to request data to the api in batches until the limit is reached or the data consumed. This is useful when you want to optimize memory or network latency. 27 | 28 | For example: 29 | 30 | .. code-block:: python 31 | 32 | messages = mailbox.get_messages(limit=100, batch=25) 33 | 34 | # messages here is a Pagination instance 35 | # when iterating over it will call the api 4 times (each requesting 25 items). 36 | 37 | for message in messages: # 100 loops with 4 requests to the api server 38 | print(message.subject) 39 | 40 | Query helper 41 | ------------ 42 | Every ``ApiComponent`` (such as ``MailBox``) implements a new_query method that will return a ``Query`` instance. This ``Query`` instance can handle the filtering, sorting, selecting, expanding and search very easily. 43 | 44 | For example: 45 | 46 | .. code-block:: python 47 | 48 | query = mailbox.new_query() # you can use the shorthand: mailbox.q() 49 | 50 | query = query.on_attribute('subject').contains('george best').chain('or').startswith('quotes') 51 | 52 | # 'created_date_time' will automatically be converted to the protocol casing. 53 | # For example when using MS Graph this will become 'createdDateTime'. 54 | 55 | query = query.chain('and').on_attribute('created_date_time').greater(datetime(2018, 3, 21)) 56 | 57 | print(query) 58 | 59 | # contains(subject, 'george best') or startswith(subject, 'quotes') and createdDateTime gt '2018-03-21T00:00:00Z' 60 | # note you can pass naive datetimes and those will be converted to you local timezone and then send to the api as UTC in iso8601 format 61 | 62 | # To use Query objetcs just pass it to the query parameter: 63 | filtered_messages = mailbox.get_messages(query=query) 64 | 65 | You can also specify specific data to be retrieved with "select": 66 | 67 | .. code-block:: python 68 | 69 | # select only some properties for the retrieved messages: 70 | query = mailbox.new_query().select('subject', 'to_recipients', 'created_date_time') 71 | 72 | messages_with_selected_properties = mailbox.get_messages(query=query) 73 | 74 | You can also search content. As said in the graph docs: 75 | 76 | You can currently search only message and person collections. A $search request returns up to 250 results. You cannot use $filter or $orderby in a search request. 77 | 78 | If you do a search on messages and specify only a value without specific message properties, the search is carried out on the default search properties of from, subject, and body. 79 | 80 | .. code-block:: python 81 | 82 | # searching is the easy part ;) 83 | query = mailbox.q().search('george best is da boss') 84 | messages = mailbox.get_messages(query=query) 85 | 86 | Request Error Handling 87 | ---------------------- 88 | Whenever a Request error raises, the connection object will raise an exception. Then the exception will be captured and logged it to the stdout with its message, and return Falsy (None, False, [], etc...) 89 | 90 | HttpErrors 4xx (Bad Request) and 5xx (Internal Server Error) are considered exceptions and 91 | raised also by the connection. You can tell the ``Connection`` to not raise http errors by passing ``raise_http_errors=False`` (defaults to True). -------------------------------------------------------------------------------- /examples/automatic_response_example.py: -------------------------------------------------------------------------------- 1 | from O365 import Account 2 | 3 | client_id = '' # Your client_id 4 | client_secret = '' # Your client_secret, create an (id, secret) at https://apps.dev.microsoft.com 5 | 6 | print("Connecting to O365") 7 | account = Account(credentials=(client_id, client_secret), auth_flow_type='authorization') 8 | if account.authenticate(scopes=['basic', 'MailboxSettings.ReadWrite']): 9 | print('Authenticated!') 10 | mailbox = account.mailbox() # here we get the storage instance that handles all the storage options. 11 | success = mailbox.set_automatic_reply("Internal response", "External response", "2022-11-05T08:00:00.0000000", "2022-12-09T16:00:00.00000000", 'Europe/Berlin') 12 | 13 | -------------------------------------------------------------------------------- /examples/before version 1/calendarCookbook.py: -------------------------------------------------------------------------------- 1 | from O365 import * 2 | 3 | # User's credentials 4 | e = 'email_address' 5 | p = 'password' 6 | 7 | # Create Schedule object, get calendars, and create empty dict 8 | schedule = Schedule((e, p)) 9 | result = schedule.getCalendars() 10 | 11 | # Funciton to invoke getName() and bind to var result 12 | def cal_name(): 13 | result = cal.getName() # This will get the name of a calendar 14 | return result 15 | 16 | # Show name of each calendar 17 | print('\nHere are your calendars:\n') 18 | for cal in schedule.calendars: 19 | print(cal_name()) 20 | 21 | # Get events for each calendar 22 | print('\nHere are all your upcoming events:\n') 23 | for cal in schedule.calendars: 24 | result = cal_name() 25 | events = cal.getEvents() # This will create an Event object 26 | for x in cal.events: 27 | contents = x.getSubject() # This will get the subject 28 | date = x.getStart() # This will get the start time and we'll parse it below 29 | print('{}-{} | {} : {}'.format(date.tm_mon, date.tm_mday, result, contents)) 30 | -------------------------------------------------------------------------------- /examples/before version 1/search_subfolders.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | from O365 import Connection, FluentInbox 3 | 4 | 5 | def main(): 6 | username = input("Username: ") 7 | password = getpass.getpass("Password: ") 8 | authentication = (username, password) 9 | Connection.login(*authentication) 10 | inbox = FluentInbox() 11 | 12 | # set inbox as current folder to use as parent, self.folder attribute 13 | inbox.from_folder("Inbox") 14 | 15 | # reset current folder as subfolder 16 | inbox.from_folder("Subfolder", parent_id=inbox.folder["Id"]) 17 | for msg in inbox.search("Subject:Urgent").fetch_first(10): 18 | print(msg.getSubject()) 19 | 20 | # reset current folder as a child folder of Subfolder 21 | inbox.from_folder("Sub_subfolder", parent_id=inbox.folder["Id"]) 22 | for msg in inbox.fetech_first(10): 23 | print(msg.getSubject()) 24 | 25 | return 0 26 | 27 | 28 | if __name__ == "__main__": 29 | main() -------------------------------------------------------------------------------- /examples/jwt_assertion.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import uuid 3 | from datetime import datetime, timezone, timedelta 4 | 5 | import jwt 6 | 7 | _ALGORITHM = "RS256" 8 | 9 | 10 | def _get_aud(tenant_id): 11 | return f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token" 12 | 13 | 14 | def create_jwt_assertion(private_key, tenant_id, thumbprint, client_id): 15 | """ 16 | Create a JWT assertion, used to obtain an auth token. 17 | 18 | 19 | @param private_key: Private key in PEM format from the certificate that was registered as credentials for the 20 | application. 21 | @param tenant_id: The directory tenant the application plans to operate against, in GUID or domain-name format. 22 | @param thumbprint: The X.509 certificate thumbprint. 23 | @param client_id: The application (client) ID that's assigned to the app. 24 | @return: JWT assertion to be used to obtain an auth token. 25 | """ 26 | x5t = codecs.encode(codecs.decode(thumbprint, "hex"), "base64").replace(b"\n", b"").decode() 27 | aud = _get_aud(tenant_id) 28 | 29 | now = datetime.now(tz=timezone.utc) 30 | exp = now + timedelta(hours=1) 31 | jti = str(uuid.uuid4()) 32 | 33 | payload = { 34 | "aud": aud, 35 | "exp": exp, 36 | "iss": client_id, 37 | "jti": jti, 38 | "nbf": now, 39 | "sub": client_id, 40 | "iat": now 41 | } 42 | headers = { 43 | "alg": _ALGORITHM, 44 | "typ": "JWT", 45 | "x5t": x5t, 46 | } 47 | encoded = jwt.encode(payload, private_key, algorithm=_ALGORITHM, headers=headers) 48 | 49 | return encoded 50 | 51 | 52 | def decode_jwt_assertion(jwt_assertion, public_key, tenant_id): 53 | """ 54 | Decode a JWT assertion, the opposite to 'create_jwt_assertion'. 55 | 56 | @param jwt_assertion: The JWT assertion obtained to be decoded. 57 | @param public_key: Public key in PEM format from the certificate that was registered as credentials for the 58 | application. 59 | @param tenant_id: The directory tenant the application plans to operate against, in GUID or domain-name format. 60 | @return: The decoded assertion. 61 | """ 62 | aud = _get_aud(tenant_id) 63 | decoded = jwt.decode(jwt_assertion, public_key, audience=aud, algorithms=[_ALGORITHM]) 64 | 65 | return decoded 66 | -------------------------------------------------------------------------------- /examples/storageDownloadFile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # To generate an Office365 token: 4 | 5 | # python3 6 | # from O365 import Account 7 | # account = Account(credentials=('yourregisteredappname', 'yoursecret')) 8 | # account.authenticate(scopes=['files.read', 'user.read', 'offline_access']) 9 | 10 | # It will return a URL, go to this in a browser, accept the permissions, then paste in the URL you are redirected to 11 | # YOU MAY HAVE TO SWITCH TO THE 'OLD' VIEW TO DO THIS! 12 | 13 | import pandas as pd 14 | from O365 import Account 15 | 16 | # Generated on the app registration portal 17 | registered_app_name='yourregisteredappname' 18 | registered_app_secret='yoursecret' 19 | 20 | # File to download, and location to download to 21 | dl_path='/path/to/download' 22 | f_name='myfile.xlsx' 23 | 24 | print("Connecting to O365") 25 | account = Account(credentials=(registered_app_name, registered_app_secret), scopes=['files.read', 'user.read', 'offline_access']) 26 | 27 | storage = account.storage() # here we get the storage instance that handles all the storage options. 28 | 29 | # get the default drive 30 | my_drive = storage.get_default_drive() 31 | 32 | print("Searching for {}...".format(f_name)) 33 | files = my_drive.search(f_name, limit=1) 34 | if files: 35 | numberDoc = files[0] 36 | print("... copying to local machine") 37 | operation = numberDoc.download(to_path=dl_path) 38 | else: 39 | print("File not found!") 40 | exit() 41 | 42 | print("Reading sheet to dataframe") 43 | df = pd.read_excel('{}/{}'.format(dl_path, f_name)) 44 | 45 | with pd.option_context('display.max_rows', None, 'display.max_columns', None): 46 | print(df) 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | dynamic = ["license"] 3 | name = "o365" 4 | version = "2.1.4" 5 | description = "O365 - Microsoft Graph and Office 365 API made easy" 6 | readme = "README.md" 7 | requires-python = ">=3.9" 8 | authors = [ 9 | { name = "Alejcas", email = "alejcas@users.noreply.github.com" }, 10 | { name = "Narcolapser", email = "narcolapser@users.noreply.github.com" }, 11 | { name = "Roycem90", email = "roycem90@users.noreply.github.com" } 12 | ] 13 | maintainers = [{ name = "Alejcas", email = "alejcas@users.noreply.github.com" }] 14 | classifiers = [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: Apache Software License", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Operating System :: OS Independent", 24 | ] 25 | dependencies = [ 26 | "beautifulsoup4>=4.12.3", 27 | "msal>=1.31.1", 28 | "python-dateutil>=2.9.0.post0", 29 | "requests>=2.32.3", 30 | "tzdata>=2024.2", 31 | "tzlocal>=5.2", 32 | ] 33 | 34 | [dependency-groups] 35 | dev = [ 36 | "click>=8.1.8", 37 | "pytest>=8.3.4", 38 | "sphinx>=7.4.7", 39 | "sphinx-rtd-theme>=3.0.2", 40 | ] 41 | 42 | # This is a fix for an issue in setuptools. See: https://github.com/pypa/setuptools/issues/4759 43 | # This shoulde be removed when the issue is resolved. 44 | [tool.setuptools] 45 | license-files = [] 46 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | requests>=2.31.0 2 | msal>=1.31.1 3 | python-dateutil>=2.7 4 | tzlocal>=5.0 5 | beautifulsoup4>=4.0.0 6 | tzdata>=2023.4 7 | Click>=7.0 8 | pytest>=3.9.0 9 | twine>=1.12.0 10 | wheel>=0.32.0 11 | -------------------------------------------------------------------------------- /requirements-pages.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | sphinx 3 | sphinx_rtd_theme 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | msal>=1.31.1 2 | requests>=2.32.0 3 | python-dateutil>=2.7 4 | tzlocal>=5.0 5 | beautifulsoup4>=4.0.0 6 | tzdata>=2023.4 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup, find_packages 4 | 5 | 6 | VERSION = '2.1.4' 7 | 8 | # Available classifiers: https://pypi.org/pypi?%3Aaction=list_classifiers 9 | CLASSIFIERS = [ 10 | 'Development Status :: 4 - Beta', 11 | 'Intended Audience :: Developers', 12 | 'License :: OSI Approved :: Apache Software License', 13 | 'Topic :: Office/Business :: Office Suites', 14 | 'Topic :: Software Development :: Libraries', 15 | 'Programming Language :: Python', 16 | 'Programming Language :: Python :: 3 :: Only', 17 | 'Programming Language :: Python :: 3.9', 18 | 'Programming Language :: Python :: 3.10', 19 | 'Programming Language :: Python :: 3.11', 20 | 'Programming Language :: Python :: 3.12', 21 | 'Programming Language :: Python :: 3.13', 22 | 'Operating System :: OS Independent', 23 | ] 24 | 25 | 26 | def read(fname): 27 | with open(os.path.join(os.path.dirname(__file__), fname), 'r') as file: 28 | return file.read() 29 | 30 | 31 | requires = [ 32 | 'requests>=2.32.0', 33 | 'msal>=1.31.1', 34 | 'python-dateutil>=2.7', 35 | 'tzlocal>=5.0', 36 | 'beautifulsoup4>=4.0.0', 37 | 'tzdata>=2023.4' 38 | ] 39 | 40 | setup( 41 | name='o365', 42 | version=VERSION, 43 | packages=find_packages(), 44 | url='https://github.com/O365/python-o365', 45 | license='Apache License 2.0', 46 | author='Alejcas, Roycem90, Narcolapser', 47 | author_email='alejcas@users.noreply.github.com', 48 | maintainer='alejcas', 49 | maintainer_email='alejcas@users.noreply.github.com', 50 | description='Microsoft Graph API made easy', 51 | long_description=read('README.md'), 52 | long_description_content_type="text/markdown", 53 | classifiers=CLASSIFIERS, 54 | python_requires=">=3.9", 55 | install_requires=requires, 56 | setup_requires=["wheel"], 57 | ) 58 | -------------------------------------------------------------------------------- /tests/run_tests_notes.txt: -------------------------------------------------------------------------------- 1 | To run this tests you will need pytest installed. 2 | 3 | This tests also needs a "config.py" file with two variables: 4 | 5 | CLIENT_ID = 'you client_id' 6 | CLIENT_SECRET = 'your client_secret' 7 | 8 | For oauth to work you will need to include the o365_token.txt file inside the tests folder once it's configured from the standard oauth authorization flow. -------------------------------------------------------------------------------- /tests/test_account.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from O365 import Account 3 | from .config import Config 4 | from O365.utils import EnvTokenBackend 5 | import logging 6 | log = logging.getLogger(__name__) 7 | 8 | class TestAccount: 9 | 10 | @pytest.mark.parametrize("pop, key_to_pop", [ 11 | (False, ""), 12 | (True, "scopes"), 13 | (True, "password"), 14 | (True, "tenant_id"), 15 | (True, "username"), 16 | (True, "auth_flow_type"), 17 | ]) 18 | def test_authentication(self, pop, key_to_pop): 19 | """ 20 | Test the new auth flow type "password" 21 | """ 22 | kwargs = { 23 | "scopes" : ["basic"], 24 | "tenant_id" : Config.TENANT_ID, 25 | "username" : Config.EMAIL, 26 | "password" : Config.PASSWORD, 27 | "auth_flow_type" : 'password' 28 | } 29 | 30 | if pop: 31 | kwargs.pop(key_to_pop) 32 | 33 | # ValueError: When using the "credentials" or "password" auth_flow the "tenant_id" must be set 34 | if "tenant_id" in key_to_pop: 35 | with pytest.raises(ValueError): 36 | account = Account((Config.CLIENT_ID),**kwargs) 37 | 38 | # ValueError: auth_flow_type is needed 39 | if "auth_flow_type" in key_to_pop: 40 | with pytest.raises(ValueError): 41 | account = Account((Config.CLIENT_ID),**kwargs) 42 | 43 | if key_to_pop not in ("tenant_id", "auth_flow_type"): 44 | account = Account((Config.CLIENT_ID),**kwargs) 45 | # instantiate an account with "offline_access" (scopes="basic" is a default scope that includes "offline_access") 46 | # will give the possibility to use a refresh_token 47 | if not pop: 48 | account.authenticate() 49 | assert account.is_authenticated 50 | assert account.con.refresh_token() 51 | 52 | # instantiate an account without scopes -> no refresh_token 53 | if "scopes" in key_to_pop: 54 | account.authenticate() 55 | assert account.is_authenticated 56 | assert not account.con.refresh_token() 57 | 58 | # Cannot account authenticate without password or username 59 | if key_to_pop in ("username", "password"): 60 | assert not account.authenticate() 61 | 62 | 63 | @pytest.mark.parametrize("token", [ 64 | "authenticate", 65 | "load", 66 | "delete", 67 | ]) 68 | def test_auth_with_environment_variable_token_storage(self, token): 69 | """ 70 | Test the authentication with the new token storage system. 71 | we will use EnvTokenBackend(BaseTokenBackend) 72 | default environment variable name is "O365TOKEN", initialize the class with another token_env_name to change it 73 | """ 74 | env_token = EnvTokenBackend() 75 | kwargs = { 76 | "scopes": ["basic"], 77 | "tenant_id": Config.TENANT_ID, 78 | "username": Config.EMAIL, 79 | "password": Config.PASSWORD, 80 | "auth_flow_type": 'password', 81 | "token_backend": env_token 82 | } 83 | 84 | if token in ("authenticate", "load"): 85 | 86 | # if "load" the account (so the connection) will be initialized with a valid token loaded from environment variable 87 | # this can work only if there is an already valid token stored 88 | kwargs["token_backend"].token = env_token.load_token() if token == "load" else None 89 | account = Account((Config.CLIENT_ID), **kwargs) 90 | # if "authenticate" token will be requested to Microsoft server and stored in environmental variable 91 | account.authenticate() if token == "authenticate" else None 92 | 93 | assert account.is_authenticated 94 | assert account.con.refresh_token() 95 | assert account.con.token_backend.check_token() 96 | 97 | else: 98 | # The authentication fails after loading a None token. 99 | env_token.delete_token() 100 | kwargs["token_backend"].token = env_token.load_token() 101 | account = Account((Config.CLIENT_ID), **kwargs) 102 | 103 | assert not account.is_authenticated 104 | assert not account.con.token_backend.check_token() -------------------------------------------------------------------------------- /tests/test_connection.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import json 3 | 4 | from O365.connection import Connection, Protocol, MSGraphProtocol, MSOffice365Protocol, DEFAULT_SCOPES 5 | 6 | 7 | class TestConnection: 8 | 9 | def setup_class(self): 10 | pass 11 | 12 | def teardown_class(self): 13 | pass 14 | 15 | def test_blank_connection(self): 16 | with pytest.raises(TypeError): 17 | c1 = Connection() 18 | 19 | -------------------------------------------------------------------------------- /tests/test_mailbox.py: -------------------------------------------------------------------------------- 1 | from O365 import Account 2 | import json 3 | 4 | class MockConnection: 5 | 6 | ret_value = None 7 | 8 | def get(self, url, params=None, **kwargs): 9 | self.url = url 10 | self.kwargs = kwargs 11 | 12 | class TestMailBox: 13 | 14 | def setup_class(self): 15 | credentials = ("client id","client secret") 16 | self.account = Account(credentials) 17 | self.mailbox = self.account.mailbox() 18 | self.mailbox.con = MockConnection() 19 | 20 | def teardown_class(self): 21 | pass 22 | 23 | def test_mailbox(self): 24 | assert self.mailbox.root 25 | 26 | # def test_get_mailbox_folders(self): 27 | # self.mailbox.con.ret_value = ['Inbox','Drafts'] 28 | # 29 | # folders = self.mailbox.get_folders(limit=5) 30 | # 31 | # assert len(folders) > 0 32 | -------------------------------------------------------------------------------- /tests/test_protocol.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import json 3 | 4 | from zoneinfo import ZoneInfoNotFoundError 5 | from tzlocal import get_localzone 6 | 7 | from O365.connection import Connection, Protocol, MSGraphProtocol, MSOffice365Protocol, DEFAULT_SCOPES 8 | 9 | TEST_SCOPES = [ 10 | 'Calendars.Read', 'Calendars.Read.Shared', 'Calendars.ReadWrite', 'Calendars.ReadWrite.Shared', 11 | 'Contacts.Read', 'Contacts.Read.Shared', 'Contacts.ReadWrite', 'Contacts.ReadWrite.Shared', 12 | 'Files.Read.All', 'Files.ReadWrite.All', 13 | 'Mail.Read', 'Mail.Read.Shared', 'Mail.ReadWrite', 'Mail.ReadWrite.Shared', 'Mail.Send', 'Mail.Send.Shared', 14 | 'MailboxSettings.ReadWrite', 15 | 'Presence.Read', 16 | 'Sites.Read.All', 'Sites.ReadWrite.All', 17 | 'Tasks.Read', 'Tasks.ReadWrite', 18 | 'User.Read', 'User.ReadBasic.All', 19 | 'offline_access' 20 | ] 21 | 22 | class TestProtocol: 23 | 24 | def setup_class(self): 25 | self.proto = Protocol(protocol_url="testing", api_version="0.0") 26 | 27 | def teardown_class(self): 28 | pass 29 | 30 | def test_blank_protocol(self): 31 | with pytest.raises(ValueError): 32 | p = Protocol() 33 | 34 | def test_to_api_case(self): 35 | assert(self.proto.to_api_case("CaseTest") == "case_test") 36 | 37 | def test_get_scopes_for(self): 38 | with pytest.raises(ValueError): 39 | self.proto.get_scopes_for(123) # should error sicne it's not a list or tuple. 40 | 41 | assert(self.proto.get_scopes_for(['mailbox']) == ['mailbox']) 42 | 43 | assert(self.proto.get_scopes_for(None) == []) 44 | 45 | assert(self.proto.get_scopes_for('mailbox') == ['mailbox']) 46 | 47 | self.proto._oauth_scopes = DEFAULT_SCOPES 48 | 49 | assert(self.proto.get_scopes_for(['mailbox']) == ['Mail.Read']) 50 | 51 | # This test verifies that the scopes in the default list don't change 52 | #without us noticing. It makes sure that all the scopes we get back are 53 | #in the current set of scopes we expect. And all the scopes that we are 54 | #expecting are in the scopes we are getting back. The list contains the 55 | #same stuff but may not be in the same order and are therefore not equal 56 | scopes = self.proto.get_scopes_for(None) 57 | for scope in scopes: 58 | assert(scope in TEST_SCOPES) 59 | for scope in TEST_SCOPES: 60 | assert(scope in scopes) 61 | 62 | assert(self.proto.get_scopes_for('mailbox') == ['Mail.Read']) 63 | 64 | def test_prefix_scope(self): 65 | assert(self.proto.prefix_scope('Mail.Read') == 'Mail.Read') 66 | 67 | self.proto.protocol_scope_prefix = 'test_prefix_' 68 | 69 | assert(self.proto.prefix_scope('test_prefix_Mail.Read') == 'test_prefix_Mail.Read') 70 | 71 | assert(self.proto.prefix_scope('Mail.Read') == 'test_prefix_Mail.Read') 72 | 73 | def test_decendant_MSOffice365Protocol(self): 74 | # Basically we just test that it can create the class w/o erroring. 75 | msp = MSOffice365Protocol() 76 | 77 | # Make sure these don't change without going noticed. 78 | assert(msp.keyword_data_store['message_type'] == 'Microsoft.OutlookServices.Message') 79 | assert(msp.keyword_data_store['file_attachment_type'] == '#Microsoft.OutlookServices.FileAttachment') 80 | assert(msp.keyword_data_store['item_attachment_type'] == '#Microsoft.OutlookServices.ItemAttachment') 81 | assert(msp.max_top_value == 999) 82 | -------------------------------------------------------------------------------- /tests/test_recipient.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from O365.utils import Recipient 4 | 5 | 6 | class TestRecipient: 7 | def setup_class(self): 8 | pass 9 | 10 | def teardown_class(self): 11 | pass 12 | 13 | def test_recipient_str(self): 14 | recipient = Recipient() 15 | assert str(recipient) == "" 16 | 17 | recipient = Recipient(address="john@example.com") 18 | assert str(recipient) == "john@example.com" 19 | 20 | recipient = Recipient(address="john@example.com", name="John Doe") 21 | assert str(recipient) == "John Doe " 22 | --------------------------------------------------------------------------------