nepher 0.1.0__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.
Files changed (45) hide show
  1. nepher/__init__.py +36 -0
  2. nepher/api/__init__.py +6 -0
  3. nepher/api/client.py +384 -0
  4. nepher/api/endpoints.py +97 -0
  5. nepher/auth.py +150 -0
  6. nepher/cli/__init__.py +2 -0
  7. nepher/cli/commands/__init__.py +6 -0
  8. nepher/cli/commands/auth.py +37 -0
  9. nepher/cli/commands/cache.py +85 -0
  10. nepher/cli/commands/config.py +77 -0
  11. nepher/cli/commands/download.py +72 -0
  12. nepher/cli/commands/list.py +75 -0
  13. nepher/cli/commands/upload.py +69 -0
  14. nepher/cli/commands/view.py +310 -0
  15. nepher/cli/main.py +30 -0
  16. nepher/cli/utils.py +28 -0
  17. nepher/config.py +202 -0
  18. nepher/core.py +67 -0
  19. nepher/env_cfgs/__init__.py +7 -0
  20. nepher/env_cfgs/base.py +32 -0
  21. nepher/env_cfgs/manipulation/__init__.py +4 -0
  22. nepher/env_cfgs/navigation/__init__.py +45 -0
  23. nepher/env_cfgs/navigation/abstract_nav_cfg.py +159 -0
  24. nepher/env_cfgs/navigation/preset_nav_cfg.py +590 -0
  25. nepher/env_cfgs/navigation/usd_nav_cfg.py +644 -0
  26. nepher/env_cfgs/registry.py +31 -0
  27. nepher/loader/__init__.py +9 -0
  28. nepher/loader/base.py +27 -0
  29. nepher/loader/category_loaders/__init__.py +2 -0
  30. nepher/loader/preset_loader.py +80 -0
  31. nepher/loader/registry.py +63 -0
  32. nepher/loader/usd_loader.py +49 -0
  33. nepher/storage/__init__.py +8 -0
  34. nepher/storage/bundle.py +78 -0
  35. nepher/storage/cache.py +145 -0
  36. nepher/storage/manifest.py +80 -0
  37. nepher/utils/__init__.py +12 -0
  38. nepher/utils/fast_spawn_sampler.py +334 -0
  39. nepher/utils/free_zone_finder.py +239 -0
  40. nepher-0.1.0.dist-info/METADATA +235 -0
  41. nepher-0.1.0.dist-info/RECORD +45 -0
  42. nepher-0.1.0.dist-info/WHEEL +5 -0
  43. nepher-0.1.0.dist-info/entry_points.txt +2 -0
  44. nepher-0.1.0.dist-info/licenses/LICENSE +97 -0
  45. nepher-0.1.0.dist-info/top_level.txt +1 -0
nepher/loader/base.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ Base loader interface.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from nepher.core import Environment
7
+ from nepher.env_cfgs.base import BaseEnvCfg
8
+
9
+
10
+ class BaseLoader(ABC):
11
+ """Base interface for environment loaders."""
12
+
13
+ @abstractmethod
14
+ def load(self, env: Environment, scene_idx: int, category: str) -> BaseEnvCfg:
15
+ """
16
+ Load scene config.
17
+
18
+ Args:
19
+ env: Environment object
20
+ scene_idx: Scene index
21
+ category: Environment category
22
+
23
+ Returns:
24
+ Category-appropriate config class
25
+ """
26
+ pass
27
+
@@ -0,0 +1,2 @@
1
+ """Category-specific loaders (optional extensions)."""
2
+
@@ -0,0 +1,80 @@
1
+ """
2
+ Preset environment loader.
3
+ """
4
+
5
+ import importlib.util
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ from nepher.core import Environment
10
+ from nepher.loader.base import BaseLoader
11
+ from nepher.env_cfgs.registry import get_config_class
12
+
13
+
14
+ def load_preset_module(preset_path: str, base_path: Optional[Path] = None):
15
+ """Load preset module from path.
16
+
17
+ Args:
18
+ preset_path: Path to preset file (e.g., "my_preset.py") or module path
19
+ base_path: Base directory for resolving relative file paths
20
+
21
+ Returns:
22
+ Preset config class
23
+
24
+ Raises:
25
+ FileNotFoundError: If preset file is not found
26
+ ImportError: If preset module cannot be loaded
27
+ ValueError: If no preset config class is found
28
+ """
29
+ if preset_path.endswith(".py"):
30
+ if base_path:
31
+ file_path = base_path / preset_path
32
+ else:
33
+ file_path = Path(preset_path)
34
+
35
+ if not file_path.exists():
36
+ raise FileNotFoundError(f"Preset file not found: {file_path}")
37
+
38
+ module_name = f"nepher_preset_{file_path.stem}_{id(file_path)}"
39
+
40
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
41
+ if spec is None or spec.loader is None:
42
+ raise ImportError(f"Could not load preset from {file_path}")
43
+
44
+ module = importlib.util.module_from_spec(spec)
45
+ sys.modules[module_name] = module
46
+ spec.loader.exec_module(module)
47
+
48
+ for attr_name in dir(module):
49
+ attr = getattr(module, attr_name)
50
+ if (isinstance(attr, type) and
51
+ (attr_name.endswith("Cfg") or attr_name.endswith("PresetCfg")) and
52
+ attr_name != "PresetNavigationEnvCfg"):
53
+ return attr
54
+
55
+ raise ValueError(f"No preset config class found in {file_path}")
56
+ else:
57
+ parts = preset_path.split(".")
58
+ module_path = ".".join(parts[:-1])
59
+ class_name = parts[-1]
60
+
61
+ module = importlib.import_module(module_path)
62
+ return getattr(module, class_name)
63
+
64
+
65
+ class PresetLoader(BaseLoader):
66
+ """Generic preset loader that returns category-specific configs."""
67
+
68
+ def load(self, env: Environment, scene_idx: int, category: str):
69
+ """Load preset scene config."""
70
+ if scene_idx >= len(env.preset_scenes):
71
+ raise IndexError(f"Preset scene index {scene_idx} out of range")
72
+
73
+ scene = env.preset_scenes[scene_idx]
74
+
75
+ if not scene.preset:
76
+ raise ValueError("Preset scene missing preset path")
77
+
78
+ preset_class = load_preset_module(scene.preset, base_path=env.cache_path)
79
+ return preset_class()
80
+
@@ -0,0 +1,63 @@
1
+ """
2
+ Loader registry and convenience functions.
3
+ """
4
+
5
+ from typing import Optional, Union
6
+ from nepher.core import Environment
7
+ from nepher.storage.cache import get_cache_manager
8
+ from nepher.storage.manifest import ManifestParser
9
+ from nepher.api.client import get_client
10
+ from nepher.loader.usd_loader import UsdLoader
11
+ from nepher.loader.preset_loader import PresetLoader
12
+
13
+
14
+ def load_env(env_id: str, category: str) -> Environment:
15
+ """
16
+ Load environment from cache or download if needed.
17
+
18
+ Args:
19
+ env_id: Environment ID
20
+ category: Environment category
21
+
22
+ Returns:
23
+ Environment object
24
+ """
25
+ cache_manager = get_cache_manager(category=category)
26
+ cache_path = cache_manager.get_env_cache_path(env_id)
27
+
28
+ if cache_manager.is_cached(env_id):
29
+ manifest_path = cache_path / "manifest.yaml"
30
+ return ManifestParser.parse(manifest_path)
31
+
32
+ raise FileNotFoundError(
33
+ f"Environment {env_id} not found in cache. Use download() first."
34
+ )
35
+
36
+
37
+ def load_scene(env: Environment, scene: Union[str, int], category: str):
38
+ """
39
+ Load scene config.
40
+
41
+ Args:
42
+ env: Environment object
43
+ scene: Scene name or index
44
+ category: Environment category
45
+
46
+ Returns:
47
+ Category-appropriate config class
48
+ """
49
+ scene_obj = env.get_scene(scene)
50
+ if not scene_obj:
51
+ raise ValueError(f"Scene {scene} not found in environment {env.id}")
52
+
53
+ if scene_obj.usd:
54
+ loader = UsdLoader()
55
+ scene_idx = env.scenes.index(scene_obj)
56
+ return loader.load(env, scene_idx, category)
57
+ elif scene_obj.preset:
58
+ loader = PresetLoader()
59
+ scene_idx = env.preset_scenes.index(scene_obj)
60
+ return loader.load(env, scene_idx, category)
61
+ else:
62
+ raise ValueError(f"Scene {scene} has no USD or preset path")
63
+
@@ -0,0 +1,49 @@
1
+ """
2
+ USD environment loader.
3
+ """
4
+
5
+ from pathlib import Path
6
+ from nepher.core import Environment
7
+ from nepher.loader.base import BaseLoader
8
+ from nepher.loader.preset_loader import load_preset_module
9
+ from nepher.env_cfgs.registry import get_config_class
10
+
11
+
12
+ class UsdLoader(BaseLoader):
13
+ """Generic USD loader that returns category-specific configs."""
14
+
15
+ def load(self, env: Environment, scene_idx: int, category: str):
16
+ """Load USD scene config.
17
+
18
+ If the scene has a Python scene file, it will be loaded and used to configure
19
+ the USD environment. Otherwise, a basic config is created from the manifest.
20
+ """
21
+ if scene_idx >= len(env.scenes):
22
+ raise IndexError(f"Scene index {scene_idx} out of range")
23
+
24
+ scene = env.scenes[scene_idx]
25
+
26
+ if scene.scene:
27
+ scene_class = load_preset_module(scene.scene, base_path=env.cache_path)
28
+ cfg = scene_class()
29
+
30
+ if scene.usd and not cfg.usd_path:
31
+ cfg.usd_path = str(scene.usd)
32
+ if scene.omap_meta and not cfg.occupancy_map_yaml:
33
+ cfg.occupancy_map_yaml = str(scene.omap_meta)
34
+ if scene.name and not cfg.name:
35
+ cfg.name = scene.name
36
+ if scene.description and not cfg.description:
37
+ cfg.description = scene.description
38
+
39
+ return cfg
40
+
41
+ config_class = get_config_class(category=category, type="usd")
42
+ cfg = config_class()
43
+ cfg.usd_path = str(scene.usd) if scene.usd else None
44
+ cfg.occupancy_map_yaml = str(scene.omap_meta) if scene.omap_meta else None
45
+ cfg.name = scene.name
46
+ cfg.description = scene.description
47
+
48
+ return cfg
49
+
@@ -0,0 +1,8 @@
1
+ """Storage and cache management."""
2
+
3
+ from nepher.storage.cache import CacheManager, get_cache_manager
4
+ from nepher.storage.bundle import BundleManager
5
+ from nepher.storage.manifest import ManifestParser
6
+
7
+ __all__ = ["CacheManager", "get_cache_manager", "BundleManager", "ManifestParser"]
8
+
@@ -0,0 +1,78 @@
1
+ """
2
+ Bundle validation and extraction.
3
+ """
4
+
5
+ import zipfile
6
+ import shutil
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ from nepher.storage.manifest import ManifestParser
10
+ from nepher.core import Environment
11
+
12
+
13
+ class BundleManager:
14
+ """Manages environment bundle operations."""
15
+
16
+ @staticmethod
17
+ def extract_bundle(zip_path: Path, dest_dir: Path) -> Environment:
18
+ """
19
+ Extract and validate bundle.
20
+
21
+ Args:
22
+ zip_path: Path to bundle ZIP file
23
+ dest_dir: Destination directory for extraction
24
+
25
+ Returns:
26
+ Environment object from manifest
27
+ """
28
+ dest_dir.mkdir(parents=True, exist_ok=True)
29
+
30
+ try:
31
+ with zipfile.ZipFile(zip_path, "r") as zip_ref:
32
+ zip_ref.extractall(dest_dir)
33
+ except zipfile.BadZipFile as e:
34
+ raise ValueError(f"Invalid ZIP file: {zip_path}") from e
35
+ except (PermissionError, OSError) as e:
36
+ raise RuntimeError(f"Cannot extract bundle to {dest_dir}: {e}") from e
37
+
38
+ manifest_path = dest_dir / "manifest.yaml"
39
+ if not manifest_path.exists():
40
+ raise ValueError(f"Manifest not found in bundle: {manifest_path}")
41
+
42
+ env = ManifestParser.parse(manifest_path)
43
+
44
+ for scene in env.scenes:
45
+ if scene.usd:
46
+ scene.usd = dest_dir / scene.usd
47
+ if scene.omap_meta:
48
+ scene.omap_meta = dest_dir / scene.omap_meta
49
+
50
+ env.cache_path = dest_dir
51
+
52
+ return env
53
+
54
+ @staticmethod
55
+ def validate_bundle(bundle_path: Path) -> bool:
56
+ """
57
+ Validate bundle structure.
58
+
59
+ Args:
60
+ bundle_path: Path to bundle (ZIP file or directory)
61
+
62
+ Returns:
63
+ True if valid, False otherwise
64
+ """
65
+ try:
66
+ if bundle_path.is_dir():
67
+ manifest_path = bundle_path / "manifest.yaml"
68
+ return manifest_path.exists()
69
+
70
+ if bundle_path.suffix.lower() == ".zip":
71
+ with zipfile.ZipFile(bundle_path, "r") as zip_ref:
72
+ namelist = zip_ref.namelist()
73
+ return "manifest.yaml" in namelist
74
+
75
+ return False
76
+ except Exception:
77
+ return False
78
+
@@ -0,0 +1,145 @@
1
+ """
2
+ Cache management for downloaded environments.
3
+
4
+ Handles user-configurable cache directories and cache operations.
5
+ """
6
+
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import List, Optional, Dict, Any
10
+ from nepher.config import get_config
11
+
12
+
13
+ class CacheManager:
14
+ """Manages local cache for environments."""
15
+
16
+ def __init__(self, cache_dir: Optional[Path] = None, category: Optional[str] = None):
17
+ """
18
+ Initialize cache manager.
19
+
20
+ Args:
21
+ cache_dir: Override cache directory (from CLI flag)
22
+ category: Category for category-specific cache
23
+ """
24
+ config = get_config()
25
+ self.cache_dir = cache_dir or config.get_cache_dir(category=category)
26
+ self.category = category
27
+
28
+ def get_env_cache_path(self, env_id: str) -> Path:
29
+ """Get cache path for a specific environment."""
30
+ return self.cache_dir / env_id
31
+
32
+ def is_cached(self, env_id: str) -> bool:
33
+ """Check if environment is cached."""
34
+ cache_path = self.get_env_cache_path(env_id)
35
+ return cache_path.exists() and (cache_path / "manifest.yaml").exists()
36
+
37
+ def list_cached(self) -> List[str]:
38
+ """
39
+ List all cached environment IDs.
40
+
41
+ Returns:
42
+ List of cached environment ID strings
43
+ """
44
+ if not self.cache_dir.exists():
45
+ return []
46
+
47
+ cached = []
48
+ try:
49
+ for item in self.cache_dir.iterdir():
50
+ if item.is_dir() and (item / "manifest.yaml").exists():
51
+ cached.append(item.name)
52
+ except (PermissionError, OSError) as e:
53
+ raise RuntimeError(f"Cannot access cache directory {self.cache_dir}: {e}") from e
54
+
55
+ return cached
56
+
57
+ def clear_cache(self, env_id: Optional[str] = None):
58
+ """
59
+ Clear cache.
60
+
61
+ Args:
62
+ env_id: Specific environment ID to clear, or None to clear all
63
+
64
+ Raises:
65
+ RuntimeError: If cache directory cannot be accessed
66
+ """
67
+ try:
68
+ if env_id:
69
+ cache_path = self.get_env_cache_path(env_id)
70
+ if cache_path.exists():
71
+ shutil.rmtree(cache_path)
72
+ else:
73
+ if self.cache_dir.exists():
74
+ shutil.rmtree(self.cache_dir)
75
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
76
+ except (PermissionError, OSError) as e:
77
+ raise RuntimeError(f"Cannot clear cache: {e}") from e
78
+
79
+ def get_cache_info(self) -> Dict[str, Any]:
80
+ """Get cache statistics."""
81
+ if not self.cache_dir.exists():
82
+ return {
83
+ "cache_dir": str(self.cache_dir),
84
+ "total_size": 0,
85
+ "env_count": 0,
86
+ "environments": [],
87
+ }
88
+
89
+ envs = self.list_cached()
90
+ total_size = 0
91
+ env_sizes = {}
92
+
93
+ for env_id in envs:
94
+ try:
95
+ env_path = self.get_env_cache_path(env_id)
96
+ size = sum(f.stat().st_size for f in env_path.rglob("*") if f.is_file())
97
+ env_sizes[env_id] = size
98
+ total_size += size
99
+ except (PermissionError, OSError):
100
+ continue
101
+
102
+ return {
103
+ "cache_dir": str(self.cache_dir),
104
+ "total_size": total_size,
105
+ "env_count": len(envs),
106
+ "environments": [{"id": eid, "size": env_sizes[eid]} for eid in envs],
107
+ }
108
+
109
+ def migrate_cache(self, new_cache_dir: Path):
110
+ """
111
+ Migrate cache to new location.
112
+
113
+ Args:
114
+ new_cache_dir: New cache directory path
115
+
116
+ Raises:
117
+ RuntimeError: If migration fails
118
+ """
119
+ if not self.cache_dir.exists():
120
+ return
121
+
122
+ try:
123
+ new_cache_dir.mkdir(parents=True, exist_ok=True)
124
+
125
+ for env_id in self.list_cached():
126
+ old_path = self.get_env_cache_path(env_id)
127
+ new_path = new_cache_dir / env_id
128
+ shutil.move(str(old_path), str(new_path))
129
+ except (PermissionError, OSError, shutil.Error) as e:
130
+ raise RuntimeError(f"Cannot migrate cache to {new_cache_dir}: {e}") from e
131
+
132
+
133
+ # Global cache manager instance
134
+ _cache_manager_instance: Optional[CacheManager] = None
135
+
136
+
137
+ def get_cache_manager(
138
+ cache_dir: Optional[Path] = None, category: Optional[str] = None
139
+ ) -> CacheManager:
140
+ """Get global cache manager instance."""
141
+ global _cache_manager_instance
142
+ if _cache_manager_instance is None:
143
+ _cache_manager_instance = CacheManager(cache_dir=cache_dir, category=category)
144
+ return _cache_manager_instance
145
+
@@ -0,0 +1,80 @@
1
+ """
2
+ Manifest parsing for environment bundles.
3
+ """
4
+
5
+ import yaml
6
+ from pathlib import Path
7
+ from typing import Dict, Any, List, Optional
8
+ from nepher.core import Environment, Scene
9
+
10
+
11
+ class ManifestParser:
12
+ """Parser for environment manifest files."""
13
+
14
+ @staticmethod
15
+ def parse(manifest_path: Path) -> Environment:
16
+ """
17
+ Parse manifest YAML file.
18
+
19
+ Args:
20
+ manifest_path: Path to manifest.yaml
21
+
22
+ Returns:
23
+ Environment object
24
+ """
25
+ try:
26
+ with open(manifest_path, "r", encoding="utf-8") as f:
27
+ data = yaml.safe_load(f)
28
+ except FileNotFoundError:
29
+ raise FileNotFoundError(f"Manifest file not found: {manifest_path}")
30
+ except yaml.YAMLError as e:
31
+ raise ValueError(f"Invalid YAML in manifest file {manifest_path}: {e}") from e
32
+
33
+ env_id = data.get("id", manifest_path.parent.name)
34
+ name = data.get("name", env_id)
35
+ description = data.get("description")
36
+ category = data.get("category", "navigation")
37
+ version = data.get("version")
38
+ author = data.get("author")
39
+ benchmark = data.get("benchmark", False)
40
+ metadata = data.get("metadata", {})
41
+
42
+ env_type = "preset" if data.get("preset_scenes") else "usd"
43
+
44
+ scenes = []
45
+ for scene_data in data.get("scenes", []):
46
+ scene = Scene(
47
+ name=scene_data.get("scene_id") or scene_data.get("name", ""),
48
+ description=scene_data.get("description"),
49
+ usd=Path(scene_data["usd"]) if scene_data.get("usd") else None,
50
+ scene=scene_data.get("scene"), # Python scene file for USD scenes
51
+ omap_meta=Path(scene_data["omap_meta"]) if scene_data.get("omap_meta") else None,
52
+ metadata=scene_data.get("metadata"),
53
+ )
54
+ scenes.append(scene)
55
+
56
+ preset_scenes = []
57
+ for preset_data in data.get("preset_scenes", []):
58
+ scene = Scene(
59
+ name=preset_data.get("scene_id") or preset_data.get("name", ""),
60
+ description=preset_data.get("description"),
61
+ preset=preset_data.get("preset"),
62
+ metadata=preset_data.get("metadata"),
63
+ )
64
+ preset_scenes.append(scene)
65
+
66
+ return Environment(
67
+ id=env_id,
68
+ name=name,
69
+ description=description,
70
+ category=category,
71
+ type=env_type,
72
+ version=version,
73
+ author=author,
74
+ scenes=scenes,
75
+ preset_scenes=preset_scenes,
76
+ benchmark=benchmark,
77
+ metadata=metadata,
78
+ cache_path=manifest_path.parent,
79
+ )
80
+
@@ -0,0 +1,12 @@
1
+ """Utility functions for the Nepher platform."""
2
+
3
+ from nepher.utils.free_zone_finder import FreeZone, Rectangle, find_free_zones
4
+ from nepher.utils.fast_spawn_sampler import FastSpawnSampler, OccupancyMapConfig
5
+
6
+ __all__ = [
7
+ "FreeZone",
8
+ "Rectangle",
9
+ "find_free_zones",
10
+ "FastSpawnSampler",
11
+ "OccupancyMapConfig",
12
+ ]