Skip to content

API Reference

Source code in meorg_client/client.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
class Client:
    def __init__(self, email: str = None, password: str = None, dev_mode: bool = False):
        """ME.org Client object.

        Supplying email and password will automatically log in.

        Parameters
        ----------
        base_url : str
            Base URL to API.
        email : str, optional
            Registered email address, by default None
        password : str, optional
            User password, by default None
        dev_mode : bool, optional
            Development mode (uses dev environment), by default False
        """

        # Initialise the mimetypes
        mt.init()

        # Dev mode can be set by the user or from the environment
        if dev_mode or mu.is_dev_mode():
            self.base_url = os.getenv("MEORG_BASE_URL_DEV", None)
        else:
            self.base_url = mcc.MEORG_BASE_URL_PROD

        self.headers = {"Cache-Control": "no-cache", "Pragma": "no-cache"}
        self.last_response = None

        # Automatically login if credentials are set.
        if email is not None and password is not None:
            self.login(email, password)

    def _make_request(
        self,
        method: str,
        endpoint: str,
        url_path_fields: dict = {},
        url_params: dict = {},
        data: dict = {},
        json: dict = {},
        headers: dict = {},
        files: dict = {},
        return_json=True,
        **kwargs,
    ):
        """Make a request against the API

        Parameters
        ----------
        method : str
            HTTP method.
        endpoint : str
            URL template for the API endpoint.
        url_path_fields : dict, optional
            Fields to interpolate into the URL template, by default {}
        url_params : dict, optional
            Parameters to add at end of URL, by default {}
        data : dict, optional
            Data to send along with the request, by default {}
        json : dict, optional
            JSON data to send along with the request, by default {}
        headers : dict, optional
            Headers to attach to the request (will be combined with client headers), by default {}
        files : dict, optional
            Files payload to attach to request, by default {}
        return_json : bool, optional
            Return a JSON dict object, by default True

        Returns
        -------
        dict or requests.Response
            Dictionary or Request object, depending on context.

        Raises
        ------
        mx.InvalidHTTPMethodException
            Raised when the specified method is invalid.
        RequestException
            Raised when the request fails.
        """

        method = method.upper()

        # Check that the method is allowed.
        if method not in mcc.VALID_METHODS:
            raise mx.InvalidHTTPMethodException(method)

        # Get the function and URL
        func = getattr(requests, method.lower())
        url = self._get_url(endpoint, url_params, **url_path_fields)

        # Assemble the headers
        _headers = self._merge_headers(headers)

        # Attach the user agent
        _headers['user-agent'] = mu.get_user_agent()

        # Make the request, set it as the last response for future use
        self.last_response = func(
            url, data=data, json=json, headers=_headers, files=files, **kwargs
        )

        # Check to see if it was successful
        if self.last_response.status_code not in mcc.HTTP_STATUS_SUCCESS_RANGE:
            raise RequestException(
                self.last_response.status_code, self.last_response.text
            )

        # This is the default
        if return_json:
            return self.last_response.json()

        # For flexibility
        return self.last_response

    def _get_url(self, endpoint: str, url_params: dict = {}, **url_path_fields: dict):
        """Get the well-formed URL for the call.

        Parameters
        ----------
        endpoint : str
            Endpoint to be appended to the base URL.
        url_path_fields : dict, optional
            Fields to interpolate into the URL template
        url_params : dict, optional
            Parameters to add at end of URL, by default {}

        Returns
        -------
        str
            URL.
        """
        # Add endpoint to base URL, interpolating url_path_fields
        url_path = urljoin(self.base_url + "/", endpoint).format(**url_path_fields)
        # Add URL parameters (if any)
        if url_params:
            url_path = f"{url_path}?{urlencode(url_params)}"
        return url_path

    def _merge_headers(self, headers: dict = dict()):
        """Merge additional headers into the client headers (i.e. Auth)

        Parameters
        ----------
        headers : dict, optional
            Additional headers to add to the client headers, by default dict()

        Returns
        -------
        dict
            Merged headers.
        """
        return {**self.headers, **headers}

    def login(self, email: str, password: str):
        """Log the user into ME.org.

        Parameters
        ----------
        email : str
            Registered email address.
        password : str
            Password (will be hashed)

        Raises
        ------
        Exception
            When the login fails.
        """

        # Assemble payload
        login_data = {
            "email": email,
            "password": hl.sha256(password.encode("UTF-8")).hexdigest(),
            "hashed": "true",
        }

        # Call
        response = self._make_request(
            method=mcc.HTTP_POST,
            endpoint=endpoints.LOGIN,
            json=login_data,
            return_json=True,
        )

        # Successful login
        if self.last_response.status_code == 200:
            auth_headers = {
                "X-User-Id": response["data"]["userId"],
                "X-Auth-Token": response["data"]["authToken"],
            }

            self.headers.update(auth_headers)

        # Unsuccessful login (technically this will have already failed)
        else:
            raise RequestException(
                self.last_response.status_code, self.last_response.text
            )

    def logout(self):
        """Log the user out. Likely not necessary, can just let sessions expire."""
        response = self._make_request(
            method=mcc.HTTP_POST, endpoint=endpoints.LOGOUT, return_json=False
        )

        # Clear the headers.
        if response.status_code == 200:
            self.headers.pop("X-User-Id", None)
            self.headers.pop("X-Auth-Token", None)

    def _upload_files_parallel(
        self,
        files: Union[str, Path, list],
        id: str,
        n: int = 2,
        progress=True,
    ):
        """Upload files in parallel.

        Parameters
        ----------
        files : Union[str, Path, list]
            A path to a file, or a list of paths.
        id : str
            Module output id to attach to, by default None.
        n : int, optional
            Number of threads to use, by default 2.

        Returns
        -------
        list
            List of dicts or response objects from upload_files.
        """

        # Ensure the object is actually iterable
        files = mu.ensure_list(files)

        # Do the parallel upload
        responses = None
        responses = meop.parallelise(
            self._upload_file, n, filepath=files, id=id, progress=progress
        )

        # These should already be a list as per the parallelise function.
        return responses

    def upload_files(
        self,
        files: Union[str, Path, list],
        id: str,
        n: int = 1,
        progress=True,
    ) -> list:
        """Upload files.

        Parameters
        ----------
        files : Union[str, Path, list]
            A filepath, or a list of filepaths.
        id : str
            Model output ID to immediately attach to.
        n : int, optional
            Number of threads to parallelise over, by default 1


        Returns
        -------
        list
            List of dicts
        """

        # Ensure the files are actually a list
        files = mu.ensure_list(files)

        # Just because someone will try to assign 0 threads...
        if n >= 1 == False:
            raise ValueError("Number of threads must be greater than or equal to 1.")

        # Sequential upload
        responses = list()
        if n == 1:
            for fp in tqdm(files, total=len(files)):
                response = self._upload_file(fp, id=id)
                responses += response
        else:
            responses += self._upload_files_parallel(
                files, n=n, id=id, progress=progress
            )

        # return mu.ensure_list(responses)
        return responses

    def _upload_file(
        self, filepath: Union[str, Path], id: str
    ) -> Union[dict, requests.Response]:
        """Upload a single file.

        Parameters
        ----------
        filepath : path-like
            Path to the file
        id : str
            model_output_id to attach the files to

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.

        Raises
        ------
        TypeError
            When supplied file is neither path-like nor readable.
        FileNotFoundError
            When supplied file cannot be found.
        """

        file_obj = None

        if isinstance(filepath, (str, Path)) and os.path.isfile(filepath):
            file_obj = open(filepath, "rb")

        # Bail out
        else:
            dtype = type(file_obj)
            raise TypeError(f"File is neither path-like nor readable ({dtype}).")

        # Prepare the payload from the files
        payload = list()

        filename = os.path.basename(file_obj.name)
        ext = filename.split(".")[-1]
        mimetype = mt.types_map[f".{ext}"]
        payload.append(("file", (filename, file_obj, mimetype)))

        # Make the request
        response = self._make_request(
            method=mcc.HTTP_POST,
            endpoint=endpoints.FILE_UPLOAD,
            files=payload,
            url_path_fields=dict(id=id),
            return_json=True,
        )

        # Close all the file descriptors (requests should do this, but just to be sure)
        for fd in payload:
            fd[1][1].close()

        return mu.ensure_list(response)

    def list_files(self, id: str) -> Union[dict, requests.Response]:
        """Get a list of model outputs.

        Parameters
        ----------
        id : str
            Model output ID

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_GET,
            endpoint=endpoints.FILE_LIST,
            url_path_fields=dict(id=id),
        )

    def delete_file_from_model_output(self, id: str, file_id: str):
        """Delete file from model output

        Parameters
        ----------
        id : str
            Model output ID.
        file_id : str
            File ID.

        Returns
        -------
        Union[dict, requests.Request]
            Response from ME.org
        """
        return self._make_request(
            method=mcc.HTTP_DELETE,
            endpoint=endpoints.FILE_DELETE,
            url_path_fields=dict(id=id, fileId=file_id),
        )

    def delete_all_files_from_model_output(self, id: str):
        """Delete file from model output

        Parameters
        ----------
        id : str
            Model output ID.

        Returns
        -------
        Union[dict, requests.Request]
            Response from ME.org
        """

        # Get a list of the files currently on the model output
        files = self.list_files(id)
        file_ids = [f.get("id") for f in files.get("data").get("files")]

        responses = list()

        # Do the delete one at a time
        for file_id in file_ids:
            response = self.delete_file_from_model_output(id=id, file_id=file_id)
            responses.append(response)

        return responses

    def start_analysis(
        self, model_output_id: str, experiment_id: str
    ) -> Union[dict, requests.Response]:
        """Start the analysis chain.

        Parameters
        ----------
        id : str
            Model output ID.

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_PUT,
            endpoint=endpoints.ANALYSIS_START,
            url_path_fields=dict(id=model_output_id, expid=experiment_id),
        )

    def model_output_create(
        self, mod_prof_id: str, name: str, **config_params
    ) -> Union[dict, requests.Response]:
        """
        Create a new model output entity
        Parameters
        ----------
        mod_prof_id : str
            Model Profile ID
        exp_id : str
            Experiment ID
        name : str
            Name of Model Output

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_POST,
            endpoint=endpoints.MODEL_OUTPUT_CREATE,
            json=dict(model=mod_prof_id, name=name) | config_params,
        )

    def model_output_query(self, model_id: str = None, name: bool = None) -> Union[dict, requests.Response]:
        """
        Get details for a specific new model output entity
        Parameters
        ----------
        model_id : str
            Model Output ID

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_GET,
            endpoint=endpoints.MODEL_OUTPUT_QUERY,
            url_params=dict(name=name) if name else dict(id=model_id),
        )

    def model_output_update(
        self, model_id: str, updated_fields: dict
    ) -> Union[dict, requests.Response]:
        """
        Update specific fields of an existing model output.
        Parameters
        ----------
        model_id : str
            Model Output ID

        params : dict
            Request body containing necessary fields to be updated

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_PATCH,
            endpoint=endpoints.MODEL_OUTPUT_UPDATE,
            url_path_fields=dict(id=model_id),
            json=updated_fields,
        )

    def model_output_benchmarks_list(
        self, model_id: str, exp_id: str
    ) -> Union[dict, requests.Response]:
        return self._make_request(
            method=mcc.HTTP_GET,
            endpoint=endpoints.MODEL_OUTPUT_BENCHMARKS,
            url_path_fields=dict(id=model_id, expId=exp_id),
        )

    def model_output_benchmarks_replace(
        self, model_id: str, exp_id: str, updated_benchmarks: list[str]
    ) -> Union[dict, requests.Response]:
        """
        Replace benchmarks.
        Parameters
        ----------
        model_id : str
            Model Output ID

        exp_id: str
            Experiment ID

        updated_benchmarks:

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_PATCH,
            endpoint=endpoints.MODEL_OUTPUT_BENCHMARKS,
            url_path_fields=dict(id=model_id, expId=exp_id),
            json=dict(benchmarks=updated_benchmarks),
        )

    def model_output_experiments_extend(
        self, model_id: str, updated_experiments: list[str]
    ) -> Union[dict, requests.Response]:
        """
        Add experiments.
        Parameters
        ----------
        model_id : str
            Model Output ID

        exp_id: str
            Experiment ID

        updated_benchmarks:

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_PATCH,
            endpoint=endpoints.MODEL_OUTPUT_EXPERIMENTS,
            url_path_fields=dict(id=model_id),
            json=dict(experiments=updated_experiments),
        )

    def model_output_experiment_delete(
        self, model_id: str, exp_id: str
    ) -> Union[dict, requests.Response]:
        return self._make_request(
            method=mcc.HTTP_DELETE,
            endpoint=endpoints.MODEL_OUTPUT_EXPERIMENTS,
            url_path_fields=dict(id=model_id),
            json=dict(experiment=exp_id),
        )

    def model_output_delete(self, model_id: str) -> Union[dict, requests.Response]:
        """
        Remove specific new model output entity
        Parameters
        ----------
        model_id : str
            Model Output ID

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_DELETE,
            endpoint=endpoints.MODEL_OUTPUT_DELETE,
            url_path_fields=dict(id=model_id),
        )

    def get_analysis_status(self, id: str) -> Union[dict, requests.Response]:
        """Check the status of the analysis chain.

        Parameters
        ----------
        id : str
            Analysis ID.

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(
            method=mcc.HTTP_GET,
            endpoint=endpoints.ANALYSIS_STATUS,
            url_path_fields=dict(id=id),
        )

    def list_endpoints(self) -> Union[dict, requests.Response]:
        """List the endpoints available to the user.

        Paths are available at .get('paths').keys()

        Returns
        -------
        Union[dict, requests.Response]
            Response from ME.org.
        """
        return self._make_request(method=mcc.HTTP_GET, endpoint=endpoints.ENDPOINT_LIST)

    def success(self) -> bool:
        """Test if the last request was successful.

        Returns
        -------
        bool
            True if successful, False otherwise.
        """
        return self.last_response.status_code in mcc.HTTP_STATUS_SUCCESS_RANGE

    def is_initialised(self, dev: bool = False) -> bool:
        """Check if the client is initialised.
        NOTE: This does not check the login actually works.
        Parameters
        ----------
        dev : bool, optional
            Use dev credentials, by default False
        Returns
        -------
        bool
            True if initialised, False otherwise.
        """
        cred_filename = "credentials.json" if not dev else "credentials-dev.json"
        cred_filepath = mu.get_user_data_filepath(cred_filename)
        return cred_filepath.exists()

__init__(email=None, password=None, dev_mode=False)

ME.org Client object.

Supplying email and password will automatically log in.

Parameters

base_url : str Base URL to API. email : str, optional Registered email address, by default None password : str, optional User password, by default None dev_mode : bool, optional Development mode (uses dev environment), by default False

Source code in meorg_client/client.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __init__(self, email: str = None, password: str = None, dev_mode: bool = False):
    """ME.org Client object.

    Supplying email and password will automatically log in.

    Parameters
    ----------
    base_url : str
        Base URL to API.
    email : str, optional
        Registered email address, by default None
    password : str, optional
        User password, by default None
    dev_mode : bool, optional
        Development mode (uses dev environment), by default False
    """

    # Initialise the mimetypes
    mt.init()

    # Dev mode can be set by the user or from the environment
    if dev_mode or mu.is_dev_mode():
        self.base_url = os.getenv("MEORG_BASE_URL_DEV", None)
    else:
        self.base_url = mcc.MEORG_BASE_URL_PROD

    self.headers = {"Cache-Control": "no-cache", "Pragma": "no-cache"}
    self.last_response = None

    # Automatically login if credentials are set.
    if email is not None and password is not None:
        self.login(email, password)

delete_all_files_from_model_output(id)

Delete file from model output

Parameters

id : str Model output ID.

Returns

Union[dict, requests.Request] Response from ME.org

Source code in meorg_client/client.py
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def delete_all_files_from_model_output(self, id: str):
    """Delete file from model output

    Parameters
    ----------
    id : str
        Model output ID.

    Returns
    -------
    Union[dict, requests.Request]
        Response from ME.org
    """

    # Get a list of the files currently on the model output
    files = self.list_files(id)
    file_ids = [f.get("id") for f in files.get("data").get("files")]

    responses = list()

    # Do the delete one at a time
    for file_id in file_ids:
        response = self.delete_file_from_model_output(id=id, file_id=file_id)
        responses.append(response)

    return responses

delete_file_from_model_output(id, file_id)

Delete file from model output

Parameters

id : str Model output ID. file_id : str File ID.

Returns

Union[dict, requests.Request] Response from ME.org

Source code in meorg_client/client.py
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
def delete_file_from_model_output(self, id: str, file_id: str):
    """Delete file from model output

    Parameters
    ----------
    id : str
        Model output ID.
    file_id : str
        File ID.

    Returns
    -------
    Union[dict, requests.Request]
        Response from ME.org
    """
    return self._make_request(
        method=mcc.HTTP_DELETE,
        endpoint=endpoints.FILE_DELETE,
        url_path_fields=dict(id=id, fileId=file_id),
    )

get_analysis_status(id)

Check the status of the analysis chain.

Parameters

id : str Analysis ID.

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
def get_analysis_status(self, id: str) -> Union[dict, requests.Response]:
    """Check the status of the analysis chain.

    Parameters
    ----------
    id : str
        Analysis ID.

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_GET,
        endpoint=endpoints.ANALYSIS_STATUS,
        url_path_fields=dict(id=id),
    )

is_initialised(dev=False)

Check if the client is initialised. NOTE: This does not check the login actually works. Parameters


dev : bool, optional Use dev credentials, by default False Returns


bool True if initialised, False otherwise.

Source code in meorg_client/client.py
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
def is_initialised(self, dev: bool = False) -> bool:
    """Check if the client is initialised.
    NOTE: This does not check the login actually works.
    Parameters
    ----------
    dev : bool, optional
        Use dev credentials, by default False
    Returns
    -------
    bool
        True if initialised, False otherwise.
    """
    cred_filename = "credentials.json" if not dev else "credentials-dev.json"
    cred_filepath = mu.get_user_data_filepath(cred_filename)
    return cred_filepath.exists()

list_endpoints()

List the endpoints available to the user.

Paths are available at .get('paths').keys()

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
640
641
642
643
644
645
646
647
648
649
650
def list_endpoints(self) -> Union[dict, requests.Response]:
    """List the endpoints available to the user.

    Paths are available at .get('paths').keys()

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(method=mcc.HTTP_GET, endpoint=endpoints.ENDPOINT_LIST)

list_files(id)

Get a list of model outputs.

Parameters

id : str Model output ID

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
def list_files(self, id: str) -> Union[dict, requests.Response]:
    """Get a list of model outputs.

    Parameters
    ----------
    id : str
        Model output ID

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_GET,
        endpoint=endpoints.FILE_LIST,
        url_path_fields=dict(id=id),
    )

login(email, password)

Log the user into ME.org.

Parameters

email : str Registered email address. password : str Password (will be hashed)

Raises

Exception When the login fails.

Source code in meorg_client/client.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def login(self, email: str, password: str):
    """Log the user into ME.org.

    Parameters
    ----------
    email : str
        Registered email address.
    password : str
        Password (will be hashed)

    Raises
    ------
    Exception
        When the login fails.
    """

    # Assemble payload
    login_data = {
        "email": email,
        "password": hl.sha256(password.encode("UTF-8")).hexdigest(),
        "hashed": "true",
    }

    # Call
    response = self._make_request(
        method=mcc.HTTP_POST,
        endpoint=endpoints.LOGIN,
        json=login_data,
        return_json=True,
    )

    # Successful login
    if self.last_response.status_code == 200:
        auth_headers = {
            "X-User-Id": response["data"]["userId"],
            "X-Auth-Token": response["data"]["authToken"],
        }

        self.headers.update(auth_headers)

    # Unsuccessful login (technically this will have already failed)
    else:
        raise RequestException(
            self.last_response.status_code, self.last_response.text
        )

logout()

Log the user out. Likely not necessary, can just let sessions expire.

Source code in meorg_client/client.py
221
222
223
224
225
226
227
228
229
230
def logout(self):
    """Log the user out. Likely not necessary, can just let sessions expire."""
    response = self._make_request(
        method=mcc.HTTP_POST, endpoint=endpoints.LOGOUT, return_json=False
    )

    # Clear the headers.
    if response.status_code == 200:
        self.headers.pop("X-User-Id", None)
        self.headers.pop("X-Auth-Token", None)

model_output_benchmarks_replace(model_id, exp_id, updated_benchmarks)

Replace benchmarks. Parameters


model_id : str Model Output ID

str

Experiment ID

updated_benchmarks:

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
def model_output_benchmarks_replace(
    self, model_id: str, exp_id: str, updated_benchmarks: list[str]
) -> Union[dict, requests.Response]:
    """
    Replace benchmarks.
    Parameters
    ----------
    model_id : str
        Model Output ID

    exp_id: str
        Experiment ID

    updated_benchmarks:

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_PATCH,
        endpoint=endpoints.MODEL_OUTPUT_BENCHMARKS,
        url_path_fields=dict(id=model_id, expId=exp_id),
        json=dict(benchmarks=updated_benchmarks),
    )

model_output_create(mod_prof_id, name, **config_params)

Create a new model output entity Parameters


mod_prof_id : str Model Profile ID exp_id : str Experiment ID name : str Name of Model Output

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
def model_output_create(
    self, mod_prof_id: str, name: str, **config_params
) -> Union[dict, requests.Response]:
    """
    Create a new model output entity
    Parameters
    ----------
    mod_prof_id : str
        Model Profile ID
    exp_id : str
        Experiment ID
    name : str
        Name of Model Output

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_POST,
        endpoint=endpoints.MODEL_OUTPUT_CREATE,
        json=dict(model=mod_prof_id, name=name) | config_params,
    )

model_output_delete(model_id)

Remove specific new model output entity Parameters


model_id : str Model Output ID

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
def model_output_delete(self, model_id: str) -> Union[dict, requests.Response]:
    """
    Remove specific new model output entity
    Parameters
    ----------
    model_id : str
        Model Output ID

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_DELETE,
        endpoint=endpoints.MODEL_OUTPUT_DELETE,
        url_path_fields=dict(id=model_id),
    )

model_output_experiments_extend(model_id, updated_experiments)

Add experiments. Parameters


model_id : str Model Output ID

str

Experiment ID

updated_benchmarks:

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
def model_output_experiments_extend(
    self, model_id: str, updated_experiments: list[str]
) -> Union[dict, requests.Response]:
    """
    Add experiments.
    Parameters
    ----------
    model_id : str
        Model Output ID

    exp_id: str
        Experiment ID

    updated_benchmarks:

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_PATCH,
        endpoint=endpoints.MODEL_OUTPUT_EXPERIMENTS,
        url_path_fields=dict(id=model_id),
        json=dict(experiments=updated_experiments),
    )

model_output_query(model_id=None, name=None)

Get details for a specific new model output entity Parameters


model_id : str Model Output ID

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
def model_output_query(self, model_id: str = None, name: bool = None) -> Union[dict, requests.Response]:
    """
    Get details for a specific new model output entity
    Parameters
    ----------
    model_id : str
        Model Output ID

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_GET,
        endpoint=endpoints.MODEL_OUTPUT_QUERY,
        url_params=dict(name=name) if name else dict(id=model_id),
    )

model_output_update(model_id, updated_fields)

Update specific fields of an existing model output. Parameters


model_id : str Model Output ID

dict

Request body containing necessary fields to be updated

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
def model_output_update(
    self, model_id: str, updated_fields: dict
) -> Union[dict, requests.Response]:
    """
    Update specific fields of an existing model output.
    Parameters
    ----------
    model_id : str
        Model Output ID

    params : dict
        Request body containing necessary fields to be updated

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_PATCH,
        endpoint=endpoints.MODEL_OUTPUT_UPDATE,
        url_path_fields=dict(id=model_id),
        json=updated_fields,
    )

start_analysis(model_output_id, experiment_id)

Start the analysis chain.

Parameters

id : str Model output ID.

Returns

Union[dict, requests.Response] Response from ME.org.

Source code in meorg_client/client.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def start_analysis(
    self, model_output_id: str, experiment_id: str
) -> Union[dict, requests.Response]:
    """Start the analysis chain.

    Parameters
    ----------
    id : str
        Model output ID.

    Returns
    -------
    Union[dict, requests.Response]
        Response from ME.org.
    """
    return self._make_request(
        method=mcc.HTTP_PUT,
        endpoint=endpoints.ANALYSIS_START,
        url_path_fields=dict(id=model_output_id, expid=experiment_id),
    )

success()

Test if the last request was successful.

Returns

bool True if successful, False otherwise.

Source code in meorg_client/client.py
652
653
654
655
656
657
658
659
660
def success(self) -> bool:
    """Test if the last request was successful.

    Returns
    -------
    bool
        True if successful, False otherwise.
    """
    return self.last_response.status_code in mcc.HTTP_STATUS_SUCCESS_RANGE

upload_files(files, id, n=1, progress=True)

Upload files.

Parameters

files : Union[str, Path, list] A filepath, or a list of filepaths. id : str Model output ID to immediately attach to. n : int, optional Number of threads to parallelise over, by default 1

Returns

list List of dicts

Source code in meorg_client/client.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
def upload_files(
    self,
    files: Union[str, Path, list],
    id: str,
    n: int = 1,
    progress=True,
) -> list:
    """Upload files.

    Parameters
    ----------
    files : Union[str, Path, list]
        A filepath, or a list of filepaths.
    id : str
        Model output ID to immediately attach to.
    n : int, optional
        Number of threads to parallelise over, by default 1


    Returns
    -------
    list
        List of dicts
    """

    # Ensure the files are actually a list
    files = mu.ensure_list(files)

    # Just because someone will try to assign 0 threads...
    if n >= 1 == False:
        raise ValueError("Number of threads must be greater than or equal to 1.")

    # Sequential upload
    responses = list()
    if n == 1:
        for fp in tqdm(files, total=len(files)):
            response = self._upload_file(fp, id=id)
            responses += response
    else:
        responses += self._upload_files_parallel(
            files, n=n, id=id, progress=progress
        )

    # return mu.ensure_list(responses)
    return responses