API docs¶
lib50¶
- exception lib50.ConnectionError(*args, **kwargs)¶
A
lib50.Error
signalling a connection has errored.
- exception lib50.Error(*args, **kwargs)¶
A generic lib50 Error.
- Variables
payload (dict) – arbitrary data
- __init__(*args, **kwargs)¶
- exception lib50.InvalidConfigError(*args, **kwargs)¶
A
lib50.Error
signalling that a config is invalid.
- exception lib50.InvalidSlugError(*args, **kwargs)¶
A
lib50.Error
signalling that a slug is invalid.
- exception lib50.MissingFilesError(files, dir=None)¶
A
lib50.Error
signaling that files are missing. This error’s payload has afiles
anddir
key.MissingFilesError.payload["files"]
are all the missing files in a list of strings.MissingFilesError.payload["dir"]
is the current working directory (cwd) from when this error was raised.- __init__(files, dir=None)¶
- Parameters
files (list of string(s) or Pathlib.path(s)) – the missing files that caused the error
- exception lib50.MissingToolError(*args, **kwargs)¶
A more specific
lib50.InvalidConfigError
signalling that an entry for a tool is missing in the config.
- class lib50.ProgressBar(message, output_stream=None)¶
A contextmanager that shows a progress bar starting with message.
Example usage:
from lib50 import ProgressBar import time with ProgressBar("uploading") as bar: time.sleep(5) bar.stop() time.sleep(5)
- __init__(message, output_stream=None)¶
- Parameters
message (str) – the message of the progress bar, what the user is waiting on
output_stream (a stream or file-like object) – a stream to write the progress bar to
- stop()¶
Stop the progress bar.
- class lib50.Slug(slug, offline=False, github_token=None)¶
A CS50 slug that uniquely identifies a location on GitHub.
A slug is formatted as follows: <org>/<repo>/<branch>/<problem> Both the branch and the problem can have an arbitrary number of slashes.
lib50.Slug
performs validation on the slug, by querrying GitHub, pulling in all branches, and then by finding a branch and problem that matches the slug.- Variables
org (str) – the GitHub organization
repo (str) – the GitHub repo
branch (str) – the branch in the repo
problem (str) – path to the problem, the directory containing
.cs50.yml
slug (str) – string representation of the slug
offline (bool) – flag signalling whether the user is offline. If set to True, the slug is parsed locally.
origin (str) – GitHub url for org/repo including authentication.
Example usage:
from lib50._api import Slug slug = Slug("cs50/problems/2019/x/hello") print(slug.org) print(slug.repo) print(slug.branch) print(slug.problem)
- __init__(slug, offline=False, github_token=None)¶
Parse <org>/<repo>/<branch>/<problem_dir> from slug.
- static normalize_case(slug)¶
Normalize the case of a slug in string form
- exception lib50.TimeoutError(*args, **kwargs)¶
A
lib50.Error
signalling a timeout has occured.
- exception lib50.TooManyFilesError(limit, dir=None)¶
A
lib50.Error
signaling that too many files were attempted to be included. The error’s payload has adir
and alimit
key.TooManyFilesError.payload["dir"]
is the directory in which the attempted submission occured.TooManyFilesError.payload["limit"]
is the max number of files allowed- __init__(limit, dir=None)¶
- lib50.authenticate(org, repo=None)¶
A contextmanager that authenticates a user with GitHub via SSH if possible, otherwise via HTTPS.
- Parameters
org (str) – GitHub organisation to authenticate with
repo (str, optional) – GitHub repo (part of the org) to authenticate with. Default is the user’s GitHub login.
- Returns
an authenticated user
- Type
lib50.User
Example usage:
from lib50 import authenticate with authenticate("me50") as user: print(user.name)
- lib50.cd(dest)¶
A contextmanager for temporarily changing directory.
- Parameters
dest (str or pathlib.Path) – the path to the directory
- Returns
dest unchanged
- Type
str or pathlib.Path
Example usage:
from lib50 import cd import os with cd("foo") as current_dir: print(os.getcwd())
- lib50.check_github_status()¶
Pings the githubstatus API. Raises a ConnectionError if the Git Operations and/or API requests components show an increase in errors.
- Returns
None
- Type
None
- Raises
lib50.ConnectionError – if the Git Operations and/or API requests components show an increase in errors.
- lib50.connect(slug, config_loader, file_limit=10000)¶
Connects to a GitHub repo indentified by slug. Then parses the
.cs50.yml
config file with theconfig_loader
. If not all required files are present, per thefiles
tag in.cs50.yml
, anError
is raised.- Parameters
slug (str) – the slug identifying a GitHub repo.
config_loader (lib50.config.Loader) – a config loader that is able to parse the .cs50.yml config file for a tool.
file_limit (int) – The maximum number of files that are allowed to be included.
- Returns
the remote configuration (org, message, callback, results), and the input for a prompt (honesty question, included files, excluded files)
- Type
tuple(dict, tuple(str, set, set))
- Raises
lib50.InvalidSlugError – if the slug is invalid for the tool
lib50.Error – if no files are staged. For instance the slug expects .c files, but there are only .py files present.
Example usage:
from lib50 import connect import submit50 open("hello.c", "w").close() remote, (honesty, included, excluded) = connect("cs50/problems/2019/x/hello", submit50.CONFIG_LOADER)
- lib50.fetch_config(slug)¶
Fetch the config file at slug from GitHub.
- Parameters
slug (str) – a slug identifying a location on GitHub to fetch the config from.
- Returns
the config in the form of unparsed json
- Type
str
- Raises
lib50.InvalidSlugError – if there is no config file at slug.
Example usage:
from lib50 import fetch_config config = fetch_config("cs50/problems/2019/x/hello") print(config)
- lib50.files(patterns, require_tags=('require',), include_tags=('include',), exclude_tags=('exclude',), root='.', limit=10000)¶
Based on a list of patterns (
lib50.config.TaggedValue
) determine which files should be included and excluded. Any pattern tagged with a tag:from
include_tags
will be includedfrom
require_tags
can only be a file, that will then be included.MissingFilesError
is raised if missing.from
exclude_tags
will be excluded
- Parameters
patterns (list of lib50.config.TaggedValue) – patterns that are processed in order, to determine which files should be included and excluded.
require_tags (list of strings, optional) – tags that mark a file as required and through that included
include_tags (list of strings, optional) – tags that mark a pattern as included
exclude_tags (list of strings, optional) – tags that mark a pattern as excluded
root (str or pathlib.Path, optional) – the root directory from which to look for files. Defaults to the current directory.
limit (int) – Maximum number of files that can be globbed.
- Returns
all included files and all excluded files
- Type
tuple(set of strings, set of strings)
Example usage:
from lib50 import files from lib50.config import TaggedValue open("foo.py", "w").close() open("bar.c", "w").close() open("baz.h", "w").close() patterns = [TaggedValue("*", "exclude"), TaggedValue("*.c", "include"), TaggedValue("baz.h", "require")] print(files(patterns)) # prints ({'bar.c', 'baz.h'}, {'foo.py'})
- lib50.get_local_slugs(tool, similar_to='')¶
Get all slugs for tool of which lib50 has a local copy. If similar_to is given, ranks and sorts local slugs by similarity to similar_to.
- Parameters
tool (str) – tool for which to get the local slugs
similar_to (str, optional) – ranks and sorts local slugs by similarity to this slug
- Returns
list of slugs
- Type
list of strings
Example usage:
from lib50 import get_local_slugs slugs = get_local_slugs("check50", similar_to="cs50/problems/2019/x/hllo") print(slugs)
- lib50.local(slug, offline=False, remove_origin=False, github_token=None)¶
Create/update local copy of the GitHub repo indentified by slug. The local copy is shallow and single branch, it contains just the last commit on the branch identified by the slug.
- Parameters
slug (str) – the slug identifying a GitHub repo.
offline (bool, optional) – a flag that indicates whether the user is offline. If so, then the local copy is only checked, but not updated.
remove_origin (bool, optional) – a flag, that when set to True, will remove origin as a remote of the git repo.
github_token (str, optional) – a GitHub authentication token used to verify the slug, only needed if the slug identifies a private repo.
- Returns
path to local copy
- Type
pathlib.Path
Example usage:
from lib50 import local path = local("cs50/problems/2019/x/hello") print(list(path.glob("**/*")))
- lib50.logout()¶
Log out from git.
- Returns
None
- Type
None
- lib50.prepare(tool, branch, user, included)¶
A contextmanager that prepares git for pushing:
Check that there are no permission errors
Add necessities to git config
Stage files
Stage files via lfs if necessary
Check that atleast one file is staged
- Parameters
tool (str) – name of the tool that started the push
branch (str) – git branch to switch to
user (lib50.User) – the user who has access to the repo, and will ultimately author a commit
included (list of string(s) or pathlib.Path(s)) – a list of files that are to be staged in git
- Returns
None
- Type
None
Example usage:
from lib50 import authenticate, prepare, upload with authenticate("me50") as user: tool = "submit50" branch = "cs50/problems/2019/x/hello" with prepare(tool, branch, user, ["hello.c"]): upload(branch, user, tool, {})
- lib50.push(tool, slug, config_loader, repo=None, data=None, prompt=<function <lambda>>, file_limit=10000)¶
Pushes to Github in name of a tool. What should be pushed is configured by the tool and its configuration in the .cs50.yml file identified by the slug. By default, this function pushes to https://github.com/org=me50/repo=<username>/branch=<slug>.
lib50.push
executes the workflow:lib50.connect
,lib50.authenticate
,lib50.prepare
andlib50.upload
.- Parameters
tool (str) – name of the tool that initialized the push
slug (str) – the slug identifying a .cs50.yml config file in a GitHub repo. This slug is also the branch in the student’s repo to which this will push.
config_loader (lib50.config.Loader) – a config loader for the tool that is able to parse the .cs50.yml config file for the tool.
repo (str, optional) – an alternative repo to push to, otherwise the default is used: github.com/me50/<github_login>
data (dict of strings, optional) – key value pairs that end up in the commit message. This can be used to communicate data with a backend.
prompt (lambda str, list, list => bool, optional) – a prompt shown just before the push. In case this prompt returns false, the push is aborted. This lambda function has access to an honesty prompt configured in .cs50,yml, and all files that will be included and excluded in the push.
file_limit (int) – maximum number of files to be matched by any globbing pattern.
- Returns
GitHub username and the commit hash
- Type
tuple(str, str)
Example usage:
from lib50 import push import submit50 name, hash = push("submit50", "cs50/problems/2019/x/hello", submit50.CONFIG_LOADER) print(name) print(hash)
- lib50.upload(branch, user, tool, data)¶
Commit + push to a branch
- Parameters
branch (str) – git branch to commit and push to
user (lib50.User) – authenticated user who can push to the repo and branch
tool (str) – name of the tool that started the push
data (dict of strings) – key value pairs that end up in the commit message. This can be used to communicate data with a backend.
- Returns
username and commit hash
- Type
tuple(str, str)
Example usage:
from lib50 import authenticate, prepare, upload with authenticate("me50") as user: tool = "submit50" branch = "cs50/problems/2019/x/hello" with prepare(tool, branch, user, ["hello.c"]): upload(branch, user, tool, {tool:True})
- lib50.working_area(files, name='')¶
A contextmanager that copies all files to a temporary directory (the working area)
- Parameters
files (list of string(s) or pathlib.Path(s)) – all files to copy to the temporary directory
name (str, optional) – name of the temporary directory
- Returns
path to the working area
- Type
pathlib.Path
Example usage:
from lib50 import working_area with working_area(["foo.c", "bar.py"], name="baz") as area: print(list(area.glob("**/*")))
lib50.config¶
An API for retrieving and parsing .cs50.yml
configs.
- class lib50.config.Loader(tool, *global_tags, default=None)¶
A config loader (parser) that can parse a tools section of
.cs50.yml
config files.The loader can be configured to parse and validate custom yaml tags. These tags can be global, in which case they can occur anywhere in a tool’s section. Or scoped to a top level key within a tool’s section, in which case these tags can only occur there.
Tags can also have defaults. In which case there does not need to be a value next to the tag in the config file.
Example usage:
from lib50 import local from lib50.config import Loader, get_config_filepath # Get a local config file path = local("cs50/problems/2019/x/hello") config_path = get_config_filepath(path) # Create a loader for the tool submit50 loader = Loader("submit50") # Allow the tags include/exclude/require to exist only within the files key of submit50 loader.scope("files", "include", "exclude", "require") # Load, parse and validate the config file with open(config_path) as f: config = loader.load(f.read()) print(config)
- __init__(tool, *global_tags, default=None)¶
- Parameters
tool (str) – the tool for which to load
global_tags (str) – any tags that can be applied globally (within the tool’s section)
default (anything, optional) – a default value for global tags
- load(content, validate=True)¶
Parse yaml content.
- Parameters
content (str) – the content of a config file
validate (bool) – if set to
False
, no validation will be performed. Tags can then occur anywhere.
- Returns
the parsed config
- Type
dict
- Raises
lib50.InvalidConfigError – in case a tag is misplaced, or the content is not valid yaml.
lib50.MissingToolError – in case the tool does not occur in the content.
- scope(key, *tags, default=None)¶
Only apply tags and default for top-level key of the tool’s section. This effectively scopes the tags to just that top-level key.
- Parameters
key (str) – the top-level key
tags (str) – any tags that can be applied within the top-level key
default (anything, optional) – a default value for these tags
- Returns
None
- Type
None
- class lib50.config.TaggedValue(value, tag)¶
A value tagged in a
.yml
file- __init__(value, tag)¶
- Parameters
value (str) – the tagged value
tag (str) – the yaml tag, with or without the syntactically required
!
- lib50.config.get_config_filepath(path)¶
Looks for the following files in order at path:
.cs50.yaml
.cs50.yml
If only one exists, returns path to that file (i.e.
<path>/.cs50.yaml
or<path>/.cs50.yml
) Raiseslib50.Error
otherwise.- Parameters
path (str or pathlib.Path) – local path to a
.cs50.yml
config file- Returns
path to the config file
- Type
pathlib.Path
- Raises
lib50.Error – if zero or more than one config files exist
Example usage:
from lib50 import local from lib50.config import get_config_filepath path = local("cs50/problems/2019/x/hello") config_path = get_config_filepath(path) print(config_path)
lib50.crypto¶
An API for verifying signed payloads such as check50 results.
- lib50.crypto.load_private_key(pem_str, password=None)¶
Load a private key from a PEM string.
“PEM is an encapsulation format, meaning keys in it can actually be any of several different key types. However these are all self-identifying, so you don’t need to worry about this detail. PEM keys are recognizable because they all begin with
-----BEGIN {format}-----
and end with-----END {format}-----
.”- Parameters
pem_str (str) – the private key to load in PEM format
password (str, optional) – a password to decode the pem_str
- Returns
a key from
cryptography.hazmat
- Type
One of RSAPrivateKey, DSAPrivateKey, DHPrivateKey, or EllipticCurvePrivateKey
- lib50.crypto.load_public_key(pem_str)¶
Load a public key from a PEM string.
“PEM is an encapsulation format, meaning keys in it can actually be any of several different key types. However these are all self-identifying, so you don’t need to worry about this detail. PEM keys are recognizable because they all begin with
-----BEGIN {format}-----
and end with-----END {format}-----
.”- Parameters
pem_str (str) – the public key to load in PEM format
- Returns
a key from
cryptography.hazmat
- Type
One of RSAPrivateKey, DSAPrivateKey, DHPrivateKey, or EllipticCurvePrivateKey
- lib50.crypto.sign(payload, private_key)¶
Sign a payload with a private key.
- Parameters
payload (str) – the payload
private_key – a private key from
lib50.crypto.load_private_key
- Returns
base64 encoded signature
- Type
bytes
- lib50.crypto.verify(payload, signature, public_key)¶
Verify payload using (base64 encoded) signature and verification key. public_key should be obtained from load_public_key Uses RSA-PSS with SHA-512 and maximum salt length. The corresponding openssl command to create signatures that this function can verify is:
openssl dgst -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 -sign <PRIVATE_KEY> <PAYLOAD> | openssl base64 -A
- Parameters
payload (str) – the payload
signature (bytes) – base64 encoded signature
public_key – a public key from
lib50.crypto.load_public_key
- Returns
True iff the payload could be verified
- Type
bool