Updating Programmatic
Most of the time, upgrading using pip is sufficient.
1
pip install --upgrade programmatic --extra-index-url "https://pypi.humanloop.com/simple"
Copied!
While Programmatic is in closed beta, we're moving fast and breaking things
. We do however strive to make Programmatic releases follow semantic versioning, meaning that you will be notified if an upgrade will result in breaking changes.
Database schema changes may mean that your existing projects are not compatible with newer versions. One way to upgrade your project is to simply create a new project with the same labelling functions!

Importing labelling functions from a snapshot

Our beta users have been extremely helpful and have created this script that imports labelling functions from a labelling functions snapshot.
1
#!/usr/bin/env python
2
""" # noqa
3
Humanloop Programmatic function re-import script
4
5
Usage:
6
7
```
8
./import-functions.py --project <path to project directory> --functions <path to function export directory> [--label label_name]
9
```
10
11
**NB: Corresponding labels must already exist in the project functions are being imported into.**
12
13
`--project` is the directory containing a humanloop project, identical to the argument passed into `humanloop run`.
14
15
`--functions` can be passed either:
16
(1) the top-level folder containing subfolders with labelling functions in,
17
stored by name of label, _or_
18
(2) a single subdirectory of such a folder containing labelling functions for a single label.
19
20
`--label`: in the case where a folder of labelling functions for the same single label is supplied (case 2 above),
21
this flag can be used to override which label the functions are uploaded for. For example, this invocation:
22
23
```
24
./import-functions.py --project my_project --functions my_exported_functions/label_one --label label_two
25
```
26
27
would upload functions exported for label_one under the folder label_two.
28
29
Function names will be uniquely suffixed where they clash with existing functions.
30
"""
31
import argparse
32
import random
33
import sqlite3
34
import string
35
import sys
36
from pathlib import Path
37
38
39
def random_letters():
40
return "".join(random.choice(string.ascii_lowercase) for i in range(8))
41
42
43
def build_parser():
44
parser = argparse.ArgumentParser(
45
description="Import exported functions into a programmatic project"
46
)
47
parser.add_argument("--project", help="Path to project", required=True)
48
parser.add_argument(
49
"--functions", help="Path to exported functions directory", required=True
50
)
51
parser.add_argument(
52
"--label",
53
help=(
54
"If provided, import functions under this label name rather "
55
"than their folder name"
56
),
57
)
58
59
return parser
60
61
62
def get_database_connection(project):
63
"""
64
Connect to a programmatic project's database
65
"""
66
project_db_file = Path(project) / ".humanloop" / "data.db"
67
68
if not project_db_file.exists():
69
print(f"Error - no database file found at {project_db_file}")
70
sys.exit(1)
71
72
return sqlite3.connect(project_db_file)
73
74
75
def get_per_label_functions(functions):
76
"""
77
Make a dictionary of label name to function files
78
"""
79
80
def get_files_list(folder: Path):
81
return [file for file in folder.glob("*.py") if not file.name.startswith("__")]
82
83
subdirectories = [
84
entry
85
for entry in functions.iterdir()
86
if entry.is_dir() and not entry.name.startswith("__")
87
]
88
89
if not subdirectories:
90
return {functions.name: get_files_list(functions)}
91
92
else:
93
return {
94
subdirectory.name: get_files_list(subdirectory)
95
for subdirectory in subdirectories
96
}
97
98
99
def get_id_for_label(connection, label_name):
100
"""
101
Fetch the ID of the given label
102
"""
103
result = connection.execute(
104
"SELECT id FROM labels WHERE name = ?", (label_name,)
105
).fetchone()
106
107
if result is None:
108
print(f"Error: - Label {label_name} does not exist")
109
raise ValueError
110
else:
111
return result[0]
112
113
114
def function_exists(connection, name):
115
"""
116
Check if a LF name already exists
117
"""
118
return connection.execute(
119
"SELECT id FROM labelling_functions WHERE name = ?", (name,)
120
).fetchone()
121
122
123
def upsert_labelling_function(connection, label_id, name, body):
124
"""
125
Upsert a labelling function into the DB
126
"""
127
if result := function_exists(connection, name):
128
func_id = result[0]
129
query = """
130
UPDATE labelling_functions SET
131
label_id = ?,
132
name = ?,
133
body = ?,
134
is_valid = ?,
135
disabled = ?,
136
run_status = ?
137
WHERE
138
id = ?
139
"""
140
connection.execute(
141
query,
142
(label_id, name, body, True, False, "pending", func_id),
143
).fetchone()
144
145
else:
146
connection.execute(
147
"INSERT INTO labelling_functions "
148
"(label_id, name, body, is_valid, disabled, run_status) "
149
"VALUES(?, ?, ?, ?, ?, ?)",
150
(label_id, name, body, True, False, "pending"),
151
).fetchone()
152
153
154
def load_function(function_path):
155
"""
156
Read the contents of a function file and its name
157
"""
158
with function_path.open() as function_file:
159
return function_path.stem, function_file.read()
160
161
162
def import_functions(project=None, functions=None, label=None):
163
"""
164
Import functions from either
165
166
- A folder with label-named subfolders containing function files, or
167
- A label-named folder containing function files
168
"""
169
functions_by_label_name = get_per_label_functions(Path(functions))
170
171
# In the case where a single directory of functions was passed in,
172
# allow for them being uploaded under a *different* label name
173
if len(functions_by_label_name) == 1 and label is not None:
174
existing_label = next(k for k in functions_by_label_name.keys())
175
functions_by_label_name[label] = functions_by_label_name.pop(existing_label)
176
177
connection = get_database_connection(project)
178
179
for label_name, function_paths in functions_by_label_name.items():
180
try:
181
label_id = get_id_for_label(connection, label_name)
182
except ValueError:
183
print(f"Skipping label {label_name}")
184
continue
185
186
for function_path in function_paths:
187
name, body = load_function(function_path)
188
189
upsert_labelling_function(connection, label_id, name, body)
190
191
# Commit all changes
192
connection.commit()
193
connection.close()
194
195
196
if __name__ == "__main__":
197
parser = build_parser()
198
199
args = parser.parse_args()
200
201
import_functions(**vars(args))
Copied!