IMS Global Learning Consortium, Inc.
The Caliper Analytics® Specification provides a structured approach to describing, collecting and exchanging learning activity data at scale. Caliper also defines an application programming interface (the Sensor API™) for marshalling and transmitting event data from instrumented applications to target endpoints for storage, analysis and use.
caliper-python is a reference implementation of the Sensor API™ written in Python.
To effectively use this
imsglobal_caliper bundle, you will need to have Python, pip, and
setuptools installed. This package also depends on several third-party packages:
aniso8601 -- (3-clause BSD licensed)
requests -- (Apache 2 licensed)
rfc3986 -- (Apache 2 licensed)
To work with the
imsglobal_caliper bundle (maintain, use, run tests) you'll need to have these
third-party packages in your local Python environment.
In test, the
ims_global package depends on these third-party packages not actually used within
the package code itself:
pytest and pytest-cov
imsglobal_caliper primarily using the Python 3.9+ platform (we no longer directly
support the Python 2 platform). Changes to the code base should ensure the tests run clean under
Python 3.7-10 and PyPy3. Currently, merges into the ongoing
imsglobal_caliper work get tested in
Travis-CI (xenial-based build environment) against Python versions 3.7-10, and PyPy3.
Build and install
imsglobal_caliper to be packagable for loading from PyPi, or direct from a source
If it's on PyPi, you can simply install it into your site-packages with:
pip3 install imsglobal_caliper
Building and installing from a source bundle
If you want to install it from a source bundle, then use these steps (note that the name of the repository on GitHub is different to the distribution package name):
Clone the repository from GitHub onto your local machine.
Use pip to install the package as a writable source bundle; there's no need to do a package build step ahead of time:
pip3 install -e caliper-python
Changing and testing
imsglobal_caliper code you would use to build and use a caliper sensor in your
application you can find in the
caliper main module.
Branches and tags. This project organizes around two, long-running branches:
master. Stable, deployable branch that stores the official release history.
develop. Unstable development branch; current work that targets a future release is merged to this branch.
Project releases are tagged and versioned using git tags of the form
MAJOR.MINOR.PATCH[-label] (for example:
1.1.0; or, for signalling a pre-release version
with a label extension,
Making contributions. We welcome the posting of issues by those who are not members of IMS
Global Learning Consortium (for example, for feature requests, bug reports, questions, and so on)
but we do not accept contributions in the form of pull requests from non-members. See
CONTRIBUTING.md for more information.
The project uses Black, and its configuration in a
pyproject.toml file as the canonical way to
format the code; code contributions should keep in line with this.
Testing. When you work with the source bundle, we include tests in the
tests directory. In
general, whenever you might make changes to code in
caliper, you should run the fixture tests.
From the source repo's top-level directory, use
tox to run all the tests on Python 3.7, 3.8, 3.9,
3.10, and PyPy3.
Before you can run the tests, you will also need a copy of the Caliper fixtures (see below).
Fixtures. The test suites are principally designed to test against the canonical common JSON
fixtures. To set up your tests, you should clone the main
Caliper specification repository
repository into the
tests directory, and pull changes there. The test utility code is set up to
find the appropriate fixtures by looking into
Ideally, if you make changes to
imsglobal_caliper you should ensure that your changes will test
well against the fixtures from the main common fixtures repository. When IMS makes updates to the
imsglobal_caliper repository, it ensures that the updates do test well against the common
Using the package
imsglobal_caliper with a lean integration layer for your application. To use the package,
your application needs only:
Your application will need awareness of these parts of the package:
caliper.HttpOptionsclass (for use with simple HTTP transport to a Caliper endpoint).
caliper.SimpleSensorclass and its API.
Entitysubclasses (found in
caliper.entities, respectively) that you will need to use for the metric profile actions you want to support (see
caliper.constantsfor the association of entities and events with profiles).
Here is a very simple example code scrap that demonstrates how an application might send a basic navigation event to a caliper endpoint:
import caliper the_config = caliper.HttpOptions( host='http://caliper-endpoint.your-school.edu/events/', auth_scheme='Bearer', api_key='your-caliper-API-key' ) # Here you build your sensor; it will have one client in its registry, # with the key 'default'. the_sensor = caliper.build_simple_sensor( sensor_id = 'http://learning-app.your-school.edu/sensor', config_options = the_config ) # Here, you will have caliper entity representations of the various # learning objects and entities in your wider system, and you provide # them into the constructor for the event that has just happened. # # Note that you don't have to pass an action into the constructor because # the NavigationEvent only supports one action, part of the # Caliper base profile: caliper.constants.BASE_PROFILE_ACTIONS['NAVIGATED_TO'] # the_event = caliper.events.NavigationEvent( actor = the_user_currently_acting_as_caliper_Actor_entity, edApp = your_application_as_caliper_SoftwareApplication_entity, group = the_course_offering_in_play_as_caliper_Organization_entity, event_object = the_caliper_DigitalResource_the_actor_is_using, referrer = the_caliper_DigitalResource_the_actor_came_from, target = the_caliper_DigitalResource_the_actor_is_going_to, eventTime = the_time_when_the_actor_did_the_action ) # Once built, you can use your sensor to send one or more often used # entities; suppose for example, you'll be sending a number of events # that all have the same actor sent_identities = the_sensor.send(the_event.actor) # The return structure from the sensor will be a dictionary of lists: each # item in the dictionary has a key corresponding to a client key, # so ret['default'] fetches back the list of URIs of all the @ids of # the fully described Caliper objects you have sent with that describe call. # # Now you can use this list with event sendings to send only the identifiers # of already-described entities, and not their full forms: the_sensor.send(the_event, described_objects=sent_identities) # You can also just send the event in its full form, with all fleshed out # entities: the_sensor.send(the_event) # You can check the status code sent back by the endpoint for the last # invocation of send(): assert the_sensor.status_code in [200, 201, 202] # If you create your configuration with debug=true, then your sensor will # keep a list of the full responses (it uses the "requests" library under the # covers, so these will be response objects from that library: the_sensor.config.DEBUG = true sent_identifies = the_sensor.send(the_event.actor) the_sensor.send(the_event, described_objects=sent_identities) responses = the_sensor.debug
Your actual use of the caliper code will certainly be more complex than this. For assistance getting from this very simple example through to more complex and realistic code-use, we encourage you to look at the unit tests in the package, and the common fixtures they test against.
Copyright and License
This project is licensed under the terms of the GNU Lesser General Public License (LGPL), version 3. See the LICENSE file for details. For additional information on licensing options for IMS members, please see the NOTICE file.
©2020 IMS Global Learning Consortium, Inc. All Rights Reserved. Trademark Information - http://www.imsglobal.org/copyright.html