Skip to content

Commit

Permalink
Add tool to upload to IBM Quantum Learning (#358)
Browse files Browse the repository at this point in the history
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
frankharkins and Eric-Arellano authored Nov 17, 2023
1 parent eac006e commit 3823b49
Show file tree
Hide file tree
Showing 10 changed files with 547 additions and 0 deletions.
66 changes: 66 additions & 0 deletions .github/workflows/learning-uploader-test.yml
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"]
})
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

.ipynb_checkpoints/
.DS_Store
__pycache__/

node_modules
tsconfig.tsbuildinfo
Expand Down
57 changes: 57 additions & 0 deletions scripts/ibm-quantum-learning-uploader/README.md
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
```
30 changes: 30 additions & 0 deletions scripts/ibm-quantum-learning-uploader/pyproject.toml
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"
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
Loading

0 comments on commit 3823b49

Please sign in to comment.