# Asset Search Library (`simready.search`)

Python library for loading a SimReady project's asset metadata cache and performing filter-based searches.

## Initialization

Construct an instance of the `AssetLibrary` class.
Use one or more of these async methods to add a search source:
- `add_cache_source` for local project_config.toml
- `add_s3_source` for S3 project_config.toml
- `add_service_source` for web-hosted search service
- `add_indexed_source` for either local or S3 directories without caches
  - Only the `SearchFilterPathContains` will work to find files indexed this way, since the indexing will discover only file paths, no metadata.

```python
import asyncio
from simready.search import (
    AssetLibrary,
    AssetLibraryNetworkError,
    SearchFilterArbitraryDictValue,
    SearchFilterClass,
    SearchFilterCountry,
    SearchFilterFeature,
    SearchFilterHeight,
    SearchFilterPathContains,
    SearchFilterScenePOI,
)

async def main():
    # Option 1: initialize with project_config.toml referring to a workspace_cache.json
    project_config_path = "d:/simready_foundations/sample_content/project_config.toml"
    asset_library = AssetLibrary()
    await asset_library.add_cache_source(project_config_path)

    # Option 2: no project_config, just index some directories
    asset_library = AssetLibrary()
    await asset_library.add_indexed_source("d:/some_content/", "local")
    await asset_library.add_indexed_source("d:/some_more_content/", "local")

    # Option 3: strict network error handling
    asset_library = AssetLibrary(raise_on_network_error=True)

asyncio.run(main())
```

`AssetLibrary` constructor args:

- `log_func` (default `None`): callback for library log messages.
- `raise_on_network_error` (default `False`): when `True`, raises `AssetLibraryNetworkError` for service/S3 failures.

When `raise_on_network_error=False`, network failures are logged and the library returns safe empty/partial results.

## Searching

`AssetLibrary.search(...)` takes 4 keyword arguments, each an optional list of search filters:

- **include_all**: include an asset if it passes **all** of these filters
- **include_any**: include an asset if it passes **any one** of these filters
- **exclude_all**: exclude an asset if it passes **all** of these filters
- **exclude_any**: exclude an asset if it passes **any one** of these filters

The return value is a list of `AssetData` objects for assets which were included and not excluded.

## Examples

### Example 1: find really tall props which are neither structures nor trees

```python
matches = asset_library.search(
    include_all=[SearchFilterClass("prop"), SearchFilterHeight(minimum=25)],
    exclude_any=[SearchFilterPathContains("structures"), SearchFilterPathContains("vegetation")],
)
for asset_data in matches:
    print(asset_data.asset_path)
```

### Example 2: find red American signs

```python
matches = asset_library.search(
    include_all=[
        SearchFilterArbitraryDictValue(["factory", "sign", "panel_color"], "red"),
        SearchFilterCountry("usa"),
    ]
)
for asset_data in matches:
    print(asset_data.asset_path)
```

### Example 3: find people (or people-height objects), except for Chinese signs

```python
matches = asset_library.search(
    include_any=[SearchFilterClass("character"), SearchFilterHeight(minimum=1.55, maximum=1.95)],
    exclude_all=[SearchFilterCountry("china"), SearchFilterClass("sign")],
)
for asset_data in matches:
    print(asset_data.asset_path)
```

## Querying all values for a filter

### Example 4: query all possible values for Scene Point Of Interest (POI) tags

```python
poi_tags = asset_library.get_all_values(SearchFilterScenePOI)
print(sorted(poi_tags))
```

### Example 4a: find scenes that contain both parking and speed bumps

```python
matches = asset_library.search(
    include_all=[SearchFilterScenePOI("parking"), SearchFilterScenePOI("speedbump")],
)
for asset_data in matches:
    print(asset_data.asset_path)
```

## Feature validation filter

### Example 5: find assets validated to have feature `FET_005` with version `1.0.0`

```python
matches = asset_library.search(
    include_all=[SearchFilterFeature("FET_005", version="1.0.0")],  # version is optional
)
for asset_data in matches:
    print(asset_data.asset_path)
```
