|
| 1 | +(querylist-filtering)= |
| 2 | + |
| 3 | +# QueryList Filtering |
| 4 | + |
| 5 | +libtmux uses `QueryList` to enable Django-style filtering on tmux objects. |
| 6 | +Every collection (`server.sessions`, `session.windows`, `window.panes`) returns |
| 7 | +a `QueryList`, letting you filter sessions, windows, and panes with a fluent, |
| 8 | +chainable API. |
| 9 | + |
| 10 | +## Basic Filtering |
| 11 | + |
| 12 | +The `filter()` method accepts keyword arguments with optional lookup suffixes: |
| 13 | + |
| 14 | +```python |
| 15 | +>>> server.sessions # doctest: +ELLIPSIS |
| 16 | +[Session($... ...)] |
| 17 | +``` |
| 18 | + |
| 19 | +### Exact Match |
| 20 | + |
| 21 | +The default lookup is `exact`: |
| 22 | + |
| 23 | +```python |
| 24 | +>>> # These are equivalent |
| 25 | +>>> server.sessions.filter(session_name=session.session_name) # doctest: +ELLIPSIS |
| 26 | +[Session($... ...)] |
| 27 | +>>> server.sessions.filter(session_name__exact=session.session_name) # doctest: +ELLIPSIS |
| 28 | +[Session($... ...)] |
| 29 | +``` |
| 30 | + |
| 31 | +### Contains and Startswith |
| 32 | + |
| 33 | +Use suffixes for partial matching: |
| 34 | + |
| 35 | +```python |
| 36 | +>>> # Create windows for this example |
| 37 | +>>> w1 = session.new_window(window_name="api-server") |
| 38 | +>>> w2 = session.new_window(window_name="api-worker") |
| 39 | +>>> w3 = session.new_window(window_name="web-frontend") |
| 40 | + |
| 41 | +>>> # Windows containing 'api' |
| 42 | +>>> api_windows = session.windows.filter(window_name__contains='api') |
| 43 | +>>> len(api_windows) >= 2 |
| 44 | +True |
| 45 | + |
| 46 | +>>> # Windows starting with 'web' |
| 47 | +>>> web_windows = session.windows.filter(window_name__startswith='web') |
| 48 | +>>> len(web_windows) >= 1 |
| 49 | +True |
| 50 | + |
| 51 | +>>> # Clean up |
| 52 | +>>> w1.kill() |
| 53 | +>>> w2.kill() |
| 54 | +>>> w3.kill() |
| 55 | +``` |
| 56 | + |
| 57 | +## Available Lookups |
| 58 | + |
| 59 | +| Lookup | Description | |
| 60 | +|--------|-------------| |
| 61 | +| `exact` | Exact match (default) | |
| 62 | +| `iexact` | Case-insensitive exact match | |
| 63 | +| `contains` | Substring match | |
| 64 | +| `icontains` | Case-insensitive substring | |
| 65 | +| `startswith` | Prefix match | |
| 66 | +| `istartswith` | Case-insensitive prefix | |
| 67 | +| `endswith` | Suffix match | |
| 68 | +| `iendswith` | Case-insensitive suffix | |
| 69 | +| `in` | Value in list | |
| 70 | +| `nin` | Value not in list | |
| 71 | +| `regex` | Regular expression match | |
| 72 | +| `iregex` | Case-insensitive regex | |
| 73 | + |
| 74 | +## Getting a Single Item |
| 75 | + |
| 76 | +Use `get()` to retrieve exactly one matching item: |
| 77 | + |
| 78 | +```python |
| 79 | +>>> window = session.windows.get(window_id=session.active_window.window_id) |
| 80 | +>>> window # doctest: +ELLIPSIS |
| 81 | +Window(@... ..., Session($... ...)) |
| 82 | +``` |
| 83 | + |
| 84 | +If no match or multiple matches are found, `get()` raises an exception: |
| 85 | + |
| 86 | +- `ObjectDoesNotExist` - no matching object found |
| 87 | +- `MultipleObjectsReturned` - more than one object matches |
| 88 | + |
| 89 | +You can provide a default value to avoid the exception: |
| 90 | + |
| 91 | +```python |
| 92 | +>>> session.windows.get(window_name="nonexistent", default=None) is None |
| 93 | +True |
| 94 | +``` |
| 95 | + |
| 96 | +## Chaining Filters |
| 97 | + |
| 98 | +Filters can be chained for complex queries: |
| 99 | + |
| 100 | +```python |
| 101 | +>>> # Create windows for this example |
| 102 | +>>> w1 = session.new_window(window_name="feature-login") |
| 103 | +>>> w2 = session.new_window(window_name="feature-signup") |
| 104 | +>>> w3 = session.new_window(window_name="bugfix-typo") |
| 105 | + |
| 106 | +>>> # Multiple conditions in one filter (AND) |
| 107 | +>>> session.windows.filter( |
| 108 | +... window_name__startswith='feature', |
| 109 | +... window_name__endswith='signup' |
| 110 | +... ) # doctest: +ELLIPSIS |
| 111 | +[Window(@... ...:feature-signup, Session($... ...))] |
| 112 | + |
| 113 | +>>> # Chained filters (also AND) |
| 114 | +>>> session.windows.filter( |
| 115 | +... window_name__contains='feature' |
| 116 | +... ).filter( |
| 117 | +... window_name__contains='login' |
| 118 | +... ) # doctest: +ELLIPSIS |
| 119 | +[Window(@... ...:feature-login, Session($... ...))] |
| 120 | + |
| 121 | +>>> # Clean up |
| 122 | +>>> w1.kill() |
| 123 | +>>> w2.kill() |
| 124 | +>>> w3.kill() |
| 125 | +``` |
| 126 | + |
| 127 | +## Case-Insensitive Filtering |
| 128 | + |
| 129 | +Use `i` prefix variants for case-insensitive matching: |
| 130 | + |
| 131 | +```python |
| 132 | +>>> # Create windows with mixed case |
| 133 | +>>> w1 = session.new_window(window_name="MyApp-Server") |
| 134 | +>>> w2 = session.new_window(window_name="myapp-worker") |
| 135 | + |
| 136 | +>>> # Case-insensitive contains |
| 137 | +>>> myapp_windows = session.windows.filter(window_name__icontains='MYAPP') |
| 138 | +>>> len(myapp_windows) >= 2 |
| 139 | +True |
| 140 | + |
| 141 | +>>> # Case-insensitive startswith |
| 142 | +>>> session.windows.filter(window_name__istartswith='myapp') # doctest: +ELLIPSIS |
| 143 | +[Window(@... ...:MyApp-Server, Session($... ...)), Window(@... ...:myapp-worker, Session($... ...))] |
| 144 | + |
| 145 | +>>> # Clean up |
| 146 | +>>> w1.kill() |
| 147 | +>>> w2.kill() |
| 148 | +``` |
| 149 | + |
| 150 | +## Regex Filtering |
| 151 | + |
| 152 | +For complex patterns, use regex lookups: |
| 153 | + |
| 154 | +```python |
| 155 | +>>> # Create windows with version-like names |
| 156 | +>>> w1 = session.new_window(window_name="app-v1.0") |
| 157 | +>>> w2 = session.new_window(window_name="app-v2.0") |
| 158 | +>>> w3 = session.new_window(window_name="app-beta") |
| 159 | + |
| 160 | +>>> # Match version pattern |
| 161 | +>>> versioned = session.windows.filter(window_name__regex=r'v\d+\.\d+$') |
| 162 | +>>> len(versioned) >= 2 |
| 163 | +True |
| 164 | + |
| 165 | +>>> # Case-insensitive regex |
| 166 | +>>> session.windows.filter(window_name__iregex=r'BETA') # doctest: +ELLIPSIS |
| 167 | +[Window(@... ...:app-beta, Session($... ...))] |
| 168 | + |
| 169 | +>>> # Clean up |
| 170 | +>>> w1.kill() |
| 171 | +>>> w2.kill() |
| 172 | +>>> w3.kill() |
| 173 | +``` |
| 174 | + |
| 175 | +## Filtering by List Membership |
| 176 | + |
| 177 | +Use `in` and `nin` (not in) for list-based filtering: |
| 178 | + |
| 179 | +```python |
| 180 | +>>> # Create test windows |
| 181 | +>>> w1 = session.new_window(window_name="dev") |
| 182 | +>>> w2 = session.new_window(window_name="staging") |
| 183 | +>>> w3 = session.new_window(window_name="prod") |
| 184 | + |
| 185 | +>>> # Filter windows in a list of names |
| 186 | +>>> target_envs = ["dev", "prod"] |
| 187 | +>>> session.windows.filter(window_name__in=target_envs) # doctest: +ELLIPSIS |
| 188 | +[Window(@... ...:dev, Session($... ...)), Window(@... ...:prod, Session($... ...))] |
| 189 | + |
| 190 | +>>> # Filter windows NOT in a list |
| 191 | +>>> non_prod = session.windows.filter(window_name__nin=["prod"]) |
| 192 | +>>> any(w.window_name == "prod" for w in non_prod) |
| 193 | +False |
| 194 | + |
| 195 | +>>> # Clean up |
| 196 | +>>> w1.kill() |
| 197 | +>>> w2.kill() |
| 198 | +>>> w3.kill() |
| 199 | +``` |
| 200 | + |
| 201 | +## Filtering Across the Hierarchy |
| 202 | + |
| 203 | +Filter at any level of the tmux hierarchy: |
| 204 | + |
| 205 | +```python |
| 206 | +>>> # All panes across all windows in the server |
| 207 | +>>> server.panes # doctest: +ELLIPSIS |
| 208 | +[Pane(%... Window(@... ..., Session($... ...)))] |
| 209 | + |
| 210 | +>>> # Filter panes by their window's name |
| 211 | +>>> pane = session.active_pane |
| 212 | +>>> pane # doctest: +ELLIPSIS |
| 213 | +Pane(%... Window(@... ..., Session($... ...))) |
| 214 | +``` |
| 215 | + |
| 216 | +## Real-World Examples |
| 217 | + |
| 218 | +### Find all editor windows |
| 219 | + |
| 220 | +```python |
| 221 | +>>> # Create sample windows |
| 222 | +>>> w1 = session.new_window(window_name="vim-main") |
| 223 | +>>> w2 = session.new_window(window_name="nvim-config") |
| 224 | +>>> w3 = session.new_window(window_name="shell") |
| 225 | + |
| 226 | +>>> # Find vim/nvim windows |
| 227 | +>>> editors = session.windows.filter(window_name__iregex=r'n?vim') |
| 228 | +>>> len(editors) >= 2 |
| 229 | +True |
| 230 | + |
| 231 | +>>> # Clean up |
| 232 | +>>> w1.kill() |
| 233 | +>>> w2.kill() |
| 234 | +>>> w3.kill() |
| 235 | +``` |
| 236 | + |
| 237 | +### Find windows by naming convention |
| 238 | + |
| 239 | +```python |
| 240 | +>>> # Create windows following a naming convention |
| 241 | +>>> w1 = session.new_window(window_name="project:frontend") |
| 242 | +>>> w2 = session.new_window(window_name="project:backend") |
| 243 | +>>> w3 = session.new_window(window_name="logs") |
| 244 | + |
| 245 | +>>> # Find all project windows |
| 246 | +>>> project_windows = session.windows.filter(window_name__startswith='project:') |
| 247 | +>>> len(project_windows) >= 2 |
| 248 | +True |
| 249 | + |
| 250 | +>>> # Get specific project window |
| 251 | +>>> backend = session.windows.get(window_name='project:backend') |
| 252 | +>>> backend.window_name |
| 253 | +'project:backend' |
| 254 | + |
| 255 | +>>> # Clean up |
| 256 | +>>> w1.kill() |
| 257 | +>>> w2.kill() |
| 258 | +>>> w3.kill() |
| 259 | +``` |
| 260 | + |
| 261 | +## API Reference |
| 262 | + |
| 263 | +See {class}`~libtmux._internal.query_list.QueryList` for the complete API. |
0 commit comments