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
21 changes: 16 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ version: 2
updates:
- package-ecosystem: "gradle"
directory: "/client-samples/java/rest"
schedule:
interval: "weekly"
day: "monday"
time: "08:00"
commit-message:
prefix: "gradle"
prefix: "gradle"

- package-ecosystem: "pip"
directory: "/client-samples/python/rest"
commit-message:
prefix: "pip-rest"

- package-ecosystem: "pip"
directory: "/client-samples/python/websockets"
commit-message:
prefix: "pip-websocket"

schedule:
interval: "monthly"
day: "monday"
time: "08:00"
33 changes: 33 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Python Tests

on:
pull_request:
branches:
- main
jobs:
python-checkout-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
working-directory:
- ./client-samples/python/rest
- ./client-samples/python/websockets
defaults:
run:
working-directory: ${{ matrix.working-directory }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Upgrade pip
run: python -m pip install --upgrade pip
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run pytest
run: pytest tests/
- name: Run black check (linting)
run: black --check .
172 changes: 172 additions & 0 deletions client-samples/python/rest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# PyPI configuration file
.pypirc
16 changes: 11 additions & 5 deletions client-samples/python/rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ Once Python is installed you can run following to launch the application. Please

Windows:
```cmd
python -m venv virtualenv
.\virtualenv\Scripts\activate
python -m venv venv
.\venv\Scripts\activate

python -m pip install --upgrade pip
python -m pip install -r requirements.txt
pip install -r requirements.txt

python client-application.py
```

Mac/Linux:
```bash
python -m venv virtualenv
. virtualenv/bin/activate
python -m venv venv
. venv/bin/activate

python -m pip install --upgrade pip
pip install -r requirements.txt
Expand All @@ -66,6 +66,12 @@ python client-application.py

The application will launch and connect to the Morgan Stanley API offering and output the result.

## Testing
The tests have been built using the `unittest` framework, but can be run using the `pytest` command.
```bash
pytest tests
```

## Linting
This project uses `black` to lint its source code for readability.
To lint the code from inside the virtual env please run the following:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
# Morgan Stanley makes this available to you under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
# Morgan Stanley makes this available to you under the Apache License, Version 2.0 (the "License"). You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
# See the NOTICE file distributed with this work for additional information regarding copyright ownership.
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and limitations under the License.

from msal import ConfidentialClientApplication
import sys
import json
import logging
import requests
import time
from typing import List
from typing import List, Union

# uncomment this line for DEBUG level logging in case of errors
# logging.basicConfig(level=logging.DEBUG)


def load_config(config_file: str):
"""
Load the config map from a JSON file with the given path.
Expand All @@ -40,7 +38,7 @@ def load_private_key(private_key_file: str):
return f.read()


def get_proxies(config: dict) -> dict | None:
def get_proxies(config: dict) -> Union[dict, None]:
"""
Returns proxy config from the config dictionary if the correct config has been provided.
Otherwise returns None.
Expand All @@ -63,7 +61,7 @@ def get_proxies(config: dict) -> dict | None:
return proxies


def get_requests_ca_bundle(config: dict) -> str | bool:
def get_requests_ca_bundle(config: dict) -> Union[str, bool]:
"""
Get the system CA bundle, if it's set. This is only necessary if your environment uses a proxy, since the bundled certificates will not work.
This returns True if no CA bundle is set; this tells requests to use the default, bundled certificates.
Expand Down Expand Up @@ -109,7 +107,7 @@ def get_client_app(config: dict):
authority=authority,
client_credential={"thumbprint": thumbprint, "private_key": private_key},
proxies=proxies,
verify=requests_ca_bundle
verify=requests_ca_bundle,
)


Expand Down Expand Up @@ -141,25 +139,31 @@ def acquire_token(app: ConfidentialClientApplication, scopes: List[str]):
return result["access_token"]


if __name__ == "__main__":
print("Starting Client application")
config = load_config("config.json")
def call_api(config: dict):
"""
Call API using access token.

Parameters
----------
config: dict
The config map to use.
"""
app = get_client_app(config)
access_token = acquire_token(app, config["scopes"])

proxies = get_proxies(config)
url = config["url"]
print("Calling API...")
# call API using access token
return requests.get(
config["url"],
headers={"Authorization": "Bearer " + access_token},
proxies=get_proxies(config),
verify=get_requests_ca_bundle(config),
)

requests_ca_bundle = get_requests_ca_bundle(config)

print("Calling API.")
# Call API using the access token
response = requests.get( # Use token to call downstream service
url,
headers={"Authorization": "Bearer " + access_token},
proxies=proxies,
verify=requests_ca_bundle
).json()
if __name__ == "__main__":
print("Starting Client application")
config = load_config("config.json")

response = call_api(config)
print("API call result: %s" % json.dumps(response, indent=2))
Binary file modified client-samples/python/rest/requirements.txt
Binary file not shown.
12 changes: 12 additions & 0 deletions client-samples/python/rest/tests/test-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"client_id": "CLIENT-ID",
"scopes": [
"API-SCOPE"
],
"thumbprint": "CERT-THUMBPRINT",
"private_key_file": "PRIVATE-KEY-PATH",
"tenant": "TENANT",
"proxy_host": "PROXY-HOST",
"proxy_port": "PROXY-PORT",
"url": "HTTP://URL/"
}
Loading
Loading