Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
name: "CodeQL"

on:
pull_request:
push:
branches: [main, develop]
schedule:
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
name: pre-commit

on:
pull_request:
push:
branches: [main, develop]
branches: [develop]

jobs:
pre-commit:
Expand Down
86 changes: 45 additions & 41 deletions .github/workflows/pull-request-labels.yaml
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
name: Add labels to the pull request on main for bumping the version
name: Labels pull request

on:
pull_request:
branches:
- 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 }}
- 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 }}
7 changes: 3 additions & 4 deletions .github/workflows/pypi-publish.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Python tests

on:
pull_request:
push:
branches: [main, develop]

Expand Down
30 changes: 1 addition & 29 deletions .github/workflows/release-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Release on push to main branch

on:
push:
branches: ["main"]
branches: [main]

jobs:
release-on-push:
Expand All @@ -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
171 changes: 171 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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="<YOUR ACCOUNT ID>",
client_id="<YOUR CLIENT ID>",
client_secret="<YOUR 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="<YOUR ACCOUNT ID>",
client_id="<YOUR CLIENT ID>",
client_secret="<YOUR 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)
```
26 changes: 26 additions & 0 deletions ms_python_client/utils/logger.py
Original file line number Diff line number Diff line change
@@ -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")
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <samuel.guillemet@telecom-sudparis.eu>"]
Expand Down
9 changes: 9 additions & 0 deletions tests/ms_python_client/utils/test_logger.py
Original file line number Diff line number Diff line change
@@ -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