lms.backend.instance

  1import typing
  2
  3import edq.net.request
  4import requests
  5
  6import lms.backend.blackboard.backend
  7import lms.backend.canvas.backend
  8import lms.backend.moodle.backend
  9import lms.model.config
 10import lms.model.constants
 11import lms.model.backend
 12
 13def get_backend(
 14        config: lms.model.config.Config,
 15        **kwargs: typing.Any) -> lms.model.backend.APIBackend:
 16    """
 17    Get an instance of an API backend from the given config information.
 18    If the backend type is not explicitly provided,
 19    this function will attempt to guess it from other information.
 20
 21    This function may modify the config and will pass ownership to the backend instance.
 22    """
 23
 24    if (config.server is None):
 25        raise ValueError("No LMS server address provided.")
 26
 27    config.server = config.server.strip()
 28    if (not config.server.startswith('http')):
 29        config.server = 'http://' + config.server
 30
 31    guess_backend_type(config)
 32    if (config.backend_type is None):
 33        raise ValueError(f"Unable to guess backend type from server: '{config.server}'.")
 34
 35    if (config.backend_type == lms.model.constants.BackendType.CANVAS):
 36        return lms.backend.canvas.backend.CanvasBackend(config = config, **kwargs)
 37    elif (config.backend_type == lms.model.constants.BackendType.MOODLE):
 38        return lms.backend.moodle.backend.MoodleBackend(config = config, **kwargs)
 39    elif (config.backend_type == lms.model.constants.BackendType.BLACKBOARD):
 40        return lms.backend.blackboard.backend.BlackboardBackend(config = config, **kwargs)
 41    elif (config.backend_type not in lms.model.constants.BackendType):
 42        raise ValueError(f"Instance creation not yet supported for backend type: '{config.backend_type.value}'.")
 43    else:
 44        raise ValueError((f"Unknown backend type: '{config.backend_type.value}'.",
 45                + f" Known backend types: {[choice.value for choice in lms.model.constants.OutputFormat]}."))
 46
 47def guess_backend_type(
 48        config: lms.model.config.Config,
 49        **kwargs: typing.Any) -> None:
 50    """
 51    Attempt to guess the backend type from a server.
 52    The result of the guess (which may be None) will be placed in the passed-in config.
 53    """
 54
 55    if (config.backend_type is not None):
 56        return
 57
 58    if (config.server is None):
 59        return
 60
 61    # Try looking at the URL string itself.
 62    config.backend_type = guess_backend_type_from_url(config.server)
 63    if (config.backend_type is not None):
 64        return
 65
 66    # Finally, make a request to the server and examine the response.
 67    config.backend_type = guess_backend_type_from_request(config.server)
 68
 69def guess_backend_type_from_request(
 70        server: str,
 71        timeout_secs: typing.Union[float, None] = None,
 72        ) -> typing.Union[lms.model.constants.BackendType, None]:
 73    """
 74    Attempt to guess the backend type by pinging the server.
 75    This function will not do any lexical analysis on the server string.
 76    """
 77
 78    options = {
 79        'allow_redirects': False,
 80    }
 81
 82    try:
 83        response, _ = edq.net.request.make_get(server,
 84                raise_for_status = False,
 85                timeout_secs = timeout_secs,
 86                additional_requests_options = options)
 87    except requests.exceptions.ConnectionError:
 88        return None
 89    except requests.exceptions.Timeout:
 90        return None
 91
 92    header_keys = [key.lower() for key in response.headers.keys()]
 93
 94    # Blackboard sends a special header.
 95    if ('x-blackboard-product' in header_keys):
 96        return lms.model.constants.BackendType.BLACKBOARD
 97
 98    # Canvas sends a special header.
 99    if ('x-canvas-meta' in header_keys):
100        return lms.model.constants.BackendType.CANVAS
101
102    # Canvas requests that a specific cookie is set.
103    if ('_normandy_session' in response.headers.get('set-cookie', '')):
104        return lms.model.constants.BackendType.CANVAS
105
106    # Moodle will try to redirect with a special header.
107    if (response.headers.get('x-redirect-by', '').lower() == 'moodle'):
108        return lms.model.constants.BackendType.MOODLE
109
110    # Moodle requests that a specific cookie is set.
111    if ('MoodleSession' in response.headers.get('set-cookie', '')):
112        return lms.model.constants.BackendType.MOODLE
113
114    return None
115
116def guess_backend_type_from_url(server: str) -> typing.Union[lms.model.constants.BackendType, None]:
117    """
118    Attempt to guess the backend type only from a string server URL.
119    This function will only do lexical analysis on the string (no HTTP requests will be made).
120    """
121
122    server = server.lower().strip()
123
124    if ('canvas' in server):
125        return lms.model.constants.BackendType.CANVAS
126
127    if ('moodle' in server):
128        return lms.model.constants.BackendType.MOODLE
129
130    if ('blackboard' in server):
131        return lms.model.constants.BackendType.BLACKBOARD
132
133    return None
def get_backend( config: lms.model.config.Config, **kwargs: Any) -> lms.model.backend.APIBackend:
14def get_backend(
15        config: lms.model.config.Config,
16        **kwargs: typing.Any) -> lms.model.backend.APIBackend:
17    """
18    Get an instance of an API backend from the given config information.
19    If the backend type is not explicitly provided,
20    this function will attempt to guess it from other information.
21
22    This function may modify the config and will pass ownership to the backend instance.
23    """
24
25    if (config.server is None):
26        raise ValueError("No LMS server address provided.")
27
28    config.server = config.server.strip()
29    if (not config.server.startswith('http')):
30        config.server = 'http://' + config.server
31
32    guess_backend_type(config)
33    if (config.backend_type is None):
34        raise ValueError(f"Unable to guess backend type from server: '{config.server}'.")
35
36    if (config.backend_type == lms.model.constants.BackendType.CANVAS):
37        return lms.backend.canvas.backend.CanvasBackend(config = config, **kwargs)
38    elif (config.backend_type == lms.model.constants.BackendType.MOODLE):
39        return lms.backend.moodle.backend.MoodleBackend(config = config, **kwargs)
40    elif (config.backend_type == lms.model.constants.BackendType.BLACKBOARD):
41        return lms.backend.blackboard.backend.BlackboardBackend(config = config, **kwargs)
42    elif (config.backend_type not in lms.model.constants.BackendType):
43        raise ValueError(f"Instance creation not yet supported for backend type: '{config.backend_type.value}'.")
44    else:
45        raise ValueError((f"Unknown backend type: '{config.backend_type.value}'.",
46                + f" Known backend types: {[choice.value for choice in lms.model.constants.OutputFormat]}."))

Get an instance of an API backend from the given config information. If the backend type is not explicitly provided, this function will attempt to guess it from other information.

This function may modify the config and will pass ownership to the backend instance.

def guess_backend_type(config: lms.model.config.Config, **kwargs: Any) -> None:
48def guess_backend_type(
49        config: lms.model.config.Config,
50        **kwargs: typing.Any) -> None:
51    """
52    Attempt to guess the backend type from a server.
53    The result of the guess (which may be None) will be placed in the passed-in config.
54    """
55
56    if (config.backend_type is not None):
57        return
58
59    if (config.server is None):
60        return
61
62    # Try looking at the URL string itself.
63    config.backend_type = guess_backend_type_from_url(config.server)
64    if (config.backend_type is not None):
65        return
66
67    # Finally, make a request to the server and examine the response.
68    config.backend_type = guess_backend_type_from_request(config.server)

Attempt to guess the backend type from a server. The result of the guess (which may be None) will be placed in the passed-in config.

def guess_backend_type_from_request( server: str, timeout_secs: Optional[float] = None) -> Optional[lms.model.constants.BackendType]:
 70def guess_backend_type_from_request(
 71        server: str,
 72        timeout_secs: typing.Union[float, None] = None,
 73        ) -> typing.Union[lms.model.constants.BackendType, None]:
 74    """
 75    Attempt to guess the backend type by pinging the server.
 76    This function will not do any lexical analysis on the server string.
 77    """
 78
 79    options = {
 80        'allow_redirects': False,
 81    }
 82
 83    try:
 84        response, _ = edq.net.request.make_get(server,
 85                raise_for_status = False,
 86                timeout_secs = timeout_secs,
 87                additional_requests_options = options)
 88    except requests.exceptions.ConnectionError:
 89        return None
 90    except requests.exceptions.Timeout:
 91        return None
 92
 93    header_keys = [key.lower() for key in response.headers.keys()]
 94
 95    # Blackboard sends a special header.
 96    if ('x-blackboard-product' in header_keys):
 97        return lms.model.constants.BackendType.BLACKBOARD
 98
 99    # Canvas sends a special header.
100    if ('x-canvas-meta' in header_keys):
101        return lms.model.constants.BackendType.CANVAS
102
103    # Canvas requests that a specific cookie is set.
104    if ('_normandy_session' in response.headers.get('set-cookie', '')):
105        return lms.model.constants.BackendType.CANVAS
106
107    # Moodle will try to redirect with a special header.
108    if (response.headers.get('x-redirect-by', '').lower() == 'moodle'):
109        return lms.model.constants.BackendType.MOODLE
110
111    # Moodle requests that a specific cookie is set.
112    if ('MoodleSession' in response.headers.get('set-cookie', '')):
113        return lms.model.constants.BackendType.MOODLE
114
115    return None

Attempt to guess the backend type by pinging the server. This function will not do any lexical analysis on the server string.

def guess_backend_type_from_url(server: str) -> Optional[lms.model.constants.BackendType]:
117def guess_backend_type_from_url(server: str) -> typing.Union[lms.model.constants.BackendType, None]:
118    """
119    Attempt to guess the backend type only from a string server URL.
120    This function will only do lexical analysis on the string (no HTTP requests will be made).
121    """
122
123    server = server.lower().strip()
124
125    if ('canvas' in server):
126        return lms.model.constants.BackendType.CANVAS
127
128    if ('moodle' in server):
129        return lms.model.constants.BackendType.MOODLE
130
131    if ('blackboard' in server):
132        return lms.model.constants.BackendType.BLACKBOARD
133
134    return None

Attempt to guess the backend type only from a string server URL. This function will only do lexical analysis on the string (no HTTP requests will be made).