-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add tool to upload to IBM Quantum Learning (#358)
This moves a custom tool from https://github.com/frankharkins/learning-content-tools to this repo as per [request](Qiskit/qiskit-ibm-runtime#1111 (comment)). This tool is not actually used in this repo, it will just live here to be owned by the Qiskit organisation. --------- Co-authored-by: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com>
- Loading branch information
1 parent
eac006e
commit 3823b49
Showing
10 changed files
with
547 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# This code is a Qiskit project. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
name: Test learning platform upload tool | ||
on: | ||
pull_request: | ||
paths: | ||
- "scripts/ibm-quantum-learning-uploader/**" | ||
workflow_dispatch: | ||
schedule: | ||
# At 08:00 on Monday (https://crontab.guru/#0_8_*_*_1) | ||
- cron: "0 8 * * 1" | ||
jobs: | ||
e2e: | ||
name: End-to-end test | ||
if: github.event.pull_request.head.repo.full_name == github.repository | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-python@v4 | ||
with: | ||
python-version: "3.11" | ||
- name: Install Firefox | ||
uses: browser-actions/setup-firefox@v1 | ||
- name: Run test | ||
run: | | ||
cd scripts/ibm-quantum-learning-uploader/test | ||
./e2e-test.sh | ||
env: | ||
LEARNING_API_TOKEN: ${{ secrets.LEARNING_API_TOKEN_STAGING }} | ||
LEARNING_API_ENVIRONMENT: staging | ||
|
||
make_issue: | ||
name: Make issue on failure | ||
needs: [e2e] | ||
if: ${{ failure() && github.event_name == 'schedule' }} | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Post issue | ||
uses: actions/github-script@v7 | ||
with: | ||
script: | | ||
const message = `Today's scheduled e2e test of the upload tool failed. | ||
Please [check the logs](https://github.com/Qiskit/documentation/actions/runs/${context.workflow}). | ||
> [!NOTE] | ||
> This issue was created by a GitHub action. | ||
` | ||
github.rest.issues.create({ | ||
owner: context.repo.owner, | ||
repo: context.repo.repo, | ||
title: "Learning upload tool test failed", | ||
body: message, | ||
assignees: ["frankharkins"] | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
|
||
.ipynb_checkpoints/ | ||
.DS_Store | ||
__pycache__/ | ||
|
||
node_modules | ||
tsconfig.tsbuildinfo | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Upload to IBM Quantum Learning | ||
|
||
This script uploads lessons to [IBM Quantum | ||
Learning](learning.quantum-computing.ibm.com). | ||
|
||
Lessons to be uploaded must be a directory containing a single notebook and any | ||
images it uses (the API can't handle image attachments inside the notebooks). | ||
|
||
```sh | ||
# Example | ||
lesson-root | ||
├── index.ipynb | ||
└── images | ||
└── image-1.png | ||
``` | ||
|
||
To sync your lessons automatically with the API, you'll need to add a | ||
`learning-platform.conf.yaml` to the root of your content folder. | ||
|
||
```yaml | ||
# learning-platform.conf.yaml | ||
# This goes in the root of your content folder | ||
lessons: | ||
- path: path/to/folder # path to folder containing the lesson | ||
# Lesson IDs in the API: | ||
idStaging: 4e85c04a-c2fb-4bfc-9077-b75bf1b73a25 | ||
idProduction: 5026731b-5e7b-4585-8cf2-f24482819e21 | ||
``` | ||
To upload lessons using our script: | ||
1. Install the package | ||
```bash | ||
pip install git+https://github.com/Qiskit/documentation.git#subdirectory=scripts/ibm-quantum-learning-uploader | ||
``` | ||
2. Run this from the root of your content folder: | ||
|
||
```bash | ||
sync-lessons | ||
``` | ||
|
||
You'll be prompted for your username and password. | ||
|
||
You can also upload just one lesson at a time using | ||
|
||
```bash | ||
sync-lessons path/to/folder | ||
``` | ||
|
||
To use this script as part of CI (Travis / GitHub actions), set the | ||
`LEARNING_API_TOKEN` environment variable to your access token before running | ||
the script, and `LEARNING_API_ENVIRONMENT` to the name of the API (`staging` or | ||
`production`). You should also pin the package to a specific commit, like so: | ||
|
||
```bash | ||
pip install git+https://github.com/Qiskit/documentation.git@<commit-hash>#subdirectory=scripts/ibm-quantum-learning-uploader | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
[build-system] | ||
requires = ["setuptools>=61.0"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[project] | ||
name = "ibm-quantum-learning-uploader" | ||
version = "0.0.1" | ||
authors = [ | ||
{ name="Qiskit Development Team", email="hello@qiskit.org" }, | ||
] | ||
description = "Tool for IBM Quantum Learning content creators to upload lessons through the API." | ||
readme = "README.md" | ||
requires-python = ">=3.8" | ||
classifiers = [ | ||
"Programming Language :: Python :: 3", | ||
"License :: OSI Approved :: Apache 2.0 License", | ||
"Operating System :: OS Independent", | ||
] | ||
dependencies = [ | ||
"pyyaml", | ||
"yaspin", | ||
"requests~=2.28" | ||
] | ||
|
||
[project.urls] | ||
"Homepage" = "https://github.com/Qiskit/documentation/tree/main/scripts/ibm-quantum-learning-uploader" | ||
"Bug Tracker" = "https://github.com/Qiskit/documentation/issues" | ||
|
||
[project.scripts] | ||
sync-lessons = "learning_uploader:sync_lessons" |
112 changes: 112 additions & 0 deletions
112
scripts/ibm-quantum-learning-uploader/src/learning_uploader/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import sys | ||
import os | ||
import yaml | ||
from pathlib import Path | ||
from .upload import Lesson, API | ||
|
||
CONF_FILE = "./learning-platform.conf.yaml" | ||
API_URLS = { | ||
"staging": "https://learning-api-dev.quantum-computing.ibm.com", | ||
"production": "https://learning-api.quantum-computing.ibm.com", | ||
} | ||
WEBSITE_URLS = { | ||
"staging": "https://learning.www-dev.quantum-computing.ibm.com", | ||
"production": "https://learning.quantum-computing.ibm.com", | ||
} | ||
|
||
|
||
def get_api_name(): | ||
""" | ||
Either from environment token or user | ||
""" | ||
if os.environ.get("LEARNING_API_ENVIRONMENT", False): | ||
return os.environ.get("LEARNING_API_ENVIRONMENT").lower() | ||
|
||
if os.environ.get("LEARNING_API_TOKEN", False): | ||
raise EnvironmentError( | ||
"Set 'LEARNING_API_ENVIRONMENT' variable to 'staging' or " | ||
"'production' when using the 'LEARNING_API_TOKEN' environment " | ||
"variable. You can unset the token using the command:\n\n " | ||
"unset LEARNING_API_TOKEN\n" | ||
) | ||
|
||
response = input("Push to staging or production? (s/p): ").lower() | ||
if response in ["s", "staging"]: | ||
return "staging" | ||
if response in ["p", "production"]: | ||
return "production" | ||
print("Not understood; enter either 's' for staging or 'p' for production.") | ||
print("Trying again...") | ||
return get_api_name() | ||
|
||
|
||
def get_switch(switch, pop=True): | ||
""" | ||
Return True if switch in sys.argv, else False | ||
If `pop`, also remove switch from sys.argv | ||
""" | ||
if switch not in sys.argv: | ||
return False | ||
if pop: | ||
sys.argv.remove(switch) | ||
return True | ||
|
||
|
||
def check_for_unrecognized_switches(): | ||
for arg in sys.argv: | ||
if arg.startswith("-"): | ||
print(f'Unsupported argument: "{arg}"') | ||
sys.exit(1) | ||
|
||
|
||
def sync_lessons(): | ||
if get_switch("--help"): | ||
print( | ||
"Usage: sync-lessons [ path(s)/to/folder(s) ]\n" | ||
"Optional switches:\n" | ||
" --hide-urls: Don't print URLs after uploading a lesson\n" | ||
" --help: Show this message and exit" | ||
) | ||
sys.exit() | ||
hide_urls = get_switch("--hide-urls") | ||
check_for_unrecognized_switches() | ||
|
||
api_name = get_api_name() | ||
api = API( | ||
name=api_name, | ||
api_url=API_URLS[api_name], | ||
website_url=WEBSITE_URLS[api_name], | ||
hide_urls=hide_urls, | ||
) | ||
|
||
lesson_ids = parse_yaml(api_name) | ||
if len(sys.argv) > 1: | ||
paths = sys.argv[1:] | ||
else: | ||
paths = lesson_ids.keys() | ||
|
||
for lesson_path in paths: | ||
lesson = Lesson(lesson_path, lesson_ids[lesson_path]) | ||
api.push(lesson) | ||
|
||
print("✨ Sync complete! ✨\n") | ||
|
||
|
||
def parse_yaml(api_name): | ||
""" | ||
Get dict of lesson paths and lesson IDs | ||
Args: | ||
api_name (str): "staging" or "production" | ||
Returns: | ||
dict: { lesson_path: lesson_id } | ||
""" | ||
with open(CONF_FILE) as f: | ||
api_info = yaml.safe_load(f.read()) | ||
|
||
output = {} | ||
for lesson in api_info["lessons"]: | ||
path = lesson["path"] | ||
lesson_id = lesson[f"id{api_name.lower().capitalize()}"] | ||
output[path] = lesson_id | ||
|
||
return output |
Oops, something went wrong.