data-finder 0.1.0__tar.gz
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.
- data_finder-0.1.0/PKG-INFO +38 -0
- data_finder-0.1.0/README.md +3 -0
- data_finder-0.1.0/calc/src/calc/__init__.py +0 -0
- data_finder-0.1.0/calc/src/calc/calc_protocol.py +56 -0
- data_finder-0.1.0/calc/src/calc/simple_price.py +24 -0
- data_finder-0.1.0/calc/tests/test_calc.py +52 -0
- data_finder-0.1.0/data_finder.egg-info/PKG-INFO +38 -0
- data_finder-0.1.0/data_finder.egg-info/SOURCES.txt +73 -0
- data_finder-0.1.0/data_finder.egg-info/dependency_links.txt +1 -0
- data_finder-0.1.0/data_finder.egg-info/requires.txt +28 -0
- data_finder-0.1.0/data_finder.egg-info/top_level.txt +1 -0
- data_finder-0.1.0/datafinder/src/datafinder/__init__.py +6 -0
- data_finder-0.1.0/datafinder/src/datafinder/attribute.py +26 -0
- data_finder-0.1.0/datafinder/src/datafinder/finder.py +7 -0
- data_finder-0.1.0/datafinder/src/datafinder/operation.py +162 -0
- data_finder-0.1.0/datafinder/src/datafinder/output.py +15 -0
- data_finder-0.1.0/datafinder/src/datafinder/runner.py +30 -0
- data_finder-0.1.0/datafinder/src/datafinder/typed_attributes.py +43 -0
- data_finder-0.1.0/datafinder/src/datafinder/typed_operations.py +80 -0
- data_finder-0.1.0/datafinder_duckdb/__init__.py +0 -0
- data_finder-0.1.0/datafinder_duckdb/duckdb_engine.py +30 -0
- data_finder-0.1.0/datafinder_generator/src/datafinder_generator/generator.py +25 -0
- data_finder-0.1.0/datafinder_ibis/src/datafinder_ibis/__init__.py +0 -0
- data_finder-0.1.0/datafinder_ibis/src/datafinder_ibis/ibis_engine.py +31 -0
- data_finder-0.1.0/datafinder_ibis/tests/test_ibis_engine.py +8 -0
- data_finder-0.1.0/datafinder_ibis_duckdb/tests/test_datafinder_ibis_duckdb.py +53 -0
- data_finder-0.1.0/example/__init__.py +0 -0
- data_finder-0.1.0/example/account_finder.py +37 -0
- data_finder-0.1.0/example/contractualposition_finder.py +56 -0
- data_finder-0.1.0/example/instrument_finder.py +37 -0
- data_finder-0.1.0/example/mappings.py +106 -0
- data_finder-0.1.0/example/queries.py +29 -0
- data_finder-0.1.0/example/trade_finder.py +47 -0
- data_finder-0.1.0/example_duckdb/__init__.py +0 -0
- data_finder-0.1.0/example_duckdb/duckdb_example.py +20 -0
- data_finder-0.1.0/example_duckdb/duckdb_trade_finder.py +24 -0
- data_finder-0.1.0/model/src/model/__init__.py +0 -0
- data_finder-0.1.0/model/src/model/m3.py +51 -0
- data_finder-0.1.0/model/src/model/mapping.py +19 -0
- data_finder-0.1.0/model/src/model/relational.py +41 -0
- data_finder-0.1.0/pyproject.toml +53 -0
- data_finder-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-finder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model driven data finders
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: atpublic==4.1.0
|
|
8
|
+
Requires-Dist: bidict==0.23.1
|
|
9
|
+
Requires-Dist: duckdb==1.0.0
|
|
10
|
+
Requires-Dist: greenlet==3.0.3
|
|
11
|
+
Requires-Dist: ibis-framework==9.0.0
|
|
12
|
+
Requires-Dist: iniconfig==2.1.0
|
|
13
|
+
Requires-Dist: jinja2==3.1.6
|
|
14
|
+
Requires-Dist: markdown-it-py==3.0.0
|
|
15
|
+
Requires-Dist: markupsafe==3.0.2
|
|
16
|
+
Requires-Dist: mdurl==0.1.2
|
|
17
|
+
Requires-Dist: numpy==1.26.4
|
|
18
|
+
Requires-Dist: packaging==25.0
|
|
19
|
+
Requires-Dist: pandas==2.2.2
|
|
20
|
+
Requires-Dist: parsy==2.1
|
|
21
|
+
Requires-Dist: pluggy==1.6.0
|
|
22
|
+
Requires-Dist: pyarrow==16.1.0
|
|
23
|
+
Requires-Dist: pyarrow-hotfix==0.7
|
|
24
|
+
Requires-Dist: pygments==2.19.1
|
|
25
|
+
Requires-Dist: pytest==8.3.4
|
|
26
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
27
|
+
Requires-Dist: pytz==2024.1
|
|
28
|
+
Requires-Dist: rich==13.9.4
|
|
29
|
+
Requires-Dist: six==1.16.0
|
|
30
|
+
Requires-Dist: sqlglot==23.12.2
|
|
31
|
+
Requires-Dist: toml==0.10.2
|
|
32
|
+
Requires-Dist: toolz==0.12.1
|
|
33
|
+
Requires-Dist: typing-extensions==4.12.2
|
|
34
|
+
Requires-Dist: tzdata==2024.1
|
|
35
|
+
|
|
36
|
+
# Experimental typed finders
|
|
37
|
+
|
|
38
|
+
This generates helper classes to query data using a logical model and mapping
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
from numpy import ndarray
|
|
4
|
+
|
|
5
|
+
from datafinder import Attribute
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DomainAwareCalcProtocol:
|
|
9
|
+
def name(self):
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
|
|
12
|
+
# TODO should this be an ordered set ? to avoid duplicates
|
|
13
|
+
def inputs_spec(self) -> list[Attribute]:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def output_spec(self) -> Attribute:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
#TODO should have key and time?
|
|
20
|
+
def calculate(self, inputs:ndarray) -> []:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# From https://charlesreid1.github.io/python-patterns-the-registry.html
|
|
25
|
+
class RegistryBase(type):
|
|
26
|
+
|
|
27
|
+
REGISTRY = {}
|
|
28
|
+
|
|
29
|
+
def __new__(cls, name, bases, attrs):
|
|
30
|
+
# instantiate a new type corresponding to the type of class being defined
|
|
31
|
+
# this is currently RegisterBase but in child classes will be the child class
|
|
32
|
+
new_cls = type.__new__(cls, name, bases, attrs)
|
|
33
|
+
cls.REGISTRY[new_cls.__name__] = new_cls
|
|
34
|
+
return new_cls
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_registry(cls):
|
|
38
|
+
return dict(cls.REGISTRY)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseRegisteredClass(metaclass=RegistryBase):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CalcEngineRegistry(metaclass=RegistryBase):
|
|
46
|
+
calcs = {}
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def register(calc:DomainAwareCalcProtocol):
|
|
50
|
+
CalcEngineRegistry.calcs[calc.name()] = calc
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DomainAwareCalc(DomainAwareCalcProtocol, ABC):
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
CalcEngineRegistry.register(self)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from calc.calc_protocol import DomainAwareCalc
|
|
2
|
+
from contractualposition_finder import ContractualPositionFinder
|
|
3
|
+
from datafinder import Attribute
|
|
4
|
+
from numpy import array
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def npv(quantity: array, price: array):
|
|
8
|
+
return quantity * price
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PositionNPVCalc(DomainAwareCalc):
|
|
12
|
+
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
return 'npv'
|
|
15
|
+
|
|
16
|
+
def inputs_spec(self) -> list[Attribute]:
|
|
17
|
+
return [ContractualPositionFinder.quantity(),
|
|
18
|
+
ContractualPositionFinder.instrument().price()]
|
|
19
|
+
|
|
20
|
+
def calculate(self, inputs):
|
|
21
|
+
return npv(inputs[0], inputs[1])
|
|
22
|
+
|
|
23
|
+
def output_spec(self) -> Attribute:
|
|
24
|
+
return ContractualPositionFinder.npv()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import duckdb
|
|
4
|
+
import numpy
|
|
5
|
+
|
|
6
|
+
from calc.calc_protocol import CalcEngineRegistry
|
|
7
|
+
from numpy.testing import assert_array_almost_equal
|
|
8
|
+
|
|
9
|
+
from datafinder import QueryRunnerBase
|
|
10
|
+
from mappings import generate_mappings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestCalc:
|
|
14
|
+
|
|
15
|
+
def setup(self):
|
|
16
|
+
#Register the Ibis engine
|
|
17
|
+
from datafinder_ibis.ibis_engine import IbisConnect
|
|
18
|
+
assert QueryRunnerBase.get_runner() == IbisConnect
|
|
19
|
+
|
|
20
|
+
generate_mappings()
|
|
21
|
+
con = duckdb.connect('test.db')
|
|
22
|
+
con.execute("DROP TABLE IF EXISTS contractualposition;")
|
|
23
|
+
con.execute(
|
|
24
|
+
"CREATE TABLE contractualposition(DATE DATE, INSTRUMENT VARCHAR, CPTY_ID INT, QUANTITY DOUBLE); COPY contractualposition FROM 'data/contractualpositions.csv'")
|
|
25
|
+
con.sql("SELECT * from contractualposition").show()
|
|
26
|
+
|
|
27
|
+
con.execute("DROP TABLE IF EXISTS price;")
|
|
28
|
+
con.execute(
|
|
29
|
+
"CREATE TABLE price(DATE_TIME DATETIME, SYM VARCHAR, PRICE DOUBLE); COPY price FROM 'data/prices.csv'")
|
|
30
|
+
|
|
31
|
+
def test_price(self):
|
|
32
|
+
self.setup()
|
|
33
|
+
# TODO - we have to import this first for it to register due to Python's dynamic nature
|
|
34
|
+
# need to do imports to force the load = https://stackoverflow.com/questions/73829483/register-classes-in-different-files-to-a-class-factory
|
|
35
|
+
from calc.simple_price import PositionNPVCalc
|
|
36
|
+
calc = PositionNPVCalc()
|
|
37
|
+
assert len(CalcEngineRegistry.calcs) == 1
|
|
38
|
+
|
|
39
|
+
inputs = calc.inputs_spec()
|
|
40
|
+
|
|
41
|
+
#Calc run
|
|
42
|
+
from contractualposition_finder import ContractualPositionFinder
|
|
43
|
+
input_data = ContractualPositionFinder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
44
|
+
inputs).to_numpy()
|
|
45
|
+
|
|
46
|
+
output = calc.calculate(input_data)
|
|
47
|
+
|
|
48
|
+
assert_array_almost_equal(output, numpy.array([200000.0, 54999.95]), decimal=2)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-finder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model driven data finders
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: atpublic==4.1.0
|
|
8
|
+
Requires-Dist: bidict==0.23.1
|
|
9
|
+
Requires-Dist: duckdb==1.0.0
|
|
10
|
+
Requires-Dist: greenlet==3.0.3
|
|
11
|
+
Requires-Dist: ibis-framework==9.0.0
|
|
12
|
+
Requires-Dist: iniconfig==2.1.0
|
|
13
|
+
Requires-Dist: jinja2==3.1.6
|
|
14
|
+
Requires-Dist: markdown-it-py==3.0.0
|
|
15
|
+
Requires-Dist: markupsafe==3.0.2
|
|
16
|
+
Requires-Dist: mdurl==0.1.2
|
|
17
|
+
Requires-Dist: numpy==1.26.4
|
|
18
|
+
Requires-Dist: packaging==25.0
|
|
19
|
+
Requires-Dist: pandas==2.2.2
|
|
20
|
+
Requires-Dist: parsy==2.1
|
|
21
|
+
Requires-Dist: pluggy==1.6.0
|
|
22
|
+
Requires-Dist: pyarrow==16.1.0
|
|
23
|
+
Requires-Dist: pyarrow-hotfix==0.7
|
|
24
|
+
Requires-Dist: pygments==2.19.1
|
|
25
|
+
Requires-Dist: pytest==8.3.4
|
|
26
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
27
|
+
Requires-Dist: pytz==2024.1
|
|
28
|
+
Requires-Dist: rich==13.9.4
|
|
29
|
+
Requires-Dist: six==1.16.0
|
|
30
|
+
Requires-Dist: sqlglot==23.12.2
|
|
31
|
+
Requires-Dist: toml==0.10.2
|
|
32
|
+
Requires-Dist: toolz==0.12.1
|
|
33
|
+
Requires-Dist: typing-extensions==4.12.2
|
|
34
|
+
Requires-Dist: tzdata==2024.1
|
|
35
|
+
|
|
36
|
+
# Experimental typed finders
|
|
37
|
+
|
|
38
|
+
This generates helper classes to query data using a logical model and mapping
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
./calc/src/calc/__init__.py
|
|
4
|
+
./calc/src/calc/calc_protocol.py
|
|
5
|
+
./calc/src/calc/simple_price.py
|
|
6
|
+
./calc/tests/test_calc.py
|
|
7
|
+
./datafinder/src/datafinder/__init__.py
|
|
8
|
+
./datafinder/src/datafinder/attribute.py
|
|
9
|
+
./datafinder/src/datafinder/finder.py
|
|
10
|
+
./datafinder/src/datafinder/operation.py
|
|
11
|
+
./datafinder/src/datafinder/output.py
|
|
12
|
+
./datafinder/src/datafinder/runner.py
|
|
13
|
+
./datafinder/src/datafinder/typed_attributes.py
|
|
14
|
+
./datafinder/src/datafinder/typed_operations.py
|
|
15
|
+
./datafinder_duckdb/__init__.py
|
|
16
|
+
./datafinder_duckdb/duckdb_engine.py
|
|
17
|
+
./datafinder_generator/src/datafinder_generator/generator.py
|
|
18
|
+
./datafinder_ibis/src/datafinder_ibis/__init__.py
|
|
19
|
+
./datafinder_ibis/src/datafinder_ibis/ibis_engine.py
|
|
20
|
+
./datafinder_ibis/tests/test_ibis_engine.py
|
|
21
|
+
./datafinder_ibis_duckdb/tests/test_datafinder_ibis_duckdb.py
|
|
22
|
+
./example/__init__.py
|
|
23
|
+
./example/account_finder.py
|
|
24
|
+
./example/contractualposition_finder.py
|
|
25
|
+
./example/instrument_finder.py
|
|
26
|
+
./example/mappings.py
|
|
27
|
+
./example/queries.py
|
|
28
|
+
./example/trade_finder.py
|
|
29
|
+
./example_duckdb/__init__.py
|
|
30
|
+
./example_duckdb/duckdb_example.py
|
|
31
|
+
./example_duckdb/duckdb_trade_finder.py
|
|
32
|
+
./model/src/model/__init__.py
|
|
33
|
+
./model/src/model/m3.py
|
|
34
|
+
./model/src/model/mapping.py
|
|
35
|
+
./model/src/model/relational.py
|
|
36
|
+
calc/src/calc/__init__.py
|
|
37
|
+
calc/src/calc/calc_protocol.py
|
|
38
|
+
calc/src/calc/simple_price.py
|
|
39
|
+
calc/tests/test_calc.py
|
|
40
|
+
data_finder.egg-info/PKG-INFO
|
|
41
|
+
data_finder.egg-info/SOURCES.txt
|
|
42
|
+
data_finder.egg-info/dependency_links.txt
|
|
43
|
+
data_finder.egg-info/requires.txt
|
|
44
|
+
data_finder.egg-info/top_level.txt
|
|
45
|
+
datafinder/src/datafinder/__init__.py
|
|
46
|
+
datafinder/src/datafinder/attribute.py
|
|
47
|
+
datafinder/src/datafinder/finder.py
|
|
48
|
+
datafinder/src/datafinder/operation.py
|
|
49
|
+
datafinder/src/datafinder/output.py
|
|
50
|
+
datafinder/src/datafinder/runner.py
|
|
51
|
+
datafinder/src/datafinder/typed_attributes.py
|
|
52
|
+
datafinder/src/datafinder/typed_operations.py
|
|
53
|
+
datafinder_duckdb/__init__.py
|
|
54
|
+
datafinder_duckdb/duckdb_engine.py
|
|
55
|
+
datafinder_generator/src/datafinder_generator/generator.py
|
|
56
|
+
datafinder_ibis/src/datafinder_ibis/__init__.py
|
|
57
|
+
datafinder_ibis/src/datafinder_ibis/ibis_engine.py
|
|
58
|
+
datafinder_ibis/tests/test_ibis_engine.py
|
|
59
|
+
datafinder_ibis_duckdb/tests/test_datafinder_ibis_duckdb.py
|
|
60
|
+
example/__init__.py
|
|
61
|
+
example/account_finder.py
|
|
62
|
+
example/contractualposition_finder.py
|
|
63
|
+
example/instrument_finder.py
|
|
64
|
+
example/mappings.py
|
|
65
|
+
example/queries.py
|
|
66
|
+
example/trade_finder.py
|
|
67
|
+
example_duckdb/__init__.py
|
|
68
|
+
example_duckdb/duckdb_example.py
|
|
69
|
+
example_duckdb/duckdb_trade_finder.py
|
|
70
|
+
model/src/model/__init__.py
|
|
71
|
+
model/src/model/m3.py
|
|
72
|
+
model/src/model/mapping.py
|
|
73
|
+
model/src/model/relational.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
atpublic==4.1.0
|
|
2
|
+
bidict==0.23.1
|
|
3
|
+
duckdb==1.0.0
|
|
4
|
+
greenlet==3.0.3
|
|
5
|
+
ibis-framework==9.0.0
|
|
6
|
+
iniconfig==2.1.0
|
|
7
|
+
jinja2==3.1.6
|
|
8
|
+
markdown-it-py==3.0.0
|
|
9
|
+
markupsafe==3.0.2
|
|
10
|
+
mdurl==0.1.2
|
|
11
|
+
numpy==1.26.4
|
|
12
|
+
packaging==25.0
|
|
13
|
+
pandas==2.2.2
|
|
14
|
+
parsy==2.1
|
|
15
|
+
pluggy==1.6.0
|
|
16
|
+
pyarrow==16.1.0
|
|
17
|
+
pyarrow-hotfix==0.7
|
|
18
|
+
pygments==2.19.1
|
|
19
|
+
pytest==8.3.4
|
|
20
|
+
python-dateutil==2.9.0.post0
|
|
21
|
+
pytz==2024.1
|
|
22
|
+
rich==13.9.4
|
|
23
|
+
six==1.16.0
|
|
24
|
+
sqlglot==23.12.2
|
|
25
|
+
toml==0.10.2
|
|
26
|
+
toolz==0.12.1
|
|
27
|
+
typing-extensions==4.12.2
|
|
28
|
+
tzdata==2024.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
data-finder
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Attribute:
|
|
5
|
+
__name: str
|
|
6
|
+
__column_db_type: str
|
|
7
|
+
__owner: str
|
|
8
|
+
__parent: Any
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
11
|
+
self.__name = name
|
|
12
|
+
self.__column_db_type = column_db_type
|
|
13
|
+
self.__owner = owner
|
|
14
|
+
self.__parent = parent
|
|
15
|
+
|
|
16
|
+
def column_name(self) -> str:
|
|
17
|
+
return self.__name
|
|
18
|
+
|
|
19
|
+
def column_type(self) -> str:
|
|
20
|
+
return self.__column_db_type
|
|
21
|
+
|
|
22
|
+
def owner(self) -> str:
|
|
23
|
+
return self.__owner
|
|
24
|
+
|
|
25
|
+
def parent(self) -> Any:
|
|
26
|
+
return self.__parent
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from datafinder.attribute import Attribute
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TableAlias:
|
|
7
|
+
def __init__(self, table: str, alias: str):
|
|
8
|
+
self.table = table
|
|
9
|
+
self.alias = alias
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ColumnAlias:
|
|
13
|
+
def __init__(self, column_name: str, table_alias: TableAlias):
|
|
14
|
+
self.column_name = column_name
|
|
15
|
+
self.table_alias = table_alias
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Join:
|
|
19
|
+
def __init__(self, source: ColumnAlias, target: ColumnAlias):
|
|
20
|
+
self.source = source
|
|
21
|
+
self.target = target
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QueryEngine:
|
|
25
|
+
_select: list[ColumnAlias]
|
|
26
|
+
_from: set[TableAlias]
|
|
27
|
+
_where: list[str]
|
|
28
|
+
_join: list[Join]
|
|
29
|
+
__table_alias_incr: int
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._where = []
|
|
33
|
+
self._select = []
|
|
34
|
+
self._from = set()
|
|
35
|
+
self._join = []
|
|
36
|
+
self.__table_alias_incr = 0
|
|
37
|
+
self.__table_aliases_by_table = {}
|
|
38
|
+
|
|
39
|
+
def select(self, cols: list[Attribute]):
|
|
40
|
+
for col in cols:
|
|
41
|
+
table = col.owner()
|
|
42
|
+
ta = self.__table_alias_for_table(table)
|
|
43
|
+
parent: JoinOperation = col.parent()
|
|
44
|
+
if parent is not None:
|
|
45
|
+
left = parent.left
|
|
46
|
+
sc = ColumnAlias(left.column_name(), self.__table_alias_for_table(left.owner()))
|
|
47
|
+
right = parent.right
|
|
48
|
+
tc = ColumnAlias(right.column_name(), self.__table_alias_for_table(right.owner()))
|
|
49
|
+
self._join.append(Join(sc, tc))
|
|
50
|
+
else:
|
|
51
|
+
self._from.add(ta)
|
|
52
|
+
ca = ColumnAlias(col.column_name(), ta)
|
|
53
|
+
self._select.append(ca)
|
|
54
|
+
|
|
55
|
+
def __table_alias_for_table(self, table: str) -> TableAlias:
|
|
56
|
+
ta = None
|
|
57
|
+
if table in self.__table_aliases_by_table:
|
|
58
|
+
ta = self.__table_aliases_by_table[table]
|
|
59
|
+
else:
|
|
60
|
+
ta = TableAlias(table, "t" + str(self.__table_alias_incr))
|
|
61
|
+
self.__table_alias_incr = self.__table_alias_incr + 1
|
|
62
|
+
self.__table_aliases_by_table[table] = ta
|
|
63
|
+
return ta
|
|
64
|
+
|
|
65
|
+
def append_where_binary_clause(self, op: str):
|
|
66
|
+
self._where.append(op)
|
|
67
|
+
|
|
68
|
+
def append_where_clause(self, attr: Attribute, op: str, value: str):
|
|
69
|
+
ta = self.__table_alias_for_table(attr.owner())
|
|
70
|
+
self._where.append(ta.alias + '.' + attr.column_name() + ' ' + op + ' ' + value)
|
|
71
|
+
|
|
72
|
+
def build_query_string(self) -> str:
|
|
73
|
+
joins = map(lambda j: ' LEFT OUTER JOIN ' + j.target.table_alias.table + ' AS ' + j.target.table_alias.alias +
|
|
74
|
+
' ON ' + j.source.table_alias.alias + '.' + j.source.column_name + ' = ' +
|
|
75
|
+
j.target.table_alias.alias + '.' + j.target.column_name, self._join)
|
|
76
|
+
return 'SELECT ' + ','.join(map(lambda ca: ca.table_alias.alias + '.' + ca.column_name, self._select)) \
|
|
77
|
+
+ ' FROM ' + ','.join(map(lambda ta: ta.table + ' AS ' + ta.alias, self._from)) \
|
|
78
|
+
+ ''.join(joins) \
|
|
79
|
+
+ self.__build_where()
|
|
80
|
+
|
|
81
|
+
def __build_where(self) -> str:
|
|
82
|
+
if len(self._where) > 0:
|
|
83
|
+
return ' WHERE ' + ''.join(self._where)
|
|
84
|
+
else:
|
|
85
|
+
return ''
|
|
86
|
+
|
|
87
|
+
def where_clauses(self):
|
|
88
|
+
return self._where
|
|
89
|
+
|
|
90
|
+
def start_and(self):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def end_and(self):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Interface
|
|
98
|
+
class Operation:
|
|
99
|
+
|
|
100
|
+
def generate_query(self, query: QueryEngine):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NoOperation(Operation):
|
|
105
|
+
def __init__(self):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class SelectOperation(Operation):
|
|
110
|
+
def __init__(self, display: list[Attribute], table: str, filter: Operation):
|
|
111
|
+
self.__display = display
|
|
112
|
+
self.__table = table
|
|
113
|
+
self.__filter = filter
|
|
114
|
+
|
|
115
|
+
def generate_query(self, qe: QueryEngine):
|
|
116
|
+
qe.select(self.__display)
|
|
117
|
+
self.__filter.generate_query(qe)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AndOperation(Operation):
|
|
121
|
+
__left: Operation
|
|
122
|
+
__right: Operation
|
|
123
|
+
|
|
124
|
+
def __init__(self, lhs: Operation, rhs: Operation):
|
|
125
|
+
self.__left = lhs
|
|
126
|
+
self.__right = rhs
|
|
127
|
+
|
|
128
|
+
def generate_query(self, query: QueryEngine):
|
|
129
|
+
query.start_and()
|
|
130
|
+
self.__left.generate_query(query)
|
|
131
|
+
query.append_where_binary_clause(" and ")
|
|
132
|
+
self.__right.generate_query(query)
|
|
133
|
+
query.end_and()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class BusinessTemporalOperation(Operation):
|
|
137
|
+
# TODO - which date format should we use
|
|
138
|
+
__business_date_from_inclusive: datetime.date
|
|
139
|
+
__business_date_to_inclusive: datetime.date
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BaseOperation(Operation):
|
|
143
|
+
|
|
144
|
+
def and_op(self, rhs: Operation):
|
|
145
|
+
return AndOperation(self, rhs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class JoinOperation(Operation):
|
|
149
|
+
left: Attribute
|
|
150
|
+
right: Attribute
|
|
151
|
+
|
|
152
|
+
def __init__(self, lhs: Attribute, rhs: Attribute):
|
|
153
|
+
self.left = lhs
|
|
154
|
+
self.right = rhs
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def select_sql_to_string(columns: list[Attribute], table: str, op: Operation) -> str:
|
|
158
|
+
qe = QueryEngine()
|
|
159
|
+
select = SelectOperation(columns, table, op)
|
|
160
|
+
select.generate_query(qe)
|
|
161
|
+
return qe.build_query_string()
|
|
162
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from datafinder import Attribute, Operation, DataFrame
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RegistryBase(type):
|
|
5
|
+
REGISTRY = {}
|
|
6
|
+
|
|
7
|
+
def __new__(cls, name, bases, attrs):
|
|
8
|
+
# instantiate a new type corresponding to the type of class being defined
|
|
9
|
+
# this is currently RegisterBase but in child classes will be the child class
|
|
10
|
+
new_cls = type.__new__(cls, name, bases, attrs)
|
|
11
|
+
cls.REGISTRY[new_cls.__name__] = new_cls
|
|
12
|
+
return new_cls
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_registry(cls):
|
|
16
|
+
return dict(cls.REGISTRY)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QueryRunnerBase(metaclass=RegistryBase):
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def select(columns: list[Attribute], table: str, op: Operation) -> DataFrame:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def get_runner():
|
|
27
|
+
for k in RegistryBase.REGISTRY.keys():
|
|
28
|
+
if k != 'QueryRunnerBase':
|
|
29
|
+
return RegistryBase.REGISTRY[k]
|
|
30
|
+
raise Exception("No query runner registered")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .operation import *
|
|
2
|
+
from .typed_operations import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StringAttribute(Attribute):
|
|
6
|
+
|
|
7
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
8
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
9
|
+
|
|
10
|
+
def eq(self, value: str) -> Operation:
|
|
11
|
+
return StringEqOperation(self, value)
|
|
12
|
+
|
|
13
|
+
def __eq__(self, value: str) -> Operation:
|
|
14
|
+
return StringEqOperation(self, value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FloatAttribute(Attribute):
|
|
18
|
+
|
|
19
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
20
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
21
|
+
|
|
22
|
+
def eq(self, value: float) -> Operation:
|
|
23
|
+
return PrimitiveEqOperation(self, value)
|
|
24
|
+
|
|
25
|
+
def __eq__(self, value: float) -> Operation:
|
|
26
|
+
return PrimitiveEqOperation(self, value)
|
|
27
|
+
|
|
28
|
+
def __gt__(self, value: float) -> Operation:
|
|
29
|
+
return PrimitiveGreaterThanOperation(self, value)
|
|
30
|
+
|
|
31
|
+
class IntegerAttribute(Attribute):
|
|
32
|
+
|
|
33
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
34
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
35
|
+
|
|
36
|
+
def eq(self, value: float) -> Operation:
|
|
37
|
+
return PrimitiveEqOperation(self, value)
|
|
38
|
+
|
|
39
|
+
def __eq__(self, value: float) -> Operation:
|
|
40
|
+
return PrimitiveEqOperation(self, value)
|
|
41
|
+
|
|
42
|
+
def __gt__(self, value: float) -> Operation:
|
|
43
|
+
return PrimitiveGreaterThanOperation(self, value)
|