qutePandas 1.1.0__tar.gz → 1.1.1__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.
Files changed (46) hide show
  1. {qutepandas-1.1.0/qutePandas.egg-info → qutepandas-1.1.1}/PKG-INFO +1 -1
  2. {qutepandas-1.1.0 → qutepandas-1.1.1}/pyproject.toml +1 -1
  3. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/__init__.py +41 -11
  4. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/apply/apply.py +7 -1
  5. qutepandas-1.1.1/qutePandas/cleaning/fillna.py +49 -0
  6. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/core/dataframe.py +0 -3
  7. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/indexing/iloc.py +31 -10
  8. {qutepandas-1.1.0 → qutepandas-1.1.1/qutePandas.egg-info}/PKG-INFO +1 -1
  9. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas.egg-info/SOURCES.txt +1 -0
  10. {qutepandas-1.1.0 → qutepandas-1.1.1}/setup.py +1 -1
  11. qutepandas-1.1.1/tests/test_pykx_setup.py +274 -0
  12. qutepandas-1.1.0/qutePandas/cleaning/fillna.py +0 -36
  13. {qutepandas-1.1.0 → qutepandas-1.1.1}/MANIFEST.in +0 -0
  14. {qutepandas-1.1.0 → qutepandas-1.1.1}/README.md +0 -0
  15. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/apply/__init__.py +0 -0
  16. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/apply/apply_col.py +0 -0
  17. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/cleaning/__init__.py +0 -0
  18. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/cleaning/dropna.py +0 -0
  19. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/cleaning/dropna_col.py +0 -0
  20. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/cleaning/remove_duplicates.py +0 -0
  21. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/core/__init__.py +0 -0
  22. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/core/connection.py +0 -0
  23. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/core/display.py +0 -0
  24. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/grouping/__init__.py +0 -0
  25. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/grouping/groupby_avg.py +0 -0
  26. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/grouping/groupby_sum.py +0 -0
  27. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/indexing/__init__.py +0 -0
  28. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/indexing/loc.py +0 -0
  29. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/introspection/__init__.py +0 -0
  30. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/introspection/dtypes.py +0 -0
  31. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/io/__init__.py +0 -0
  32. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/io/from_csv.py +0 -0
  33. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/io/to_csv.py +0 -0
  34. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/joining/__init__.py +0 -0
  35. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/joining/merge.py +0 -0
  36. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/transformation/__init__.py +0 -0
  37. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/transformation/cast.py +0 -0
  38. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/transformation/drop_col.py +0 -0
  39. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/transformation/rename.py +0 -0
  40. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas/utils.py +0 -0
  41. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas.egg-info/dependency_links.txt +0 -0
  42. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas.egg-info/requires.txt +0 -0
  43. {qutepandas-1.1.0 → qutepandas-1.1.1}/qutePandas.egg-info/top_level.txt +0 -0
  44. {qutepandas-1.1.0 → qutepandas-1.1.1}/requirements.txt +0 -0
  45. {qutepandas-1.1.0 → qutepandas-1.1.1}/setup.cfg +0 -0
  46. {qutepandas-1.1.0 → qutepandas-1.1.1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qutePandas
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: A pandas-like library for kdb+/q using pykx
5
5
  Home-page: https://ishapatro.github.io/qutePandas/
6
6
  Author: Isha Patro
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "qutePandas"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  authors = [
9
9
  { name="Isha Patro", email="ishapatro21@gmail.com" },
10
10
  ]
@@ -1,11 +1,11 @@
1
- """
2
- qutePandas - A pandas-like library for q/kdb+
3
- """
4
-
5
1
  import os
2
+ import sys
6
3
 
7
- def _setup_environment():
8
- """Validates and sets up the environment for PyKX."""
4
+ def _setup_pykx_environment():
5
+ """
6
+ Sets up the environment for PyKX BEFORE any PyKX imports.
7
+ This must run before importing any module that uses PyKX.
8
+ """
9
9
  root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
10
10
  env_path = os.path.join(root, ".env")
11
11
 
@@ -20,17 +20,47 @@ def _setup_environment():
20
20
  if len(parts) == 2:
21
21
  key, value = parts
22
22
  value = value.strip().strip('"').strip("'")
23
+ # Only set if not already in environment
23
24
  if key.strip() and key.strip() not in os.environ:
24
25
  os.environ[key.strip()] = value
25
-
26
+
27
+ # Check for valid license files in priority order
26
28
  qutepandas_home = os.path.expanduser("~/.qutepandas")
27
29
  local_kdb = os.path.join(root, "kdb_lic")
30
+
31
+ license_found = False
32
+ valid_license_path = None
33
+
28
34
  if os.path.exists(os.path.join(local_kdb, "kc.lic")):
29
- os.environ['QLIC'] = local_kdb
35
+ valid_license_path = local_kdb
36
+ license_found = True
30
37
  elif os.path.exists(os.path.join(qutepandas_home, "kc.lic")):
31
- os.environ['QLIC'] = qutepandas_home
38
+ valid_license_path = qutepandas_home
39
+ license_found = True
40
+
41
+ if license_found and valid_license_path:
42
+ os.environ['QLIC'] = valid_license_path
43
+ os.environ.pop('PYKX_UNLICENSED', None)
44
+ elif 'QLIC' in os.environ:
45
+ qlic_path = os.environ['QLIC']
46
+ if not (os.path.exists(os.path.join(qlic_path, "kc.lic")) or
47
+ os.path.exists(os.path.join(qlic_path, "k4.lic"))):
48
+ del os.environ['QLIC']
49
+ if 'QHOME' in os.environ:
50
+ del os.environ['QHOME']
51
+ license_found = False
52
+
53
+ if not license_found and 'PYKX_UNLICENSED' not in os.environ:
54
+ os.environ['PYKX_UNLICENSED'] = 'true'
55
+
56
+ if 'PYKX_RELEASE_GIL' not in os.environ:
57
+ os.environ['PYKX_RELEASE_GIL'] = 'true'
58
+
59
+ if 'PYKX_ENFORCE_EMBEDDED_IMPORT' not in os.environ:
60
+ os.environ['PYKX_ENFORCE_EMBEDDED_IMPORT'] = '0'
61
+
62
+ _setup_pykx_environment()
32
63
 
33
- _setup_environment()
34
64
  from .core.dataframe import DataFrame
35
65
  from .core.connection import connect, get_license_info, install_license
36
66
  from .core.display import py, np, pd, pa, pt, print
@@ -59,4 +89,4 @@ from .introspection.dtypes import dtypes
59
89
 
60
90
  from .indexing import loc, iloc
61
91
 
62
- __version__ = "1.0.0"
92
+ __version__ = "1.1.1"
@@ -2,6 +2,10 @@ import pykx as kx
2
2
  import pandas as pd
3
3
  from ..utils import _ensure_q_table, _handle_return
4
4
 
5
+ _VECTORIZED_AXIS1 = {
6
+ 'sum': '{sum (0^) each value flip x}',
7
+ }
8
+
5
9
 
6
10
  def apply(df, func, axis=0, return_type='q'):
7
11
  """
@@ -39,7 +43,9 @@ def apply(df, func, axis=0, return_type='q'):
39
43
  return pd.Series(ret) if return_type == 'p' else ret
40
44
 
41
45
  if isinstance(func, str):
42
- if axis == 1:
46
+ if axis == 1 and func in _VECTORIZED_AXIS1:
47
+ result = kx.q(_VECTORIZED_AXIS1[func], q_table)
48
+ elif axis == 1:
43
49
  result = kx.q(f"{{({func}) each x}}", q_table)
44
50
  else:
45
51
  result = kx.q(f"{{({func}) each flip x}}", q_table)
@@ -0,0 +1,49 @@
1
+ import pykx as kx
2
+ import pandas as pd
3
+ from ..utils import _ensure_q_table, _handle_return
4
+
5
+ def fillna(df, col_or_values, fill_value=None, return_type='q'):
6
+ """
7
+ Fills null values in specified columns.
8
+
9
+ Can be called in two ways:
10
+ fillna(df, values_dict, return_type='q')
11
+ fillna(df, col_name, fill_value, return_type='q')
12
+
13
+ Parameters
14
+ ----------
15
+ df : pandas.DataFrame or pykx.Table
16
+ Input DataFrame.
17
+ col_or_values : str or dict
18
+ If str, the column name to fill (requires fill_value).
19
+ If dict, a mapping of column names to fill values.
20
+ fill_value : scalar, optional
21
+ The value to fill nulls with when col_or_values is a column name.
22
+ return_type : str, default 'q'
23
+ Desired return type ('p' or 'q').
24
+
25
+ Returns
26
+ -------
27
+ pandas.DataFrame or pykx.Table
28
+ DataFrame with nulls filled.
29
+ """
30
+ try:
31
+ if isinstance(col_or_values, str):
32
+ if fill_value is None:
33
+ raise ValueError("fill_value is required when col_or_values is a column name")
34
+ values = {col_or_values: fill_value}
35
+ elif isinstance(col_or_values, dict):
36
+ values = col_or_values
37
+ else:
38
+ raise ValueError("col_or_values must be a column name (str) or a dictionary")
39
+
40
+ q_table = _ensure_q_table(df)
41
+ result = q_table
42
+
43
+ for col, val in values.items():
44
+ fill_val = f'`{val}' if isinstance(val, str) else str(val)
45
+ result = kx.q(f"{{update {col}:{fill_val}^{col} from x}}", result)
46
+
47
+ return _handle_return(result, return_type)
48
+ except Exception as e:
49
+ raise RuntimeError(f"Failed to fillna: {e}")
@@ -1,11 +1,8 @@
1
1
  import pykx as kx
2
2
  import pandas as pd
3
- import os
4
3
  import atexit
5
4
  from ..utils import _handle_return
6
5
 
7
- os.environ['PYKX_ENFORCE_EMBEDDED_IMPORT'] = '0'
8
-
9
6
  def DataFrame(data, columns=None):
10
7
  """
11
8
  Creates a qutePandas DataFrame (internal pykx Table).
@@ -22,10 +22,15 @@ def iloc(df, rows=None, cols=None, return_type='q'):
22
22
  Subset of the inputs.
23
23
  """
24
24
  table = _ensure_q_table(df)
25
-
25
+
26
26
  count = kx.q("count", table).py()
27
27
  all_cols = kx.q("cols", table).py()
28
-
28
+
29
+ # Track whether we can use the fast sublist path for contiguous row slices
30
+ _use_sublist = False
31
+ _slice_start = 0
32
+ _slice_count = 0
33
+
29
34
  if rows is None:
30
35
  row_indices = None
31
36
  elif isinstance(rows, int):
@@ -33,7 +38,14 @@ def iloc(df, rows=None, cols=None, return_type='q'):
33
38
  if q_rows < 0: q_rows += count
34
39
  row_indices = [q_rows]
35
40
  elif isinstance(rows, slice):
36
- row_indices = list(range(*rows.indices(count)))
41
+ start, stop, step = rows.indices(count)
42
+ if step == 1:
43
+ _use_sublist = True
44
+ _slice_start = start
45
+ _slice_count = stop - start
46
+ row_indices = None # Not needed — sublist handles it
47
+ else:
48
+ row_indices = list(range(start, stop, step))
37
49
  else:
38
50
  row_indices = list(rows)
39
51
  row_indices = [r + count if r < 0 else r for r in row_indices]
@@ -47,27 +59,36 @@ def iloc(df, rows=None, cols=None, return_type='q'):
47
59
  target_cols = [all_cols[i] for i in range(*cols.indices(len(all_cols)))]
48
60
  else:
49
61
  target_cols = [all_cols[i] for i in cols]
50
-
51
- if row_indices is None and target_cols is None:
62
+
63
+ if not _use_sublist and row_indices is None and target_cols is None:
52
64
  return _handle_return(table, return_type)
53
65
 
54
- if row_indices is not None and target_cols is not None:
66
+ # Fast path: contiguous row slice via q's sublist (no Python list overhead)
67
+ if _use_sublist:
68
+ sublist_args = kx.LongVector([_slice_start, _slice_count])
69
+ if target_cols is not None:
70
+ syms = kx.SymbolVector(target_cols)
71
+ cols_dict = kx.q('!', syms, syms)
72
+ q_res = kx.q('{?[sublist[y;x];();0b;z]}', table, sublist_args, cols_dict)
73
+ else:
74
+ q_res = kx.q('{sublist[y;x]}', table, sublist_args)
75
+ elif row_indices is not None and target_cols is not None:
55
76
  q_query = '{?[x y;();0b;z]}'
56
77
  syms = kx.SymbolVector(target_cols)
57
78
  cols_dict = kx.q('!', syms, syms)
58
79
  q_res = kx.q(q_query, table, kx.LongVector(row_indices), cols_dict)
59
-
80
+
60
81
  elif row_indices is not None:
61
82
  q_query = '{x y}'
62
83
  q_res = kx.q(q_query, table, kx.LongVector(row_indices))
63
-
84
+
64
85
  elif target_cols is not None:
65
86
  q_query = '{?[x;();0b;y]}'
66
87
  syms = kx.SymbolVector(target_cols)
67
88
  cols_dict = kx.q('!', syms, syms)
68
89
  q_res = kx.q(q_query, table, cols_dict)
69
-
90
+
70
91
  else:
71
92
  q_res = table
72
93
 
73
- return _handle_return(q_res, return_type)
94
+ return _handle_return(q_res, return_type)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qutePandas
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: A pandas-like library for kdb+/q using pykx
5
5
  Home-page: https://ishapatro.github.io/qutePandas/
6
6
  Author: Isha Patro
@@ -39,4 +39,5 @@ qutePandas/transformation/__init__.py
39
39
  qutePandas/transformation/cast.py
40
40
  qutePandas/transformation/drop_col.py
41
41
  qutePandas/transformation/rename.py
42
+ tests/test_pykx_setup.py
42
43
  tests/test_utils.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="qutePandas",
5
- version="1.1.0",
5
+ version="1.1.1",
6
6
  packages=find_packages(),
7
7
  package_data={
8
8
  'qutePandas': ['*.q'],
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PyKX + qutePandas Setup Verification Test
4
+
5
+ This standalone test file verifies that:
6
+ 1. PyKX license is properly configured
7
+ 2. Embedded q initializes successfully
8
+ 3. qutePandas imports without errors
9
+ 4. Basic DataFrame operations work correctly
10
+
11
+ Run this file to diagnose and verify your PyKX + qutePandas setup.
12
+ """
13
+
14
+ import sys
15
+ import os
16
+ from pathlib import Path
17
+
18
+
19
+ def print_section(title):
20
+ """Print a formatted section header."""
21
+ print(f"\n{'=' * 70}")
22
+ print(f" {title}")
23
+ print('=' * 70)
24
+
25
+
26
+ def print_result(test_name, passed, message=""):
27
+ """Print a test result with status indicator."""
28
+ status = "✅ PASS" if passed else "❌ FAIL"
29
+ print(f"{status}: {test_name}")
30
+ if message:
31
+ print(f" {message}")
32
+
33
+
34
+ def check_python_version():
35
+ """Verify Python version is 3.8 or higher."""
36
+ print_section("Python Version Check")
37
+ version = sys.version_info
38
+ print(f"Python version: {version.major}.{version.minor}.{version.micro}")
39
+
40
+ if version.major >= 3 and version.minor >= 8:
41
+ print_result("Python Version", True, "Python 3.8+ detected")
42
+ return True
43
+ else:
44
+ print_result("Python Version", False, "Python 3.8+ required")
45
+ return False
46
+
47
+
48
+ def check_license_files():
49
+ """Check for kdb+ license files in common locations."""
50
+ print_section("License File Detection")
51
+
52
+ locations = [
53
+ Path.home() / ".qutepandas" / "kc.lic",
54
+ Path(__file__).parent.parent / "kdb_lic" / "kc.lic",
55
+ Path.home() / "kdb+" / "kc.lic",
56
+ Path.home() / "q" / "kc.lic",
57
+ ]
58
+
59
+ found_licenses = []
60
+ for loc in locations:
61
+ if loc.exists():
62
+ found_licenses.append(str(loc))
63
+ print(f" Found: {loc}")
64
+
65
+ qlic = os.environ.get('QLIC')
66
+ kx_token = os.environ.get('KX_TOKEN') or os.environ.get('KDB_TOKEN')
67
+ pykx_unlicensed = os.environ.get('PYKX_UNLICENSED')
68
+
69
+ print(f"\nEnvironment Variables:")
70
+ print(f" QLIC: {qlic if qlic else 'Not set'}")
71
+ print(f" KX_TOKEN: {'Set' if kx_token else 'Not set'}")
72
+ print(f" PYKX_UNLICENSED: {pykx_unlicensed if pykx_unlicensed else 'Not set'}")
73
+
74
+ if found_licenses or kx_token or pykx_unlicensed == 'true':
75
+ print_result("License Detection", True,
76
+ f"Found {len(found_licenses)} license file(s) or valid configuration")
77
+ return True
78
+ else:
79
+ print_result("License Detection", False,
80
+ "No license found - will attempt unlicensed mode")
81
+ return False
82
+
83
+
84
+ def test_pykx_import():
85
+ """Test PyKX import and initialization."""
86
+ print_section("PyKX Import and Initialization")
87
+
88
+ try:
89
+ import pykx as kx
90
+ print(f"PyKX version: {kx.__version__}")
91
+ print_result("PyKX Import", True)
92
+ return True, kx
93
+ except Exception as e:
94
+ print_result("PyKX Import", False, str(e))
95
+ return False, None
96
+
97
+
98
+ def test_embedded_q(kx):
99
+ """Test embedded q functionality."""
100
+ print_section("Embedded Q Initialization")
101
+
102
+ if kx is None:
103
+ print_result("Embedded Q", False, "PyKX not available")
104
+ return False
105
+
106
+ try:
107
+ # Test basic q expression
108
+ result = kx.q('1+1')
109
+ expected = 2
110
+
111
+ if result.py() == expected:
112
+ print_result("Basic Q Expression", True, f"1+1 = {result.py()}")
113
+ else:
114
+ print_result("Basic Q Expression", False,
115
+ f"Expected {expected}, got {result.py()}")
116
+ return False
117
+
118
+ # Test table creation
119
+ table = kx.q('([] a:1 2 3; b:`x`y`z)')
120
+ print(f"Created q table:\n{table}")
121
+ print_result("Q Table Creation", True)
122
+
123
+ return True
124
+ except Exception as e:
125
+ print_result("Embedded Q", False, str(e))
126
+ return False
127
+
128
+
129
+ def test_qutepandas_import():
130
+ """Test qutePandas import."""
131
+ print_section("qutePandas Import")
132
+
133
+ try:
134
+ import qutePandas as qpd
135
+ print(f"qutePandas version: {qpd.__version__}")
136
+ print_result("qutePandas Import", True)
137
+ return True, qpd
138
+ except Exception as e:
139
+ print_result("qutePandas Import", False, str(e))
140
+ print(f"\nFull error:\n{e}")
141
+ import traceback
142
+ traceback.print_exc()
143
+ return False, None
144
+
145
+
146
+ def test_qutepandas_operations(qpd):
147
+ """Test basic qutePandas DataFrame operations."""
148
+ print_section("qutePandas DataFrame Operations")
149
+
150
+ if qpd is None:
151
+ print_result("DataFrame Operations", False, "qutePandas not available")
152
+ return False
153
+
154
+ try:
155
+ # Test 1: Create DataFrame from dict
156
+ df = qpd.DataFrame({
157
+ 'id': [1, 2, 3, 4, 5],
158
+ 'name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
159
+ 'score': [95.5, 87.3, 92.1, 78.9, 88.4]
160
+ })
161
+ print(f"Created DataFrame:\n{df}")
162
+ print_result("DataFrame Creation", True)
163
+
164
+ # Test 2: Get dtypes
165
+ dtypes = qpd.dtypes(df)
166
+ print(f"\nDataFrame dtypes:\n{dtypes}")
167
+ print_result("Get Dtypes", True)
168
+
169
+ # Test 3: Convert to pandas
170
+ pdf = df.pd()
171
+ print(f"\nConverted to pandas:\n{pdf}")
172
+ print_result("Convert to Pandas", True, f"Shape: {pdf.shape}")
173
+
174
+ # Test 4: Filter operation
175
+ filtered = qpd.apply(df, lambda x: x['score'] > 85)
176
+ print(f"\nFiltered (score > 85):\n{filtered}")
177
+ print_result("Filter Operation", True)
178
+
179
+ # Test 5: Groupby operation
180
+ df2 = qpd.DataFrame({
181
+ 'category': ['A', 'B', 'A', 'B', 'A'],
182
+ 'value': [10, 20, 30, 40, 50]
183
+ })
184
+ grouped = qpd.groupby_sum(df2, 'category', 'value')
185
+ print(f"\nGroupby sum:\n{grouped}")
186
+ print_result("Groupby Operation", True)
187
+
188
+ return True
189
+ except Exception as e:
190
+ print_result("DataFrame Operations", False, str(e))
191
+ import traceback
192
+ traceback.print_exc()
193
+ return False
194
+
195
+
196
+ def print_diagnostic_info():
197
+ """Print diagnostic information for troubleshooting."""
198
+ print_section("Diagnostic Information")
199
+
200
+ import platform
201
+ print(f"OS: {platform.system()} {platform.release()}")
202
+ print(f"Architecture: {platform.machine()}")
203
+ print(f"Python: {sys.version}")
204
+
205
+ print(f"\nEnvironment Variables:")
206
+ for var in ['QHOME', 'QLIC', 'PYKX_UNLICENSED', 'PYKX_RELEASE_GIL',
207
+ 'PYKX_ENFORCE_EMBEDDED_IMPORT', 'KX_TOKEN', 'KDB_TOKEN']:
208
+ value = os.environ.get(var, 'Not set')
209
+ print(f" {var}: {value}")
210
+
211
+ print(f"\nPython Path:")
212
+ for path in sys.path[:5]:
213
+ print(f" {path}")
214
+
215
+
216
+ def main():
217
+ """Run all tests."""
218
+ print("\n" + "=" * 70)
219
+ print(" PyKX + qutePandas Setup Verification")
220
+ print("=" * 70)
221
+
222
+ results = []
223
+
224
+ # Test 1: Python version
225
+ results.append(check_python_version())
226
+
227
+ # Test 2: License detection
228
+ results.append(check_license_files())
229
+
230
+ # Test 3: PyKX import
231
+ pykx_ok, kx = test_pykx_import()
232
+ results.append(pykx_ok)
233
+
234
+ # Test 4: Embedded q
235
+ if pykx_ok:
236
+ results.append(test_embedded_q(kx))
237
+ else:
238
+ results.append(False)
239
+
240
+ # Test 5: qutePandas import
241
+ qpd_ok, qpd = test_qutepandas_import()
242
+ results.append(qpd_ok)
243
+
244
+ # Test 6: qutePandas operations
245
+ if qpd_ok:
246
+ results.append(test_qutepandas_operations(qpd))
247
+ else:
248
+ results.append(False)
249
+
250
+ # Print diagnostic info
251
+ print_diagnostic_info()
252
+
253
+ # Summary
254
+ print_section("Test Summary")
255
+ passed = sum(results)
256
+ total = len(results)
257
+
258
+ print(f"\nTests Passed: {passed}/{total}")
259
+
260
+ if passed == total:
261
+ print("\n🎉 SUCCESS! PyKX + qutePandas is properly configured.")
262
+ return 0
263
+ else:
264
+ print("\n⚠️ ISSUES DETECTED. See failures above for details.")
265
+ print("\nCommon solutions:")
266
+ print(" 1. Ensure kc.lic is in ~/.qutepandas/ or project kdb_lic/")
267
+ print(" 2. Set PYKX_UNLICENSED=true for unlicensed mode")
268
+ print(" 3. Check that Python is 3.8+")
269
+ print(" 4. Reinstall: pip install --upgrade pykx")
270
+ return 1
271
+
272
+
273
+ if __name__ == "__main__":
274
+ sys.exit(main())
@@ -1,36 +0,0 @@
1
- import pykx as kx
2
- import pandas as pd
3
- from ..utils import _ensure_q_table, _handle_return
4
-
5
- def fillna(df, values, return_type='q'):
6
- """
7
- Fills null values using a dictionary mapping of columns to fill values.
8
-
9
- Parameters
10
- ----------
11
- df : pandas.DataFrame or pykx.Table
12
- Input DataFrame.
13
- values : dict
14
- Mapping of column names to fill values.
15
- return_type : str, default 'q'
16
- Desired return type ('p' or 'q').
17
-
18
- Returns
19
- -------
20
- pandas.DataFrame or pykx.Table
21
- DataFrame with nulls filled.
22
- """
23
- try:
24
- if not isinstance(values, dict):
25
- raise ValueError("values must be a dictionary")
26
-
27
- q_table = _ensure_q_table(df)
28
- result = q_table
29
-
30
- for col, val in values.items():
31
- fill_val = f'`{val}' if isinstance(val, str) else str(val)
32
- result = kx.q(f"{{update {col}:{fill_val}^{col} from x}}", result)
33
-
34
- return _handle_return(result, return_type)
35
- except Exception as e:
36
- raise RuntimeError(f"Failed to fillna: {e}")
File without changes
File without changes
File without changes
File without changes