The basic user data can be requested by passing the Medium Integration token via a GET request. The Medium Integration Token needs to be requested from the writer's Medium profile page. For now, this token should be stored as an environment variable under the user's $HOME/.profile
file:
export MEDIUM_TOKEN=<the-token>
I may consider using keyring to store the token locally as that may more user friendly.
a = fetch_user_data()
a.json()
Which is equivalent to the curl
alternative:
!curl -s -H "Authorization: Bearer $MEDIUM_TOKEN" https://api.medium.com/v1/me | jq
assert 'data' in fetch_user_data().json().keys()
get_user_id()
We can also request the user's publications. In Medium's definition a publication is not an article but rather an editorial-like group under which articles are written (e.g. Towards Data Science, Elemental AI...)
fetch_publications().json()
assert 'data' in fetch_publications().json().keys()
As easily an article can be submitted. Many options are available which can be explored in the Medium API official docs. The possible parameters and parameter values are as follow:
Parameter | Type | Required? | Description |
---|---|---|---|
title | string | required | The title of the post. Note that this title is used for SEO and when rendering the post as a listing, but will not appear in the actual post—for that, the title must be specified in the content field as well. Titles longer than 100 characters will be ignored. In that case, a title will be synthesized from the first content in the post when it is published. |
contentFormat | string | required | The format of the "content" field. There are two valid values, "html", and "markdown" |
content | string | required | The body of the post, in a valid, semantic, HTML fragment, or Markdown. Further markups may be supported in the future. For a full list of accepted HTML tags, see here. If you want your title to appear on the post page, you must also include it as part of the post content. |
tags | string array | optional | Tags to classify the post. Only the first three will be used. Tags longer than 25 characters will be ignored. |
canonicalUrl | string | optional | The original home of this content, if it was originally published elsewhere. |
publishStatus | enum | optional | The status of the post. Valid values are “public”, “draft”, or “unlisted”. The default is “public”. |
license | enum | optional | The license of the post. Valid values are “all-rights-reserved”, “cc-40-by”, “cc-40-by-sa”, “cc-40-by-nd”, “cc-40-by-nc”, “cc-40-by-nc-nd”, “cc-40-by-nc-sa”, “cc-40-zero”, “public-domain”. The default is “all-rights-reserved”. |
notifyFollowers | bool | optional | Whether to notifyFollowers that the user has published. |
The default publishStatus
for posting articles will always be set to 'draft' because that is how I would always like to use this API.
Which is equivalent to:
This command posts an article via a simple POST request. If the article is correctly submitted a JSON response like the below will be returned
my_post = post_article('Test from nb',
'# Markdown title \n\n markdown text')
my_post.json()
The request should return the HTTP 201 code
my_post_from_file = post_article('Test from file',open('../samples/LEARNING.md', 'rb').read())
my_post_from_file.json()
assert my_post_from_file.ok
assert my_post_from_file.status_code == 201
my_img = post_image('../samples/github-logo.png')
my_img.json()
assert my_img.ok
assert my_img.status_code == 201
from io import BytesIO
img = BytesIO(open('../samples/github-logo.png', 'rb').read()).getvalue()
print(f"Which is some sort of byte stream: like {img[:10]}")
image_from_bytes = post_image(filename = 'github-logo.png', img = img).json()
image_from_bytes
It may not become apparent why this functionality is useful right now. Being able to upload a file as a binary stream is actually really useful because we can avoid saving the images to memory and upload them directly to Medium, the nbconvert
modules that we will be using later represent images in such intermediate states
Which as a curl
call would be:
!curl -X POST https://api.medium.com/v1/images \
-H "Authorization: Bearer $MEDIUM_TOKEN" \
-F 'name="image"; filename="../samples/github-logo.png" ; type="image/png";' \
-F 'image=@../samples/github-logo.png' | jq
It turns out that uploading a file with an online image may be easier
with open('../samples/test-offline-image.md', 'rb') as article:
my_post_from_file_with_online_image = post_article(
'Test from file with online image',
article.read())
my_post_from_file_with_online_image.json()
For those reading this code above, there is no way for me to show that the draft was posted succesfully and that the image is displayed correctly as the draft is posted to my account, but trust me it is posted correctly. Where as obtaining a correct JSON back is a good sign that the article was posted succesfully, the posted draft still needs to be verified and potentially modified.
with open('../samples/test-offline-image.md', 'rb') as article:
my_post_from_file_with_offline_image = post_article(
'Test from file with offline image',
article.read())
my_post_from_file_with_offline_image.json()
In this case, we referred to a local image in our markdown document as opposed to one found oneline. When we post an article with references to offline/local images, the medium Markdown renderer won't recognise the path to those images and will faily to display the image. What would need to be done in this case is to get the local image paths, upload them with the post_image()
function and then replace the local path reference by the one in the Medium DB.