Skip to content
GitLab
Explore
Sign in
Learn Renku
Teaching on Renku
Advanced teaching automation
Compare revisions
fa96f29538e8cce267694a3b8b6c78eee5766f61 to e25717aaf5f09348e69034f04a58dc3b6c731f3d
Commits on Source (3)
add standard ingest_token function
· bef28d68
Cyril Matthey-Doret
authored
Jul 21, 2022
bef28d68
use ingest_token in every command
· dd0f2bae
Cyril Matthey-Doret
authored
Jul 21, 2022
dd0f2bae
modularize send_feedback
· e25717aa
Cyril Matthey-Doret
authored
Jul 21, 2022
e25717aa
Hide whitespace changes
Inline
Side-by-side
requirements.txt
View file @
e25717aa
click
GitPython
requests
pytz
pandas
teach_utils/collect_forks.py
View file @
e25717aa
...
...
@@ -16,6 +16,7 @@ import click
from
datetime
import
datetime
import
pytz
from
teach_utils.common_requests
import
parse_repo_url
,
get_project_id
from
teach_utils.utils
import
ingest_token
def
validate_iso_date
(
date
:
str
)
->
str
:
...
...
@@ -134,17 +135,12 @@ def format_fork_metadata(
)
@click.argument
(
"
repo_url
"
,
type
=
str
)
@click.option
(
"
--token
"
,
"
--token
-path
"
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"
Path to a file containing the Gitlab API token. If not provided, you will be prompted for the token.
"
,
)
def
main
(
repo_url
,
token
,
deadline
=
None
):
if
token
is
None
:
token
=
click
.
prompt
(
"
Please enter your Gitlab API token
"
,
hide_input
=
True
,
err
=
True
)
else
:
token
=
open
(
token
).
read
().
strip
()
def
main
(
repo_url
,
token_path
,
deadline
=
None
):
token
=
ingest_token
(
token_path
)
# Check for valid deadline format
if
deadline
is
not
None
:
validate_iso_date
(
deadline
)
...
...
teach_utils/invite_students.py
View file @
e25717aa
...
...
@@ -6,6 +6,7 @@ from typing import Dict
import
requests
import
click
from
teach_utils.common_requests
import
parse_repo_url
,
get_group_id
from
teach_utils.utils
import
ingest_token
def
invite_student_email
(
...
...
@@ -24,10 +25,10 @@ def find_email(line: str) -> str:
"""
Find and return email address in input string.
Raises an error if none found and only return the first
email if there are more than one.
Examples
--------
>>>
find_email
(
'
robert,smith,robert.smith@no.where,123
'
)
'
robert.smith@no.where
'
>>>
find_email
(
'
mike.rock@yes.no,mrock@yes.no
'
)
...
...
@@ -47,20 +48,15 @@ def find_email(line: str) -> str:
)
@click.argument
(
"
group-url
"
)
@click.option
(
"
--token
"
,
"
--token
-path
"
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"
Path to a file containing the Gitlab API token. If not provided, you will be prompted for the token.
"
,
)
def
main
(
emails_file
,
group_url
,
token
):
def
main
(
emails_file
,
group_url
,
token
_path
):
"""
Send invitations to join input gitlab group to all emails in the emails_file.
The file should have one email per line.
"""
if
token
is
None
:
token
=
click
.
prompt
(
"
Please enter your Gitlab API token
"
,
hide_input
=
True
,
err
=
True
)
else
:
token
=
open
(
token
).
read
().
strip
()
token
=
ingest_token
(
token_path
)
header
=
{
"
PRIVATE-TOKEN
"
:
token
}
# Recover the email on each line, in case there are multiple columns
...
...
teach_utils/moodle_to_student_groups.py
View file @
e25717aa
...
...
@@ -8,6 +8,7 @@ import requests
import
pandas
as
pd
import
click
from
teach_utils.common_requests
import
get_group_id
,
parse_group_url
from
teach_utils.utils
import
ingest_token
def
create_student_group
(
...
...
@@ -52,23 +53,17 @@ def generate_student_code(first_name: str, last_name: str) -> str:
@click.argument
(
"
student_table
"
)
@click.argument
(
"
parent_url
"
)
@click.option
(
"
--token
"
,
"
--token
-path
"
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"
Path to a file containing the Gitlab API token. If not provided, you will be prompted for the token.
"
,
)
def
main
(
student_table
,
parent_url
,
token
):
def
main
(
student_table
,
parent_url
,
token
_path
):
"""
Given a parent-group URL and a CSV file of students exported from Moodle,
create one private subgroup for each student in the parent group and invite students
in their personal group. The student table must contain columns:
"
Email address
"
,
"
First name
"
and
"
Surname
"
"""
if
token
is
None
:
token
=
click
.
prompt
(
"
Please enter your Gitlab API token
"
,
hide_input
=
True
,
err
=
True
)
else
:
token
=
open
(
token
).
read
().
strip
()
token
=
ingest_token
(
token_path
)
header
=
{
"
PRIVATE-TOKEN
"
:
token
}
students
=
pd
.
read_csv
(
student_table
)
...
...
teach_utils/send_feedback.py
View file @
e25717aa
...
...
@@ -10,11 +10,13 @@
import
sys
import
json
from
string
import
Formatter
from
typing
import
Dict
from
typing
import
Dict
,
Optional
import
requests
from
pathlib
import
Path
import
click
import
pandas
as
pd
from
teach_utils.common_requests
import
parse_repo_url
from
teach_utils.utils
import
ingest_token
FEEDBACK_TEMPLATE
=
"""
...
...
@@ -58,10 +60,39 @@ def fill_template(content: Dict[str, str], template: str) -> str:
raise
ValueError
(
f
"
Fields missing from content:
{
missing_flds
}
"
)
from
None
def
gather_fill_data
(
fork
:
Dict
,
feedback_csv
:
Optional
[
Path
]
=
None
)
->
Dict
:
"""
Gather metadata and feedback for a single fork into a dictionary
to use for filling the template. The resulting dictionary contains
fields: project, group, members, url, commit and visibility from the fork
metadata, as well as all columns present in the feedback csv file.
"""
# Get basic fields in fork metadata
_
,
group
,
repo
=
parse_repo_url
(
fork
[
"
url
"
])
members
=
"
\n
"
.
join
([
f
"
*
{
member
[
'
name
'
]
}
"
for
member
in
fork
[
"
members
"
]])
fill_data
=
{
"
project
"
:
repo
.
removesuffix
(
"
.git
"
),
"
group
"
:
group
,
"
members
"
:
members
,
}
# add commit, url and visibility fields from fork data
fill_data
|=
{
key
:
fork
.
get
(
key
)
for
key
in
[
"
url
"
,
"
commit
"
,
"
visibility
"
]}
# If a CSV file with feedback is provided, read the fields
# of the current fork (row) to the fill data.
if
feedback_csv
:
fill_data
|=
(
pd
.
read_csv
(
feedback_csv
)
.
query
(
f
'
id ==
{
fork
[
"
id
"
]
}
'
)
.
iloc
[
0
]
.
drop
(
"
id
"
)
.
to_dict
()
)
return
fill_data
@click.command
()
@click.argument
(
"
forks
"
,
type
=
click
.
File
(
mode
=
"
r
"
),
default
=
sys
.
stdin
)
@click.option
(
"
--token
"
,
"
--token
-path
"
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"
Path to a file containing the Gitlab API token. If not provided, you will be prompted for the token.
"
,
)
...
...
@@ -74,29 +105,22 @@ def fill_template(content: Dict[str, str], template: str) -> str:
default
=
None
,
)
@click.option
(
"
--feedback
"
,
"
--feedback
-csv
"
,
type
=
click
.
Path
(
exists
=
True
),
help
=
"
CSV file with column
'
id
'
, and additional columns matching fields in the template
"
"
The id column should match a gitlab project ID from the JSON input.
"
,
default
=
None
,
)
@click.option
(
"
--dry-run
"
,
is_flag
=
True
,
help
=
"
Print feedback to stdout instead of opening issues
"
"
--dry-run
"
,
is_flag
=
True
,
help
=
"
Print feedback to stdout instead of opening issues
"
)
def
main
(
forks
,
feedback
,
token
,
template
,
dry_run
):
def
main
(
forks
,
feedback
_csv
,
token
_path
,
template
,
dry_run
):
"""
Send feedback to repositories in input JSON by opening issues.
"""
# Build header with authentication token
if
token
is
None
:
token
=
click
.
prompt
(
"
Please enter your Gitlab API token
"
,
hide_input
=
True
,
err
=
True
)
else
:
token
=
open
(
token
).
read
().
strip
()
token
=
ingest_token
(
token_path
)
header
=
{
"
PRIVATE-TOKEN
"
:
token
}
forks
=
json
.
load
(
forks
)
...
...
@@ -108,30 +132,11 @@ def main(forks, feedback, token, template, dry_run):
feedback_template
=
FEEDBACK_TEMPLATE
for
fork
in
forks
:
# Get basic fields from repo metadata in json
base
,
group
,
repo
=
parse_repo_url
(
fork
[
"
url
"
])
members
=
"
\n
"
.
join
([
f
"
*
{
member
[
'
name
'
]
}
"
for
member
in
fork
[
"
members
"
]])
fill_data
=
{
"
project
"
:
repo
.
removesuffix
(
"
.git
"
),
"
group
"
:
group
,
"
members
"
:
members
,
}
# add commit, url and visibility fields from fork data
fill_data
|=
{
key
:
fork
.
get
(
key
)
for
key
in
[
"
url
"
,
"
commit
"
,
"
visibility
"
]}
# If provided, get additional fields from feedback table
if
feedback
:
feedback_fields
=
(
pd
.
read_csv
(
feedback
)
.
query
(
f
'
id ==
{
fork
[
"
id
"
]
}
'
)
.
iloc
[
0
]
.
drop
(
"
id
"
)
.
to_dict
()
)
fill_data
|=
feedback_fields
base
=
parse_repo_url
(
fork
[
"
url
"
])[
0
]
fill_data
=
gather_fill_data
(
fork
,
feedback_csv
)
# Replace fields in the template by their value for the current fork
description
=
fill_template
(
fill_data
,
feedback_template
)
# Create issue
# Create issue
, or print to stdout if dry run
if
dry_run
:
print
(
fork
)
print
(
description
)
...
...
teach_utils/utils.py
0 → 100644
View file @
e25717aa
from
typing
import
Optional
from
pathlib
import
Path
import
click
def
ingest_token
(
token_path
:
Optional
[
Path
]
=
None
)
->
str
:
"""
Read token from target file if provided.
If input is None, prompt for token.
"""
if
token_path
is
None
:
token
=
click
.
prompt
(
"
Please enter your Gitlab API token
"
,
hide_input
=
True
,
err
=
True
)
else
:
token
=
open
(
token_path
).
read
().
strip
()
return
token