xython 4.6.4__tar.gz → 4.7.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 (32) hide show
  1. {xython-4.6.4/src/xython.egg-info → xython-4.7.1}/PKG-INFO +1 -1
  2. {xython-4.6.4 → xython-4.7.1}/pyproject.toml +1 -1
  3. {xython-4.6.4 → xython-4.7.1}/src/xython/__init__.py +3 -3
  4. xython-4.7.1/src/xython/alias_installer.py +119 -0
  5. xython-4.7.1/src/xython/locales_loader.py +53 -0
  6. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_auto.py +69 -683
  7. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_chrome.py +17 -1
  8. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_color.py +772 -836
  9. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_common.py +498 -478
  10. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_db.py +28 -89
  11. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_edge.py +101 -721
  12. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_excel.py +1925 -2703
  13. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_hwp.py +63 -470
  14. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_list.py +24 -71
  15. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_map.py +23 -49
  16. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_outlook.py +434 -474
  17. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_re.py +278 -532
  18. xython-4.7.1/src/xython/xy_time.py +3824 -0
  19. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_util.py +701 -71
  20. xython-4.7.1/src/xython/xy_word.py +6265 -0
  21. {xython-4.6.4 → xython-4.7.1/src/xython.egg-info}/PKG-INFO +1 -1
  22. {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/SOURCES.txt +2 -0
  23. xython-4.6.4/src/xython/xy_time.py +0 -5915
  24. xython-4.6.4/src/xython/xy_word.py +0 -6707
  25. {xython-4.6.4 → xython-4.7.1}/MANIFEST.in +0 -0
  26. {xython-4.6.4 → xython-4.7.1}/README.md +0 -0
  27. {xython-4.6.4 → xython-4.7.1}/setup.cfg +0 -0
  28. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_excel_event.py +0 -0
  29. {xython-4.6.4 → xython-4.7.1}/src/xython/xy_manual.py +0 -0
  30. {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/dependency_links.txt +0 -0
  31. {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/requires.txt +0 -0
  32. {xython-4.6.4 → xython-4.7.1}/src/xython.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xython
3
- Version: 4.6.4
3
+ Version: 4.7.1
4
4
  Summary: xython package
5
5
  Author-email: "SJ.Park" <sjpkorea@naver.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xython"
7
- version = "4.6.4"
7
+ version = "4.7.1"
8
8
  description = "xython package"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
10
  requires-python = ">=3.8"
@@ -1,12 +1,12 @@
1
1
  # __init__.py
2
- # Copyright (C) 2026 (sjpkorea@naver.com) and contributors
2
+ # Copyright (C) 2025 (sjpkorea@naver.com) and contributors
3
+
3
4
 
4
- #https://blog.naver.com/xython
5
5
  import inspect
6
6
  import os
7
7
  import sys
8
8
 
9
- __version__ = '4.4.3' #1
9
+ __version__ = '4.3.5' #1
10
10
  real_path = os.path.dirname(os.path.abspath(__file__)).replace("\\","/") #2
11
11
  sys.path.append(real_path)
12
12
 
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ alias_installer.py (개선판 - 각 모듈의 호출부는 그대로 두면 됨)
4
+
5
+ 방식: 인스턴스마다 alias 를 setattr 하지 않고, __getattr__ 에서 '처음 쓸 때' 만든다.
6
+ - 인스턴스 생성이 가볍다 (280개 wrapper 를 매번 만들지 않음)
7
+ - alias -> 원본 역방향 표를 (모듈, 언어) 단위로 1번만 만들고 캐시
8
+ - 만든 wrapper 는 클래스 단위로 캐시하고, 인스턴스에는 바인딩만 한다
9
+ (인스턴스 __dict__ 가 깨끗해서 pickle / copy.deepcopy 도 안전)
10
+ - functools.wraps 로 원본 시그니처 유지 -> help(), IDE 에서 인자 목록이 보인다
11
+ - make_dir_fallback 을 쓰면 dir()/탭 자동완성에도 alias 가 보인다
12
+ """
13
+ import difflib, types
14
+ import functools
15
+ from functools import lru_cache
16
+
17
+ from .locales_loader import load_locales_for_module
18
+
19
+
20
+ def _normalize_langs(lang):
21
+ if lang is None:
22
+ return ()
23
+ if isinstance(lang, str):
24
+ return (lang,)
25
+ return tuple(lang)
26
+
27
+
28
+ def build_combined_doc(orig_name, locales, active_langs):
29
+ """여러 언어의 doc 을 순서대로 이어 붙인 docstring 생성."""
30
+ lines = []
31
+ for lang in active_langs:
32
+ doc = locales.get(lang, {}).get("docs", {}).get(orig_name)
33
+ if doc:
34
+ lines.append(f"[{lang}] {doc}")
35
+ return "\n".join(lines) if lines else None
36
+
37
+
38
+ @lru_cache(maxsize=None)
39
+ def _alias_table(module_name, langs):
40
+ """(alias -> 원본이름) 역방향 표와 locales 를 반환. langs 는 tuple."""
41
+ locales = load_locales_for_module(module_name, langs)
42
+ reverse = {}
43
+ for lg in langs:
44
+ for orig, alias in locales[lg]["aliases"].items():
45
+ if alias and alias != orig: # 빈 문자열(미번역)은 무시
46
+ reverse.setdefault(alias, orig) # 충돌 시 앞쪽 언어가 우선
47
+ return reverse, locales
48
+
49
+
50
+ @lru_cache(maxsize=None)
51
+ def _make_alias_function(owner_cls, module_name, orig_name, alias_name, langs):
52
+ orig_func = getattr(owner_cls, orig_name, None)
53
+ if orig_func is None or not callable(orig_func):
54
+ return None
55
+
56
+ _, locales = _alias_table(module_name, langs)
57
+ doc = build_combined_doc(orig_name, locales, langs) or orig_func.__doc__
58
+
59
+ @functools.wraps(orig_func)
60
+ def wrapper(self, *args, **kwargs):
61
+ return orig_func(self, *args, **kwargs)
62
+
63
+ wrapper.__name__ = alias_name
64
+ wrapper.__qualname__ = f"{owner_cls.__name__}.{alias_name}"
65
+ wrapper.__doc__ = doc
66
+ return wrapper
67
+
68
+
69
+ # alias_installer.py
70
+ def install_instance_aliases(instance, module_name, active_langs):
71
+ langs = _normalize_langs(active_langs)
72
+ reverse, _ = _alias_table(module_name, langs) # JSON 캐시 데움
73
+ owner_cls = type(instance)
74
+ for alias_name in reverse:
75
+ # 클래스에 이미 있으면(@alias 로 등록된 것) 건너뜀 → 충돌 방지
76
+ if hasattr(owner_cls, alias_name):
77
+ continue
78
+ func = _make_alias_function(owner_cls, module_name,
79
+ reverse[alias_name], alias_name, langs)
80
+ if func is not None:
81
+ # 인스턴스에 바인딩 (인스턴스마다 wrapper 1개씩 생김 — 비용 있음)
82
+ setattr(instance, alias_name, types.MethodType(func, instance))
83
+
84
+
85
+ def make_getattr_fallback(module_name):
86
+ """클래스에 `__getattr__ = make_getattr_fallback("xy_time")` 로 붙인다."""
87
+ def _getattr(self, name):
88
+ if name.startswith("__") and name.endswith("__"):
89
+ raise AttributeError(name)
90
+
91
+ langs = _normalize_langs(self.__dict__.get("lang"))
92
+ msg = f"'{type(self).__name__}' object has no attribute '{name}'"
93
+
94
+ if langs:
95
+ reverse, _ = _alias_table(module_name, langs)
96
+ orig = reverse.get(name)
97
+ if orig is not None:
98
+ func = _make_alias_function(type(self), module_name, orig, name, langs)
99
+ if func is not None:
100
+ return func.__get__(self, type(self))
101
+ close = difflib.get_close_matches(name, reverse, n=1)
102
+ if close:
103
+ msg += f". Did you mean '{close[0]}'?"
104
+
105
+ raise AttributeError(msg)
106
+
107
+ return _getattr
108
+
109
+
110
+ def make_dir_fallback(module_name):
111
+ """(선택) 클래스에 `__dir__ = make_dir_fallback("xy_time")` 를 붙이면 자동완성에 alias 가 보인다."""
112
+ def _dir(self):
113
+ names = set(object.__dir__(self))
114
+ langs = _normalize_langs(self.__dict__.get("lang"))
115
+ if langs:
116
+ names.update(_alias_table(module_name, langs)[0])
117
+ return sorted(names)
118
+
119
+ return _dir
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ locales_loader.py
4
+
5
+ locales/{lang}/{module_name}.json 을 읽어서 dict 로 반환.
6
+ - aliases : {원본함수명: 언어별이름}
7
+ - docs : {원본함수명: 언어별설명}
8
+ """
9
+ import json
10
+ import os
11
+
12
+ _LOCALES_DIR = os.path.join(os.path.dirname(__file__), "locales")
13
+ _cache = {}
14
+
15
+
16
+ def load_locale(module_name, lang):
17
+ key = (module_name, lang)
18
+ if key in _cache:
19
+ return _cache[key]
20
+
21
+ path = os.path.join(_LOCALES_DIR, lang, f"{module_name}.json")
22
+ if not os.path.exists(path):
23
+ _cache[key] = {"aliases": {}, "docs": {}}
24
+ return _cache[key]
25
+
26
+ try:
27
+ # utf-8-sig: 윈도우 메모장/일부 에디터가 붙이는 BOM 이 있어도 읽힌다
28
+ with open(path, "r", encoding="utf-8-sig") as f:
29
+ data = json.load(f)
30
+ except json.JSONDecodeError as e:
31
+ raise ValueError(f"locale 파일 JSON 오류: {path} ({e})") from e
32
+
33
+ result = {
34
+ "aliases": data.get("aliases", {}),
35
+ "docs": data.get("docs", {}),
36
+ }
37
+ _cache[key] = result
38
+ return result
39
+
40
+
41
+ def load_locales_for_module(module_name, langs):
42
+ return {lang: load_locale(module_name, lang) for lang in langs}
43
+
44
+
45
+ def clear_cache():
46
+ """JSON 을 고친 뒤 재시작 없이 다시 읽고 싶을 때 (GUI/테스트용)."""
47
+ _cache.clear()
48
+ try:
49
+ from .alias_installer import _alias_table, _make_alias_function
50
+ _alias_table.cache_clear()
51
+ _make_alias_function.cache_clear()
52
+ except ImportError:
53
+ pass