{"id":"08026cc0-6abe-4473-91af-429825de2a01","shortId":"NPWC2v","kind":"skill","title":"python-services","tagline":">-","description":"# Python Services & CLI\n\n## Modern Tooling\n\n| Tool | Replaces | Purpose |\n|------|----------|---------|\n| **uv** | pip, virtualenv, pyenv, pipx | Package/dependency management |\n| **ruff** | flake8, black, isort | Linting + formatting |\n| **ty** | mypy, pyright | Type checking (Astral, faster) |\n\n- `uv init --package myproject` for distributable packages, `uv init` for apps\n- `uv add <pkg>`, `uv add --group dev <pkg>`, never edit pyproject.toml deps manually\n- `uv run <cmd>` instead of activating venvs -- auto-activates the venv without explicit activation\n- `uv add --upgrade <pkg>` to upgrade a single package without touching others\n- `uv tree --outdated` to preview what would be upgraded before committing\n- `uv.lock` goes in version control\n- Use `[dependency-groups]` (PEP 735) for dev/test/docs, not `[project.optional-dependencies]`\n- PEP 723 inline metadata for standalone scripts with deps\n- `ruff check --fix . && ruff format .` for lint+format in one pass\n\n**Standard project layout:**\n```\nsrc/mypackage/\n    __init__.py\n    main.py\n    services/\n    models/\ntests/\n    conftest.py\n    test_main.py\npyproject.toml\n```\n\nSee [cli-tools.md](./references/cli-tools.md) for Click patterns, argparse, and CLI project layout.\n\n## Parallelism\n\n| Workload | Approach |\n|----------|----------|\n| Many concurrent I/O calls | `asyncio` (gather, create_task) |\n| CPU-bound computation | `multiprocessing.Pool` or `concurrent.futures.ProcessPoolExecutor` |\n| Mixed I/O + CPU | `asyncio.to_thread()` to offload blocking work |\n| Simple scripts, few connections | Stay synchronous |\n\n### Sync vs Async Decision\n\n**Use async (asyncio) when:**\n- I/O-bound work has multiple concurrent operations (HTTP calls, database queries, file I/O happening in parallel)\n- WebSocket servers or long-lived connections require it\n- The framework requires it (FastAPI async endpoints, aiohttp)\n\n**Stay synchronous when:**\n- Work is CPU-bound (computation, data transformation) -- async adds nothing, use multiprocessing instead\n- Building simple scripts and CLI tools with sequential I/O\n- All I/O is sequential anyway (one DB query, process result, one API call)\n- The team lacks async debugging experience (asyncio stack traces are harder to read)\n\n**Rule of thumb:** if the code is not waiting on multiple I/O operations concurrently, sync is simpler and correct. Do not add async complexity for a single sequential pipeline.\n\n**Key rule:** Stay fully sync or fully async within a call path.\n\n**asyncio patterns:**\n- `asyncio.gather(*tasks)` for concurrent I/O -- use `return_exceptions=True` for partial failure tolerance\n- `asyncio.TaskGroup` (3.11+) for structured concurrency -- automatic cancellation of sibling tasks on failure; prefer over `gather` when all tasks must succeed\n- `asyncio.Semaphore(n)` to limit concurrency (rate limiting external APIs)\n- `asyncio.wait_for(coro, timeout=N)` for timeouts\n- `asyncio.Queue` for producer-consumer\n- `asyncio.Lock` when coroutines share mutable state\n- Never block the event loop: `asyncio.to_thread(sync_fn)` for sync libs, `aiohttp`/`httpx.AsyncClient` for HTTP\n- Handle `CancelledError` -- always re-raise after cleanup\n- Async generators (`async for`) for streaming/pagination\n\n**multiprocessing** for CPU-bound:\n```python\nfrom concurrent.futures import ProcessPoolExecutor\nwith ProcessPoolExecutor(max_workers=4) as pool:\n    results = list(pool.map(cpu_task, items))\n```\n\nSee [fastapi.md](./references/fastapi.md) for project structure, lifespan, config, DI, async DB, and repository pattern.\n\n## Background Jobs\n\n- Return job ID immediately, process async. Client polls `/jobs/{id}` for status\n- **Celery**: `@app.task(bind=True, max_retries=3, autoretry_for=(ConnectionError,))` -- exponential backoff: `raise self.retry(countdown=2**self.request.retries * 60)`\n- **Alternatives**: Dramatiq (modern Celery), RQ (simple Redis), cloud-native (SQS+Lambda, Cloud Tasks)\n- **Idempotency is mandatory** -- tasks may retry. Use idempotency keys for external calls, check-before-write, upsert patterns\n- Dead letter queue for permanently failed tasks after max retries\n- Task workflows: `chain(a.s(), b.s())` for sequential, `group(...)` for parallel, `chord(group, callback)` for fan-out/fan-in\n\n## Resilience\n\n**Retries with tenacity:**\n```python\nfrom tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type\n\n@retry(\n    retry=retry_if_exception_type((ConnectionError, TimeoutError)),\n    stop=stop_after_attempt(5) | stop_after_delay(60),\n    wait=wait_exponential_jitter(initial=1, max=30),\n    before_sleep=log_retry_attempt,\n)\ndef call_api(url: str) -> dict: ...\n```\n\n- Retry only transient errors: network, 429/502/503/504. Never retry 4xx (except 429), auth errors, validation errors\n- Every network call needs a timeout\n- `@fail_safe(default=[])` decorator for non-critical paths -- return cached/default on failure\n- `functools.lru_cache(maxsize=N)` for pure-function memoization; `functools.cache` (unbounded) for small domains\n- Stack decorators: `@traced @with_timeout(30) @retry(...)` -- separate infra from business logic\n\n**Connection pooling** is mandatory for production: reuse `httpx.AsyncClient()` across requests, configure SQLAlchemy `pool_size`/`max_overflow`, use `aiohttp.TCPConnector(limit=N)`.\n\n## Production Resilience\n\n- **Fail-fast config validation**: use a Pydantic `BaseSettings` model with `model_validator` to parse and validate all environment variables at startup. If invalid, crash before serving traffic. Never discover a missing secret on the first request that needs it.\n- **Health endpoints**: expose `/health` (shallow liveness -- returns 200 if the process responds) and `/ready` (deep readiness -- verifies database, Redis, and critical dependencies are reachable). Load balancers route traffic based on `/ready`; orchestrators restart based on `/health`.\n\n## Observability\n\n- **structlog** for JSON structured logging. Configure once at startup with `JSONRenderer`, `TimeStamper`, `merge_contextvars`\n- **Correlation IDs** -- generate at ingress (`X-Correlation-ID` header), bind to `contextvars`, propagate to downstream calls\n- **Log levels**: DEBUG=diagnostics, INFO=operations, WARNING=anomalies handled, ERROR=failures needing attention. Never log expected behavior at ERROR\n- **Prometheus metrics** -- track latency (Histogram), traffic (Counter), errors (Counter), saturation (Gauge). Keep label cardinality bounded (no user IDs)\n- **OpenTelemetry** for distributed tracing across services\n- **Never mutate `LogRecord` attributes from a `Formatter`.** A custom `logging.Formatter.format()` that rewrites `record.name` (or any record attribute) in place leaks to every other handler attached to the same logger and to pytest `caplog`. `Logger.callHandlers` passes the same `LogRecord` object to each handler — whichever formats first wins the mutation, and downstream handlers and test filters see the modified state. Tests filtering by full logger name (`if r.name == \"src.services.foo\"`) then silently miss; routing handlers doing `LOGGER_TO_MODEL.get(record.name)` fall through to defaults. Use a `logging.Filter` that adds a non-mutating attribute (`record.short_name`) and reference it in the format string as `%(short_name)s`, or override `formatMessage` instead of `format`. `try`/`finally` restore works for synchronous handler chains but is fragile under async handlers that interleave.\n\n## Discipline\n\n- Simplicity first -- every change as simple as possible, impact minimal code\n- Only touch what's necessary -- avoid introducing unrelated changes\n- No hacky workarounds -- if a fix feels wrong, step back and implement the clean solution\n- Before adding a new abstraction, verify it appears in 3+ places. If not, inline it.\n- Verify: see Verify section below -- pass all checks with zero warnings before declaring done\n- Coverage target: 80%+ (`uv run pytest --cov --cov-report=html`)\n\n## Testing Patterns\n\n- **pytest flags**: `--lf` (last failed), `-x` (stop on first failure), `-k \"pattern\"` (filter), `--pdb` (debugger on failure)\n- **Fixtures**: use `conftest.py` for shared fixtures. Scope wisely: `@pytest.fixture(scope=\"session\")` for expensive setup (DB connections), `scope=\"function\"` (default) for test isolation\n- **`tmp_path`**: built-in fixture for temp files -- no manual cleanup needed\n- **Parametrize with IDs**: `@pytest.mark.parametrize(\"input,expected\", [...], ids=[\"empty\", \"single\", \"overflow\"])` for readable test names\n- **Mock discipline**: always `autospec=True` on mocks to catch API drift. `assert_awaited_once()` for async mocks.\n- **Test markers**: register in `pyproject.toml` under `[tool.pytest.ini_options]` with `markers = [\"slow\", \"integration\"]`. Run fast tests with `-m \"not slow\"`.\n- **Protocol duck typing**: use `class Renderable(Protocol)` for structural typing at service boundaries -- enables testing with plain objects instead of mocks\n- **Context managers**: `@contextmanager` for connection/transaction lifecycle. Always implement `__exit__` cleanup.\n\n## Error Handling\n\n- Validate inputs at boundaries before expensive ops. Report all errors at once when possible\n- Use specific exceptions: `ValueError`, `TypeError`, `KeyError`, not bare `Exception`\n- `raise ServiceError(\"upload failed\") from e` -- always chain to preserve debug trail\n- Convert external data to domain types (enums, Pydantic models) at system boundaries\n- Batch processing: `BatchResult(succeeded={}, failed={})` -- don't let one item abort the batch\n- Pydantic `BaseModel` with `field_validator` for complex input validation\n\n## Migrations\n\n- Separate schema and data migrations -- data backfills in their own migration file\n- Renames/removals use expand-contract: add new column → backfill → switch reads → drop old (see `ia-postgresql` skill for the full pattern)\n- Never edit a migration that has already run in a shared environment\n- Alembic: use `--autogenerate` as a starting point, always review generated SQL before committing\n- Test migrations against production-sized data -- a migration that takes 2ms on dev can lock a table for minutes in production\n\n## API Design\n\n- **Contract-first**: define Pydantic `BaseModel` request/response schemas and FastAPI `response_model` before writing endpoint logic. The schema is the contract -- implementation follows. Generate OpenAPI docs from these models automatically.\n- **Hyrum's Law awareness**: every observable response field, ordering, or timing becomes a dependency for callers. Use explicit `response_model` and `model_config = ConfigDict(extra=\"forbid\")` to control exactly what's serialized -- never return raw dicts or ORM objects from endpoints.\n- **Addition over modification**: add new optional fields (`field: str | None = None`) rather than changing or removing existing ones. Removing a Pydantic field from a response model breaks callers silently. Deprecate first (`Field(deprecated=True)`), remove in a later version.\n- **Consistent error structure**: all exceptions should produce the same envelope: `{\"error\": {\"code\": \"...\", \"message\": \"...\", \"details\": ...}}`. Register `@app.exception_handler` for `RequestValidationError`, `HTTPException`, and application-specific exceptions to normalize into one format. Callers build error handling once.\n- **Boundary validation via Pydantic**: validate at the endpoint/handler level with Pydantic models and FastAPI's automatic request parsing. Internal services and repositories trust that input was validated at entry -- no redundant validation scattered through business logic.\n- **Third-party responses are untrusted data**: validate shape and content of external API responses before using them in logic, rendering, or decision-making. A compromised or misbehaving service can return unexpected types, malicious content, or missing fields. Parse through a Pydantic model before use.\n\n## Verify\n\n- `uv run pytest` passes with zero failures\n- `uv run ruff check .` passes with zero warnings\n- `uv run ty check .` passes with zero errors\n- Coverage target: 80%+ (`uv run pytest --cov`)","tags":["python","services","skills","iliaal","agent-skills","ai-coding-assistant","ai-tools","claude-code"],"capabilities":["skill","source-iliaal","skill-python-services","topic-agent-skills","topic-ai-coding-assistant","topic-ai-tools","topic-claude-code","topic-skills"],"categories":["ai-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/iliaal/ai-skills/python-services","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add iliaal/ai-skills","source_repo":"https://github.com/iliaal/ai-skills","install_from":"skills.sh"}},"qualityScore":"0.456","qualityRationale":"deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 13 github stars · SKILL.md body (12,063 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:07:03.309Z","embedding":null,"createdAt":"2026-05-09T01:05:36.358Z","updatedAt":"2026-05-18T19:07:03.309Z","lastSeenAt":"2026-05-18T19:07:03.309Z","tsv":"'/fan-in':536 '/health':717,749 '/jobs':455 '/ready':727,744 '/references/cli-tools.md':141 '/references/fastapi.md':433 '1':578 '2':474 '200':721 '2ms':1302 '3':465,994 '3.11':332 '30':580,645 '4':422 '429':602 '429/502/503/504':597 '4xx':600 '5':568 '60':476,572 '723':107 '735':100 '80':1016,1568 'a.s':522 'abort':1219 'abstract':989 'across':660,823 'activ':58,62,67 'ad':986 'add':44,46,69,235,296,908,1249,1389 'addit':1386 'aiohttp':222,390 'aiohttp.tcpconnector':669 'alemb':1278 'alreadi':1272 'altern':477 'alway':396,1095,1156,1191,1285 'anomali':789 'anyway':253 'api':260,359,588,1102,1313,1509 'app':42 'app.exception':1440 'app.task':460 'appear':992 'applic':1447 'application-specif':1446 'approach':152 'argpars':145 'assert':1104 'astral':30 'async':185,188,220,234,265,297,311,402,404,440,452,945,1108 'asyncio':157,189,268,316 'asyncio.gather':318 'asyncio.lock':372 'asyncio.queue':367 'asyncio.semaphore':351 'asyncio.taskgroup':331 'asyncio.to':171,383 'asyncio.wait':360 'attach':849 'attempt':548,567,585 'attent':794 'attribut':828,841,913 'auth':603 'auto':61 'auto-activ':60 'autogener':1280 'automat':336,1344,1475 'autoretri':466 'autospec':1096 'avoid':966 'await':1105 'awar':1348 'b.s':523 'back':979 'backfil':1238,1252 'background':445 'backoff':470 'balanc':739 'bare':1183 'base':742,747 'basemodel':1223,1320 'baseset':682 'batch':1209,1221 'batchresult':1211 'becom':1356 'behavior':798 'bind':461,775 'black':21 'block':175,379 'bound':163,230,412,815 'boundari':1141,1165,1208,1460 'break':1412 'build':240,1456 'built':1069 'built-in':1068 'busi':650,1494 'cach':627 'cached/default':623 'call':156,198,261,314,502,587,609,781 'callback':531 'caller':1360,1413,1455 'cancel':337 'cancellederror':395 'caplog':857 'cardin':814 'catch':1101 'celeri':459,480 'chain':521,940,1192 'chang':953,969,1399 'check':29,116,504,1007,1553,1561 'check-before-writ':503 'chord':529 'class':1133 'clean':983 'cleanup':401,1077,1159 'cli':6,147,244 'cli-tools.md':140 'click':143 'client':453 'cloud':485,489 'cloud-nat':484 'code':280,960,1436 'column':1251 'commit':89,1290 'complex':298,1228 'compromis':1522 'comput':164,231 'concurr':154,195,288,321,335,355 'concurrent.futures':415 'concurrent.futures.processpoolexecutor':167 'config':438,677,1367 'configdict':1368 'configur':662,756 'conftest.py':136,1046 'connect':180,212,652,1059 'connection/transaction':1154 'connectionerror':468,562 'consist':1425 'consum':371 'content':1506,1531 'context':1150 'contextmanag':1152 'contextvar':764,777 'contract':1248,1316,1335 'contract-first':1315 'control':94,1372 'convert':1197 'coro':362 'coroutin':374 'correct':293 'correl':765,772 'countdown':473 'counter':807,809 'cov':1020,1022,1572 'cov-report':1021 'coverag':1014,1566 'cpu':162,170,229,411,428 'cpu-bound':161,228,410 'crash':698 'creat':159 'critic':620,734 'custom':833 'data':232,1199,1235,1237,1297,1502 'databas':199,731 'db':255,441,1058 'dead':509 'debug':266,784,1195 'debugg':1041 'decis':186,1519 'decision-mak':1518 'declar':1012 'decor':616,641 'deep':728 'def':586 'default':615,903,1062 'defin':1318 'delay':571 'dep':52,114 'depend':97,105,735,1358 'dependency-group':96 'deprec':1415,1418 'design':1314 'detail':1438 'dev':48,1304 'dev/test/docs':102 'di':439 'diagnost':785 'dict':591,1380 'disciplin':949,1094 'discov':703 'distribut':37,821 'doc':1340 'domain':639,1201 'done':1013 'downstream':780,874 'dramatiq':478 'drift':1103 'drop':1255 'duck':1130 'e':1190 'edit':50,1267 'empti':1086 'enabl':1142 'endpoint':221,715,1329,1385 'endpoint/handler':1467 'entri':1488 'enum':1203 'envelop':1434 'environ':692,1277 'error':595,604,606,791,800,808,1160,1171,1426,1435,1457,1565 'event':381 'everi':607,846,952,1349 'exact':1373 'except':325,554,560,601,1178,1184,1429,1449 'exist':1402 'exit':1158 'expand':1247 'expand-contract':1246 'expect':797,1084 'expens':1056,1167 'experi':267 'explicit':66,1362 'exponenti':469,550,575 'expos':716 'extern':358,501,1198,1508 'extra':1369 'fail':514,613,675,1031,1188,1213 'fail-fast':674 'failur':329,342,625,792,1036,1043,1549 'fall':900 'fan':534 'fan-out':533 'fast':676,1123 'fastapi':219,1324,1473 'fastapi.md':432 'faster':31 'feel':976 'field':1225,1352,1392,1393,1407,1417,1534 'file':201,1074,1243 'filter':878,884,1039 'final':934 'first':709,869,951,1035,1317,1416 'fix':117,975 'fixtur':1044,1049,1071 'flag':1028 'flake8':20 'fn':386 'follow':1337 'forbid':1370 'format':24,119,122,868,921,932,1454 'formatmessag':929 'formatt':831 'fragil':943 'framework':216 'full':886,1264 'fulli':307,310 'function':633,1061 'functools.cache':635 'functools.lru':626 'gather':158,345 'gaug':811 'generat':403,767,1287,1338 'goe':91 'group':47,98,526,530 'hacki':971 'handl':394,790,1161,1458 'handler':848,866,875,896,939,946,1441 'happen':203 'harder':272 'header':774 'health':714 'histogram':805 'html':1024 'http':197,393 'httpexcept':1444 'httpx.asyncclient':391,659 'hyrum':1345 'i/o':155,169,202,248,250,286,322 'i/o-bound':191 'ia':1259 'ia-postgresql':1258 'id':449,456,766,773,818,1081,1085 'idempot':491,498 'immedi':450 'impact':958 'implement':981,1157,1336 'import':416,544 'info':786 'infra':648 'ingress':769 'init':33,40,130 'initi':577 'inlin':108,998 'input':1083,1163,1229,1484 'instead':56,239,930,1147 'integr':1121 'interleav':948 'intern':1478 'introduc':967 'invalid':697 'isol':1065 'isort':22 'item':430,1218 'jitter':551,576 'job':446,448 'json':753 'jsonrender':761 'k':1037 'keep':812 'key':304,499 'keyerror':1181 'label':813 'lack':264 'lambda':488 'last':1030 'latenc':804 'later':1423 'law':1347 'layout':128,149 'leak':844 'let':1216 'letter':510 'level':783,1468 'lf':1029 'lib':389 'lifecycl':1155 'lifespan':437 'limit':354,357,670 'lint':23,121 'list':426 'live':211,719 'load':738 'lock':1306 'log':583,755,782,796 'logger':853,887 'logger.callhandlers':858 'logger_to_model.get':898 'logging.filter':906 'logging.formatter.format':834 'logic':651,1330,1495,1515 'logrecord':827,862 'long':210 'long-liv':209 'loop':382 'm':1126 'main.py':132 'make':1520 'malici':1530 'manag':18,1151 'mandatori':493,655 'mani':153 'manual':53,1076 'marker':1111,1119 'max':420,463,517,579,666 'maxsiz':628 'may':495 'memoiz':634 'merg':763 'messag':1437 'metadata':109 'metric':802 'migrat':1231,1236,1242,1269,1292,1299 'minim':959 'minut':1310 'misbehav':1524 'miss':705,894,1533 'mix':168 'mock':1093,1099,1109,1149 'model':134,683,685,1205,1326,1343,1364,1366,1411,1471,1539 'modern':7,479 'modif':1388 'modifi':881 'multipl':194,285 'multiprocess':238,408 'multiprocessing.pool':165 'must':349 'mutabl':376 'mutat':826,872,912 'mypi':26 'myproject':35 'n':352,364,629,671 'name':888,915,925,1092 'nativ':486 'necessari':965 'need':610,712,793,1078 'network':596,608 'never':49,378,598,702,795,825,1266,1377 'new':988,1250,1390 'non':619,911 'non-crit':618 'non-mut':910 'none':1395,1396 'normal':1451 'noth':236 'object':863,1146,1383 'observ':750,1350 'offload':174 'old':1256 'one':124,254,259,1217,1403,1453 'op':1168 'openapi':1339 'opentelemetri':819 'oper':196,287,787 'option':1117,1391 'orchestr':745 'order':1353 'orm':1382 'other':78 'outdat':81 'overflow':667,1088 'overrid':928 'packag':34,38,75 'package/dependency':17 'parallel':150,205,528 'parametr':1079 'pars':688,1477,1535 'parti':1498 'partial':328 'pass':125,859,1005,1546,1554,1562 'path':315,621,1067 'pattern':144,317,444,508,1026,1038,1265 'pdb':1040 'pep':99,106 'perman':513 'pip':13 'pipelin':303 'pipx':16 'place':843,995 'plain':1145 'point':1284 'poll':454 'pool':424,653,664 'pool.map':427 'possibl':957,1175 'postgresql':1260 'prefer':343 'preserv':1194 'preview':83 'process':257,451,724,1210 'processpoolexecutor':417,419 'produc':370,1431 'producer-consum':369 'product':657,672,1295,1312 'production-s':1294 'project':127,148,435 'project.optional':104 'prometheus':801 'propag':778 'protocol':1129,1135 'pure':632 'pure-funct':631 'purpos':11 'py':131 'pydant':681,1204,1222,1319,1406,1463,1470,1538 'pyenv':15 'pyproject.toml':51,138,1114 'pyright':27 'pytest':856,1019,1027,1545,1571 'pytest.fixture':1052 'pytest.mark.parametrize':1082 'python':2,4,413,541 'python-servic':1 'queri':200,256 'queue':511 'r.name':890 'rais':399,471,1185 'rate':356 'rather':1397 'raw':1379 're':398 're-rais':397 'reachabl':737 'read':274,1254 'readabl':1090 'readi':729 'record':840 'record.name':837,899 'record.short':914 'redi':483,732 'redund':1490 'refer':917 'regist':1112,1439 'remov':1401,1404,1420 'renames/removals':1244 'render':1134,1516 'replac':10 'report':1023,1169 'repositori':443,1481 'request':661,710,1476 'request/response':1321 'requestvalidationerror':1443 'requir':213,217 'resili':537,673 'respond':725 'respons':1325,1351,1363,1410,1499,1510 'restart':746 'restor':935 'result':258,425 'retri':464,496,518,538,545,552,556,557,558,584,592,599,646 'return':324,447,622,720,1378,1527 'reus':658 'review':1286 'rewrit':836 'rout':740,895 'rq':481 'ruff':19,115,118,1552 'rule':275,305 'run':55,1018,1122,1273,1544,1551,1559,1570 'safe':614 'satur':810 'scatter':1492 'schema':1233,1322,1332 'scope':1050,1053,1060 'script':112,178,242 'secret':706 'section':1003 'see':139,431,879,1001,1257 'self.request.retries':475 'self.retry':472 'separ':647,1232 'sequenti':247,252,302,525 'serial':1376 'serv':700 'server':207 'servic':3,5,133,824,1140,1479,1525 'serviceerror':1186 'session':1054 'setup':1057 'shallow':718 'shape':1504 'share':375,1048,1276 'short':924 'sibl':339 'silent':893,1414 'simpl':177,241,482,955 'simpler':291 'simplic':950 'singl':74,301,1087 'size':665,1296 'skill':1261 'skill-python-services' 'sleep':582 'slow':1120,1128 'small':638 'solut':984 'source-iliaal' 'specif':1177,1448 'sql':1288 'sqlalchemi':663 'sqs':487 'src.services.foo':891 'src/mypackage':129 'stack':269,640 'standalon':111 'standard':126 'start':1283 'startup':695,759 'state':377,882 'status':458 'stay':181,223,306 'step':978 'stop':546,564,565,569,1033 'str':590,1394 'streaming/pagination':407 'string':922 'structlog':751 'structur':334,436,754,1137,1427 'succeed':350,1212 'switch':1253 'sync':183,289,308,385,388 'synchron':182,224,938 'system':1207 'tabl':1308 'take':1301 'target':1015,1567 'task':160,319,340,348,429,490,494,515,519 'team':263 'temp':1073 'tenac':540,543 'test':135,877,883,1025,1064,1091,1110,1124,1143,1291 'test_main.py':137 'third':1497 'third-parti':1496 'thread':172,384 'thumb':277 'time':1355 'timeout':363,366,612,644 'timeouterror':563 'timestamp':762 'tmp':1066 'toler':330 'tool':8,9,245 'tool.pytest.ini':1116 'topic-agent-skills' 'topic-ai-coding-assistant' 'topic-ai-tools' 'topic-claude-code' 'topic-skills' 'touch':77,962 'trace':270,642,822 'track':803 'traffic':701,741,806 'trail':1196 'transform':233 'transient':594 'tree':80 'tri':933 'true':326,462,1097,1419 'trust':1482 'ty':25,1560 'type':28,555,561,1131,1138,1202,1529 'typeerror':1180 'unbound':636 'unexpect':1528 'unrel':968 'untrust':1501 'upgrad':70,72,87 'upload':1187 'upsert':507 'url':589 'use':95,187,237,323,497,668,679,904,1045,1132,1176,1245,1279,1361,1512,1541 'user':817 'uv':12,32,39,43,45,54,68,79,1017,1543,1550,1558,1569 'uv.lock':90 'valid':605,678,686,690,1162,1226,1230,1461,1464,1486,1491,1503 'valueerror':1179 'variabl':693 'venv':59,64 'verifi':730,990,1000,1002,1542 'version':93,1424 'via':1462 'virtualenv':14 'vs':184 'wait':283,549,573,574 'warn':788,1010,1557 'websocket':206 'whichev':867 'win':870 'wise':1051 'within':312 'without':65,76 'work':176,192,226,936 'workaround':972 'worker':421 'workflow':520 'workload':151 'would':85 'write':506,1328 'wrong':977 'x':771,1032 'x-correlation-id':770 'zero':1009,1548,1556,1564","prices":[{"id":"48165973-53aa-42b4-ab04-21f28aab8626","listingId":"08026cc0-6abe-4473-91af-429825de2a01","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"iliaal","category":"ai-skills","install_from":"skills.sh"},"createdAt":"2026-05-09T01:05:36.358Z"}],"sources":[{"listingId":"08026cc0-6abe-4473-91af-429825de2a01","source":"github","sourceId":"iliaal/ai-skills/python-services","sourceUrl":"https://github.com/iliaal/ai-skills/tree/master/skills/python-services","isPrimary":false,"firstSeenAt":"2026-05-09T01:05:36.358Z","lastSeenAt":"2026-05-18T19:07:03.309Z"}],"details":{"listingId":"08026cc0-6abe-4473-91af-429825de2a01","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"iliaal","slug":"python-services","github":{"repo":"iliaal/ai-skills","stars":13,"topics":["agent-skills","ai-coding-assistant","ai-tools","claude-code","skills"],"license":"mit","html_url":"https://github.com/iliaal/ai-skills","pushed_at":"2026-05-16T13:15:17Z","description":"Curated collection of agent skills for AI coding assistants.","skill_md_sha":"68eaff5bdca824e0c84dd993f901945965e67c0f","skill_md_path":"skills/python-services/SKILL.md","default_branch":"master","skill_tree_url":"https://github.com/iliaal/ai-skills/tree/master/skills/python-services"},"layout":"multi","source":"github","category":"ai-skills","frontmatter":{"name":"python-services","description":">-"},"skills_sh_url":"https://skills.sh/iliaal/ai-skills/python-services"},"updatedAt":"2026-05-18T19:07:03.309Z"}}