Skip to main content

Hybrid SPARQL query engine for timeseries data

Project description

chrontext: High-performance hybrid query engine for knowledge graphs and analytical data (e.g. time-series)

Chrontext allows you to use your knowledge graph to access large amounts of time-series or other analytical data. It uses a commodity SPARQL Triplestore and your existing data storage infrastructure. It currently supports time-series stored in a PostgreSQL-compatible Database such as DuckDB, Google Cloud BigQuery (SQL) and OPC UA HA, but can easily be extended to other APIs and databases. Chrontext Architecture

Chrontext forms a semantic layer that allows self-service data access, abstracting away technical infrastructure. Users can create query-based inputs for data products, that maintains these data products as the knowledge graph is maintained, and that can be deployed across heterogeneous on-premise and cloud infrastructures with the same API.

Chrontext is a high-performance Python library built in Rust using Polars, and relies heavily on packages from the Oxigraph project. Chrontext works with Apache Arrow, prefers time-series transport using Apache Arrow Flight and delivers results as Polars DataFrames.

Please reach out to Data Treehouse if you would like help trying Chrontext, or require support for a different database backend.

Installing

Chrontext is in pip, just use:

pip install chrontext

The API is documented HERE.

Example query in Python

The code assumes that we have a SPARQL-endpoint and BigQuery set up with time-series.

...
q = """
PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
PREFIX ct:<https://github.com/DataTreehouse/chrontext#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> 
PREFIX rds: <https://github.com/DataTreehouse/solar_demo/rds_power#> 
SELECT ?path ?t ?ts_pow_value ?ts_irr_value
WHERE {
    ?site a rds:Site;
    rdfs:label "Jonathanland";
    rds:functionalAspect ?block.
    # At the Block level there is an irradiation measurement:
    ?block a rds:A;
    ct:hasTimeseries ?ts_irr.
    ?ts_irr rdfs:label "RefCell1_Wm2".
    
    # At the Inverter level, there is a Power measurement
    ?block rds:functionalAspect+ ?inv.
    ?inv a rds:TBB;
    rds:path ?path;
    ct:hasTimeseries ?ts_pow.
    ?ts_pow rdfs:label "InvPDC_kW".
    
    ?ts_pow ct:hasDataPoint ?ts_pow_datapoint.
    ?ts_pow_datapoint ct:hasValue ?ts_pow_value;
        ct:hasTimestamp ?t.
    ?ts_irr ct:hasDataPoint ?ts_irr_datapoint.
    ?ts_irr_datapoint ct:hasValue ?ts_irr_value;
        ct:hasTimestamp ?t.
    FILTER(
        ?t >= "2018-08-24T12:00:00+00:00"^^xsd:dateTime && 
        ?t <= "2018-08-24T13:00:00+00:00"^^xsd:dateTime)
} ORDER BY ?path ?t 
"""
df = engine.query(q)

This produces the following DataFrame:

path t ts_pow_value ts_irr_value
str datetime[ns, UTC] f64 f64
=.A1.RG1.TBB1 2018-08-24 12:00:00 UTC 39.74 184.0
=.A1.RG1.TBB1 2018-08-24 12:00:01 UTC 39.57 184.0
=.A1.RG1.TBB1 2018-08-24 12:00:02 UTC 40.1 184.0
=.A1.RG1.TBB1 2018-08-24 12:00:03 UTC 40.05 184.0
=.A1.RG1.TBB1 2018-08-24 12:00:04 UTC 40.02 184.0
=.A5.RG9.TBB1 2018-08-24 12:59:56 UTC 105.5 427.5
=.A5.RG9.TBB1 2018-08-24 12:59:57 UTC 104.9 427.6
=.A5.RG9.TBB1 2018-08-24 12:59:58 UTC 105.6 428.0
=.A5.RG9.TBB1 2018-08-24 12:59:59 UTC 105.9 428.0
=.A5.RG9.TBB1 2018-08-24 13:00:00 UTC 105.7 428.5

API

The API is documented HERE.

Tutorial using DuckDB

In the following tutorial, we assume that you have a couple of CSV-files on disk that you want to query. We assume that you have DuckDB and chrontext installed, if not, do pip install chrontext duckdb. Installing chrontext will also install sqlalchemy, which we rely on to define the virtualized DuckDB tables.

CSV files

Our csv files look like this.

ts1.csv :

timestamp,value
2022-06-01T08:46:52,1
2022-06-01T08:46:53,10
..
2022-06-01T08:46:59,105

ts2.csv:

timestamp,value
2022-06-01T08:46:52,2
2022-06-01T08:46:53,20
...
2022-06-01T08:46:59,206

DuckDB setup:

We need to create a class with a method query that takes a SQL string its argument, returning a Polars DataFrame. In this class, we just hard code the DuckDB setup in the constructor.

import duckdb
import polars as pl

class MyDuckDB():
    def __init__(self):
        con = duckdb.connect()
        con.execute("SET TIME ZONE 'UTC';")
        con.execute("""CREATE TABLE ts1 ("timestamp" TIMESTAMPTZ, "value" INTEGER)""")
        ts_1 = pl.read_csv("ts1.csv", try_parse_dates=True).with_columns(pl.col("timestamp").dt.replace_time_zone("UTC"))
        con.append("ts1", df=ts_1.to_pandas())
        con.execute("""CREATE TABLE ts2 ("timestamp" TIMESTAMPTZ, "value" INTEGER)""")
        ts_2 = pl.read_csv("ts2.csv", try_parse_dates=True).with_columns(pl.col("timestamp").dt.replace_time_zone("UTC"))
        con.append("ts2", df=ts_2.to_pandas())
        self.con = con


    def query(self, sql:str) -> pl.DataFrame:
        # We execute the query and return it as a Polars DataFrame.
        # Chrontext expects this method to exist in the provided class.
        df = self.con.execute(sql).pl()
        return df

my_db = MyDuckDB()

Defining a virtualized SQL

We first define a sqlalchemy select query involving the two tables. It is crucial that we have a column labelled "id" here. Chrontext will modify this query when executing hybrid queries.

from sqlalchemy import MetaData, Table, Column, bindparam
metadata = MetaData()
ts1_table = Table(
    "ts1",
    metadata,
    Column("timestamp"),
    Column("value")
)
ts2_table = Table(
    "ts2",
    metadata,
    Column("timestamp"),
    Column("value")
)
ts1 = ts1_table.select().add_columns(
    bindparam("id1", "ts1").label("id"),
)
ts2 = ts2_table.select().add_columns(
    bindparam("id2", "ts2").label("id"),
)
sql = ts1.union(ts2)

Now, we are ready to define the virtualized backend. We will annotate nodes of the graph with a resource data property. These data properties will be linked to virtualized RDF triples in the DuckDB backend. The resource_sql_map decides which SQL is used for each resource property.

from chrontext import VirtualizedPythonDatabase

vdb = VirtualizedPythonDatabase(
    database=my_db,
    resource_sql_map={"my_resource": sql},
    sql_dialect="postgres"
)

The triple below will link the ex:myWidget1 to triples defined by the above sql.

ex:myWidget1 ct:hasResource "my_resource" . 

However, it will only be linked to those triples corresponding to rows where the identifier column equals the identifier associated with ex:myWidget1. Below, we define that ex:instanceA is only linked to those rows where the id column is ts1.

ex:myWidget1 ct:hasIdentifier "ts1" . 

In any such resource sql, the id column is mandatory.

Relating the Database to RDF Triples

Next, we want to relate the rows in this sql, each containing id, timestamp, value to RDF triples, using a template. It is crucial to have the column id.

from chrontext import Prefix, Variable, Template, Parameter, RDFType, Triple, XSD
ct = Prefix("ct", "https://github.com/DataTreehouse/chrontext#")
xsd = XSD()
id = Variable("id")
timestamp = Variable("timestamp")
value = Variable("value")
dp = Variable("dp")
resources = {
    "my_resource": Template(
        iri=ct.suf("my_resource"),
        parameters=[
            Parameter(id, rdf_type=RDFType.Literal(xsd.string)),
            Parameter(timestamp, rdf_type=RDFType.Literal(xsd.dateTime)),
            Parameter(value, rdf_type=RDFType.Literal(xsd.double)),
        ],
        instances=[
            Triple(id, ct.suf("hasDataPoint"), dp),
            Triple(dp, ct.suf("hasValue"), value),
            Triple(dp, ct.suf("hasTimestamp"), timestamp)
        ]
)}

This means that our instance ex:myWidget1, will be associated with a value and a timestamp (and a blank data point) for each row in ts1.csv. For instance, the first row means we have:

ex:widget1 ct:hasDataPoint _:b1 .
_:b1 ct:hasTimestamp "2022-06-01T08:46:52Z"^^xsd:dateTime .
_:b1 ct:hasValue 1 .

Chrontext is created for those cases when this is infeasibly many triples, so we do not want to materialize them, but query them.

Creating the engine and querying:

The context for our analytical data (e.g. a model of an industrial asset) has to be stored in a SPARQL endpoint. In this case, we use an embedded Oxigraph engine that comes with chrontext. Now we assemble the pieces and create the engine.

from chrontext import Engine, SparqlEmbeddedOxigraph
oxigraph_store = SparqlEmbeddedOxigraph(rdf_file="my_graph.ttl", path="oxigraph_db_tutorial")
engine = Engine(
    resources,
    virtualized_python_database=vdb,
    sparql_embedded_oxigraph=oxigraph_store)
engine.init()

Now we can use our context to query the dataset. The aggregation below are pushed into DuckDB. The example below is a bit simple, but complex conditions can identify the ?w and ?s.

q = """
    PREFIX xsd:<http://www.w3.org/2001/XMLSchema#>
    PREFIX chrontext:<https://github.com/DataTreehouse/chrontext#>
    PREFIX types:<http://example.org/types#>
    SELECT ?w (SUM(?v) as ?sum_v) WHERE {
        ?w types:hasSensor ?s .
        ?s a types:ThingCounter .
        ?s chrontext:hasTimeseries ?ts .
        ?ts chrontext:hasDataPoint ?dp .
        ?dp chrontext:hasTimestamp ?t .
        ?dp chrontext:hasValue ?v .
        FILTER(?t > "2022-06-01T08:46:53Z"^^xsd:dateTime) .
    } GROUP BY ?w
    """
df = engine.query(q)
print(df)

This produces the following result:

w sum_v
str decimal[38,0]
http://example.org/case#myWidget1 1215
http://example.org/case#myWidget2 1216

Roadmap in brief

Let us know if you have suggestions!

Stabilization

Chrontext will be put into use in the energy industry during the period, and will be stabilized as part of this process. We are very interested in your bug reports!

Support for Azure Data Explorer / KustoQL

We are likely adding support for ADX/KustoQL. Let us know if this is something that would be useful for you.

Support for Databricks SQL

We are likely adding support for Databricks SQL as the virtualization backend.

Generalization to analytical data (not just time series!)

While chrontext is currently focused on time series data, we are incrementally adding support for contextualization of arbitrary analytical data.

Support for multiple databases

Currently, we only support one database backend at a given time. We plan to support hybrid queries across multiple virtualized databases.

References

Chrontext is joint work by Magnus Bakken and Professor Ahmet Soylu at OsloMet. To read more about Chrontext, read the article Chrontext: Portable Sparql Queries Over Contextualised Time Series Data in Industrial Settings.

License

All code produced since August 1st. 2023 is copyrighted to Data Treehouse AS with an Apache 2.0 license unless otherwise noted.

All code which was produced before August 1st. 2023 copyrighted to Prediktor AS with an Apache 2.0 license unless otherwise noted, and has been financed by The Research Council of Norway (grant no. 316656) and Prediktor AS as part of a PhD Degree. The code at this state is archived in the repository at https://github.com/DataTreehouse/chrontext.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chrontext-0.9.9.tar.gz (189.7 kB view details)

Uploaded Source

Built Distributions

chrontext-0.9.9-cp312-cp312-manylinux_2_28_x86_64.whl (30.8 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.28+ x86-64

chrontext-0.9.9-cp311-none-win_amd64.whl (25.7 MB view details)

Uploaded CPython 3.11 Windows x86-64

chrontext-0.9.9-cp311-cp311-manylinux_2_28_x86_64.whl (30.8 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.28+ x86-64

chrontext-0.9.9-cp311-cp311-macosx_12_0_arm64.whl (26.0 MB view details)

Uploaded CPython 3.11 macOS 12.0+ ARM64

chrontext-0.9.9-cp311-cp311-macosx_11_0_arm64.whl (26.1 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

chrontext-0.9.9-cp310-none-win_amd64.whl (25.7 MB view details)

Uploaded CPython 3.10 Windows x86-64

chrontext-0.9.9-cp310-cp310-manylinux_2_28_x86_64.whl (30.8 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.28+ x86-64

chrontext-0.9.9-cp310-cp310-macosx_12_0_arm64.whl (26.0 MB view details)

Uploaded CPython 3.10 macOS 12.0+ ARM64

chrontext-0.9.9-cp310-cp310-macosx_11_0_arm64.whl (26.1 MB view details)

Uploaded CPython 3.10 macOS 11.0+ ARM64

chrontext-0.9.9-cp39-none-win_amd64.whl (25.7 MB view details)

Uploaded CPython 3.9 Windows x86-64

chrontext-0.9.9-cp39-cp39-manylinux_2_28_x86_64.whl (30.8 MB view details)

Uploaded CPython 3.9 manylinux: glibc 2.28+ x86-64

chrontext-0.9.9-cp39-cp39-macosx_12_0_arm64.whl (26.0 MB view details)

Uploaded CPython 3.9 macOS 12.0+ ARM64

chrontext-0.9.9-cp39-cp39-macosx_11_0_arm64.whl (26.1 MB view details)

Uploaded CPython 3.9 macOS 11.0+ ARM64

chrontext-0.9.9-cp38-none-win_amd64.whl (25.7 MB view details)

Uploaded CPython 3.8 Windows x86-64

chrontext-0.9.9-cp38-cp38-manylinux_2_28_x86_64.whl (30.8 MB view details)

Uploaded CPython 3.8 manylinux: glibc 2.28+ x86-64

chrontext-0.9.9-cp38-cp38-macosx_12_0_arm64.whl (26.0 MB view details)

Uploaded CPython 3.8 macOS 12.0+ ARM64

chrontext-0.9.9-cp38-cp38-macosx_11_0_arm64.whl (26.1 MB view details)

Uploaded CPython 3.8 macOS 11.0+ ARM64

File details

Details for the file chrontext-0.9.9.tar.gz.

File metadata

  • Download URL: chrontext-0.9.9.tar.gz
  • Upload date:
  • Size: 189.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.7.1

File hashes

Hashes for chrontext-0.9.9.tar.gz
Algorithm Hash digest
SHA256 8ffd67597e1393153acb233e572e7db56becaa2fb290f05c6b4a35befde25df3
MD5 c86bd00fe079c491983775fc35a7659c
BLAKE2b-256 84fd16bd5679feb3d09eec50ae128b028910a42db4a529031e754885956cb118

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp312-cp312-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp312-cp312-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 346338bd010742bca027b159fda40098ffe13c7018604c27f8b14c2ecb1d144f
MD5 74d83134689dad7ba012290d2c6418aa
BLAKE2b-256 388346821c0f986c3a13c9a130284401becb4cf08c683e697fce91b5da474688

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp311-none-win_amd64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp311-none-win_amd64.whl
Algorithm Hash digest
SHA256 deb70caa8f908a60bff40166e019a23226689f766407bcb90590ed899ac09171
MD5 72b2fbe9a6c9f731b64ef4690e8b47df
BLAKE2b-256 224ecd3042c12ee8f5e93cdedfebf8543a779a6651a0e13f0b947f5d417bd0fa

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp311-cp311-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp311-cp311-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e17a6e9ae527163bb21f35a2788d4fc0344c892bb88fb5bcdcec3a07c70d0ecb
MD5 28d5aa65880676e17c00f2fb1077cda9
BLAKE2b-256 5cd3ecf6a3d4566237a94e576c6b93c21b08cbfbdc07c94d5f198b26aa11f0c0

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp311-cp311-macosx_12_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp311-cp311-macosx_12_0_arm64.whl
Algorithm Hash digest
SHA256 a736ccc222201b87ace33c95a03bfa37221bf3882031bf43836c231c0ed1dd5d
MD5 1a580e21f7eb724b1da76d5adbbe2c56
BLAKE2b-256 3de2c20cc2a07f9e79f7503d0fd0f45da4f80404ebb850c485d7b2519f7d0bd9

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 bc67bee46c25a93dbf86886ca140ebd58f0c5c0846d4007df8a840d6db7eaaf5
MD5 e0755b969d78f5e2b388dce42e7dc02d
BLAKE2b-256 4aff773a73d15378840abb8301ac1b6c27f68900bd3a347595102a5c474f2835

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp310-none-win_amd64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp310-none-win_amd64.whl
Algorithm Hash digest
SHA256 4acbd66312fd72909fb536d58528718f90ae8298365af3b8645328ff6c03bbde
MD5 36dfa0b243c24472bb95a72c3f385869
BLAKE2b-256 fc8e6ed0345e4051a46d79cf29bb35f2b9e77e0abbd27443089d9a2e98d8e348

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp310-cp310-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp310-cp310-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f37e4f51293310cbef05e72b6f14bbc772c1f662714013e3ab40222cc62b223b
MD5 c791f1a2057956c11eb6f3487b5aed89
BLAKE2b-256 ff4269bb371265e42acacf2e6b982a2d13537183d8e9aa75145ccc178c6551e7

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp310-cp310-macosx_12_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp310-cp310-macosx_12_0_arm64.whl
Algorithm Hash digest
SHA256 09d890ef77be456fddbe7485aa35b1f3577f0e8b64d4eea8975c7f3816e944ba
MD5 b13bd4888a84246cb2a43ed0655bcf7c
BLAKE2b-256 36acd933b3e83d9542075e724fd6fb133c55a4ede975bb1132172407ee62b1d9

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2a56bb80cf2725b676d1271e71e61d2e7288cdf5ec78a00efa54883e45230afe
MD5 35899f4a9fc8fe30d31f3d4bce032b61
BLAKE2b-256 7037a521f62854a59a9b5d84874f506540e981d8a2b33bb77418c1eeb04a619a

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp39-none-win_amd64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp39-none-win_amd64.whl
Algorithm Hash digest
SHA256 ece8e0cf538f27ead9430ec631aef0f6725a79ad8d0a764fa91ccf40f832b4d8
MD5 62f0e03b1cb577021dcfb9b2e37a72c1
BLAKE2b-256 fdd6a96a8192272c926728b6c21eeb24ed0e31e4045de9748f82ad9e3d0b05c3

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp39-cp39-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp39-cp39-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 88b4c189b9cd9ba69d73ceab0c7f3f0ec204be4c8e9292637701e1a840949452
MD5 1357f022013060e6a8a1ca36a940efa9
BLAKE2b-256 4530ccfe02f664e00028c994161cd400bb9c59d0c695385128dbca1d94874f4b

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp39-cp39-macosx_12_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp39-cp39-macosx_12_0_arm64.whl
Algorithm Hash digest
SHA256 dedebbe04b89882b39c479e4004db70fb79435efdfb35ecd954f76d8e87e7a01
MD5 49f4d27cbcab7d7e9e7d7eb794e8dc98
BLAKE2b-256 800294853d64544449d1a643b8db3ff56d93617cbaf4e1688587543152d9a678

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp39-cp39-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp39-cp39-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 192062084aef5b38f5fde2977b248b3917ccc379de18570449a65a42a3105530
MD5 8910cff05a85ab6725800529cb95917b
BLAKE2b-256 0d10781816f84f2ee144e10d3dc3e5a8833db47e5ccaa79489fd867059d32425

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp38-none-win_amd64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp38-none-win_amd64.whl
Algorithm Hash digest
SHA256 85dc3226217bff9dcdb3b380fc4573c88dde8ff78e0b89d8ba7713c2bc8ca6e2
MD5 e1d701ca941b734d25f54bae9c87024a
BLAKE2b-256 d414d936a0c07e4b02ba017f4b0e9522f528340cc9c8b5c36327a3e252a9dfce

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp38-cp38-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp38-cp38-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2b4d5e27582c3aa887a525eb81524e3d10e5a5b44b8a8b060f298f1eb16c2217
MD5 cbfac3b0040f27eb0c0ec147b2ad6287
BLAKE2b-256 bd798daec1606cc7b5fe3bda11591d056f9e3ddbb88e71797bfa116507bfc63b

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp38-cp38-macosx_12_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp38-cp38-macosx_12_0_arm64.whl
Algorithm Hash digest
SHA256 ef72ff1dbd23cd32a375201aa41ec9cfd5ec81bd1bcd8105d5b4f45ea5062a40
MD5 1b003d9733d44d29dac8a836b441fba6
BLAKE2b-256 00c89245e513e09e02b7d849dc9c09e750a8ba3fb0018d5e78afca0e4b563e79

See more details on using hashes here.

File details

Details for the file chrontext-0.9.9-cp38-cp38-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for chrontext-0.9.9-cp38-cp38-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 786517d465041aa03834356aa2089637e7e2684b8f415232b6ac682ea6a02d09
MD5 f8e0f0d04997ed8f9c51ac1bbe5f3086
BLAKE2b-256 0c5ec3c2e77e0da4e7a436dff5c13b8c4e1d977a5f9da2881a7bf6d023b39e28

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page