diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 51b3e43..5c329ce 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,7 +12,6 @@ name: "CodeQL" on: - pull_request: push: branches: [main, develop] schedule: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 8e48735..d4d418e 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,9 +1,8 @@ name: pre-commit on: - pull_request: push: - branches: [main, develop] + branches: [develop] jobs: pre-commit: diff --git a/.github/workflows/pull-request-labels.yaml b/.github/workflows/pull-request-labels.yaml index 3aec93f..a3a48b0 100644 --- a/.github/workflows/pull-request-labels.yaml +++ b/.github/workflows/pull-request-labels.yaml @@ -1,4 +1,4 @@ -name: Add labels to the pull request on main for bumping the version +name: Labels pull request on: pull_request: @@ -6,45 +6,49 @@ on: - main jobs: - add-labels: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 + add-labels: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 - - name: Get the current version - id: get_current_version - run: | - echo "$(cat pyproject.toml | grep version | cut -d' ' -f3 | sed 's/"//g')" > $VERSION + - name: Get the current version + id: get_current_version + run: | + echo "version=$(cat pyproject.toml | grep version | cut -d' ' -f3 | sed 's/"//g')" >> $GITHUB_OUTPUT - - name: Get the last release version - uses: actions/github-script@v3 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const labels = ["bump:patch", "bump:minor", "bump:major"]; - const last_release = await github.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - const last_release_tag = last_release.data.tag_name; - const last_release_version = last_release_tag.replace("v", ""); - if (last_release_version === process.env.VERSION) { - core.setFailed("The version has not been bumped"); - } else { - const last_release_version_split = last_release_version.split("."); - const current_version_split = process.env.VERSION.split("."); - if (last_release_version_split[0] !== current_version_split[0]) { - core.setOutput("label", labels[2]); - } else if (last_release_version_split[1] !== current_version_split[1]) { - core.setOutput("label", labels[1]); - } else { - core.setOutput("label", labels[0]); - } - } - - name: Add labels - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: ${{ steps.get_current_version.outputs.label }} \ No newline at end of file + - name: Get the label to add + id: get_label + uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labels = ["release:patch", "release:minor", "release:major"]; + const last_release = await github.repos.getLatestRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + const last_release_tag = last_release.data.tag_name; + const last_release_version = last_release_tag.replace("v", ""); + if (last_release_version === process.env.VERSION) { + core.setFailed("The version has not been bumped"); + } else { + const last_release_version_split = last_release_version.split("."); + const current_version_split = process.env.VERSION.split("."); + if (last_release_version_split[0] !== current_version_split[0]) { + core.setOutput("label", labels[2]); + } else if (last_release_version_split[1] !== current_version_split[1]) { + core.setOutput("label", labels[1]); + } else { + core.setOutput("label", labels[0]); + } + } + env: + VERSION: ${{ steps.get_current_version.outputs.version }} + + - name: Add labels + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: ${{ steps.get_label.outputs.label }} diff --git a/.github/workflows/pypi-publish.yml b/.github/workflows/pypi-publish.yml index 1dda7cb..94f5ac3 100644 --- a/.github/workflows/pypi-publish.yml +++ b/.github/workflows/pypi-publish.yml @@ -1,9 +1,8 @@ name: Build and publish Python package on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" # for beta releases + release: + types: [created] + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 224411b..bb7a4e0 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -1,7 +1,6 @@ name: Python tests on: - pull_request: push: branches: [main, develop] diff --git a/.github/workflows/release-on-push.yml b/.github/workflows/release-on-push.yml index 0719340..8e48c50 100644 --- a/.github/workflows/release-on-push.yml +++ b/.github/workflows/release-on-push.yml @@ -4,7 +4,7 @@ name: Release on push to main branch on: push: - branches: ["main"] + branches: [main] jobs: release-on-push: @@ -26,31 +26,3 @@ jobs: outputs: tag_name: ${{ steps.release.outputs.tag_name }} version: ${{ steps.release.outputs.version }} - - update-pyproject: - needs: release-on-push - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Get the version built by the previous job and remove the v prefix - id: get-version - run: echo "${{ needs.release-on-push.outputs.version }}" | sed 's/^v//' > $GITHUB_ENV_VERSION - - - name: Verify the matching version in pyproject.toml - run: | - if [ "$(cat pyproject.toml | grep version | cut -d' ' -f3 | sed 's/"//g')" != "$GITHUB_ENV_VERSION" ]; then - echo "The version in pyproject.toml does not match the version built by the previous job" - sed -i "s/version = .*/version = \"$GITHUB_ENV_VERSION\"/" pyproject.toml - fi - - - name: Update pyproject.toml - run: | - git config --global user.name "GitHub Action" - git config --global user.email "SamuelGuillemet@users.noreply.github.com" - git add pyproject.toml - git commit -m "Update pyproject.toml version to $GITHUB_ENV_VERSION" - git push diff --git a/README.md b/README.md new file mode 100644 index 0000000..1cab31e --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# Microsoft Python client + +[![Python tests](https://github.com/cern-vc/MS-python-client/actions/workflows/python-tests.yml/badge.svg)](https://github.com/cern-vc/MS-python-client/actions/workflows/python-tests.yml) +[![pre-commit](https://github.com/cern-vc/MS-python-client/actions/workflows/pre-commit.yaml/badge.svg)](https://github.com/cern-vc/MS-python-client/actions/workflows/pre-commit.yaml) +[![CodeQL](https://github.com/cern-vc/MS-python-client/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/cern-vc/MS-python-client/actions/workflows/codeql-analysis.yml) +[![codecov](https://codecov.io/gh/cern-vc/MS-python-client/branch/main/graph/badge.svg?token=04EY0K0P2S)](https://codecov.io/gh/cern-vc/MS-python-client) + +Microsoft graph API Python client with support for [Server to Server Oauth tokens](https://learn.microsoft.com/en-us/graph/auth/auth-concepts?view=graph-rest-1.0) with App Only access. + +## Install + +This package is [available on Pypi](https://pypi.org/project/MS-python-client/) + +```bash +pip install MS-python-client +``` + +## Requirements + +- Python >= 3.10 + +## Usage + +### Defining your env variables + +Define the following variables in your `env` or your `.env` file: + +- ms_ACCOUNT_ID +- ms_CLIENT_ID +- ms_CLIENT_SECRET + +#### For testing purposes + +For testing purposes, you can use the following values: + +- ms_ACCESS_TOKEN + +This token could be obtained from the [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) by clicking on the `Sign in with Microsoft` button and then clicking on the `Access Token` tab. + +### Initialize the MSApiClient from environment variables + +```python +from ms_python_client.ms_api_client import MSApiClient + +ms_client = MSApiClient.init_from_env() +``` + +### Initialize the MSApiClient from .env + +```python +from ms_python_client.ms_api_client import MSApiClient + +ms_client = MSApiClient.init_from_dotenv() +``` + +### Initialize the MSApiClient manually + +```python +from ms_python_client.ms_api_client import MSApiClient + +ms_client = MSApiClient( + account_id="", + client_id="", + client_secret="") +``` + +### Use the file system to store the access token instead of environment + +There are some cases where you might want to store the access token in the file system in order to share its value with other elements of the application (Ex. different pods on a Kubernetes/Openshift application). + +You can define the path where the token will be stored, passing the `use_path` variable to the constructor: + +```python +from ms_python_client.ms_api_client import MSApiClient + +ms_client = MSApiClient( + account_id="", + client_id="", + client_secret="", + use_path="/path/to/token/folder") +``` + +## How to make API calls + +```python +USER_ID = "12345" +SUBJECT = "Test meeting" + +query = {"$count": "true", "$filter": f"contains(subject,'{SUBJECT}')"} +result = cern_ms_client.events.list_events(USER_ID, query) +``` + +## Optional: How to configure the logging + +```python +from ms_python_client.utils.logger import setup_logs + +setup_logs(log_level=logging.DEBUG) +``` + +## Available endpoints + +### **events**: + +1. get all events +2. get a single event +3. create an event +4. update an event +5. delete an event + +## CERN specific endpoints + +Instead of using the `MSApiClient` class, you can use the `CERNMSApiClient` class, which is a subclass of the `MSApiClient` class. +This class will provide you some more utilities but it will only work for CERN users (obviously). + +This will be used in the context of synchronizing the events between the CERN Indico system and the calendars of the Zoom Rooms. + +### How to initialize the CERNMSApiClient + +```python +from ms_python_client.cern_ms_api_client import CERNMSApiClient + +cern_ms_client = CERNMSApiClient.init_from_env() +``` + +### Available endpoints + +#### **events**: + +1. get all events +2. get a single event using indico id +3. create an event +4. update an event using indico id +5. delete an event using indico id + +You will find useful the `EventParameters` and `PartialEventParameters` classes, which will help you to create the events. + +**indico_event_id** is the id of the event in the indico system which is mandatory to create an event. + +**USER_ID** is the email of the Zoom Room. + +```python +from ms_python_client.cern_ms_api_client import ( + CERNMSApiClient, + EventParameters, + PartialEventParameters, + ) + +cern_ms_client = CERNMSApiClient.init_from_env() + +USER_ID = os.getenv("USER_ID") # Which is the email of the Zoom Room +INDICO_EVENT_ID = os.getenv("INDICO_EVENT_ID") + +event_parameters = EventParameters( + subject="Test meeting", + start_time="2021-10-01T12:00:00", + end_time="2021-10-01T13:00:00", + timezone="Europe/Zurich", + indico_event_id=INDICO_EVENT_ID, + zoom_url="https://cern.zoom.us/******", +) + +partial_event_parameters = PartialEventParameters( + indico_event_id=INDICO_EVENT_ID, + end_time="2021-10-01T14:00:00", +) # You can update only the end_time of the event for example + +cern_ms_client.events.create_event(USER_ID, event_parameters) +cern_ms_client.events.update_event_by_indico_id(USER_ID, partial_event_parameters) +cern_ms_client.events.delete_event_by_indico_id(USER_ID, INDICO_EVENT_ID) +``` diff --git a/ms_python_client/utils/logger.py b/ms_python_client/utils/logger.py new file mode 100644 index 0000000..2feb83c --- /dev/null +++ b/ms_python_client/utils/logger.py @@ -0,0 +1,26 @@ +import logging +import sys + + +def setup_logs(log_level=logging.WARNING): + logger = logging.getLogger("ms_python_client") + + logger.setLevel(log_level) + + formatter = logging.Formatter( + "%(asctime)s | %(levelname)s | %(name)s | %(message)s | %(pathname)s:%(lineno)d | %(funcName)s()" + ) + + configure_stdout_logging(logger=logger, formatter=formatter, log_level=log_level) + + return logger + + +def configure_stdout_logging(logger, formatter=None, log_level=logging.WARNING): + stream_handler = logging.StreamHandler(stream=sys.stdout) + + stream_handler.setFormatter(formatter) + stream_handler.setLevel(log_level) + + logger.addHandler(stream_handler) + print(f"Logging {str(logger)} to stdout -> True") diff --git a/pyproject.toml b/pyproject.toml index 41a3ceb..7f28a12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "ms-python-client" -version = "0.0.1" +version = "0.0.2" exclude = ["tests*", "example*", ".github*", ".git*", ".vscode*"] description = "This package is used to interact with the microsoft grap API" authors = ["Samuel Guillemet "] diff --git a/tests/ms_python_client/utils/test_logger.py b/tests/ms_python_client/utils/test_logger.py new file mode 100644 index 0000000..5a3e542 --- /dev/null +++ b/tests/ms_python_client/utils/test_logger.py @@ -0,0 +1,9 @@ +import logging + +from ms_python_client.utils.logger import setup_logs + + +def test_setup_logs(): + logger = setup_logs(log_level=logging.DEBUG) + + assert logger is not None