neo-data-svc 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.
Potentially problematic release.
This version of neo-data-svc might be problematic. Click here for more details.
- neo_data_svc-0.1.0/.gitignore +86 -0
- neo_data_svc-0.1.0/PKG-INFO +12 -0
- neo_data_svc-0.1.0/README.md +1 -0
- neo_data_svc-0.1.0/pyproject.toml +28 -0
- neo_data_svc-0.1.0/src/neo_data_svc/__init__.py +8 -0
- neo_data_svc-0.1.0/src/neo_data_svc/core.py +59 -0
- neo_data_svc-0.1.0/src/neo_data_svc/session.py +95 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
|
|
5
|
+
*.so
|
|
6
|
+
|
|
7
|
+
.Python
|
|
8
|
+
env/
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
|
|
25
|
+
*.manifest
|
|
26
|
+
*.spec
|
|
27
|
+
|
|
28
|
+
pip-log.txt
|
|
29
|
+
pip-delete-this-directory.txt
|
|
30
|
+
|
|
31
|
+
htmlcov/
|
|
32
|
+
.tox/
|
|
33
|
+
.coverage
|
|
34
|
+
.coverage.*
|
|
35
|
+
.cache
|
|
36
|
+
nosetests.xml
|
|
37
|
+
coverage.xml
|
|
38
|
+
*.cover
|
|
39
|
+
.hypothesis/
|
|
40
|
+
.pytest_cache/
|
|
41
|
+
|
|
42
|
+
*.mo
|
|
43
|
+
*.pot
|
|
44
|
+
|
|
45
|
+
*.log
|
|
46
|
+
local_settings.py
|
|
47
|
+
|
|
48
|
+
instance/
|
|
49
|
+
.webassets-cache
|
|
50
|
+
|
|
51
|
+
.scrapy
|
|
52
|
+
|
|
53
|
+
docs/_build/
|
|
54
|
+
|
|
55
|
+
target/
|
|
56
|
+
|
|
57
|
+
.ipynb_checkpoints
|
|
58
|
+
|
|
59
|
+
dask-worker-space/
|
|
60
|
+
|
|
61
|
+
.python-version
|
|
62
|
+
|
|
63
|
+
celerybeat-schedule
|
|
64
|
+
|
|
65
|
+
*.sage.py
|
|
66
|
+
|
|
67
|
+
.env
|
|
68
|
+
|
|
69
|
+
.venv
|
|
70
|
+
venv/
|
|
71
|
+
ENV/
|
|
72
|
+
|
|
73
|
+
.spyderproject
|
|
74
|
+
.spyproject
|
|
75
|
+
|
|
76
|
+
.ropeproject
|
|
77
|
+
|
|
78
|
+
/site
|
|
79
|
+
|
|
80
|
+
.mypy_cache/
|
|
81
|
+
|
|
82
|
+
.vscode/
|
|
83
|
+
.idea/
|
|
84
|
+
|
|
85
|
+
src/emdm/files/
|
|
86
|
+
uv.lock
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neo-data-svc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A data base svc
|
|
5
|
+
Author-email: Eric <email@example.com>
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Requires-Python: >=3.12.3
|
|
8
|
+
Requires-Dist: delta-spark==2.4.0
|
|
9
|
+
Requires-Dist: pyspark==3.4.4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# neo-data-svc
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# neo-data-svc
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "neo-data-svc"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A data base svc"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Eric", email = "email@example.com"},
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12.3"
|
|
10
|
+
license = {text = "Proprietary"}
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pyspark==3.4.4",
|
|
14
|
+
"delta-spark==2.4.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
build-backend = "hatchling.build"
|
|
20
|
+
|
|
21
|
+
[tool.pyright]
|
|
22
|
+
reportMissingImports = false
|
|
23
|
+
reportPrivateImportUsage = false
|
|
24
|
+
reportUndefinedVariable = false
|
|
25
|
+
enableTypeIgnoreComments = true
|
|
26
|
+
reportPrivateUsage = false
|
|
27
|
+
reportGeneralTypeIssues = false
|
|
28
|
+
reportWildcardImportFromLibrary = false
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from delta.tables import DeltaTable
|
|
4
|
+
from pyspark.sql import DataFrame
|
|
5
|
+
|
|
6
|
+
from .session import M
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def neo_write(self, path, mode="overwrite", keys=None, **options):
|
|
12
|
+
writer = self.write.format("delta")
|
|
13
|
+
|
|
14
|
+
for key, value in options.items():
|
|
15
|
+
writer = writer.option(key, value)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
if mode == "overwrite":
|
|
19
|
+
writer.mode("overwrite").save(path)
|
|
20
|
+
|
|
21
|
+
elif mode == "append":
|
|
22
|
+
writer.mode("append").save(path)
|
|
23
|
+
|
|
24
|
+
elif mode == "merge":
|
|
25
|
+
if not keys:
|
|
26
|
+
raise ValueError("keys required for merge mode")
|
|
27
|
+
|
|
28
|
+
delta_table = DeltaTable.forPath(self.sparkSession, path)
|
|
29
|
+
condition = " AND ".join(
|
|
30
|
+
[f"target.{k} = source.{k}" for k in keys])
|
|
31
|
+
|
|
32
|
+
(delta_table.alias("target")
|
|
33
|
+
.merge(self.alias("source"), condition)
|
|
34
|
+
.whenMatchedUpdateAll()
|
|
35
|
+
.whenNotMatchedInsertAll()
|
|
36
|
+
.execute())
|
|
37
|
+
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError(f"Unsupported mode: {mode}")
|
|
40
|
+
|
|
41
|
+
logger.info(f"Successfully wrote to {path} using {mode}")
|
|
42
|
+
|
|
43
|
+
except Exception as e:
|
|
44
|
+
if "Path does not exist" in str(e) and mode == "merge":
|
|
45
|
+
writer.mode("overwrite").save(path)
|
|
46
|
+
logger.info(f"Created new Delta table at {path}")
|
|
47
|
+
else:
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def install():
|
|
52
|
+
if not hasattr(DataFrame, 'neo_write'):
|
|
53
|
+
setattr(DataFrame, "neo_write", neo_write)
|
|
54
|
+
|
|
55
|
+
logger.info("Neo Data Service installed")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_session():
|
|
59
|
+
return M.get_session()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pyspark.sql import SparkSession
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class M:
|
|
11
|
+
_instance = None
|
|
12
|
+
_config = None
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_session(cls):
|
|
16
|
+
if cls._instance is None:
|
|
17
|
+
cls._init_session()
|
|
18
|
+
|
|
19
|
+
if cls._instance:
|
|
20
|
+
return (cls._instance, cls._instance.sql)
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def _discover_config(cls):
|
|
24
|
+
if cls._config is not None:
|
|
25
|
+
return cls._config
|
|
26
|
+
|
|
27
|
+
stack = []
|
|
28
|
+
frame = sys._getframe()
|
|
29
|
+
for _ in range(6):
|
|
30
|
+
if frame is None:
|
|
31
|
+
return stack
|
|
32
|
+
frame = frame.f_back
|
|
33
|
+
|
|
34
|
+
while frame:
|
|
35
|
+
mod = frame.f_globals.get('__name__', '')
|
|
36
|
+
if mod and not mod.startswith('neo_data_svc'):
|
|
37
|
+
stack.append(mod)
|
|
38
|
+
frame = frame.f_back
|
|
39
|
+
|
|
40
|
+
logger.info(f"Import stack: {stack}")
|
|
41
|
+
|
|
42
|
+
config_paths = cls._generate_config_paths(stack)
|
|
43
|
+
config = {}
|
|
44
|
+
|
|
45
|
+
for path in config_paths:
|
|
46
|
+
try:
|
|
47
|
+
module = importlib.import_module(path)
|
|
48
|
+
cls._config = cls._parse_config(module, config)
|
|
49
|
+
logger.info(f"config from: {path}")
|
|
50
|
+
return cls._config
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
logger.debug(f"Failed to import {path}: {e}")
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
cls._config = config
|
|
56
|
+
return cls._config
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def _generate_config_paths(cls, stack):
|
|
60
|
+
paths = []
|
|
61
|
+
|
|
62
|
+
for module_name in stack:
|
|
63
|
+
if '.' in module_name:
|
|
64
|
+
root_package = module_name.split('.')[0]
|
|
65
|
+
paths.extend([f"{root_package}.config.Cfg"])
|
|
66
|
+
|
|
67
|
+
unique_paths = list(dict.fromkeys(paths))
|
|
68
|
+
return unique_paths
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def _parse_config(cls, module, base_config):
|
|
72
|
+
config = base_config.copy()
|
|
73
|
+
|
|
74
|
+
for attr_name in dir(module):
|
|
75
|
+
value = getattr(module, attr_name)
|
|
76
|
+
config[attr_name] = value
|
|
77
|
+
return config
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _init_session(cls):
|
|
81
|
+
config = cls._discover_config()
|
|
82
|
+
cls._instance = SparkSession.builder.appName(config['APP']).getOrCreate() # type: ignore
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get_config(cls):
|
|
86
|
+
if cls._config is None:
|
|
87
|
+
cls._discover_config()
|
|
88
|
+
return cls._config
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def stop_session(cls):
|
|
92
|
+
if cls._instance:
|
|
93
|
+
cls._instance.stop()
|
|
94
|
+
cls._instance = None
|
|
95
|
+
cls._config = None
|