sequentialist-core 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sequentialist/core/__init__.py +84 -0
- sequentialist/core/audit.py +134 -0
- sequentialist/core/canonical.py +50 -0
- sequentialist/core/cli.py +95 -0
- sequentialist/core/engine.py +589 -0
- sequentialist/core/errors.py +40 -0
- sequentialist/core/privacy.py +30 -0
- sequentialist/core/py.typed +0 -0
- sequentialist/core/replay.py +40 -0
- sequentialist/core/report.py +141 -0
- sequentialist/core/schema.py +410 -0
- sequentialist/core/simulate.py +58 -0
- sequentialist/core/sinks.py +98 -0
- sequentialist/core/spec.py +120 -0
- sequentialist/core/storage/__init__.py +24 -0
- sequentialist/core/storage/_lock.py +91 -0
- sequentialist/core/storage/base.py +102 -0
- sequentialist/core/storage/files.py +174 -0
- sequentialist/core/storage/memory.py +64 -0
- sequentialist/core/storage/sqlite.py +150 -0
- sequentialist/core/version.py +4 -0
- sequentialist_core-0.1.0.dist-info/METADATA +129 -0
- sequentialist_core-0.1.0.dist-info/RECORD +28 -0
- sequentialist_core-0.1.0.dist-info/WHEEL +5 -0
- sequentialist_core-0.1.0.dist-info/entry_points.txt +6 -0
- sequentialist_core-0.1.0.dist-info/licenses/COMMERCIAL.md +89 -0
- sequentialist_core-0.1.0.dist-info/licenses/LICENSE +661 -0
- sequentialist_core-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (C) 2026 Novikov Laboratories LLC
|
|
3
|
+
# Commercial licenses for use outside the AGPL: see COMMERCIAL.md
|
|
4
|
+
"""Storage in one SQLite database file (the sqlite3 module of the standard library).
|
|
5
|
+
|
|
6
|
+
The state and the clearing of the journal are one transaction. The
|
|
7
|
+
database runs in WAL mode; `sync="fsync"` sets ``synchronous=FULL``
|
|
8
|
+
(survives a power failure), ``"flush"`` sets ``NORMAL`` (survives a crash of
|
|
9
|
+
the process). Writers lock tasks with files next to the database
|
|
10
|
+
(``<database>.locks/<task>.lock``); ``":memory:"`` locks within the process.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sqlite3
|
|
16
|
+
import threading
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Sequence
|
|
19
|
+
|
|
20
|
+
from ..canonical import loads
|
|
21
|
+
from ..errors import ConflictError, StorageError
|
|
22
|
+
from ._lock import FileLock, Lock, MemoryLock
|
|
23
|
+
from .base import Storage, check_document_name, check_task_id
|
|
24
|
+
|
|
25
|
+
FORMAT = "sequentialist.sqlite/1"
|
|
26
|
+
|
|
27
|
+
_SCHEMA = """
|
|
28
|
+
create table if not exists meta (key text primary key, value text not null);
|
|
29
|
+
create table if not exists tasks (task_id text primary key, version integer not null, state text not null);
|
|
30
|
+
create table if not exists journal (task_id text not null, pos integer not null, entry text not null,
|
|
31
|
+
primary key (task_id, pos));
|
|
32
|
+
create table if not exists audit (task_id text not null, n integer not null, entry text not null,
|
|
33
|
+
primary key (task_id, n));
|
|
34
|
+
create table if not exists documents (task_id text not null, name text not null, document text not null,
|
|
35
|
+
primary key (task_id, name));
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _dump(value: Any) -> str:
|
|
40
|
+
return json.dumps(value, sort_keys=True, ensure_ascii=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SQLiteStorage(Storage):
|
|
44
|
+
def __init__(self, path: str | Path, *, sync: str = "fsync"):
|
|
45
|
+
if sync not in ("fsync", "flush"):
|
|
46
|
+
raise ValueError("sync must be 'fsync' or 'flush'")
|
|
47
|
+
self.path = str(path)
|
|
48
|
+
self._memory = self.path == ":memory:"
|
|
49
|
+
self._guard = threading.Lock()
|
|
50
|
+
self._held: set[str] = set()
|
|
51
|
+
self._db = sqlite3.connect(self.path, check_same_thread=False, isolation_level=None)
|
|
52
|
+
with self._guard:
|
|
53
|
+
if not self._memory:
|
|
54
|
+
self._db.execute("pragma journal_mode=wal")
|
|
55
|
+
self._db.execute(f"pragma synchronous={'full' if sync == 'fsync' else 'normal'}")
|
|
56
|
+
self._db.executescript(_SCHEMA)
|
|
57
|
+
row = self._db.execute("select value from meta where key = 'format'").fetchone()
|
|
58
|
+
if row is None:
|
|
59
|
+
self._db.execute("insert into meta values ('format', ?)", (FORMAT,))
|
|
60
|
+
elif row[0] != FORMAT:
|
|
61
|
+
raise StorageError(f"{self.path}: format {row[0]!r}, this version reads {FORMAT!r}")
|
|
62
|
+
|
|
63
|
+
def lock(self, task_id: str) -> Lock:
|
|
64
|
+
check_task_id(task_id)
|
|
65
|
+
if self._memory:
|
|
66
|
+
return MemoryLock(self._held, self._guard, task_id, f"task {task_id!r}")
|
|
67
|
+
locks = Path(self.path + ".locks")
|
|
68
|
+
locks.mkdir(exist_ok=True)
|
|
69
|
+
return FileLock(str(locks / f"{task_id}.lock"), f"task {task_id!r} in {self.path}")
|
|
70
|
+
|
|
71
|
+
def _query(self, sql: str, args: tuple[Any, ...] = ()) -> list[tuple[Any, ...]]:
|
|
72
|
+
with self._guard:
|
|
73
|
+
return self._db.execute(sql, args).fetchall()
|
|
74
|
+
|
|
75
|
+
def tasks(self) -> list[str]:
|
|
76
|
+
return [r[0] for r in self._query("select task_id from tasks order by task_id")]
|
|
77
|
+
|
|
78
|
+
def load(self, task_id: str) -> tuple[int, dict[str, Any]] | None:
|
|
79
|
+
rows = self._query("select version, state from tasks where task_id = ?", (check_task_id(task_id),))
|
|
80
|
+
return None if not rows else (rows[0][0], loads(rows[0][1]))
|
|
81
|
+
|
|
82
|
+
def save(self, task_id: str, state: dict[str, Any], *, expected: int, clear_journal: bool = False) -> int:
|
|
83
|
+
check_task_id(task_id)
|
|
84
|
+
with self._guard:
|
|
85
|
+
db = self._db
|
|
86
|
+
db.execute("begin immediate")
|
|
87
|
+
try:
|
|
88
|
+
row = db.execute("select version from tasks where task_id = ?", (task_id,)).fetchone()
|
|
89
|
+
current = 0 if row is None else row[0]
|
|
90
|
+
if current != expected:
|
|
91
|
+
raise ConflictError(f"task {task_id!r}: stored version {current}, expected {expected}")
|
|
92
|
+
db.execute("insert or replace into tasks values (?, ?, ?)", (task_id, current + 1, _dump(state)))
|
|
93
|
+
if clear_journal:
|
|
94
|
+
db.execute("delete from journal where task_id = ?", (task_id,))
|
|
95
|
+
db.execute("commit")
|
|
96
|
+
except BaseException:
|
|
97
|
+
db.execute("rollback")
|
|
98
|
+
raise
|
|
99
|
+
return current + 1
|
|
100
|
+
|
|
101
|
+
def _append(self, table: str, task_id: str, entries: Sequence[dict[str, Any]]) -> None:
|
|
102
|
+
column = "pos" if table == "journal" else "n"
|
|
103
|
+
with self._guard:
|
|
104
|
+
db = self._db
|
|
105
|
+
db.execute("begin immediate")
|
|
106
|
+
try:
|
|
107
|
+
row = db.execute(f"select max({column}) from {table} where task_id = ?", (task_id,)).fetchone()
|
|
108
|
+
start = 0 if row[0] is None else row[0] + 1
|
|
109
|
+
db.executemany(f"insert into {table} values (?, ?, ?)",
|
|
110
|
+
[(task_id, start + i, _dump(e)) for i, e in enumerate(entries)])
|
|
111
|
+
db.execute("commit")
|
|
112
|
+
except BaseException:
|
|
113
|
+
db.execute("rollback")
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
def journal_append(self, task_id: str, entries: Sequence[dict[str, Any]]) -> None:
|
|
117
|
+
if entries:
|
|
118
|
+
self._append("journal", check_task_id(task_id), entries)
|
|
119
|
+
|
|
120
|
+
def journal_read(self, task_id: str) -> list[dict[str, Any]]:
|
|
121
|
+
rows = self._query("select entry from journal where task_id = ? order by pos", (check_task_id(task_id),))
|
|
122
|
+
return [loads(r[0]) for r in rows]
|
|
123
|
+
|
|
124
|
+
def audit_append(self, task_id: str, entry: dict[str, Any]) -> None:
|
|
125
|
+
self._append("audit", check_task_id(task_id), [entry])
|
|
126
|
+
|
|
127
|
+
def audit_read(self, task_id: str) -> list[dict[str, Any]]:
|
|
128
|
+
rows = self._query("select entry from audit where task_id = ? order by n", (check_task_id(task_id),))
|
|
129
|
+
return [loads(r[0]) for r in rows]
|
|
130
|
+
|
|
131
|
+
def put_document(self, task_id: str, name: str, document: dict[str, Any]) -> None:
|
|
132
|
+
with self._guard:
|
|
133
|
+
self._db.execute("insert or replace into documents values (?, ?, ?)",
|
|
134
|
+
(check_task_id(task_id), check_document_name(name), _dump(document)))
|
|
135
|
+
|
|
136
|
+
def get_document(self, task_id: str, name: str) -> dict[str, Any] | None:
|
|
137
|
+
rows = self._query("select document from documents where task_id = ? and name = ?",
|
|
138
|
+
(check_task_id(task_id), check_document_name(name)))
|
|
139
|
+
if not rows:
|
|
140
|
+
return None
|
|
141
|
+
found: dict[str, Any] = loads(rows[0][0])
|
|
142
|
+
return found
|
|
143
|
+
|
|
144
|
+
def documents(self, task_id: str) -> list[str]:
|
|
145
|
+
return [r[0] for r in self._query("select name from documents where task_id = ? order by name",
|
|
146
|
+
(check_task_id(task_id),))]
|
|
147
|
+
|
|
148
|
+
def close(self) -> None:
|
|
149
|
+
with self._guard:
|
|
150
|
+
self._db.close()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sequentialist-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Common layer of the sequentialist libraries: specs, a checkpointed engine over sequential-analysis runs, storage, audit log, reports
|
|
5
|
+
Author: Andrej Novikov
|
|
6
|
+
Maintainer-email: Novikov Laboratories LLC <andrei@hobukob.ru>
|
|
7
|
+
License-Expression: AGPL-3.0-only OR LicenseRef-Commercial
|
|
8
|
+
Project-URL: Source, https://github.com/andre-a-no/sequentialist-core
|
|
9
|
+
Project-URL: Issues, https://github.com/andre-a-no/sequentialist-core/issues
|
|
10
|
+
Project-URL: Commercial license, https://github.com/andre-a-no/sequentialist-core/blob/main/COMMERCIAL.md
|
|
11
|
+
Keywords: sequential analysis,anytime-valid inference,audit log,checkpoint,reproducibility
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
License-File: COMMERCIAL.md
|
|
26
|
+
Requires-Dist: sequential-analysis<0.5,>=0.4
|
|
27
|
+
Provides-Extra: yaml
|
|
28
|
+
Requires-Dist: PyYAML>=5.1; extra == "yaml"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# sequentialist-core
|
|
32
|
+
|
|
33
|
+
The common layer of the sequentialist libraries: applications of sequential
|
|
34
|
+
statistics built on
|
|
35
|
+
[sequential-analysis](https://github.com/andre-a-no/sequential-analysis).
|
|
36
|
+
The statistics live in sequential-analysis; this package turns its
|
|
37
|
+
procedures into applications that run for months, survive crashes and can
|
|
38
|
+
be audited, and every conclusion they report names its guarantee
|
|
39
|
+
(`exact`, `asymptotic`, `model`, `approximate` or `none`), as the procedure
|
|
40
|
+
behind it states it.
|
|
41
|
+
|
|
42
|
+
| Library | Repository | Status |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `sequentialist-core` | this one | 0.1.2: specs, the checkpointed engine, storage, audit log, reports, simulations |
|
|
45
|
+
| `sequentialist-ab`, `@sequentialist/ab-client` (npm) | [sequentialist-ab](https://github.com/andre-a-no/sequentialist-ab) | 0.1.0: the engine of an A/B testing platform |
|
|
46
|
+
| `sequentialist-quality` | [sequentialist-quality](https://github.com/andre-a-no/sequentialist-quality) | 0.1.0: sequential acceptance sampling of lots |
|
|
47
|
+
| `sequentialist-monitor` | [sequentialist-monitor](https://github.com/andre-a-no/sequentialist-monitor) | planned: equipment, processes and energy |
|
|
48
|
+
| `sequentialist-markets` | [sequentialist-markets](https://github.com/andre-a-no/sequentialist-markets) | planned: risk, backtests, strategy decay |
|
|
49
|
+
| `sequentialist-clinical` | [sequentialist-clinical](https://github.com/andre-a-no/sequentialist-clinical) | planned: trials, drug safety, quality of care |
|
|
50
|
+
| `sequentialist-release`, `sequentialist-mlops` | | planned: canary releases, models in production |
|
|
51
|
+
|
|
52
|
+
The libraries are installed separately and share the `sequentialist`
|
|
53
|
+
namespace. None is on PyPI yet.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pip install sequentialist-core # Python 3.8+, depends only on sequential-analysis
|
|
57
|
+
pip install "sequentialist-core[yaml]" # specs in YAML as well as JSON
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Module | What it does |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `spec` | Specs with declarative schemas: every error reported at once with its JSON pointer; a normal form (durations in seconds, timestamps in epoch seconds) and a spec id (SHA-256 of its canonical JSON); JSON Schema for other tools |
|
|
63
|
+
| `engine` | Tasks of several streams, each a `Run` of sequential-analysis: idempotent intake by (source, seq), a journal written before processing, checkpoints, replay after a crash, segments (a new run after an alarm or a window), commands of people, one writer per task, shards |
|
|
64
|
+
| `storage` | `MemoryStorage`, `FileStorage` (a directory of JSON files), `SQLiteStorage`; `open_storage("files:...")` |
|
|
65
|
+
| `audit` | The hash-chained log of decisions and actions, with the evidence behind each: procedure, guarantee, step, history digest; `verify_audit` |
|
|
66
|
+
| `report` | Reports in JSON and Markdown; a `Finding` cannot be made without its guarantee |
|
|
67
|
+
| `simulate` | Error rates of a whole pipeline by simulation, with Monte Carlo intervals |
|
|
68
|
+
| `replay` | Recorded data through the live code, on the data's own clock: the same audit log every time |
|
|
69
|
+
| `sinks` | Notifications of decisions: log, JSON lines, webhooks with retries |
|
|
70
|
+
| `privacy` | Pseudonyms for identifiers of people (HMAC-SHA-256 under the data owner's key) |
|
|
71
|
+
|
|
72
|
+
A library implements a `Handler`: it declares its streams and processes
|
|
73
|
+
records, the passing of time and commands. The engine does the rest:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from sequentialist.core import Engine, Handler, Record, SQLiteStorage, Stream
|
|
77
|
+
from sequential_analysis.procedures import MeanEDetector
|
|
78
|
+
|
|
79
|
+
class Watch(Handler):
|
|
80
|
+
def __init__(self, spec):
|
|
81
|
+
self.spec = spec
|
|
82
|
+
|
|
83
|
+
def streams(self):
|
|
84
|
+
return {"mean": Stream(MeanEDetector(m=self.spec["m"], arl=10000, direction="increase"))}
|
|
85
|
+
|
|
86
|
+
def on_record(self, record, ctx):
|
|
87
|
+
event = ctx.step("mean", {"x": record.data["x"]})
|
|
88
|
+
if event is not None and event.terminal:
|
|
89
|
+
ctx.record("decision", "alarm", basis={"t": event.t}, evidence=["mean"])
|
|
90
|
+
ctx.restart("mean", "alarm")
|
|
91
|
+
|
|
92
|
+
engine = Engine(SQLiteStorage("tasks.db"))
|
|
93
|
+
task = engine.create("line-3", "example.watch/1", {"m": 0.2}, Watch({"m": 0.2}))
|
|
94
|
+
task.ingest([Record("sensor-7", 0, {"x": 0.18}, time=1793491200.0)])
|
|
95
|
+
task.close()
|
|
96
|
+
task = engine.open("line-3", Watch) # restores the checkpoint, replays the journal
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`sequentialist tasks STORAGE` lists stored tasks; `sequentialist audit
|
|
100
|
+
STORAGE TASK` verifies and prints a task's audit log. Installed libraries
|
|
101
|
+
add their own subcommands.
|
|
102
|
+
|
|
103
|
+
**Limits of this version.** The whole state of a task is written at every
|
|
104
|
+
checkpoint, so its size (and the cost of a checkpoint) grows with what the
|
|
105
|
+
handler keeps; the A/B handler keeps one entry per unit. Locks protect a
|
|
106
|
+
task within one host; across hosts the storage must be shared and locked
|
|
107
|
+
by other means (Postgres storage is planned).
|
|
108
|
+
|
|
109
|
+
## What the engine guarantees
|
|
110
|
+
|
|
111
|
+
* **Idempotent intake**: a record is identified by its source and sequence
|
|
112
|
+
number; sending it again changes nothing.
|
|
113
|
+
* **Nothing accepted is lost**: records are journaled before they are
|
|
114
|
+
processed; after a crash the task is restored from its checkpoint and the
|
|
115
|
+
journal is replayed, and the result is the uninterrupted run bit for bit
|
|
116
|
+
(tested on every storage, with crashes at every point that matters).
|
|
117
|
+
* **An audit log of every decision**, chained by SHA-256, with the
|
|
118
|
+
procedure, its guarantee, the step and the history digest of
|
|
119
|
+
sequential-analysis that each decision rests on (`sequentialist audit`
|
|
120
|
+
verifies it).
|
|
121
|
+
* **One writer per task**, enforced by locks and version checks.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
GNU AGPL v3 or a commercial license ([COMMERCIAL.md](COMMERCIAL.md)).
|
|
126
|
+
Contributions need the [Contributor License Agreement](CLA.md); see
|
|
127
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
128
|
+
|
|
129
|
+
Contact: andrei@hobukob.ru, a.hobukob@gmail.com.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
sequentialist/core/__init__.py,sha256=A3CD2zwo9pMsOv9qtTK9leGhPuMRbHbU-_x0Ao_7OtY,2483
|
|
2
|
+
sequentialist/core/audit.py,sha256=38_KnPApnamLQNowgPPpy9vgvFWbJIXVIFmvpcUFJeo,5931
|
|
3
|
+
sequentialist/core/canonical.py,sha256=8GAf8PeoNKZeMV8vxaPmkN5BMaNccbFwtybz6j7uNho,1601
|
|
4
|
+
sequentialist/core/cli.py,sha256=BKrQE_6uPgGX09gpJ39FPpkrGZPh_N2yAV4IleRjzUU,3758
|
|
5
|
+
sequentialist/core/engine.py,sha256=ApGeS5iEu8q19ia69K-yilYi7PjUBo_P5SjOjnboNE4,27189
|
|
6
|
+
sequentialist/core/errors.py,sha256=lsz2K3hcL7SkUv5thlXdXIZ7UxxSNN-XYWkrrlaKRZ4,1487
|
|
7
|
+
sequentialist/core/privacy.py,sha256=aOI9eMKLzCPlq4YCSjIxVQ9AUmWq1Y-8byFnHuMfOK0,1404
|
|
8
|
+
sequentialist/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
sequentialist/core/replay.py,sha256=njeREhx2aU9UyFBevlEHO9mKppCp2vyGzFnfKJKD824,1641
|
|
10
|
+
sequentialist/core/report.py,sha256=jKuI7Z9Qbq2ihgbpGy039kQJUSExMG739rmollcGQIk,5318
|
|
11
|
+
sequentialist/core/schema.py,sha256=2kDz8xcI_Ri5yEedgCCFkvJUPLZaz_gvAuRVyH7dgJI,17277
|
|
12
|
+
sequentialist/core/simulate.py,sha256=MMY3EybPbUciB08Hrgm4AQkg5owM9x_Uqnpm_xUl2hA,2243
|
|
13
|
+
sequentialist/core/sinks.py,sha256=Ns9QNf2_ZJTxaKJcCLM9V_rO3hkO4Cn4wEHVeNDtpko,3872
|
|
14
|
+
sequentialist/core/spec.py,sha256=7gHsKzKSMX8vhmWYCeAY_dFpAc7hB2heSqtejFNWd7g,4472
|
|
15
|
+
sequentialist/core/version.py,sha256=CsfruV3uKgWYX83KIO66KnP0vrJNWBI15uYWd-5cGEw,175
|
|
16
|
+
sequentialist/core/storage/__init__.py,sha256=0Jn2OfLg2TVq40jZtD0KUJ63bDvaNJ6Ar7gu-_skuhE,1081
|
|
17
|
+
sequentialist/core/storage/_lock.py,sha256=Yk2Md-DBnLLtIxL2KHTEp2-cMViAkkJnZr__l2EtiV0,2577
|
|
18
|
+
sequentialist/core/storage/base.py,sha256=30BEXjMR9xHmMA919sScyKFQwqYt2FU5ylu3RMVo5hE,4050
|
|
19
|
+
sequentialist/core/storage/files.py,sha256=-mI8zpUqcjxPW27ja_CupRPdFGwiRPGcq_Zk-ma1VCw,7118
|
|
20
|
+
sequentialist/core/storage/memory.py,sha256=NfHr9QFwvkqo-tggmwEShBGDdIrRd478GaQwkonKWQE,2966
|
|
21
|
+
sequentialist/core/storage/sqlite.py,sha256=o0Jz9A4SaoaG3hiXQaGGsxZhYbyCi46SXl5GrKvHjh8,7118
|
|
22
|
+
sequentialist_core-0.1.0.dist-info/licenses/COMMERCIAL.md,sha256=4cW3QcGfbCjxNYZq5bNHdsWCh8wwJjHKWk4dXJNa7O4,5819
|
|
23
|
+
sequentialist_core-0.1.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
24
|
+
sequentialist_core-0.1.0.dist-info/METADATA,sha256=Y5oz1XC3MeE-0Zm__QbUm10iQydgfbj_sZK-qzX6f4s,7090
|
|
25
|
+
sequentialist_core-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
26
|
+
sequentialist_core-0.1.0.dist-info/entry_points.txt,sha256=kvVM3CuWVivOcJm37VxT8fATBftSaOxs11x5j46OQDU,178
|
|
27
|
+
sequentialist_core-0.1.0.dist-info/top_level.txt,sha256=xPS1PnoauDjS39ioTd_o3S_P227_qgXDCp-ND0IIaSs,14
|
|
28
|
+
sequentialist_core-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Licensing
|
|
2
|
+
|
|
3
|
+
The sequentialist libraries are dual-licensed: open source under the GNU
|
|
4
|
+
AGPL v3, and under a commercial license for products whose source code
|
|
5
|
+
stays closed. They are built on
|
|
6
|
+
[sequential-analysis](https://github.com/andre-a-no/sequential-analysis),
|
|
7
|
+
which is licensed the same way.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Do I need a commercial license?
|
|
11
|
+
|
|
12
|
+
| Your use | License |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Research, teaching, study, personal projects | AGPL v3 — free |
|
|
15
|
+
| An open-source project released under the AGPL v3 (or the GPL v3, which can be combined with it) | AGPL v3 — free |
|
|
16
|
+
| Internal use in an organisation, with no copies given to others and no access over a network for people outside it | AGPL v3 — free |
|
|
17
|
+
| A product or service you distribute or offer over a network, with its complete source code published under the AGPL v3 | AGPL v3 — free |
|
|
18
|
+
| A closed-source product that includes a sequentialist library (application, library, device, firmware) | **commercial license** |
|
|
19
|
+
| A network service (SaaS, API, web application, an experimentation platform) built with a sequentialist library whose source code you do not publish | **commercial license** |
|
|
20
|
+
| Your organisation's policy does not allow AGPL software, or you need warranty, support or indemnification | **commercial license** |
|
|
21
|
+
|
|
22
|
+
Every sequentialist library depends on sequential-analysis. A commercial
|
|
23
|
+
license for a sequentialist library therefore covers sequential-analysis as
|
|
24
|
+
used by it; both are licensed by the same company.
|
|
25
|
+
|
|
26
|
+
## Open source: GNU AGPL v3
|
|
27
|
+
|
|
28
|
+
The code is available under the [GNU Affero General Public License,
|
|
29
|
+
version 3](LICENSE) (`AGPL-3.0-only`). You may use, study, modify and
|
|
30
|
+
redistribute it free of charge, provided that you comply with the AGPL. In
|
|
31
|
+
short: if you distribute software that includes a sequentialist library, or
|
|
32
|
+
let users interact with it over a network, you must make the complete
|
|
33
|
+
corresponding source code of that software available under the AGPL as
|
|
34
|
+
well. This text is a summary; the license itself decides.
|
|
35
|
+
|
|
36
|
+
## Commercial license
|
|
37
|
+
|
|
38
|
+
A commercial license releases the licensed product from the obligations of
|
|
39
|
+
the AGPL: you may keep its source code closed. Terms (per product, per
|
|
40
|
+
organisation, with or without support) are agreed individually.
|
|
41
|
+
|
|
42
|
+
Copyright holder and licensor: **Novikov Laboratories LLC**
|
|
43
|
+
(ООО «Лаборатории Новиковых»; OGRN 1231600027548, INN 1685011563).
|
|
44
|
+
|
|
45
|
+
- Email: andrei@hobukob.ru, a.hobukob@gmail.com
|
|
46
|
+
|
|
47
|
+
Please describe your product and how you intend to use the libraries.
|
|
48
|
+
|
|
49
|
+
## Contributions
|
|
50
|
+
|
|
51
|
+
Contributions are accepted under a [Contributor License Agreement](CLA.md),
|
|
52
|
+
so that the project can continue to be offered under both licenses. See
|
|
53
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# Лицензирование
|
|
58
|
+
|
|
59
|
+
Библиотеки sequentialist распространяются по двойной лицензии: как открытое
|
|
60
|
+
ПО по GNU AGPL v3 и по коммерческой лицензии для продуктов с закрытым
|
|
61
|
+
исходным кодом. Они построены на
|
|
62
|
+
[sequential-analysis](https://github.com/andre-a-no/sequential-analysis),
|
|
63
|
+
которая лицензируется так же.
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Нужна ли вам коммерческая лицензия?
|
|
67
|
+
|
|
68
|
+
| Как вы используете | Лицензия |
|
|
69
|
+
|---|---|
|
|
70
|
+
| Исследования, преподавание, обучение, личные проекты | AGPL v3 — бесплатно |
|
|
71
|
+
| Открытый проект под AGPL v3 (или GPL v3, совместимой с ней) | AGPL v3 — бесплатно |
|
|
72
|
+
| Внутреннее использование в организации без передачи копий и без сетевого доступа для посторонних | AGPL v3 — бесплатно |
|
|
73
|
+
| Продукт или сервис, который вы распространяете или предоставляете по сети, с полным исходным кодом, открытым по AGPL v3 | AGPL v3 — бесплатно |
|
|
74
|
+
| Закрытый продукт, включающий библиотеку sequentialist (приложение, библиотека, устройство, прошивка) | **коммерческая лицензия** |
|
|
75
|
+
| Сетевой сервис (SaaS, API, веб-приложение, платформа экспериментов) на основе библиотеки sequentialist, исходный код которого вы не публикуете | **коммерческая лицензия** |
|
|
76
|
+
| Политика вашей организации не допускает ПО под AGPL, или вам нужны гарантии, поддержка, защита от претензий | **коммерческая лицензия** |
|
|
77
|
+
|
|
78
|
+
Каждая библиотека sequentialist зависит от sequential-analysis.
|
|
79
|
+
Коммерческая лицензия на библиотеку sequentialist поэтому покрывает и
|
|
80
|
+
sequential-analysis в том объёме, в каком библиотека её использует;
|
|
81
|
+
правообладатель у обеих один.
|
|
82
|
+
|
|
83
|
+
Правообладатель и лицензиар — ООО «Лаборатории Новиковых» (Novikov
|
|
84
|
+
Laboratories LLC; ОГРН 1231600027548, ИНН 1685011563).
|
|
85
|
+
Контакты: andrei@hobukob.ru, a.hobukob@gmail.com. Опишите, пожалуйста, ваш
|
|
86
|
+
продукт и то, как вы собираетесь использовать библиотеки.
|
|
87
|
+
|
|
88
|
+
Вклад в проект принимается по [лицензионному соглашению участника](CLA.md).
|
|
89
|
+
Краткое изложение выше не заменяет текст лицензии AGPL v3.
|