funcing


Namefuncing JSON
Version 1.0.0 PyPI version JSON
download
home_pagehttps://github.com/tikipiya/funcing
SummarySimplified, error-safe threading for Python
upload_time2025-07-08 23:38:05
maintainerNone
docs_urlNone
authortikisan
requires_python>=3.7
licenseNone
keywords threading parallel async concurrent simple
VCS
bugtrack_url
requirements pytest pytest-cov pytest-mock black flake8 mypy
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Funcing - Python向け簡単並列実行ライブラリ

Pythonの標準threadingを超簡略化&エラー安全にした薄型並列実行ツール

## 🚀 なぜFuncing?

Pythonの`threading`モジュールは強力ですが、複雑になりがちです。Funcingは**超簡単**なAPIで並列実行を可能にし、自動エラーハンドリングを提供します。

```python
from funcing import run_in_parallel

def task1():
    return "Hello"

def task2():
    return "World"

# たったこれだけ!一行で並列実行
result = run_in_parallel([task1, task2])
print(result.results)  # ['Hello', 'World']
```

## ✨ 特徴

- **超簡単API**: `run_in_parallel([func1, func2])` だけ
- **エラー安全**: 自動例外処理とレポート機能
- **結果収集**: 全ての結果とエラーを一箇所で管理
- **タイムアウト対応**: 組み込みタイムアウト処理
- **依存関係ゼロ**: Python標準ライブラリのみ使用
- **包括的な統計**: 成功率、実行時間など詳細情報

## 📦 インストール

```bash
pip install funcing
```

## 🔥 クイックスタート

### 基本的な使い方

```python
from funcing import run_in_parallel

def fetch_data():
    # 何らかの処理をシミュレート
    import time
    time.sleep(1)
    return "データを取得しました"

def process_data():
    import time
    time.sleep(1)
    return "データを処理しました"

def save_data():
    import time
    time.sleep(1)
    return "データを保存しました"

# 全てのタスクを並列実行
result = run_in_parallel([fetch_data, process_data, save_data])

print(f"成功: {result.success_count}")  # 成功: 3
print(f"実行時間: {result.total_time:.2f}秒")   # 実行時間: ~1.00秒 (逐次実行なら3.00秒)
print(f"結果: {result.results}")        # 結果: ['データを取得しました', 'データを処理しました', 'データを保存しました']
```

### エラーハンドリング

```python
from funcing import run_in_parallel

def working_task():
    return "成功!"

def failing_task():
    raise ValueError("何かがうまくいきませんでした!")

result = run_in_parallel([working_task, failing_task])

print(f"成功: {result.success_count}")  # 成功: 1
print(f"エラー: {result.error_count}")       # エラー: 1
print(f"成功率: {result.success_rate:.1f}%")  # 成功率: 50.0%
print(f"全て成功: {result.all_successful}")   # 全て成功: False
```

### 引数付き関数

```python
from funcing import run_with_args

def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

def greet(name, greeting="こんにちは"):
    return f"{greeting}、{name}さん!"

# 引数付き関数
pairs = [
    (add, (1, 2)),
    (multiply, (3, 4)),
    (greet, ("太郎",), {"greeting": "やあ"})
]

result = run_with_args(pairs)
print(result.results)  # [3, 12, 'やあ、太郎さん!']
```

### 高度なオプション

```python
from funcing import run_in_parallel

functions = [task1, task2, task3, task4, task5]

result = run_in_parallel(
    functions,
    timeout=30.0,           # 最大30秒
    max_workers=3,          # 3つのスレッドのみ使用
    return_exceptions=True  # エラーを収集(例外を発生させない)
)

print(f"{result.total_time:.2f}秒で完了")
print(f"成功率: {result.success_rate:.1f}%")
```

## 📊 結果オブジェクト

`FuncingResult`オブジェクトは包括的な情報を提供します:

```python
result = run_in_parallel([func1, func2, func3])

# プロパティ
result.results          # 成功した結果のリスト
result.errors           # 例外のリスト
result.success_count    # 成功した関数の数
result.error_count      # 失敗した関数の数
result.total_time       # 総実行時間
result.function_names   # 実行された関数の名前
result.success_rate     # 成功率(パーセント)
result.all_successful   # エラーが無い場合True
```

## 🎯 使用例

- **Webスクレイピング**: 複数のURLを同時に取得
- **API呼び出し**: 複数のAPIリクエストを並列実行
- **ファイル処理**: 複数のファイルを同時に処理
- **データベース操作**: 独立したクエリを並列実行
- **データ検証**: 複数の入力を同時に検証

## 🛡️ エラー安全性

Funcingはデフォルトでエラー安全に設計されています:

- 個別の関数の失敗が全体の実行をクラッシュさせない
- 元の例外による詳細なエラー報告
- タイムアウト処理でハングを防止
- 自動的なリソースクリーンアップ

## 🔧 高度な機能

### カスタムスレッドプールサイズ

```python
# I/O集約的なタスクには多くのスレッドを使用
result = run_in_parallel(io_functions, max_workers=50)

# CPU集約的なタスクには少ないスレッドを使用
result = run_in_parallel(cpu_functions, max_workers=4)
```

### タイムアウト処理

```python
# 全体の実行にタイムアウトを設定
result = run_in_parallel(functions, timeout=10.0)

if result.errors:
    print("いくつかの関数がタイムアウトしました!")
```

### 例外処理モード

```python
# 例外を収集(デフォルト)
result = run_in_parallel(functions, return_exceptions=True)

# 最初の例外で停止
try:
    result = run_in_parallel(functions, return_exceptions=False)
except Exception as e:
    print(f"実行失敗: {e}")
```

## 📜 ライセンス

MIT License - 詳細はLICENSEファイルをご覧ください。

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/tikipiya/funcing",
    "name": "funcing",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "threading parallel async concurrent simple",
    "author": "tikisan",
    "author_email": "s2501082@sendai-nct.jp",
    "download_url": "https://files.pythonhosted.org/packages/5c/35/c9d9191d4b57a94e2be519bdf1c084558d8d5071a518ce70b8d534cc3c4d/funcing-1.0.0.tar.gz",
    "platform": null,
    "description": "# Funcing - Python\u5411\u3051\u7c21\u5358\u4e26\u5217\u5b9f\u884c\u30e9\u30a4\u30d6\u30e9\u30ea\r\n\r\nPython\u306e\u6a19\u6e96threading\u3092\u8d85\u7c21\u7565\u5316\uff06\u30a8\u30e9\u30fc\u5b89\u5168\u306b\u3057\u305f\u8584\u578b\u4e26\u5217\u5b9f\u884c\u30c4\u30fc\u30eb\r\n\r\n## \ud83d\ude80 \u306a\u305cFuncing\uff1f\r\n\r\nPython\u306e`threading`\u30e2\u30b8\u30e5\u30fc\u30eb\u306f\u5f37\u529b\u3067\u3059\u304c\u3001\u8907\u96d1\u306b\u306a\u308a\u304c\u3061\u3067\u3059\u3002Funcing\u306f**\u8d85\u7c21\u5358**\u306aAPI\u3067\u4e26\u5217\u5b9f\u884c\u3092\u53ef\u80fd\u306b\u3057\u3001\u81ea\u52d5\u30a8\u30e9\u30fc\u30cf\u30f3\u30c9\u30ea\u30f3\u30b0\u3092\u63d0\u4f9b\u3057\u307e\u3059\u3002\r\n\r\n```python\r\nfrom funcing import run_in_parallel\r\n\r\ndef task1():\r\n    return \"Hello\"\r\n\r\ndef task2():\r\n    return \"World\"\r\n\r\n# \u305f\u3063\u305f\u3053\u308c\u3060\u3051\uff01\u4e00\u884c\u3067\u4e26\u5217\u5b9f\u884c\r\nresult = run_in_parallel([task1, task2])\r\nprint(result.results)  # ['Hello', 'World']\r\n```\r\n\r\n## \u2728 \u7279\u5fb4\r\n\r\n- **\u8d85\u7c21\u5358API**: `run_in_parallel([func1, func2])` \u3060\u3051\r\n- **\u30a8\u30e9\u30fc\u5b89\u5168**: \u81ea\u52d5\u4f8b\u5916\u51e6\u7406\u3068\u30ec\u30dd\u30fc\u30c8\u6a5f\u80fd\r\n- **\u7d50\u679c\u53ce\u96c6**: \u5168\u3066\u306e\u7d50\u679c\u3068\u30a8\u30e9\u30fc\u3092\u4e00\u7b87\u6240\u3067\u7ba1\u7406\r\n- **\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u5bfe\u5fdc**: \u7d44\u307f\u8fbc\u307f\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u51e6\u7406\r\n- **\u4f9d\u5b58\u95a2\u4fc2\u30bc\u30ed**: Python\u6a19\u6e96\u30e9\u30a4\u30d6\u30e9\u30ea\u306e\u307f\u4f7f\u7528\r\n- **\u5305\u62ec\u7684\u306a\u7d71\u8a08**: \u6210\u529f\u7387\u3001\u5b9f\u884c\u6642\u9593\u306a\u3069\u8a73\u7d30\u60c5\u5831\r\n\r\n## \ud83d\udce6 \u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\r\n\r\n```bash\r\npip install funcing\r\n```\r\n\r\n## \ud83d\udd25 \u30af\u30a4\u30c3\u30af\u30b9\u30bf\u30fc\u30c8\r\n\r\n### \u57fa\u672c\u7684\u306a\u4f7f\u3044\u65b9\r\n\r\n```python\r\nfrom funcing import run_in_parallel\r\n\r\ndef fetch_data():\r\n    # \u4f55\u3089\u304b\u306e\u51e6\u7406\u3092\u30b7\u30df\u30e5\u30ec\u30fc\u30c8\r\n    import time\r\n    time.sleep(1)\r\n    return \"\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\u3057\u307e\u3057\u305f\"\r\n\r\ndef process_data():\r\n    import time\r\n    time.sleep(1)\r\n    return \"\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3057\u307e\u3057\u305f\"\r\n\r\ndef save_data():\r\n    import time\r\n    time.sleep(1)\r\n    return \"\u30c7\u30fc\u30bf\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f\"\r\n\r\n# \u5168\u3066\u306e\u30bf\u30b9\u30af\u3092\u4e26\u5217\u5b9f\u884c\r\nresult = run_in_parallel([fetch_data, process_data, save_data])\r\n\r\nprint(f\"\u6210\u529f: {result.success_count}\")  # \u6210\u529f: 3\r\nprint(f\"\u5b9f\u884c\u6642\u9593: {result.total_time:.2f}\u79d2\")   # \u5b9f\u884c\u6642\u9593: ~1.00\u79d2 (\u9010\u6b21\u5b9f\u884c\u306a\u30893.00\u79d2)\r\nprint(f\"\u7d50\u679c: {result.results}\")        # \u7d50\u679c: ['\u30c7\u30fc\u30bf\u3092\u53d6\u5f97\u3057\u307e\u3057\u305f', '\u30c7\u30fc\u30bf\u3092\u51e6\u7406\u3057\u307e\u3057\u305f', '\u30c7\u30fc\u30bf\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f']\r\n```\r\n\r\n### \u30a8\u30e9\u30fc\u30cf\u30f3\u30c9\u30ea\u30f3\u30b0\r\n\r\n```python\r\nfrom funcing import run_in_parallel\r\n\r\ndef working_task():\r\n    return \"\u6210\u529f\uff01\"\r\n\r\ndef failing_task():\r\n    raise ValueError(\"\u4f55\u304b\u304c\u3046\u307e\u304f\u3044\u304d\u307e\u305b\u3093\u3067\u3057\u305f\uff01\")\r\n\r\nresult = run_in_parallel([working_task, failing_task])\r\n\r\nprint(f\"\u6210\u529f: {result.success_count}\")  # \u6210\u529f: 1\r\nprint(f\"\u30a8\u30e9\u30fc: {result.error_count}\")       # \u30a8\u30e9\u30fc: 1\r\nprint(f\"\u6210\u529f\u7387: {result.success_rate:.1f}%\")  # \u6210\u529f\u7387: 50.0%\r\nprint(f\"\u5168\u3066\u6210\u529f: {result.all_successful}\")   # \u5168\u3066\u6210\u529f: False\r\n```\r\n\r\n### \u5f15\u6570\u4ed8\u304d\u95a2\u6570\r\n\r\n```python\r\nfrom funcing import run_with_args\r\n\r\ndef add(a, b):\r\n    return a + b\r\n\r\ndef multiply(a, b):\r\n    return a * b\r\n\r\ndef greet(name, greeting=\"\u3053\u3093\u306b\u3061\u306f\"):\r\n    return f\"{greeting}\u3001{name}\u3055\u3093\uff01\"\r\n\r\n# \u5f15\u6570\u4ed8\u304d\u95a2\u6570\r\npairs = [\r\n    (add, (1, 2)),\r\n    (multiply, (3, 4)),\r\n    (greet, (\"\u592a\u90ce\",), {\"greeting\": \"\u3084\u3042\"})\r\n]\r\n\r\nresult = run_with_args(pairs)\r\nprint(result.results)  # [3, 12, '\u3084\u3042\u3001\u592a\u90ce\u3055\u3093\uff01']\r\n```\r\n\r\n### \u9ad8\u5ea6\u306a\u30aa\u30d7\u30b7\u30e7\u30f3\r\n\r\n```python\r\nfrom funcing import run_in_parallel\r\n\r\nfunctions = [task1, task2, task3, task4, task5]\r\n\r\nresult = run_in_parallel(\r\n    functions,\r\n    timeout=30.0,           # \u6700\u592730\u79d2\r\n    max_workers=3,          # 3\u3064\u306e\u30b9\u30ec\u30c3\u30c9\u306e\u307f\u4f7f\u7528\r\n    return_exceptions=True  # \u30a8\u30e9\u30fc\u3092\u53ce\u96c6\uff08\u4f8b\u5916\u3092\u767a\u751f\u3055\u305b\u306a\u3044\uff09\r\n)\r\n\r\nprint(f\"{result.total_time:.2f}\u79d2\u3067\u5b8c\u4e86\")\r\nprint(f\"\u6210\u529f\u7387: {result.success_rate:.1f}%\")\r\n```\r\n\r\n## \ud83d\udcca \u7d50\u679c\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\r\n\r\n`FuncingResult`\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306f\u5305\u62ec\u7684\u306a\u60c5\u5831\u3092\u63d0\u4f9b\u3057\u307e\u3059\uff1a\r\n\r\n```python\r\nresult = run_in_parallel([func1, func2, func3])\r\n\r\n# \u30d7\u30ed\u30d1\u30c6\u30a3\r\nresult.results          # \u6210\u529f\u3057\u305f\u7d50\u679c\u306e\u30ea\u30b9\u30c8\r\nresult.errors           # \u4f8b\u5916\u306e\u30ea\u30b9\u30c8\r\nresult.success_count    # \u6210\u529f\u3057\u305f\u95a2\u6570\u306e\u6570\r\nresult.error_count      # \u5931\u6557\u3057\u305f\u95a2\u6570\u306e\u6570\r\nresult.total_time       # \u7dcf\u5b9f\u884c\u6642\u9593\r\nresult.function_names   # \u5b9f\u884c\u3055\u308c\u305f\u95a2\u6570\u306e\u540d\u524d\r\nresult.success_rate     # \u6210\u529f\u7387\uff08\u30d1\u30fc\u30bb\u30f3\u30c8\uff09\r\nresult.all_successful   # \u30a8\u30e9\u30fc\u304c\u7121\u3044\u5834\u5408True\r\n```\r\n\r\n## \ud83c\udfaf \u4f7f\u7528\u4f8b\r\n\r\n- **Web\u30b9\u30af\u30ec\u30a4\u30d4\u30f3\u30b0**: \u8907\u6570\u306eURL\u3092\u540c\u6642\u306b\u53d6\u5f97\r\n- **API\u547c\u3073\u51fa\u3057**: \u8907\u6570\u306eAPI\u30ea\u30af\u30a8\u30b9\u30c8\u3092\u4e26\u5217\u5b9f\u884c\r\n- **\u30d5\u30a1\u30a4\u30eb\u51e6\u7406**: \u8907\u6570\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u540c\u6642\u306b\u51e6\u7406\r\n- **\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u64cd\u4f5c**: \u72ec\u7acb\u3057\u305f\u30af\u30a8\u30ea\u3092\u4e26\u5217\u5b9f\u884c\r\n- **\u30c7\u30fc\u30bf\u691c\u8a3c**: \u8907\u6570\u306e\u5165\u529b\u3092\u540c\u6642\u306b\u691c\u8a3c\r\n\r\n## \ud83d\udee1\ufe0f \u30a8\u30e9\u30fc\u5b89\u5168\u6027\r\n\r\nFuncing\u306f\u30c7\u30d5\u30a9\u30eb\u30c8\u3067\u30a8\u30e9\u30fc\u5b89\u5168\u306b\u8a2d\u8a08\u3055\u308c\u3066\u3044\u307e\u3059\uff1a\r\n\r\n- \u500b\u5225\u306e\u95a2\u6570\u306e\u5931\u6557\u304c\u5168\u4f53\u306e\u5b9f\u884c\u3092\u30af\u30e9\u30c3\u30b7\u30e5\u3055\u305b\u306a\u3044\r\n- \u5143\u306e\u4f8b\u5916\u306b\u3088\u308b\u8a73\u7d30\u306a\u30a8\u30e9\u30fc\u5831\u544a\r\n- \u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u51e6\u7406\u3067\u30cf\u30f3\u30b0\u3092\u9632\u6b62\r\n- \u81ea\u52d5\u7684\u306a\u30ea\u30bd\u30fc\u30b9\u30af\u30ea\u30fc\u30f3\u30a2\u30c3\u30d7\r\n\r\n## \ud83d\udd27 \u9ad8\u5ea6\u306a\u6a5f\u80fd\r\n\r\n### \u30ab\u30b9\u30bf\u30e0\u30b9\u30ec\u30c3\u30c9\u30d7\u30fc\u30eb\u30b5\u30a4\u30ba\r\n\r\n```python\r\n# I/O\u96c6\u7d04\u7684\u306a\u30bf\u30b9\u30af\u306b\u306f\u591a\u304f\u306e\u30b9\u30ec\u30c3\u30c9\u3092\u4f7f\u7528\r\nresult = run_in_parallel(io_functions, max_workers=50)\r\n\r\n# CPU\u96c6\u7d04\u7684\u306a\u30bf\u30b9\u30af\u306b\u306f\u5c11\u306a\u3044\u30b9\u30ec\u30c3\u30c9\u3092\u4f7f\u7528\r\nresult = run_in_parallel(cpu_functions, max_workers=4)\r\n```\r\n\r\n### \u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u51e6\u7406\r\n\r\n```python\r\n# \u5168\u4f53\u306e\u5b9f\u884c\u306b\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3092\u8a2d\u5b9a\r\nresult = run_in_parallel(functions, timeout=10.0)\r\n\r\nif result.errors:\r\n    print(\"\u3044\u304f\u3064\u304b\u306e\u95a2\u6570\u304c\u30bf\u30a4\u30e0\u30a2\u30a6\u30c8\u3057\u307e\u3057\u305f\uff01\")\r\n```\r\n\r\n### \u4f8b\u5916\u51e6\u7406\u30e2\u30fc\u30c9\r\n\r\n```python\r\n# \u4f8b\u5916\u3092\u53ce\u96c6\uff08\u30c7\u30d5\u30a9\u30eb\u30c8\uff09\r\nresult = run_in_parallel(functions, return_exceptions=True)\r\n\r\n# \u6700\u521d\u306e\u4f8b\u5916\u3067\u505c\u6b62\r\ntry:\r\n    result = run_in_parallel(functions, return_exceptions=False)\r\nexcept Exception as e:\r\n    print(f\"\u5b9f\u884c\u5931\u6557: {e}\")\r\n```\r\n\r\n## \ud83d\udcdc \u30e9\u30a4\u30bb\u30f3\u30b9\r\n\r\nMIT License - \u8a73\u7d30\u306fLICENSE\u30d5\u30a1\u30a4\u30eb\u3092\u3054\u89a7\u304f\u3060\u3055\u3044\u3002\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Simplified, error-safe threading for Python",
    "version": "1.0.0",
    "project_urls": {
        "Bug Reports": "https://github.com/tikipiya/funcing/issues",
        "Homepage": "https://github.com/tikipiya/funcing",
        "Source": "https://github.com/tikipiya/funcing"
    },
    "split_keywords": [
        "threading",
        "parallel",
        "async",
        "concurrent",
        "simple"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f96929a9a4839ab9c37124a449213fea240996d3efa5171d099a05e4457c31ef",
                "md5": "1b431378814634345d26a260501710cd",
                "sha256": "f9b73879b176d466760dfb38dab0222883e364eb4438c0a614a215244b02b641"
            },
            "downloads": -1,
            "filename": "funcing-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1b431378814634345d26a260501710cd",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 9134,
            "upload_time": "2025-07-08T23:38:04",
            "upload_time_iso_8601": "2025-07-08T23:38:04.675148Z",
            "url": "https://files.pythonhosted.org/packages/f9/69/29a9a4839ab9c37124a449213fea240996d3efa5171d099a05e4457c31ef/funcing-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5c35c9d9191d4b57a94e2be519bdf1c084558d8d5071a518ce70b8d534cc3c4d",
                "md5": "6e8f21b2eeb546fc5fdac741309cc3eb",
                "sha256": "67397e3153ef1b03a66792296f32a8e6409517bfbedaa67976c038b8184681bf"
            },
            "downloads": -1,
            "filename": "funcing-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "6e8f21b2eeb546fc5fdac741309cc3eb",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 8765,
            "upload_time": "2025-07-08T23:38:05",
            "upload_time_iso_8601": "2025-07-08T23:38:05.769079Z",
            "url": "https://files.pythonhosted.org/packages/5c/35/c9d9191d4b57a94e2be519bdf1c084558d8d5071a518ce70b8d534cc3c4d/funcing-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-08 23:38:05",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "tikipiya",
    "github_project": "funcing",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "pytest",
            "specs": [
                [
                    ">=",
                    "6.0.0"
                ]
            ]
        },
        {
            "name": "pytest-cov",
            "specs": [
                [
                    ">=",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "pytest-mock",
            "specs": [
                [
                    ">=",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "black",
            "specs": [
                [
                    ">=",
                    "21.0.0"
                ]
            ]
        },
        {
            "name": "flake8",
            "specs": [
                [
                    ">=",
                    "3.8.0"
                ]
            ]
        },
        {
            "name": "mypy",
            "specs": [
                [
                    ">=",
                    "0.910"
                ]
            ]
        }
    ],
    "lcname": "funcing"
}
        
Elapsed time: 0.57425s