pyxfr


Namepyxfr JSON
Version 0.6.4 PyPI version JSON
download
home_pageNone
SummaryThe Transferase Python API
upload_time2025-07-18 04:11:15
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseMIT
keywords dna methylation epigenetics epigenomics genomics methylation methylome
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pyxfr

The pyxfr package is an API for transferase. Learn more about transferase
[here](https://github.com/andrewdavidsmith/transferase). This package allows
the same queries to be done within Python as with the transferase command line
app. Almost all other utilities for manipulating transferase data are
available through pyxfr.

Although documentation is still sparse for pyxfr, each class and function in
pyxfr has built-in documentation:

```python
from pyxfr import pyxfr, pyxfr_utils
help(pyxfr)
help(pyxfr_utils)
```

## Requirements

- Linux: Python >= 3.12
- macOS: Python >= 3.12 and macOS >= 13 (at least Ventura)

## Usage examples

First we import the pyxfr module so we can set our preferred log level for the
session or in your Python scripts. Setting it to "debug" let's us see
everything. It will be a lot of information, most of it actually for
debugging.

```python
import pyxfr
pyxfr.set_log_level("debug")
```

Next we want to set up transferase for the user (i.e., your login account) on
the host system (e.g., your laptop). The following will do a default setup,
and might take up to a minute:

```python
from pyxfr import MConfig
config = MConfig()
config.install(["hg38"])
```

This will create files in `~/.config/transferase` which are safe to delete
anytime because you can just run the same command again. The reason this is
done in two steps is because you might want to change something in the
`config` before doing the installation. By typing the name of the variable
`config` you will see a dump of its values. For now, leave them as their
default values -- they only need to be changed if you are using local data.

You can select other genomes in the `install` step (e.g., mm39, rn7, bosTau9,
etc.). If the genomes don't exist or are not on the server, you should see a
`RuntimeError` exception in Python, indicating a problem downloading. The
server can't tell the difference between an invalid genome assembly name, one
that is misspelled, and a real one that simply isn't on the server. You can
find the list of available genomes by checking out MethBase2 through the UCSC
Genome Browser, or using a command I will show below.

With the setup has completed, we can get a client object:

```python
from pyxfr import MClient
client = MClient()
```

The client object is what makes the queries. Our query will be based on a set
of genomic intervals, which you would get from a BED format file. However,
before working with the genomic intervals we need to first load a genome
index, which guarantees that we are working with the exact reference genome
that the transferase server expects.

```python
from pyxfr import GenomeIndex
genome_index = GenomeIndex.read(client.get_index_dir(), "hg38")
```

We will now read genomic intervals. If you have a BED format file for hg38,
for example around 100k intervals, you can use it. Otherwise you can find the
[intervals.bed.gz](https://github.com/andrewdavidsmith/transferase/blob/main/docs/intervals.bed.gz)
in the docs directory of the repo (likely alongside this file), gunzip it and
put it in your working directory.

```python
from pyxfr import GenomicInterval
intervals = GenomicInterval.read(genome_index, "intervals.bed")
```

At this point we can do a query:

```python
genome_name = "hg38"
methylome_names = ["ERX9474770","ERX9474769"]
levels = client.get_levels(genome_name, methylome_names, intervals)
```

The `levels` is an object of class `MLevels`, which is a matrix where rows
correspond to intervals and columns correspond to methylomes in the query.

The following loop will allow us to see the first 10 methylation levels that
were retrieved for the second (`ERX9474769`) of the two methylomes in our
query:

```python
print("\n".join([str(levels.at(i, 1)) for i in range(10)]))
```

It should look like this:

```console
(4, 1)
(1, 471)
(0, 0)
(45, 29)
(0, 0)
(334, 346)
(62, 1581)
(51, 1755)
(74, 664)
(199, 1753)
```

If you are doing multiple queries that involve the same set of genomic
intervals, it's more efficient to make a `MQuery` object out of them. This is
done internally by the query above, but the work to do it can be skipped if
they you already have them. Here is an example:

```python
genome_name = "hg38"
methylome_names = ["ERX9474770","ERX9474769"]
query = genome_index.make_query(intervals)
levels = client.get_levels_covered(genome_name, methylome_names, query)
```

If you want to see the methylation levels alongside the original genomic
intervals, including printing the chromosome names for each genomic interval,
you can do it as follows:

```python
for col in range(levels.n_methylomes):
    for row in range(10):
        print(intervals[row].to_string(genome_index), levels.at(row, col))
    print()
```

The `at` function for class `MLevels` will return a tuple of
`(n_meth,n_unmeth)` and if the `_covered` version of the query was used, it
will return a tuple of `(n_meth,n_unmeth,n_covered)`. These create the tuple
objects that they return Python. If you want the corresponding values without
creating the tuple, you can get them with the `get_n_meth(i,j)`,
`get_n_unmeth(i,j)` and `get_n_covered(i,j)` functions, where `i` is the
row/interval, and `j` is the column/methylome. Here is an example:

```python
for i in range(10):  # 10 for brevity
    for j in range(levels.n_methylomes):
        print(levels.get_n_covered(i, j), end=' ')
    print()
```

The `get_n_covered(i,j)` will raise an exception if the information about
covered sites was not requested in the query. There is also a `get_wmean(i,j)`
function that can be applied similarly, which gives the weighted mean
methylation level for the corresponding interval. This is likely the most
useful summary statistic in all of methylome analysis.

You can convert an `MLevels` object into a numpy array, which is already
familiar to many Python users. The following commands assume that the `levels`
object was obtained from a `get_levels_covered` query, hence the `3` as the
final dimension in `a.shape`:

```python
a = levels.view_nparray()
methylome_names = ["ERX9474770","ERX9474769"]
assert a.shape == (len(methylome_names), len(intervals), 3)
```

The `intervals` in that command came from an earlier command above. The full
set of weighted mean methylation levels can be obtained as a numpy array
(matrix) using the `all_wmeans(min_reads)` function, applied to the entire
`levels` object. The `min_reads` parameter indicates a value below which the
fraction is not interpretable. This is needed, because the information about
whether there are even any reads at all for a given interval would be
lost. Entries without enough reads are assigned a value of `-1.0`. Here is an
example (the output is a numpy array):

```python
min_reads = 2
means = levels.all_wmeans(min_reads)
print(means[0:10])
```

As we have seen, the queries named with `_covered` return, along with
methylation levels, the number of CpG sites covered by reads in each query
interval. It's also helpful to know how many total CpG sites are in the
interval. This is a property of the reference genome, not a particular
methylome. If you have a set of intervals and a GenomeIndex, you can get the
total number of CpG sites in each interval like this:

```python
n_cpgs_intervals = genome_index.get_n_cpgs(intervals)
print("\n".join([str(i) for i in n_cpgs_intervals[0:10]]))
```

Two other kinds of queries are supported: bins and windows. Bins are
fixed-width intervals that tile the genome -- no CpG is in more than one
bin. Windows slide, so each CpG can fall within multiple windows. These are
based on genome coordinates and not the CpG identify. Here is an example of a
bins query:

```python
bin_size = 10000
genome_name = "hg38"
methylome_names = ["ERX9474770","ERX9474769"]
levels = client.get_levels(genome_name, methylome_names, bin_size)
```

Since the human genome is a bit more than 3G in size, this query will return a
bit more than 300,000 level values for each of the two query methylomes. Be
careful with a query like this, because you can easily create massive output
files if your bins are too small. The public transferase server will also
restrict queries for very small bin sizes -- 100bp bins could mean many GB of
data transmitted.

A windows query is like a bins query but the windows slide and are therefore
overlapping. A step size is needed in addition to a window size:

```python
window_size = 30000
window_step = 10000
genome_name = "hg38"
methylome_names = ["ERX9474770","ERX9474769"]
levels = client.get_levels(genome_name, methylome_names, window_size, window_step)
```

The above command uses a window size of 30Kbp, and a step size of 10Kbp. The
number of windows will be just over 300,000, because one window begins at each
10Kbp offset. But in contrast with the bins query, each of the windows will
span 30Kbp.

The output format for bins and windows queries is the same as for queries
based on a given set of genomic intervals. This means you can use the same
functions to manipulate the levels, for example `get_wmean(i,j)` or
`get_n_meth(i, j)`, and the conversion to numpy arrays.

You can get the metadata for MethBase2 methylomes available on the public
transferase server. Here is the command and along with what you might expect
to see:

```python
methbase_metadata = pyxfr_utils.load_methbase_metadata("hg38")
sum(methbase_metadata.sample_name == "Sperm")
# 174
list(methbase_metadata.columns.values)
# ['study', 'experiment', 'bsrate', 'meth', 'depth', 'mapping', 'uniq',...
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pyxfr",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": "Andrew D Smith <andrewds@usc.edu>",
    "keywords": "DNA methylation, epigenetics, epigenomics, genomics, methylation, methylome",
    "author": null,
    "author_email": "Andrew D Smith <andrewds@usc.edu>",
    "download_url": null,
    "platform": null,
    "description": "# pyxfr\n\nThe pyxfr package is an API for transferase. Learn more about transferase\n[here](https://github.com/andrewdavidsmith/transferase). This package allows\nthe same queries to be done within Python as with the transferase command line\napp. Almost all other utilities for manipulating transferase data are\navailable through pyxfr.\n\nAlthough documentation is still sparse for pyxfr, each class and function in\npyxfr has built-in documentation:\n\n```python\nfrom pyxfr import pyxfr, pyxfr_utils\nhelp(pyxfr)\nhelp(pyxfr_utils)\n```\n\n## Requirements\n\n- Linux: Python >= 3.12\n- macOS: Python >= 3.12 and macOS >= 13 (at least Ventura)\n\n## Usage examples\n\nFirst we import the pyxfr module so we can set our preferred log level for the\nsession or in your Python scripts. Setting it to \"debug\" let's us see\neverything. It will be a lot of information, most of it actually for\ndebugging.\n\n```python\nimport pyxfr\npyxfr.set_log_level(\"debug\")\n```\n\nNext we want to set up transferase for the user (i.e., your login account) on\nthe host system (e.g., your laptop). The following will do a default setup,\nand might take up to a minute:\n\n```python\nfrom pyxfr import MConfig\nconfig = MConfig()\nconfig.install([\"hg38\"])\n```\n\nThis will create files in `~/.config/transferase` which are safe to delete\nanytime because you can just run the same command again. The reason this is\ndone in two steps is because you might want to change something in the\n`config` before doing the installation. By typing the name of the variable\n`config` you will see a dump of its values. For now, leave them as their\ndefault values -- they only need to be changed if you are using local data.\n\nYou can select other genomes in the `install` step (e.g., mm39, rn7, bosTau9,\netc.). If the genomes don't exist or are not on the server, you should see a\n`RuntimeError` exception in Python, indicating a problem downloading. The\nserver can't tell the difference between an invalid genome assembly name, one\nthat is misspelled, and a real one that simply isn't on the server. You can\nfind the list of available genomes by checking out MethBase2 through the UCSC\nGenome Browser, or using a command I will show below.\n\nWith the setup has completed, we can get a client object:\n\n```python\nfrom pyxfr import MClient\nclient = MClient()\n```\n\nThe client object is what makes the queries. Our query will be based on a set\nof genomic intervals, which you would get from a BED format file. However,\nbefore working with the genomic intervals we need to first load a genome\nindex, which guarantees that we are working with the exact reference genome\nthat the transferase server expects.\n\n```python\nfrom pyxfr import GenomeIndex\ngenome_index = GenomeIndex.read(client.get_index_dir(), \"hg38\")\n```\n\nWe will now read genomic intervals. If you have a BED format file for hg38,\nfor example around 100k intervals, you can use it. Otherwise you can find the\n[intervals.bed.gz](https://github.com/andrewdavidsmith/transferase/blob/main/docs/intervals.bed.gz)\nin the docs directory of the repo (likely alongside this file), gunzip it and\nput it in your working directory.\n\n```python\nfrom pyxfr import GenomicInterval\nintervals = GenomicInterval.read(genome_index, \"intervals.bed\")\n```\n\nAt this point we can do a query:\n\n```python\ngenome_name = \"hg38\"\nmethylome_names = [\"ERX9474770\",\"ERX9474769\"]\nlevels = client.get_levels(genome_name, methylome_names, intervals)\n```\n\nThe `levels` is an object of class `MLevels`, which is a matrix where rows\ncorrespond to intervals and columns correspond to methylomes in the query.\n\nThe following loop will allow us to see the first 10 methylation levels that\nwere retrieved for the second (`ERX9474769`) of the two methylomes in our\nquery:\n\n```python\nprint(\"\\n\".join([str(levels.at(i, 1)) for i in range(10)]))\n```\n\nIt should look like this:\n\n```console\n(4, 1)\n(1, 471)\n(0, 0)\n(45, 29)\n(0, 0)\n(334, 346)\n(62, 1581)\n(51, 1755)\n(74, 664)\n(199, 1753)\n```\n\nIf you are doing multiple queries that involve the same set of genomic\nintervals, it's more efficient to make a `MQuery` object out of them. This is\ndone internally by the query above, but the work to do it can be skipped if\nthey you already have them. Here is an example:\n\n```python\ngenome_name = \"hg38\"\nmethylome_names = [\"ERX9474770\",\"ERX9474769\"]\nquery = genome_index.make_query(intervals)\nlevels = client.get_levels_covered(genome_name, methylome_names, query)\n```\n\nIf you want to see the methylation levels alongside the original genomic\nintervals, including printing the chromosome names for each genomic interval,\nyou can do it as follows:\n\n```python\nfor col in range(levels.n_methylomes):\n    for row in range(10):\n        print(intervals[row].to_string(genome_index), levels.at(row, col))\n    print()\n```\n\nThe `at` function for class `MLevels` will return a tuple of\n`(n_meth,n_unmeth)` and if the `_covered` version of the query was used, it\nwill return a tuple of `(n_meth,n_unmeth,n_covered)`. These create the tuple\nobjects that they return Python. If you want the corresponding values without\ncreating the tuple, you can get them with the `get_n_meth(i,j)`,\n`get_n_unmeth(i,j)` and `get_n_covered(i,j)` functions, where `i` is the\nrow/interval, and `j` is the column/methylome. Here is an example:\n\n```python\nfor i in range(10):  # 10 for brevity\n    for j in range(levels.n_methylomes):\n        print(levels.get_n_covered(i, j), end=' ')\n    print()\n```\n\nThe `get_n_covered(i,j)` will raise an exception if the information about\ncovered sites was not requested in the query. There is also a `get_wmean(i,j)`\nfunction that can be applied similarly, which gives the weighted mean\nmethylation level for the corresponding interval. This is likely the most\nuseful summary statistic in all of methylome analysis.\n\nYou can convert an `MLevels` object into a numpy array, which is already\nfamiliar to many Python users. The following commands assume that the `levels`\nobject was obtained from a `get_levels_covered` query, hence the `3` as the\nfinal dimension in `a.shape`:\n\n```python\na = levels.view_nparray()\nmethylome_names = [\"ERX9474770\",\"ERX9474769\"]\nassert a.shape == (len(methylome_names), len(intervals), 3)\n```\n\nThe `intervals` in that command came from an earlier command above. The full\nset of weighted mean methylation levels can be obtained as a numpy array\n(matrix) using the `all_wmeans(min_reads)` function, applied to the entire\n`levels` object. The `min_reads` parameter indicates a value below which the\nfraction is not interpretable. This is needed, because the information about\nwhether there are even any reads at all for a given interval would be\nlost. Entries without enough reads are assigned a value of `-1.0`. Here is an\nexample (the output is a numpy array):\n\n```python\nmin_reads = 2\nmeans = levels.all_wmeans(min_reads)\nprint(means[0:10])\n```\n\nAs we have seen, the queries named with `_covered` return, along with\nmethylation levels, the number of CpG sites covered by reads in each query\ninterval. It's also helpful to know how many total CpG sites are in the\ninterval. This is a property of the reference genome, not a particular\nmethylome. If you have a set of intervals and a GenomeIndex, you can get the\ntotal number of CpG sites in each interval like this:\n\n```python\nn_cpgs_intervals = genome_index.get_n_cpgs(intervals)\nprint(\"\\n\".join([str(i) for i in n_cpgs_intervals[0:10]]))\n```\n\nTwo other kinds of queries are supported: bins and windows. Bins are\nfixed-width intervals that tile the genome -- no CpG is in more than one\nbin. Windows slide, so each CpG can fall within multiple windows. These are\nbased on genome coordinates and not the CpG identify. Here is an example of a\nbins query:\n\n```python\nbin_size = 10000\ngenome_name = \"hg38\"\nmethylome_names = [\"ERX9474770\",\"ERX9474769\"]\nlevels = client.get_levels(genome_name, methylome_names, bin_size)\n```\n\nSince the human genome is a bit more than 3G in size, this query will return a\nbit more than 300,000 level values for each of the two query methylomes. Be\ncareful with a query like this, because you can easily create massive output\nfiles if your bins are too small. The public transferase server will also\nrestrict queries for very small bin sizes -- 100bp bins could mean many GB of\ndata transmitted.\n\nA windows query is like a bins query but the windows slide and are therefore\noverlapping. A step size is needed in addition to a window size:\n\n```python\nwindow_size = 30000\nwindow_step = 10000\ngenome_name = \"hg38\"\nmethylome_names = [\"ERX9474770\",\"ERX9474769\"]\nlevels = client.get_levels(genome_name, methylome_names, window_size, window_step)\n```\n\nThe above command uses a window size of 30Kbp, and a step size of 10Kbp. The\nnumber of windows will be just over 300,000, because one window begins at each\n10Kbp offset. But in contrast with the bins query, each of the windows will\nspan 30Kbp.\n\nThe output format for bins and windows queries is the same as for queries\nbased on a given set of genomic intervals. This means you can use the same\nfunctions to manipulate the levels, for example `get_wmean(i,j)` or\n`get_n_meth(i, j)`, and the conversion to numpy arrays.\n\nYou can get the metadata for MethBase2 methylomes available on the public\ntransferase server. Here is the command and along with what you might expect\nto see:\n\n```python\nmethbase_metadata = pyxfr_utils.load_methbase_metadata(\"hg38\")\nsum(methbase_metadata.sample_name == \"Sperm\")\n# 174\nlist(methbase_metadata.columns.values)\n# ['study', 'experiment', 'bsrate', 'meth', 'depth', 'mapping', 'uniq',...\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "The Transferase Python API",
    "version": "0.6.4",
    "project_urls": {
        "Documentation": "https://github.com/andrewdavidsmith/transferase",
        "Homepage": "https://github.com/andrewdavidsmith/transferase",
        "Issues": "https://github.com/andrewdavidsmith/transferase/issues",
        "Repository": "https://github.com/andrewdavidsmith/transferase"
    },
    "split_keywords": [
        "dna methylation",
        " epigenetics",
        " epigenomics",
        " genomics",
        " methylation",
        " methylome"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "bb15c6a6b2f32ecead74ff6a023efd8426b9e9b9d87b41ea7a344fe4a39a3012",
                "md5": "c26cee04f6582659297769a2a1a54ef3",
                "sha256": "c43d85a6dae6eda71f8c4dfd8b1bb5b8dce87c8dfbb118b2a5a61db4a5672f5f"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp312-none-macosx_13_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "c26cee04f6582659297769a2a1a54ef3",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.12",
            "size": 2261790,
            "upload_time": "2025-07-18T04:11:15",
            "upload_time_iso_8601": "2025-07-18T04:11:15.550713Z",
            "url": "https://files.pythonhosted.org/packages/bb/15/c6a6b2f32ecead74ff6a023efd8426b9e9b9d87b41ea7a344fe4a39a3012/pyxfr-0.6.4-cp312-none-macosx_13_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "7f7a605ff324a800753aa2a82585e5553e3b17aed1e99848b5fe68959f0ea790",
                "md5": "3e8f0721b4ffc199befbf586c841f408",
                "sha256": "db4251b186d59447815f018e53a6adec7be31be93c91209a0de9786583754c71"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp312-none-macosx_13_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "3e8f0721b4ffc199befbf586c841f408",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.12",
            "size": 2367578,
            "upload_time": "2025-07-18T04:11:18",
            "upload_time_iso_8601": "2025-07-18T04:11:18.008086Z",
            "url": "https://files.pythonhosted.org/packages/7f/7a/605ff324a800753aa2a82585e5553e3b17aed1e99848b5fe68959f0ea790/pyxfr-0.6.4-cp312-none-macosx_13_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "4c1a445f4e4744c4c19bda4fa1572b999cb274b5a0ceb40122ed0cb59631fa18",
                "md5": "d871aee1afec3c6985c765299ad4bfca",
                "sha256": "5a538279b4fa96da9b98ed324c19658948423bea3c553b0fe3e926d2d30018fc"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp312-none-manylinux_2_17_x86_64.whl",
            "has_sig": false,
            "md5_digest": "d871aee1afec3c6985c765299ad4bfca",
            "packagetype": "bdist_wheel",
            "python_version": "cp312",
            "requires_python": ">=3.12",
            "size": 2249113,
            "upload_time": "2025-07-18T04:11:20",
            "upload_time_iso_8601": "2025-07-18T04:11:20.344751Z",
            "url": "https://files.pythonhosted.org/packages/4c/1a/445f4e4744c4c19bda4fa1572b999cb274b5a0ceb40122ed0cb59631fa18/pyxfr-0.6.4-cp312-none-manylinux_2_17_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d2c496d14c4052355d7265d70f113ca3896fb6e3e1ae8cdff6fba7dde0c2417a",
                "md5": "04b63f7fb99bbbd892552a30c544f79a",
                "sha256": "da9c7e1d704ea8fffa752d54e8a590e21a6e6244ee06ddc273719eb7fbecd324"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp313-none-macosx_13_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "04b63f7fb99bbbd892552a30c544f79a",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.12",
            "size": 2261777,
            "upload_time": "2025-07-18T04:11:22",
            "upload_time_iso_8601": "2025-07-18T04:11:22.905708Z",
            "url": "https://files.pythonhosted.org/packages/d2/c4/96d14c4052355d7265d70f113ca3896fb6e3e1ae8cdff6fba7dde0c2417a/pyxfr-0.6.4-cp313-none-macosx_13_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "8beb1c525f5e8d19838c695f248bb0e3b25adc0745bdb82fbc80c8870f3b6f38",
                "md5": "4fc1dc18d57dd0043d655546e50da70f",
                "sha256": "7c94772bc99e951e61072cb5b971fea4c6e24bf8d8791b38f7e220a717b1fabb"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp313-none-macosx_13_0_x86_64.whl",
            "has_sig": false,
            "md5_digest": "4fc1dc18d57dd0043d655546e50da70f",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.12",
            "size": 2367608,
            "upload_time": "2025-07-18T04:11:25",
            "upload_time_iso_8601": "2025-07-18T04:11:25.182244Z",
            "url": "https://files.pythonhosted.org/packages/8b/eb/1c525f5e8d19838c695f248bb0e3b25adc0745bdb82fbc80c8870f3b6f38/pyxfr-0.6.4-cp313-none-macosx_13_0_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3bdc81b79aba05ee2623cf350d7a880553e55f4f4d60b19dccda48648409f340",
                "md5": "5e19240347bf7ee013fdc70ae0a11f50",
                "sha256": "292ceb741a50cee52336ead964a1d1cf8cf5ab0cfdbc870f9a9f46170e0932fe"
            },
            "downloads": -1,
            "filename": "pyxfr-0.6.4-cp313-none-manylinux_2_17_x86_64.whl",
            "has_sig": false,
            "md5_digest": "5e19240347bf7ee013fdc70ae0a11f50",
            "packagetype": "bdist_wheel",
            "python_version": "cp313",
            "requires_python": ">=3.12",
            "size": 2249099,
            "upload_time": "2025-07-18T04:11:27",
            "upload_time_iso_8601": "2025-07-18T04:11:27.500432Z",
            "url": "https://files.pythonhosted.org/packages/3b/dc/81b79aba05ee2623cf350d7a880553e55f4f4d60b19dccda48648409f340/pyxfr-0.6.4-cp313-none-manylinux_2_17_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-18 04:11:15",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "andrewdavidsmith",
    "github_project": "transferase",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pyxfr"
}
        
Elapsed time: 0.40471s