arcade-core 4.2.0__py3-none-any.whl → 4.2.2__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.
arcade_core/toolkit.py CHANGED
@@ -299,32 +299,50 @@ class Toolkit(BaseModel):
299
299
  # Skipping this file is necessary because tools are discovered via AST parsing, but those tools
300
300
  # aren't in the module's namespace yet since the file is still executing.
301
301
  current_file = None
302
+ current_module_name = None
302
303
  main_module = sys.modules.get("__main__")
303
- if main_module and hasattr(main_module, "__file__") and main_module.__file__:
304
- with contextlib.suppress(Exception):
305
- current_file = Path(main_module.__file__).resolve()
304
+ if main_module:
305
+ if hasattr(main_module, "__file__") and main_module.__file__:
306
+ with contextlib.suppress(Exception):
307
+ current_file = Path(main_module.__file__).resolve()
308
+ # Get module name from __spec__ if available (used when paths don't match,
309
+ # e.g., script runs from bundle but package is in site-packages)
310
+ main_spec = getattr(main_module, "__spec__", None)
311
+ if main_spec and main_spec.name:
312
+ current_module_name = main_spec.name
306
313
 
307
314
  tools: dict[str, list[str]] = {}
308
315
 
309
316
  for module_path in modules:
310
- # Skip adding tools from the currently executing file
317
+ # Build import path first (needed for module name comparison in skip logic)
318
+ relative_path = module_path.relative_to(package_dir)
319
+ relative_parts = relative_path.with_suffix("").parts
320
+ import_path = ".".join(relative_parts)
321
+ if relative_parts and relative_parts[0] == package_name:
322
+ full_import_path = import_path
323
+ else:
324
+ full_import_path = f"{package_name}.{import_path}" if import_path else package_name
325
+
326
+ # Skip logic: check by file path OR by module name
327
+ # This handles cases where the script is run from a different location than
328
+ # where the package is installed (e.g., deployment scenarios)
329
+ should_skip = False
311
330
  if current_file:
312
331
  try:
313
332
  module_path_resolved = module_path.resolve()
314
333
  if module_path_resolved == current_file:
315
- continue
334
+ should_skip = True
316
335
  except Exception: # noqa: S110
317
336
  pass
318
337
 
319
- relative_path = module_path.relative_to(package_dir)
338
+ # Secondary check: compare module names when paths don't match
339
+ if not should_skip and current_module_name and full_import_path == current_module_name:
340
+ should_skip = True
341
+
342
+ if should_skip:
343
+ continue
344
+
320
345
  cls.validate_file(module_path)
321
- # Build import path and avoid duplicating the package prefix if it already exists
322
- relative_parts = relative_path.with_suffix("").parts
323
- import_path = ".".join(relative_parts)
324
- if relative_parts and relative_parts[0] == package_name:
325
- full_import_path = import_path
326
- else:
327
- full_import_path = f"{package_name}.{import_path}" if import_path else package_name
328
346
  tools[full_import_path] = get_tools_from_file(str(module_path))
329
347
 
330
348
  if not tools:
@@ -6,7 +6,6 @@ supporting pre-login anonymous tracking, post-login identity stitching,
6
6
  and logout identity rotation.
7
7
  """
8
8
 
9
- import fcntl
10
9
  import json
11
10
  import os
12
11
  import tempfile
@@ -14,6 +13,7 @@ import uuid
14
13
  from typing import Any
15
14
 
16
15
  import httpx
16
+ import portalocker
17
17
  import yaml
18
18
 
19
19
  from arcade_core.constants import ARCADE_CONFIG_PATH, CREDENTIALS_FILE_PATH
@@ -46,18 +46,16 @@ class UsageIdentity:
46
46
  if os.path.exists(self.usage_file_path):
47
47
  try:
48
48
  with open(self.usage_file_path) as f:
49
- # lock file
50
- if os.name != "nt": # Unix-like systems
51
- fcntl.flock(f.fileno(), fcntl.LOCK_SH)
49
+ # Lock file for reading (shared lock)
50
+ portalocker.lock(f, portalocker.LOCK_SH)
52
51
  try:
53
52
  data = json.load(f)
54
53
  if isinstance(data, dict) and KEY_ANON_ID in data:
55
54
  self._data = data
56
55
  return self._data
57
56
  finally:
58
- # unlock file
59
- if os.name != "nt":
60
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
57
+ # Unlock file
58
+ portalocker.unlock(f)
61
59
  except Exception: # noqa: S110
62
60
  pass
63
61
 
@@ -80,18 +78,18 @@ class UsageIdentity:
80
78
 
81
79
  try:
82
80
  with os.fdopen(temp_fd, "w") as f:
83
- # lock file
84
- if os.name != "nt": # Unix-like systems
85
- fcntl.flock(f.fileno(), fcntl.LOCK_EX)
81
+ # Lock file for writing (exclusive lock)
82
+ portalocker.lock(f, portalocker.LOCK_EX)
86
83
  try:
87
84
  json.dump(data, f, indent=2)
88
85
  f.flush()
89
86
  os.fsync(f.fileno()) # ensure data is written to disk
90
87
  finally:
91
- if os.name != "nt":
92
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
88
+ portalocker.unlock(f)
93
89
 
94
- os.rename(temp_path, self.usage_file_path)
90
+ # Use os.replace() for cross-platform atomic file replacement
91
+ # os.replace() is atomic on both Unix and Windows (Python 3.3+)
92
+ os.replace(temp_path, self.usage_file_path)
95
93
  except Exception:
96
94
  # clean up
97
95
  import contextlib
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arcade-core
3
- Version: 4.2.0
3
+ Version: 4.2.2
4
4
  Summary: Arcade Core - Core library for Arcade platform
5
5
  Author-email: Arcade <dev@arcade.dev>
6
6
  License: MIT
@@ -16,6 +16,8 @@ Requires-Python: >=3.10
16
16
  Requires-Dist: httpx>=0.27.0
17
17
  Requires-Dist: loguru>=0.7.0
18
18
  Requires-Dist: packaging>=24.1
19
+ Requires-Dist: portalocker>=2.10.0
20
+ Requires-Dist: posthog<7.0.0,>=6.7.6
19
21
  Requires-Dist: pydantic>=2.7.0
20
22
  Requires-Dist: pyjwt>=2.8.0
21
23
  Requires-Dist: pyyaml>=6.0
@@ -14,7 +14,7 @@ arcade_core/output.py,sha256=CMY1pHlQIR27Beiz2I-Yg1aO-P-pbsEbhBZ1RdYuflc,4040
14
14
  arcade_core/parse.py,sha256=arKGKL9C6g__tRfZ4re6IM_wAqr1v3LrOzTOBEDLhDc,2366
15
15
  arcade_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  arcade_core/schema.py,sha256=y32Ndlb1b3Hw7_Wm3eVXxh0kSg3oGitWUYBfwFLjHuY,21198
17
- arcade_core/toolkit.py,sha256=lLlOL6fA6Lmo-dtLTMMcPCzKDf9YQObwxG1LdVADv3E,14431
17
+ arcade_core/toolkit.py,sha256=FO_I-ZVNL7-IPMubfY74Zas_pQKEzwyUvFB9g-aFbgY,15272
18
18
  arcade_core/utils.py,sha256=_3bM-yfIDFmMVqt-NFYp2Lx1QcNWp7xytGjUQzPs2LY,3255
19
19
  arcade_core/version.py,sha256=CpXi3jGlx23RvRyU7iytOMZrnspdWw4yofS8lpP1AJU,18
20
20
  arcade_core/converters/__init__.py,sha256=ckb7c0LCq62_bARxoV7XlRYF7vtGbG1SJnDTWZ9Z7P4,827
@@ -26,9 +26,9 @@ arcade_core/network/org_transport.py,sha256=DZdIYdldtYZ6gtmWA1C8dXmsNuOv7tePhlpB
26
26
  arcade_core/usage/__init__.py,sha256=SUR5mqF-bjdbl-P-OOHN6OFAjXZu4agXyPhr7xdVXCw,234
27
27
  arcade_core/usage/__main__.py,sha256=rSJkE1G9hlV3HRRA6EJE5Lmy3wKyan7rAxBXHX9A1cI,1577
28
28
  arcade_core/usage/constants.py,sha256=1FQIhkFFMZUhU-H4A7GvMb7KQ3qLFrNAZb2-LEvSF3k,1052
29
- arcade_core/usage/identity.py,sha256=egclRR26jGP1vVvoOoaaZdcS4AtSTZ8fLHpBq1HRgHw,8452
29
+ arcade_core/usage/identity.py,sha256=h_THm-cN3j8nqW7_Lew7OdBRA70valCKJ7zvhDvja_E,8416
30
30
  arcade_core/usage/usage_service.py,sha256=xzWWSEktm58liiNYugBHRactSru8V5foriHcsoH0j1A,3407
31
31
  arcade_core/usage/utils.py,sha256=FqBOmlhwT68cbnpI5Vx9ZW6vLRYPVg4FJ0GaMEp8qEM,398
32
- arcade_core-4.2.0.dist-info/METADATA,sha256=9MoBNcEU1QcYlpXatMEe6Lj_OhbJUKCuyaCuZ0FQ52E,2412
33
- arcade_core-4.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
- arcade_core-4.2.0.dist-info/RECORD,,
32
+ arcade_core-4.2.2.dist-info/METADATA,sha256=F4w5igIIi19asDJdZmVrSIWS6NREaQdKTqbfUw1etDg,2484
33
+ arcade_core-4.2.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ arcade_core-4.2.2.dist-info/RECORD,,