{"id":43312978,"url":"https://github.com/jakesherman/fonoapi","last_synced_at":"2026-02-01T21:38:42.575Z","repository":{"id":57431854,"uuid":"98315991","full_name":"jakesherman/fonoapi","owner":"jakesherman","description":":iphone: Python wrapper for FonoApi, which provides mobile device information","archived":false,"fork":false,"pushed_at":"2019-10-08T14:51:41.000Z","size":35,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-09-28T00:23:20.359Z","etag":null,"topics":["api","api-documentation","api-wrapper","mobile-analytics"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jakesherman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-25T14:34:32.000Z","updated_at":"2020-02-09T18:08:51.000Z","dependencies_parsed_at":"2022-09-02T12:40:28.317Z","dependency_job_id":null,"html_url":"https://github.com/jakesherman/fonoapi","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/jakesherman/fonoapi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakesherman%2Ffonoapi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakesherman%2Ffonoapi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakesherman%2Ffonoapi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakesherman%2Ffonoapi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jakesherman","download_url":"https://codeload.github.com/jakesherman/fonoapi/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jakesherman%2Ffonoapi/sbom","scorecard":{"id":502672,"data":{"date":"2025-08-11","repo":{"name":"github.com/jakesherman/fonoapi","commit":"01248a6d3a9f56fa9613f387ae66820a73b48742"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"11 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2021-856 / GHSA-5545-2q6w-2gh6","Warn: Project is vulnerable to: GHSA-6p56-wp2h-9hxr","Warn: Project is vulnerable to: PYSEC-2019-108 / GHSA-9fq2-x9r6-wfmf","Warn: Project is vulnerable to: PYSEC-2021-857 / GHSA-f7c7-j99h-c22f","Warn: Project is vulnerable to: GHSA-fpfv-jqm9-f5jm","Warn: Project is vulnerable to: PYSEC-2017-1 / GHSA-frgw-fgh6-9g52","Warn: Project is vulnerable to: PYSEC-2020-73","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: PYSEC-2018-28 / GHSA-x84v-xcm2-53pg"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T22:22:33.314Z","repository_id":57431854,"created_at":"2025-08-19T22:22:33.314Z","updated_at":"2025-08-19T22:22:33.314Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28991777,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T20:57:35.821Z","status":"ssl_error","status_checked_at":"2026-02-01T20:57:29.580Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","api-documentation","api-wrapper","mobile-analytics"],"created_at":"2026-02-01T21:38:42.006Z","updated_at":"2026-02-01T21:38:42.570Z","avatar_url":"https://github.com/jakesherman.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fonoapi - Python wrapper around Fono Api\n\nhttps://fonoapi.freshpixl.com/\n\nThe Fono Api is an API which can provide **mobile device descriptions** such as model, brand, cpu, gpu, dimensions, release date, and more. This package package provides a convenient wrapper around the Fono Api via the `requests` package.\n\nThe [API](https://github.com/shakee93/fonoapi) was developed [shakee93](https://github.com/shakee93). This package started off as a fork of a [package](https://github.com/jesusperiago/fonoapi) written by [jesusperiago](https://github.com/jesusperiago) - I added the `getlatest` method to take advantage of the [getlatest API method](https://fonoapi.freshpixl.com/v1/getlatest), and made a lot of under-the-hood organizational changes in order to submit this package to `PyPI` to make it more easily available.\n\n### Installation\n\n```bash\npip install fonoapi\n```\n\n## Tutorial\n\nBefore starting, make sure to [generate an API token](https://fonoapi.freshpixl.com/token/generate#). We are going to start by creating a `FonoAPI` object, which we initialize with our API token in order to start interacting with the Fono Api:\n\n```python\nfrom fonoapi import FonoAPI\nfon = FonoAPI('TOKEN')\n```\n\n### Getting devices matching a specific device name\n\nImagine we have a specific device in mind, say the **iPhone 7**, that we wish to learn more about. We can use the `getdevice` method to return information from the API about this specific device:\n\n```python\ndevice = 'iPhone 7'\niPhone_7 = fon.getdevice(device)\nprint(iPhone_7)\n```\n\n    | Devices Object: mobile device data|\n    ------------------------------------\n    Number of devices : 4\n    Input parameters : {'device': 'iPhone 7', 'position': None, 'brand': None}\n\nThe `getdevice` method returns a `Devices` object, an object that makes it easy to retrieve data from the Fono Api. Printing out the object gives us information on how many devices we retrieved information for, and what parameters were passed to `getdevice`.\n\nWe can output the data in the `Devices` object in three ways by calling the following methods on the `Devices` object:\n- `dataframe` : As a Pandas DataFrame, where each row corresponds to a phone\n- `list_of_dicts` : As a list of dictionaries, with one dict per phone\n- `list_of_lists : `As a list of lists, where each sublist corresponds to a phone\n\nNot all mobile devices in the Fono Api have every possible attribute associated with them. In the case of `list_of_dicts`, only the attributes associated with each phone is included in each phone's dictionary. In the cases of `dataframe` or `list_of_lists`, you may choose specific columns to include for every phone (if you don't specify columns to include, all possible columns are included). In this case, devices with no value for a particular column will have values of `numpy.nan` or `None`, respectively.\n\nIn our case, let's look at the attributes `Brand, DeviceName, body_c` for the devices returned by our API call:\n\n```python\nprint(iPhone_7.dataframe(['Brand', 'DeviceName', 'body_c']))\n```\n\nBrand | DeviceName | body_c |\n| ---       | ---                           | ---  |\n| Prestigio | Prestigio MultiPhone 7500     | None |\n| Prestigio | Prestigio MultiPhone 7600 Duo | None |\n| Apple     | Apple iPhone 7 Plus           | - IP67 certified - dust and water resistant\\r\\... |\n| Apple     | Apple iPhone 7                | - IP67 certified - dust and water resistant\\r\\... |\n\n- There are two non-Apple devices by Prestigio in the list! The model names of the two devices begin with Prestigio Mult**iPhone 7**500, so it's understandable that they would show up when we searched for the string 'iPhone 7'\n- The two Prestigio devices don't have a value for the `body_c` attribute, so they have `NaN` values for that column\n\nIn order to get rid of the Prestigio devices in our results, all we have to do is specify the `brand` argument to the `getdevice` method:\n\n```python\ndevice, brand = 'iPhone 7', 'Apple'\niPhone_7 = (\n    fon\n    .getdevice(device, brand)\n    .dataframe(['Brand', 'DeviceName', 'body_c'])\n)\nprint(iPhone_7)\n```\n\nBrand   | DeviceName          | body_c |\n| ---   | --- | --- |\n| Apple | Apple iPhone 7 Plus | - IP67 certified - dust and water resistant\\r\\... |\n| Apple | Apple iPhone 7      | - IP67 certified - dust and water resistant\\r\\... |\n\n### Getting the latest devices for a specific brand\n\n`getlatest` will return information about the most recent devices for a given brand. For example, let's imagine that we wish to get data on the lastest mobile devices from Apple:\n\n```python\nbrand = 'Apple'\nlatest_apples = (\n    fon\n    .getlatest(brand, limit=5)\n    .dataframe(['DeviceName', 'announced', '_3_5mm_jack_', 'talk_time'])\n)\nprint(latest_apples)\n```\n\nDeviceName                        | announced | _3_5mm_jack_ | talk_time |\n| --- | --- | --- | --- |\n| Apple iPad Pro 12.9             | 2017, June | Yes | Up to 10 h (multimedia) |\n| Apple iPad Pro 10.5             | 2017, June | Yes | Up to 10 h (multimedia) |\n| Apple iPad 9.7                  | 2017, March | Yes | Up to 10 h (multimedia) |\n| Apple iPhone 8                  | Not announced yet | No | None |\n| Apple Watch Series 1 Sport 42mm | 2016, September | No | Up to 3 h 40 min |\n\nFinally, perhaps we want to retrieve data on the most recent mobile devices for a whole host of brands ... but we're not sure if we spelled the brand names correctly. By default, when `getlatest` (or `getdevice`) don't retrieve any results from the API, they return an empty `Devices` object. That empty `Devices` object has a value of `True` for its `null` class attribute (and a value of `False` for its `not_null` class attribute). For example:\n\n```python\nbrands = ['Apple', 'Samsung', 'LG', 'Huawei', 'SonyEricsson']\nbrand_devices = []\nfor brand in brands:\n    devices = fon.getlatest(brand, limit=3)\n    brand_devices.append(devices)\n```\n\n    Could not retrieve brand information for brand SonyEricsson from the Fono API.\n\n\n```python\n# Print out the Devices object for SonyEricsson\nprint(brand_devices[-1])\n```\n\n    | Devices Object: mobile device data|\n    ------------------------------------\n    Number of devices : 0\n    Input parameters : {'brand': 'SonyEricsson', 'limit': 3}\n\nThe problem here is that there is no brand SonyEricsson in the API, the correct name would have been just Ericsson. Let's say that we want to take all of the device information that we stored in `brand_devices`, a list of `Devices` object, and create a single Pandas DataFrame:\n\n```python\nimport pandas as pd\ncolumns = ['Brand', 'DeviceName', 'announced', 'talk_time']\nbrand_devices = [devices.dataframe(columns) for devices\n                 in brand_devices if devices.not_null]\nall_brands = pd.concat(brand_devices)\nprint(all_brands)\n```\n\nBrand     | DeviceName | announced | talk_time |\n| --- | --- | --- | --- |\n| Apple   | Apple iPad Pro 12.9 | 2017, June | Up to 10 h (multimedia) |\n| Apple   | Apple iPad Pro 10.5 | 2017, June | Up to 10 h (multimedia) |\n| Apple   | Apple iPad 9.7 | 2017, March | Up to 10 h (multimedia) |\n| Samsung | Samsung Galaxy Tab A 8.0 (2017) | Not announced yet | NaN |\n| Samsung | Samsung Galaxy C10 | Not announced yet | NaN |\n| Samsung | Samsung Galaxy J5 (2017) | 2017, June | Up to 21 h (3G) |\n| LG      | LG V30 | Not announced yet | NaN |\n| LG      | LG X venture | 2017, May | Up to 24 h (3G) |\n| LG      | LG Stylo 3 Plus | 2017, May | Up to 14 h (3G) |\n| Huawei  | Huawei MediaPad M3 Lite 8 | 2017, June | NaN |\n| Huawei  | Huawei Honor 9 | 2017, June | NaN |\n| Huawei  | Huawei nova 2 plus | 2017, May | NaN |\n\n## Tests\n\nPass a valid API token to `py.test` to run the package's unit tests.\n\n```bash\npy.test --apitoken \u003cTOKEN\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakesherman%2Ffonoapi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjakesherman%2Ffonoapi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjakesherman%2Ffonoapi/lists"}