API

Extract wheel from a GitHub action artifact

Based on Artifact ID

This API endpoint allows you to extract wheels from GitHub actions artifacts. This makes it possible to install a wheel from the latest successful run of your GitHub action by including a link in your requirements.txt.

The link URL is structured as follows:

https://py.cafe/gh/artifact/{owner}/{repo}/{artifact_id}/{full_path_to_wheel}

An example requirement file that could be used for Panel:

# requirements.txt
# The normal requirement for panel is just panel:
# panel
# based on the run of this action https://github.com/holoviz/panel/actions/runs/11204446095
# we have a link to the artifact
#  https://github.com/holoviz/panel/actions/runs/11204446095/artifacts/2021154790
#  which contains the file pip/panel-1.5.2-py3-none-any.whl
https://py.cafe/gh/artifact/holoviz/panel/2021154790/panel-1.5.2-py3-none-any.whl

Note that the Artifact ID cannot be obtained from within a running GitHub Action, and therefore it is not possible to generate messages or links from a GitHub Action itself.

Note: the runId is not used, only the artifactId is used to extract the wheel.

Based on Run ID

You can also extract a wheel from a artifact based on the runId. In this case the URL is structured as follows:

https://py.cafe/gh/artifact/{owner}/{repo}/actions/runs/{run_id}/{artifact_name}/{wheel_filename}

For example:

# requirements.txt
# The normal requirement for panel is just panel:
# panel
# based on the run of this action https://github.com/holoviz/panel/actions/runs/11204446095
#  which contains an artifact with the name pip and a file
#   panel-1.5.2-py3-none-any.whl (you do not need to enter the full path for this endpoint)
https://py.cafe/gh/artifact/holoviz/panel/actions/runs/11204446095/pip/panel-1.5.2-py3-none-any.whl

The artifact name can be a regular expression if ?regex is appended to the url. So in the above case, p.*p would also work:

https://py.cafe/gh/artifact/holoviz/panel/actions/runs/11204446095/p.p/panel-1.5.2-py3-none-any.whl?regex

wheel_filename does not have to be the full pathname, as long as the full path in the zipfile matches wheel_filename

The advantage of this endpoint is that we can generate links to the artifact from within the GitHub Action because each GitHub action has access to a Run ID via the {{ github.run_id }} variable.

Note that the artifact_name is a name given to the artifact in the GitHub Action workflow file.

Based on a PR

If you are only interested in extracting a wheel from the last run that was triggered in a PR, you can use the following endpoint:

https://py.cafe/gh/artifact/{owner}/{repo}/pull/{pr_number}/{workflow_name}/{artifact_name}/{wheel_filename}

An example url is:

https://py.cafe/gh/artifact/mckinsey/vizro/pull/766/PyCafe.*/pip/vizro-0.1.26.dev0-py3-none-any.whl?regex

Note that the workflow_name is in the GitHub Action workflow files. Here we use a regular expression (PyCafe.*) to match it and therefore pass the ?regex parameter.

Snippet links are self-contained links that include everything needed by an app hosted on PyCafe. These links can be created without interacting with the PyCafe website, and can be shared in GitHub issues, Discord, or in other places where you want to share code.

The simplest form of a snippet link is a link that only includes the code. This can be done by adding the code to the URL as a query parameter or hash parameter. The code should be quoted to make sure spaces and special characters are correctly encoded.

from urllib.parse import quote
 
code = quote("""import streamlit as st
 
if st.button("PyCafe is great"):
    st.balloons()
""")
 
# Note: you can also use a link as a source for code
# code = quote("https://raw.githubusercontent.com/streamlit/streamlit/refs/heads/develop/lib/tests/streamlit/test_data/widgets_script.py")
 
url = f"https://py.cafe/snippet/streamlit/v1#code={code}"
print(url)

This should result in a the following link:

https://py.cafe/snippet/streamlit/v1#code=import%20streamlit%20as%20st%0A%0Aif%20st.button%28%22PyCafe%20is%20great%22%29%3A%0A%20%20%20%20st.balloons%28%29%0A

You can play with this example code using the following PyCafe project:

example

Note that the code argument can also be an URL.

from urllib.parse import quote
 
code = quote("""import streamlit as st
import pandas as pd
 
chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
st.area_chart(chart_data)
""")
 
requirements = quote("""streamlit==1.27.2  # currently pinned to this version
altair
pandas
""")
 
url = f"https://py.cafe/snippet/streamlit/v1#code={code}&requirements={requirements}"
print(url)

Use this project to play with this example code.

There are three ways to add a file to a snippet link:

  • Provide a URL to a file that will be downloaded on the fly
  • Provide the content of the file directly (text only)
  • Provide the content of the file directly, but encoded in base64

Once you start adding files, the URL will get longer, so it’s recommended to compress the JSON object using gzip, and then encode it in base64. This will make the URL much shorter.

The following example shows how to create a snippet link with code, requirements, and extra files.

from urllib.parse import quote, urlencode
import base64
import json
import gzip
 
code = """import streamlit as st
import pandas as pd
 
chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
st.area_chart(chart_data)
"""
 
requirements = """streamlit==1.27.2  # currently pinned to this version
altair
pandas
"""
 
files = [
    {
        "name": "some_file_loaded_from_a_url.py",
        # this file will be downloaded on the fly, this will keep the generated url short, and the content always up to date
        "url": "https://github.com/projectmesa/mesa-examples/raw/main/examples/virus_on_network/virus_on_network/model.py",
    },
    {
        "name": "other_file_from_data.py",
        # if content is provided, it's assumed to be text (utf8 encoded)
        "content": "plain text",
    },
    {
        "name": "binary_data.dat",
        # encode binary data in base64, but make sure to include the encoding
        "content": base64.b64encode(b"binary data").decode("utf8"),
        "encoding": "base64"
    }
]
 
 
json_object = {
    "code": code,
    "requirements": requirements,
    "files": files
}
json_text = json.dumps(json_object)
# compress using gzip to make the url shorter
compressed_json_text = gzip.compress(json_text.encode("utf8"))
# but encode in base64
base64_text = base64.b64encode(compressed_json_text).decode("utf8")
c = quote(base64_text)
# use the c= argument, c stands for compressed
url = f"https://py.cafe/snippet/streamlit/v1#c={c}"
print(url)

You can play with this example code on PyCafe