clickhouse-orm 3.0.1__py2.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.
- clickhouse_orm/__init__.py +14 -0
- clickhouse_orm/database.py +457 -0
- clickhouse_orm/engines.py +346 -0
- clickhouse_orm/fields.py +665 -0
- clickhouse_orm/funcs.py +1841 -0
- clickhouse_orm/migrations.py +287 -0
- clickhouse_orm/models.py +617 -0
- clickhouse_orm/query.py +701 -0
- clickhouse_orm/system_models.py +170 -0
- clickhouse_orm/utils.py +176 -0
- clickhouse_orm-3.0.1.dist-info/METADATA +90 -0
- clickhouse_orm-3.0.1.dist-info/RECORD +14 -0
- clickhouse_orm-3.0.1.dist-info/WHEEL +5 -0
- clickhouse_orm-3.0.1.dist-info/licenses/LICENSE +27 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This file contains system readonly models that can be got from the database
|
|
3
|
+
https://clickhouse.tech/docs/en/system_tables/
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .database import Database
|
|
9
|
+
from .fields import DateTimeField, StringField, UInt8Field, UInt32Field, UInt64Field
|
|
10
|
+
from .models import Model
|
|
11
|
+
from .utils import comma_join
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SystemPart(Model):
|
|
15
|
+
"""
|
|
16
|
+
Contains information about parts of a table in the MergeTree family.
|
|
17
|
+
This model operates only fields, described in the reference. Other fields are ignored.
|
|
18
|
+
https://clickhouse.tech/docs/en/system_tables/system.parts/
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
OPERATIONS = frozenset({"DETACH", "DROP", "ATTACH", "FREEZE", "FETCH"})
|
|
22
|
+
|
|
23
|
+
_readonly = True
|
|
24
|
+
_system = True
|
|
25
|
+
|
|
26
|
+
database = StringField() # Name of the database where the table that this part belongs to is located.
|
|
27
|
+
table = StringField() # Name of the table that this part belongs to.
|
|
28
|
+
engine = StringField() # Name of the table engine, without parameters.
|
|
29
|
+
partition = StringField() # Name of the partition, in the format YYYYMM.
|
|
30
|
+
name = StringField() # Name of the part.
|
|
31
|
+
|
|
32
|
+
# This field is present in the docs (https://clickhouse.tech/docs/en/single/index.html#system-parts),
|
|
33
|
+
# but is absent in ClickHouse (in version 1.1.54245)
|
|
34
|
+
# replicated = UInt8Field() # Whether the part belongs to replicated data.
|
|
35
|
+
|
|
36
|
+
# Whether the part is used in a table, or is no longer needed and will be deleted soon.
|
|
37
|
+
# Inactive parts remain after merging.
|
|
38
|
+
active = UInt8Field()
|
|
39
|
+
|
|
40
|
+
# Number of marks - multiply by the index granularity (usually 8192)
|
|
41
|
+
# to get the approximate number of rows in the part.
|
|
42
|
+
marks = UInt64Field()
|
|
43
|
+
|
|
44
|
+
bytes = UInt64Field() # Number of bytes when compressed.
|
|
45
|
+
|
|
46
|
+
# Time the directory with the part was modified. Usually corresponds to the part's creation time.
|
|
47
|
+
modification_time = DateTimeField()
|
|
48
|
+
remove_time = DateTimeField() # For inactive parts only - the time when the part became inactive.
|
|
49
|
+
|
|
50
|
+
# The number of places where the part is used. A value greater than 2 indicates
|
|
51
|
+
# that this part participates in queries or merges.
|
|
52
|
+
refcount = UInt32Field()
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def table_name(cls):
|
|
56
|
+
return "parts"
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
Next methods return SQL for some operations, which can be done with partitions
|
|
60
|
+
https://clickhouse.tech/docs/en/query_language/queries/#manipulations-with-partitions-and-parts
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def _partition_operation_sql(self, operation, settings=None, from_part=None):
|
|
64
|
+
"""
|
|
65
|
+
Performs some operation over partition
|
|
66
|
+
|
|
67
|
+
- `db`: Database object to execute operation on
|
|
68
|
+
- `operation`: Operation to execute from SystemPart.OPERATIONS set
|
|
69
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
70
|
+
|
|
71
|
+
Returns: Operation execution result
|
|
72
|
+
"""
|
|
73
|
+
operation = operation.upper()
|
|
74
|
+
assert operation in self.OPERATIONS, "operation must be in [%s]" % comma_join(self.OPERATIONS)
|
|
75
|
+
|
|
76
|
+
sql = "ALTER TABLE `%s`.`%s` %s PARTITION %s" % (self._database.db_name, self.table, operation, self.partition)
|
|
77
|
+
if from_part is not None:
|
|
78
|
+
sql += " FROM %s" % from_part
|
|
79
|
+
self._database.raw(sql, settings=settings, stream=False)
|
|
80
|
+
|
|
81
|
+
def detach(self, settings=None):
|
|
82
|
+
"""
|
|
83
|
+
Move a partition to the 'detached' directory and forget it.
|
|
84
|
+
|
|
85
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
86
|
+
|
|
87
|
+
Returns: SQL Query
|
|
88
|
+
"""
|
|
89
|
+
return self._partition_operation_sql("DETACH", settings=settings)
|
|
90
|
+
|
|
91
|
+
def drop(self, settings=None):
|
|
92
|
+
"""
|
|
93
|
+
Delete a partition
|
|
94
|
+
|
|
95
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
96
|
+
|
|
97
|
+
Returns: SQL Query
|
|
98
|
+
"""
|
|
99
|
+
return self._partition_operation_sql("DROP", settings=settings)
|
|
100
|
+
|
|
101
|
+
def attach(self, settings=None):
|
|
102
|
+
"""
|
|
103
|
+
Add a new part or partition from the 'detached' directory to the table.
|
|
104
|
+
|
|
105
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
106
|
+
|
|
107
|
+
Returns: SQL Query
|
|
108
|
+
"""
|
|
109
|
+
return self._partition_operation_sql("ATTACH", settings=settings)
|
|
110
|
+
|
|
111
|
+
def freeze(self, settings=None):
|
|
112
|
+
"""
|
|
113
|
+
Create a backup of a partition.
|
|
114
|
+
|
|
115
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
116
|
+
|
|
117
|
+
Returns: SQL Query
|
|
118
|
+
"""
|
|
119
|
+
return self._partition_operation_sql("FREEZE", settings=settings)
|
|
120
|
+
|
|
121
|
+
def fetch(self, zookeeper_path, settings=None):
|
|
122
|
+
"""
|
|
123
|
+
Download a partition from another server.
|
|
124
|
+
|
|
125
|
+
- `zookeeper_path`: Path in zookeeper to fetch from
|
|
126
|
+
- `settings`: Settings for executing request to ClickHouse over db.raw() method
|
|
127
|
+
|
|
128
|
+
Returns: SQL Query
|
|
129
|
+
"""
|
|
130
|
+
return self._partition_operation_sql("FETCH", settings=settings, from_part=zookeeper_path)
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def get(cls, database, conditions=""):
|
|
134
|
+
"""
|
|
135
|
+
Get all data from system.parts table
|
|
136
|
+
|
|
137
|
+
- `database`: A database object to fetch data from.
|
|
138
|
+
- `conditions`: WHERE clause conditions. Database condition is added automatically
|
|
139
|
+
|
|
140
|
+
Returns: A list of SystemPart objects
|
|
141
|
+
"""
|
|
142
|
+
assert isinstance(database, Database), "database must be database.Database class instance"
|
|
143
|
+
assert isinstance(conditions, str), "conditions must be a string"
|
|
144
|
+
if conditions:
|
|
145
|
+
conditions += " AND"
|
|
146
|
+
field_names = ",".join(cls.fields())
|
|
147
|
+
return database.select(
|
|
148
|
+
"SELECT %s FROM `system`.%s WHERE %s database='%s'"
|
|
149
|
+
% (field_names, cls.table_name(), conditions, database.db_name),
|
|
150
|
+
model_class=cls,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
def get_active(cls, database, conditions=""):
|
|
155
|
+
"""
|
|
156
|
+
Gets active data from system.parts table
|
|
157
|
+
|
|
158
|
+
- `database`: A database object to fetch data from.
|
|
159
|
+
- `conditions`: WHERE clause conditions. Database and active conditions are added automatically
|
|
160
|
+
|
|
161
|
+
Returns: A list of SystemPart objects
|
|
162
|
+
"""
|
|
163
|
+
if conditions:
|
|
164
|
+
conditions += " AND "
|
|
165
|
+
conditions += "active"
|
|
166
|
+
return SystemPart.get(database, conditions=conditions)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Expose only relevant classes in import *
|
|
170
|
+
__all__ = [c.__name__ for c in [SystemPart]]
|
clickhouse_orm/utils.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import codecs
|
|
4
|
+
import importlib
|
|
5
|
+
import pkgutil
|
|
6
|
+
import re
|
|
7
|
+
from datetime import date, datetime, timedelta, tzinfo
|
|
8
|
+
from inspect import isclass
|
|
9
|
+
from typing import TYPE_CHECKING, NamedTuple
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Iterable
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Page(NamedTuple):
|
|
18
|
+
"""A simple data structure for paginated results."""
|
|
19
|
+
|
|
20
|
+
objects: list[Any]
|
|
21
|
+
number_of_objects: int
|
|
22
|
+
pages_total: int
|
|
23
|
+
number: int
|
|
24
|
+
page_size: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def escape(value: str, quote: bool = True) -> str:
|
|
28
|
+
"""
|
|
29
|
+
If the value is a string, escapes any special characters and optionally
|
|
30
|
+
surrounds it with single quotes. If the value is not a string (e.g. a number),
|
|
31
|
+
converts it to one.
|
|
32
|
+
"""
|
|
33
|
+
value = codecs.escape_encode(value.encode("utf-8"))[0].decode("utf-8")
|
|
34
|
+
if quote:
|
|
35
|
+
value = "'" + value + "'"
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def unescape(value: str) -> str | None:
|
|
41
|
+
if value == "\\N":
|
|
42
|
+
return None
|
|
43
|
+
return codecs.escape_decode(value)[0].decode("utf-8")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def string_or_func(obj):
|
|
47
|
+
return obj.to_sql() if hasattr(obj, "to_sql") else obj
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def arg_to_sql(arg: Any) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Converts a function argument to SQL string according to its type.
|
|
53
|
+
Supports functions, model fields, strings, dates, datetimes, timedeltas, booleans,
|
|
54
|
+
None, numbers, timezones, arrays/iterables.
|
|
55
|
+
"""
|
|
56
|
+
from clickhouse_orm import DateTimeField, F, Field, QuerySet, StringField
|
|
57
|
+
|
|
58
|
+
if isinstance(arg, F):
|
|
59
|
+
return arg.to_sql()
|
|
60
|
+
if isinstance(arg, Field):
|
|
61
|
+
return "`%s`" % arg
|
|
62
|
+
if isinstance(arg, str):
|
|
63
|
+
return StringField().to_db_string(arg)
|
|
64
|
+
if isinstance(arg, datetime):
|
|
65
|
+
return "toDateTime(%s)" % DateTimeField().to_db_string(arg)
|
|
66
|
+
if isinstance(arg, date):
|
|
67
|
+
return "toDate('%s')" % arg.isoformat()
|
|
68
|
+
if isinstance(arg, timedelta):
|
|
69
|
+
return "toIntervalSecond(%d)" % int(arg.total_seconds())
|
|
70
|
+
if isinstance(arg, bool):
|
|
71
|
+
return str(int(arg))
|
|
72
|
+
if isinstance(arg, tzinfo):
|
|
73
|
+
return StringField().to_db_string(arg.tzname(None))
|
|
74
|
+
if arg is None:
|
|
75
|
+
return "NULL"
|
|
76
|
+
if isinstance(arg, QuerySet):
|
|
77
|
+
return "(%s)" % arg
|
|
78
|
+
if isinstance(arg, tuple):
|
|
79
|
+
return "(" + comma_join(arg_to_sql(x) for x in arg) + ")"
|
|
80
|
+
if is_iterable(arg):
|
|
81
|
+
return "[" + comma_join(arg_to_sql(x) for x in arg) + "]"
|
|
82
|
+
return str(arg)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_tsv(line: bytes | str) -> list[str]:
|
|
86
|
+
if isinstance(line, bytes):
|
|
87
|
+
line = line.decode()
|
|
88
|
+
if line and line[-1] == "\n":
|
|
89
|
+
line = line[:-1]
|
|
90
|
+
return [unescape(value) for value in line.split("\t")]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def parse_array(array_string: str) -> list[Any]:
|
|
94
|
+
"""
|
|
95
|
+
Parse an array or tuple string as returned by clickhouse. For example:
|
|
96
|
+
"['hello', 'world']" ==> ["hello", "world"]
|
|
97
|
+
"(1,2,3)" ==> [1, 2, 3]
|
|
98
|
+
"""
|
|
99
|
+
# Sanity check
|
|
100
|
+
if len(array_string) < 2 or array_string[0] not in "[(" or array_string[-1] not in "])":
|
|
101
|
+
raise ValueError('Invalid array string: "%s"' % array_string)
|
|
102
|
+
# Drop opening brace
|
|
103
|
+
array_string = array_string[1:]
|
|
104
|
+
# Go over the string, lopping off each value at the beginning until nothing is left
|
|
105
|
+
values = []
|
|
106
|
+
while True:
|
|
107
|
+
if array_string in "])":
|
|
108
|
+
# End of array
|
|
109
|
+
return values
|
|
110
|
+
elif array_string[0] in ", ":
|
|
111
|
+
# In between values
|
|
112
|
+
array_string = array_string[1:]
|
|
113
|
+
elif array_string[0] == "'":
|
|
114
|
+
# Start of quoted value, find its end
|
|
115
|
+
match = re.search(r"[^\\]'", array_string)
|
|
116
|
+
if match is None:
|
|
117
|
+
raise ValueError('Missing closing quote: "%s"' % array_string)
|
|
118
|
+
values.append(array_string[1 : match.start() + 1])
|
|
119
|
+
array_string = array_string[match.end() :]
|
|
120
|
+
else:
|
|
121
|
+
# Start of non-quoted value, find its end
|
|
122
|
+
match = re.search(r",|\]", array_string)
|
|
123
|
+
values.append(array_string[0 : match.start()])
|
|
124
|
+
array_string = array_string[match.end() - 1 :]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def import_submodules(package_name: str) -> dict[str, ModuleType]:
|
|
128
|
+
"""
|
|
129
|
+
Import all submodules of a module.
|
|
130
|
+
"""
|
|
131
|
+
package = importlib.import_module(package_name)
|
|
132
|
+
return {
|
|
133
|
+
name: importlib.import_module(package_name + "." + name)
|
|
134
|
+
for _, name, _ in pkgutil.iter_modules(package.__path__)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def comma_join(items: Iterable[str]) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Joins an iterable of strings with commas.
|
|
141
|
+
"""
|
|
142
|
+
return ", ".join(items)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def is_iterable(obj: Any) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Checks if the given object is iterable.
|
|
148
|
+
"""
|
|
149
|
+
try:
|
|
150
|
+
iter(obj)
|
|
151
|
+
return True
|
|
152
|
+
except TypeError:
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_subclass_names(locals: dict[str, Any], base_class: type):
|
|
157
|
+
return [c.__name__ for c in locals.values() if isclass(c) and issubclass(c, base_class)]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class NoValue:
|
|
161
|
+
"""
|
|
162
|
+
A sentinel for fields with an expression for a default value,
|
|
163
|
+
that were not assigned a value yet.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
def __repr__(self):
|
|
167
|
+
return "NO_VALUE"
|
|
168
|
+
|
|
169
|
+
def __copy__(self):
|
|
170
|
+
return self
|
|
171
|
+
|
|
172
|
+
def __deepcopy__(self, memo):
|
|
173
|
+
return self
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
NO_VALUE = NoValue()
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clickhouse_orm
|
|
3
|
+
Version: 3.0.1
|
|
4
|
+
Summary: A simple ORM for working with the Clickhouse database. Maintainance fork of infi.clickhouse_orm.
|
|
5
|
+
Author-email: Oliver Margetts <oliver.margetts@gmail.com>
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: Intended Audience :: System Administrators
|
|
9
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: requests
|
|
20
|
+
Requires-Dist: pytz
|
|
21
|
+
Requires-Dist: docker==7.1.0 ; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest==9.0.2 ; extra == "dev"
|
|
23
|
+
Requires-Dist: ruff==0.14.14 ; extra == "dev"
|
|
24
|
+
Project-URL: Homepage, https://github.com/SuadeLabs/clickhouse_orm
|
|
25
|
+
Project-URL: Repository, https://github.com/SuadeLabs/clickhouse_orm
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
|
|
28
|
+
A fork of [infi.clikchouse_orm](https://github.com/Infinidat/infi.clickhouse_orm) aimed at more frequent maintenance and bugfixes.
|
|
29
|
+
|
|
30
|
+
[](https://github.com/SuadeLabs/clickhouse_orm/actions/workflows/python-test.yml)
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
Introduction
|
|
34
|
+
============
|
|
35
|
+
|
|
36
|
+
This project is simple ORM for working with the [ClickHouse database](https://clickhouse.yandex/).
|
|
37
|
+
It allows you to define model classes whose instances can be written to the database and read from it.
|
|
38
|
+
|
|
39
|
+
Let's jump right in with a simple example of monitoring CPU usage. First we need to define the model class,
|
|
40
|
+
connect to the database and create a table for the model:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from clickhouse_orm import Database, Model, DateTimeField, UInt16Field, Float32Field, Memory, F
|
|
44
|
+
|
|
45
|
+
class CPUStats(Model):
|
|
46
|
+
|
|
47
|
+
timestamp = DateTimeField()
|
|
48
|
+
cpu_id = UInt16Field()
|
|
49
|
+
cpu_percent = Float32Field()
|
|
50
|
+
|
|
51
|
+
engine = Memory()
|
|
52
|
+
|
|
53
|
+
db = Database('demo')
|
|
54
|
+
db.create_table(CPUStats)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Now we can collect usage statistics per CPU, and write them to the database:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import psutil, time, datetime
|
|
61
|
+
|
|
62
|
+
psutil.cpu_percent(percpu=True) # first sample should be discarded
|
|
63
|
+
while True:
|
|
64
|
+
time.sleep(1)
|
|
65
|
+
stats = psutil.cpu_percent(percpu=True)
|
|
66
|
+
timestamp = datetime.datetime.now()
|
|
67
|
+
db.insert([
|
|
68
|
+
CPUStats(timestamp=timestamp, cpu_id=cpu_id, cpu_percent=cpu_percent)
|
|
69
|
+
for cpu_id, cpu_percent in enumerate(stats)
|
|
70
|
+
])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Querying the table is easy, using either the query builder or raw SQL:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Calculate what percentage of the time CPU 1 was over 95% busy
|
|
77
|
+
queryset = CPUStats.objects_in(db)
|
|
78
|
+
total = queryset.filter(CPUStats.cpu_id == 1).count()
|
|
79
|
+
busy = queryset.filter(CPUStats.cpu_id == 1, CPUStats.cpu_percent > 95).count()
|
|
80
|
+
print('CPU 1 was busy {:.2f}% of the time'.format(busy * 100.0 / total))
|
|
81
|
+
|
|
82
|
+
# Calculate the average usage per CPU
|
|
83
|
+
for row in queryset.aggregate(CPUStats.cpu_id, average=F.avg(CPUStats.cpu_percent)):
|
|
84
|
+
print('CPU {row.cpu_id}: {row.average:.2f}%'.format(row=row))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This and other examples can be found in the `examples` folder.
|
|
88
|
+
|
|
89
|
+
To learn more please visit the [documentation](docs/toc.md).
|
|
90
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
clickhouse_orm/__init__.py,sha256=ksYW9scTnD6VVCj8Rzv7B41VkvHDn-JWpyIh1Tjserc,478
|
|
2
|
+
clickhouse_orm/database.py,sha256=v4I2NKxkwF6Bm2hJAM6vCg-n9ss10THA9AIoxuHnU1g,17828
|
|
3
|
+
clickhouse_orm/engines.py,sha256=AV80AmLRM3hlEXj3o6GPjjKDPk8YyRdTEns9SZCppA0,11166
|
|
4
|
+
clickhouse_orm/fields.py,sha256=Wfm3NkctRhnaw2bc9KqlncV04FCvBEb_74ky9ZF2aq4,24039
|
|
5
|
+
clickhouse_orm/funcs.py,sha256=8S1kdwCjIJ45imvA2XkxHjDhGOYlNg4aS1vMU2CQ224,42108
|
|
6
|
+
clickhouse_orm/migrations.py,sha256=_AmD6uQQv3CEm0wc7JWawMcYsKQRId5nDFXr4IT-J6Y,10470
|
|
7
|
+
clickhouse_orm/models.py,sha256=pUkLBClnfyXPg5xuHTpobNhFqHtKuHlXTNwWv5PnV_U,23088
|
|
8
|
+
clickhouse_orm/query.py,sha256=3-v1Z5G1xO1c8ywVhmZiz0tpAvBmH19xzeyxVpxc6uU,24103
|
|
9
|
+
clickhouse_orm/system_models.py,sha256=pU-Pvq69GH6QD4iKIrSRxw3Q6n-qlgQzGLqpYwAF12U,6447
|
|
10
|
+
clickhouse_orm/utils.py,sha256=2fEmTrBSDNpPzbbnUOZeCQ2NX5httkOKrJKzgdTezH4,5179
|
|
11
|
+
clickhouse_orm-3.0.1.dist-info/licenses/LICENSE,sha256=MP0KDNu_tFQJrr35YkSFnwwp2F8p2a7Q1Mi2lOs8FLw,1503
|
|
12
|
+
clickhouse_orm-3.0.1.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
13
|
+
clickhouse_orm-3.0.1.dist-info/METADATA,sha256=ttRTc2omK1LNWMgA4FDcj02yuKom3wujU4A0dFsxkz8,3400
|
|
14
|
+
clickhouse_orm-3.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2021 Suade Labs
|
|
2
|
+
Copyright (c) 2017 INFINIDAT
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
15
|
+
may be used to endorse or promote products derived from this software
|
|
16
|
+
without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|