==========
TorchApp
==========
.. image:: https://raw.githubusercontent.com/rbturnbull/torchapp/master/docs/images/torchapp-banner.svg
.. start-badges
|pypi badge| |testing badge| |coverage badge| |docs badge| |black badge| |git3moji badge| |torchapp badge|
.. |pypi badge| image:: https://img.shields.io/pypi/v/torchapp?color=blue
:alt: PyPI - Version
:target: https://pypi.org/project/torchapp/
.. |torchapp badge| image:: https://img.shields.io/badge/torch-app-B1230A.svg
:target: https://rbturnbull.github.io/torchapp/
.. |testing badge| image:: https://github.com/rbturnbull/torchapp/actions/workflows/testing.yml/badge.svg
:target: https://github.com/rbturnbull/torchapp/actions
.. |docs badge| image:: https://github.com/rbturnbull/torchapp/actions/workflows/docs.yml/badge.svg
:target: https://rbturnbull.github.io/torchapp
.. |black badge| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black
.. |coverage badge| image:: https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/rbturnbull/506563cd9b49c8126284e34864c862d0/raw/coverage-badge.json
:target: https://rbturnbull.github.io/torchapp/coverage/
.. |git3moji badge| image:: https://img.shields.io/badge/git3moji-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg
:target: https://robinpokorny.github.io/git3moji/
.. end-badges
**A Tool for Packaging PyTorch Models as Reusable Applications**
Deploying machine learning models remains a challenge, particularly in scientific contexts where models are often shared only as standalone scripts or Jupyter notebooks—frequently without parameters and in forms that hinder reuse. TorchApp streamlines this process by enabling users to generate fully structured Python packages from PyTorch models, complete with documentation, testing, and interfaces for training, validation, and prediction. Users subclass the TorchApp base class and override methods to define the model and data loaders; the arguments to these methods are automatically exposed via a command-line interface. TorchApp also supports hyperparameter tuning by allowing argument distributions to be specified, and integrates with experiment tracking tools such as Weights & Biases. Optionally, a graphical user interface can be auto-generated using function signatures and type hints. TorchApp applications are easily testable using a built-in testing framework and are readily publishable to PyPI or Conda, making it simple to share deep learning tools with a broader audience.
Documentation at https://rbturnbull.github.io/torchapp/
.. start-quickstart
Installation
=======================
The software can be installed using ``pip``
.. code-block:: bash
pip install torchapp
To install the latest version from the repository, you can use this command:
.. code-block:: bash
pip install git+https://github.com/rbturnbull/torchapp.git
.. warning::
Earlier versions of torchapp used fastai but current versions use Lightning.
If you used torchapp before, please check your code for compatibility with the new version or restrict to using torch below version 0.4.
Writing an App
=======================
Inherit a class from :code:`TorchApp` to make an app. The parent class includes several methods for training and hyper-parameter tuning.
The minimum requirement is that you fill out the dataloaders method and the model method.
The :code:`data` method requires that you return a ``LightningDataModule`` object. This is a collection of dataloader objects.
Typically it contains one dataloader for training and another for testing. For more information see https://lightning.ai/docs/pytorch/stable/data/datamodule.html
You can add parameter values with typing hints in the function signature and these will be automatically added to any mehtod that requires the training data (e.g. ``train``).
The :code:`model` method requires that you return a PyTorch module. Parameters in the function signature will be added to the ``train`` method.
Here's an example for doing training on the Iris dataset, a classic dataset for classification tasks:
.. code-block:: Python
from pathlib import Path
from torch.utils.data import DataLoader, Dataset
from sklearn.datasets import load_iris
from torch import nn
import torchapp as ta
import torch
import pandas as pd
import lightning as L
from dataclasses import dataclass
from torchapp.metrics import accuracy
@dataclass
class IrisDataset(Dataset):
df: pd.DataFrame
feature_names: list[str]
def __len__(self):
return len(self.df)
def __getitem__(self, idx):
row = self.df.iloc[idx]
x = torch.tensor(row[self.feature_names].values, dtype=torch.float32)
y = torch.tensor(row['target'], dtype=int)
return x, y
@dataclass
class Standardize():
mean:torch.Tensor
std:torch.Tensor
def __call__(self, x:torch.Tensor|float) -> torch.Tensor|float:
return (x - self.mean) / self.std
def reverse(self, x:torch.Tensor|float) -> torch.Tensor|float:
return x * self.std + self.mean
def standardize_and_get_transform(x:torch.Tensor|pd.Series) -> tuple[torch.Tensor|pd.Series, Standardize]:
transform = Standardize(mean=x.mean(), std=x.std())
return transform(x), transform
class IrisApp(ta.TorchApp):
"""
A classification app to predict the type of iris from sepal and petal lengths and widths.
A classic dataset publised in:
Fisher, R.A. “The Use of Multiple Measurements in Taxonomic Problems” Annals of Eugenics, 7, Part II, 179–188 (1936).
For more information about the dataset, see:
https://scikit-learn.org/stable/datasets/toy_dataset.html#iris-plants-dataset
"""
@ta.method
def setup(self):
iris_data = load_iris(as_frame=True)
df = iris_data['frame']
self.feature_names = iris_data['feature_names']
self.target_names = iris_data['target_names']
self.df = df
@ta.method
def data(self, validation_fraction: float = 0.2, batch_size: int = 32, seed: int = 42):
df = self.df
# Standardize and save the transforms
self.transforms = {}
for column in self.feature_names:
df[column], self.transforms[column] = standardize_and_get_transform(df[column])
validation_df = df.sample(frac=validation_fraction, random_state=seed)
train_df = df.drop(validation_df.index)
train_dataset = IrisDataset(train_df, self.feature_names)
val_dataset = IrisDataset(validation_df, self.feature_names)
data_module = L.LightningDataModule()
data_module.train_dataloader = lambda: DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
data_module.val_dataloader = lambda: DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
return data_module
@ta.method
def metrics(self):
return [accuracy]
@ta.method
def extra_hyperparameters(self):
return dict(target_names=self.target_names, transforms=self.transforms)
@ta.method
def model(
self,
hidden_size:int=ta.Param(default=8, tune=True, tune_min=4, tune_max=128, tune_log=True),
intermediate_layers:int=ta.Param(default=1, tune=True, tune_min=0, tune_max=3),
):
in_features = 4
output_categories = 3
modules = [nn.Linear(in_features, hidden_size)]
for _ in range(intermediate_layers):
modules.append(nn.ReLU())
modules.append(nn.Linear(hidden_size, hidden_size))
modules.append(nn.ReLU())
modules.append(nn.Linear(hidden_size, output_categories))
return nn.Sequential(*modules)
@ta.method
def loss_function(self):
return nn.CrossEntropyLoss()
@ta.method
def get_bibtex_files(self):
files = super().get_bibtex_files()
files.append(Path(__file__).parent / "iris.bib")
return files
@ta.method
def prediction_dataloader(
self,
module,
sepal_length:float=ta.Param(...,help="The sepal length in cm."),
sepal_width:float=ta.Param(...,help="The sepal width in cm."),
petal_length:float=ta.Param(...,help="The petal length in cm."),
petal_width:float=ta.Param(...,help="The petal width in cm."),
) -> list:
assert sepal_width is not None
assert sepal_length is not None
assert petal_width is not None
assert petal_length is not None
self.target_names = module.hparams.target_names
# data must be in the same order as the feature_names
data = [sepal_length, sepal_width, petal_length, petal_width]
transformed_data = [transform(x) for x,transform in zip(data, module.hparams.transforms.values())]
dataset = [torch.tensor(transformed_data, dtype=torch.float32)]
return DataLoader(dataset, batch_size=1)
@ta.method
def output_results(
self,
results,
):
assert results.shape == (3,)
probabilities = torch.softmax(results, dim=0)
predicted_class = results.argmax().item()
predicted_name = self.target_names[predicted_class]
print(f"Predicted class: {predicted_name} ({probabilities[predicted_class]:.2%})")
Programmatic Interface
=======================
To use the app in Python, simply instantiate it:
.. code-block:: Python
app = IrisApp()
Then you can train with the method:
.. code-block:: Python
app.train(csv=training_csv_path)
This takes the arguments of both the :code:`data` method and the :code:`train` method.
Predictions are made by simply calling the app object.
.. code-block:: Python
app(data_csv_path)
Command-Line Interface
=======================
Command-line interfaces are created simply by using the Poetry package management tool. Just add line like this in :code:`pyproject.toml` (assuming your package is called ``iris``):
.. code-block:: toml
iris = "iris.apps:IrisApp.main"
iris-tools = "iris.apps:IrisApp.tools"
Now we can train with the command line:
.. code-block:: bash
iris-tools train --csv training_csv_path
All the arguments for the dataloader and the model can be set through arguments in the CLI. To see them run
.. code-block:: bash
iris-tools train --help
Predictions are made like this:
.. code-block:: bash
iris --csv data_csv_path
See information for other commands by running:
.. code-block:: bash
iris-tools --help
Hyperparameter Tuning
=======================
All the arguments in the dataloader and the model can be tuned using a variety of hyperparameter tuning libraries including.
In Python run this:
.. code-block:: python
app.tune(runs=10)
Or from the command line, run
.. code-block:: bash
iris-tools tune --runs 10
These commands will connect with W&B and your runs will be visible on the wandb.ai site.
Project Generation
=======================
To use a template to construct a package for your app, simply run:
.. code-block:: bash
torchapp-generator
.. end-quickstart
Credits
=======================
.. start-credits
torchapp was created created by `Robert Turnbull <https://robturnbull.com>`_ with contributions from Wytamma Wirth, Jonathan Garber and Simone Bae.
Citation details to follow.
Logo elements derived from icons by `ProSymbols <https://thenounproject.com/icon/flame-797130/>`_ and `Philipp Petzka <https://thenounproject.com/icon/parcel-2727677/>`_.
.. end-credits
Raw data
{
"_id": null,
"home_page": "https://github.com/rbturnbull/torchapp",
"name": "torchapp",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.13,>=3.10",
"maintainer_email": null,
"keywords": "PyTorch, pytorch, deep learning, command-line interface",
"author": "Robert Turnbull",
"author_email": "robert.turnbull@unimelb.edu.au",
"download_url": "https://files.pythonhosted.org/packages/b7/67/2731b8478e58ecbf03f17ada80d1fd4be15a854c69cf69f88927ccdd7e76/torchapp-0.5.7.tar.gz",
"platform": null,
"description": "==========\nTorchApp\n==========\n\n.. image:: https://raw.githubusercontent.com/rbturnbull/torchapp/master/docs/images/torchapp-banner.svg\n\n.. start-badges\n\n|pypi badge| |testing badge| |coverage badge| |docs badge| |black badge| |git3moji badge| |torchapp badge|\n\n.. |pypi badge| image:: https://img.shields.io/pypi/v/torchapp?color=blue\n :alt: PyPI - Version\n :target: https://pypi.org/project/torchapp/\n\n.. |torchapp badge| image:: https://img.shields.io/badge/torch-app-B1230A.svg\n :target: https://rbturnbull.github.io/torchapp/\n\n.. |testing badge| image:: https://github.com/rbturnbull/torchapp/actions/workflows/testing.yml/badge.svg\n :target: https://github.com/rbturnbull/torchapp/actions\n\n.. |docs badge| image:: https://github.com/rbturnbull/torchapp/actions/workflows/docs.yml/badge.svg\n :target: https://rbturnbull.github.io/torchapp\n \n.. |black badge| image:: https://img.shields.io/badge/code%20style-black-000000.svg\n :target: https://github.com/psf/black\n \n.. |coverage badge| image:: https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/rbturnbull/506563cd9b49c8126284e34864c862d0/raw/coverage-badge.json\n :target: https://rbturnbull.github.io/torchapp/coverage/\n\n.. |git3moji badge| image:: https://img.shields.io/badge/git3moji-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg\n :target: https://robinpokorny.github.io/git3moji/\n\n.. end-badges\n\n**A Tool for Packaging PyTorch Models as Reusable Applications**\n\nDeploying machine learning models remains a challenge, particularly in scientific contexts where models are often shared only as standalone scripts or Jupyter notebooks\u2014frequently without parameters and in forms that hinder reuse. TorchApp streamlines this process by enabling users to generate fully structured Python packages from PyTorch models, complete with documentation, testing, and interfaces for training, validation, and prediction. Users subclass the TorchApp base class and override methods to define the model and data loaders; the arguments to these methods are automatically exposed via a command-line interface. TorchApp also supports hyperparameter tuning by allowing argument distributions to be specified, and integrates with experiment tracking tools such as Weights & Biases. Optionally, a graphical user interface can be auto-generated using function signatures and type hints. TorchApp applications are easily testable using a built-in testing framework and are readily publishable to PyPI or Conda, making it simple to share deep learning tools with a broader audience.\n\nDocumentation at https://rbturnbull.github.io/torchapp/\n\n.. start-quickstart\n\nInstallation\n=======================\n\nThe software can be installed using ``pip``\n\n.. code-block:: bash\n\n pip install torchapp\n\nTo install the latest version from the repository, you can use this command:\n\n.. code-block:: bash\n\n pip install git+https://github.com/rbturnbull/torchapp.git\n\n\n.. warning::\n\n Earlier versions of torchapp used fastai but current versions use Lightning. \n If you used torchapp before, please check your code for compatibility with the new version or restrict to using torch below version 0.4.\n\nWriting an App\n=======================\n\nInherit a class from :code:`TorchApp` to make an app. The parent class includes several methods for training and hyper-parameter tuning. \nThe minimum requirement is that you fill out the dataloaders method and the model method.\n\nThe :code:`data` method requires that you return a ``LightningDataModule`` object. This is a collection of dataloader objects. \nTypically it contains one dataloader for training and another for testing. For more information see https://lightning.ai/docs/pytorch/stable/data/datamodule.html\nYou can add parameter values with typing hints in the function signature and these will be automatically added to any mehtod that requires the training data (e.g. ``train``).\n\nThe :code:`model` method requires that you return a PyTorch module. Parameters in the function signature will be added to the ``train`` method.\n\nHere's an example for doing training on the Iris dataset, a classic dataset for classification tasks:\n\n.. code-block:: Python\n \n from pathlib import Path\n from torch.utils.data import DataLoader, Dataset\n from sklearn.datasets import load_iris\n from torch import nn\n import torchapp as ta\n import torch\n import pandas as pd\n import lightning as L\n from dataclasses import dataclass\n from torchapp.metrics import accuracy\n\n\n @dataclass\n class IrisDataset(Dataset):\n df: pd.DataFrame\n feature_names: list[str]\n\n def __len__(self):\n return len(self.df)\n\n def __getitem__(self, idx):\n row = self.df.iloc[idx]\n x = torch.tensor(row[self.feature_names].values, dtype=torch.float32)\n y = torch.tensor(row['target'], dtype=int)\n return x, y\n\n\n @dataclass\n class Standardize():\n mean:torch.Tensor\n std:torch.Tensor\n\n def __call__(self, x:torch.Tensor|float) -> torch.Tensor|float:\n return (x - self.mean) / self.std\n\n def reverse(self, x:torch.Tensor|float) -> torch.Tensor|float:\n return x * self.std + self.mean\n\n\n def standardize_and_get_transform(x:torch.Tensor|pd.Series) -> tuple[torch.Tensor|pd.Series, Standardize]:\n transform = Standardize(mean=x.mean(), std=x.std())\n return transform(x), transform\n\n\n class IrisApp(ta.TorchApp):\n \"\"\"\n A classification app to predict the type of iris from sepal and petal lengths and widths.\n\n A classic dataset publised in:\n Fisher, R.A. \u201cThe Use of Multiple Measurements in Taxonomic Problems\u201d Annals of Eugenics, 7, Part II, 179\u2013188 (1936).\n For more information about the dataset, see:\n https://scikit-learn.org/stable/datasets/toy_dataset.html#iris-plants-dataset\n \"\"\"\n @ta.method\n def setup(self):\n iris_data = load_iris(as_frame=True)\n df = iris_data['frame']\n self.feature_names = iris_data['feature_names']\n self.target_names = iris_data['target_names']\n self.df = df\n\n @ta.method\n def data(self, validation_fraction: float = 0.2, batch_size: int = 32, seed: int = 42):\n df = self.df\n\n # Standardize and save the transforms\n self.transforms = {}\n for column in self.feature_names:\n df[column], self.transforms[column] = standardize_and_get_transform(df[column])\n\n validation_df = df.sample(frac=validation_fraction, random_state=seed)\n train_df = df.drop(validation_df.index)\n train_dataset = IrisDataset(train_df, self.feature_names)\n val_dataset = IrisDataset(validation_df, self.feature_names)\n data_module = L.LightningDataModule()\n\n data_module.train_dataloader = lambda: DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n data_module.val_dataloader = lambda: DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n return data_module\n\n @ta.method\n def metrics(self):\n return [accuracy]\n\n @ta.method\n def extra_hyperparameters(self):\n return dict(target_names=self.target_names, transforms=self.transforms)\n\n @ta.method\n def model(\n self, \n hidden_size:int=ta.Param(default=8, tune=True, tune_min=4, tune_max=128, tune_log=True),\n intermediate_layers:int=ta.Param(default=1, tune=True, tune_min=0, tune_max=3),\n ):\n in_features = 4\n output_categories = 3\n\n modules = [nn.Linear(in_features, hidden_size)]\n for _ in range(intermediate_layers):\n modules.append(nn.ReLU())\n modules.append(nn.Linear(hidden_size, hidden_size))\n\n modules.append(nn.ReLU())\n modules.append(nn.Linear(hidden_size, output_categories))\n return nn.Sequential(*modules)\n\n @ta.method\n def loss_function(self):\n return nn.CrossEntropyLoss()\n\n @ta.method\n def get_bibtex_files(self):\n files = super().get_bibtex_files()\n files.append(Path(__file__).parent / \"iris.bib\")\n return files\n\n @ta.method\n def prediction_dataloader(\n self, \n module, \n sepal_length:float=ta.Param(...,help=\"The sepal length in cm.\"), \n sepal_width:float=ta.Param(...,help=\"The sepal width in cm.\"), \n petal_length:float=ta.Param(...,help=\"The petal length in cm.\"), \n petal_width:float=ta.Param(...,help=\"The petal width in cm.\"), \n ) -> list:\n assert sepal_width is not None\n assert sepal_length is not None\n assert petal_width is not None\n assert petal_length is not None\n\n self.target_names = module.hparams.target_names\n\n # data must be in the same order as the feature_names\n data = [sepal_length, sepal_width, petal_length, petal_width]\n transformed_data = [transform(x) for x,transform in zip(data, module.hparams.transforms.values())]\n dataset = [torch.tensor(transformed_data, dtype=torch.float32)]\n return DataLoader(dataset, batch_size=1)\n\n @ta.method\n def output_results(\n self, \n results,\n ):\n assert results.shape == (3,)\n probabilities = torch.softmax(results, dim=0)\n predicted_class = results.argmax().item()\n predicted_name = self.target_names[predicted_class]\n print(f\"Predicted class: {predicted_name} ({probabilities[predicted_class]:.2%})\")\n \n\nProgrammatic Interface\n=======================\n\nTo use the app in Python, simply instantiate it:\n\n.. code-block:: Python\n\n app = IrisApp()\n\nThen you can train with the method:\n\n.. code-block:: Python\n\n app.train(csv=training_csv_path)\n\nThis takes the arguments of both the :code:`data` method and the :code:`train` method.\n\nPredictions are made by simply calling the app object.\n\n.. code-block:: Python\n\n app(data_csv_path)\n\nCommand-Line Interface\n=======================\n\nCommand-line interfaces are created simply by using the Poetry package management tool. Just add line like this in :code:`pyproject.toml` (assuming your package is called ``iris``):\n\n.. code-block:: toml\n\n iris = \"iris.apps:IrisApp.main\"\n iris-tools = \"iris.apps:IrisApp.tools\"\n\nNow we can train with the command line:\n\n.. code-block:: bash\n\n iris-tools train --csv training_csv_path\n\nAll the arguments for the dataloader and the model can be set through arguments in the CLI. To see them run\n\n.. code-block:: bash\n\n iris-tools train --help\n\nPredictions are made like this:\n\n.. code-block:: bash\n\n iris --csv data_csv_path\n\nSee information for other commands by running:\n\n.. code-block:: bash\n\n iris-tools --help\n\nHyperparameter Tuning\n=======================\n\nAll the arguments in the dataloader and the model can be tuned using a variety of hyperparameter tuning libraries including.\n\nIn Python run this:\n\n.. code-block:: python\n\n app.tune(runs=10)\n\nOr from the command line, run\n\n.. code-block:: bash\n\n iris-tools tune --runs 10\n\nThese commands will connect with W&B and your runs will be visible on the wandb.ai site.\n\nProject Generation\n=======================\n\nTo use a template to construct a package for your app, simply run:\n\n.. code-block:: bash\n\n torchapp-generator\n\n.. end-quickstart\n\nCredits\n=======================\n\n.. start-credits\n\ntorchapp was created created by `Robert Turnbull <https://robturnbull.com>`_ with contributions from Wytamma Wirth, Jonathan Garber and Simone Bae.\n\nCitation details to follow.\n\nLogo elements derived from icons by `ProSymbols <https://thenounproject.com/icon/flame-797130/>`_ and `Philipp Petzka <https://thenounproject.com/icon/parcel-2727677/>`_.\n\n.. end-credits",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "A wrapper for PyTorch projects to create easy command-line interfaces and manage hyper-parameter tuning.",
"version": "0.5.7",
"project_urls": {
"Documentation": "https://rbturnbull.github.io/torchapp/",
"Homepage": "https://github.com/rbturnbull/torchapp",
"Repository": "https://github.com/rbturnbull/torchapp"
},
"split_keywords": [
"pytorch",
" pytorch",
" deep learning",
" command-line interface"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9c4fee629e86677b74e5ca06dc6c0be890f9816fac954c2ccb795a76eca62800",
"md5": "0bd9008980cad703a9927bd8db7d50eb",
"sha256": "94db981582143dc8a76a7d3a20d7d0a6bd8e4a2899de1a71bbcd6e255b2c9cc0"
},
"downloads": -1,
"filename": "torchapp-0.5.7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0bd9008980cad703a9927bd8db7d50eb",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.13,>=3.10",
"size": 102335,
"upload_time": "2025-07-16T23:13:09",
"upload_time_iso_8601": "2025-07-16T23:13:09.509333Z",
"url": "https://files.pythonhosted.org/packages/9c/4f/ee629e86677b74e5ca06dc6c0be890f9816fac954c2ccb795a76eca62800/torchapp-0.5.7-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b7672731b8478e58ecbf03f17ada80d1fd4be15a854c69cf69f88927ccdd7e76",
"md5": "c1a1f211faec3c1006638d6bbb254f74",
"sha256": "adcb7a82403fefe3c8612d793b2190cf7b9ee79250af1444b2ab76a56cce031f"
},
"downloads": -1,
"filename": "torchapp-0.5.7.tar.gz",
"has_sig": false,
"md5_digest": "c1a1f211faec3c1006638d6bbb254f74",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.13,>=3.10",
"size": 83394,
"upload_time": "2025-07-16T23:13:10",
"upload_time_iso_8601": "2025-07-16T23:13:10.941937Z",
"url": "https://files.pythonhosted.org/packages/b7/67/2731b8478e58ecbf03f17ada80d1fd4be15a854c69cf69f88927ccdd7e76/torchapp-0.5.7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-07-16 23:13:10",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "rbturnbull",
"github_project": "torchapp",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "torchapp"
}