├── .github └── stale.yml ├── .gitignore ├── .travis.yml ├── CHANGES.rst ├── CONTRIBUTORS.rst ├── LICENSE ├── LICENSE-REQUESTS ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs └── source │ ├── _static │ └── yarg.png │ ├── _templates │ ├── sidebarintro.html │ └── sidebarlogo.html │ ├── _themes │ ├── LICENSE │ ├── README │ └── jinja │ │ ├── layout.html │ │ ├── relations.html │ │ ├── static │ │ ├── FiraMono-Regular.woff │ │ ├── FiraSans-Bold.woff │ │ ├── FiraSans-Light.woff │ │ ├── FiraSans-Regular.woff │ │ ├── FiraSans-SemiBold.woff │ │ └── jinja.css_t │ │ └── theme.conf │ ├── api-rss.rst │ ├── api-search.rst │ ├── changelog.rst │ ├── conf.py │ ├── contributors.rst │ ├── faq.rst │ ├── index.rst │ ├── intro.rst │ └── testing.rst ├── requirements-docs.txt ├── requirements-pypi.txt ├── requirements-test.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── newest.xml ├── package.json ├── package_no_homepage_bugtrack_one_release.json ├── test_client.py ├── test_exceptions.py ├── test_package.py ├── test_parse.py ├── test_release.py └── updated.xml ├── tox.ini └── yarg ├── __about__.py ├── __init__.py ├── client.py ├── exceptions.py ├── package.py ├── parse.py └── release.py /.github/stale.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/.github/stale.yml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build/ 4 | .tox 5 | dist/ 6 | .coverage 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | python: 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | - "pypy" 10 | - "pypy3" 11 | dist: trusty 12 | sudo: false 13 | group: edge 14 | install: 15 | - make deps 16 | - make deps-test 17 | script: 18 | - coverage run --source=yarg runtests.py 19 | after_success: 20 | - coveralls 21 | - curl -X POST https://readthedocs.org/build/yarg 22 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Release History 2 | =============== 3 | 4 | 0.1.10 (2024-08-09) 5 | - Python 3.12 support 6 | 7 | 0.1.9 (2014-08-11) 8 | ------------------ 9 | 10 | Splatting bugs 11 | ~~~~~~~~~~~~~~ 12 | 13 | - Added `decode` call on the response object for Python 3 in 14 | :meth:`yarg.newest_packages` and :meth:`yarg.latest_updated_packages`. 15 | 16 | 0.1.8 (2014-08-10) 17 | ------------------ 18 | 19 | Splatting bugs 20 | ~~~~~~~~~~~~~~ 21 | 22 | - Integration issue with Python 3, requests, yarg and JSON. Attempt to decode 23 | requests response if decode attribute exists. 24 | 25 | 0.1.6 & 0.1.7 (2014-08-10) 26 | -------------------------- 27 | 28 | Splatting bugs 29 | ~~~~~~~~~~~~~~ 30 | 31 | - Bug in setup.py causing installs to fail for sdist (source) releases. 32 | 33 | 0.1.5 (2014-08-10) 34 | ------------------ 35 | 36 | API changes 37 | ~~~~~~~~~~~ 38 | 39 | - Changed sort order of :attr:`yarg.package.Package.release_ids` to sort 40 | based on the upload time of the release ID. 41 | 42 | Splatting bugs 43 | ~~~~~~~~~~~~~~ 44 | 45 | - :attr:`yarg.package.Package.latest_release_id` will now return the latest 46 | release ID from the PyPI info source, rather than the final list item in 47 | :attr:`yarg.package.Package.release_ids`. 48 | 49 | Addtionally :attr:`yarg.package.Package.latest_release` will do the same as 50 | it gets the latest release information from 51 | :attr:`yarg.package.Package.latest_release_id`. 52 | 53 | 0.1.4 (2014-08-09) 54 | ------------------ 55 | 56 | API changes 57 | ~~~~~~~~~~~ 58 | 59 | - New method :meth:`yarg.newest_packages` for querying new packages 60 | from the PyPI RSS feed. 61 | - New method :meth:`yarg.latest_updated_packages` for querying 62 | the latest updated packages from the PyPI RSS feed. 63 | 64 | Other 65 | ~~~~~ 66 | 67 | - Additional test coverage 68 | - Additional documentation coverage 69 | 70 | 0.1.2 (2014-08-08) 71 | ------------------ 72 | 73 | Bug fixes 74 | ~~~~~~~~~ 75 | 76 | - :meth:`yarg.get` will now raise an Exception for errors **including** 77 | 300 and above. Previously only raised for above 300. 78 | - Fix an issue on Python 3.X and PyPy3 where 79 | :class:`yarg.exceptions.HTTPError` was using a method that was 80 | removed in Python 3. 81 | - Added dictionary key lookups for `home_page`, `bugtrack_url` 82 | and `docs_url`. Caused `KeyError` exceptions if they were not 83 | returned by PyPI. 84 | 85 | Other 86 | ~~~~~ 87 | 88 | - More test coverage. 89 | 90 | 0.1.1 (2014-08-08) 91 | ------------------ 92 | 93 | API changes 94 | ~~~~~~~~~~~ 95 | 96 | - New :class:`yarg.package.Package` property `has_wheel`. 97 | - New :class:`yarg.package.Package` property `has_egg`. 98 | - New :class:`yarg.package.Package` property `has_source`. 99 | - New :class:`yarg.package.Package` property `python_versions`. 100 | - New :class:`yarg.package.Package` property `python_implementations`. 101 | - Added :class:`yarg.exceptions.HTTPError` to :mod:`yarg.__init__` 102 | for easier access. 103 | - Added :meth:`yarg.json2package` to :mod:`yarg.__init__` to expose it for 104 | use. 105 | 106 | 0.1.0 (2014-08-08) 107 | ------------------ 108 | 109 | - Initial release 110 | -------------------------------------------------------------------------------- /CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============ 3 | 4 | Developer 5 | --------- 6 | 7 | - Kura 8 | 9 | Requests library 10 | ---------------- 11 | 12 | - Kenneth Reitz 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Kura 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE-REQUESTS: -------------------------------------------------------------------------------- 1 | Copyright 2014 Kenneth Reitz 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTORS.rst README.rst CHANGES.rst LICENSE LICENSE-REQUESTS 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean coverage deps deps-docs deps-test docs flake8 install pypi rtd test tox uninstall 2 | 3 | clean: 4 | find . -name "*.pyc" -delete 5 | 6 | coverage: deps-test 7 | coverage run -m pytest 8 | 9 | deps: 10 | pip install -r requirements.txt 11 | 12 | deps-docs: 13 | pip install -r requirements-docs.txt 14 | 15 | deps-test: 16 | pip install -r requirements-test.txt 17 | 18 | docs: deps-docs 19 | sphinx-build -b html docs/source docs/build 20 | 21 | flake8: 22 | pip install flake8 23 | flake8 yarg --show-source 24 | 25 | install: 26 | python setup.py install 27 | 28 | pypi: rtd 29 | pip install -r requirements-pypi.txt 30 | python setup.py register 31 | python setup.py sdist bdist_wheel 32 | twine upload dist/* 33 | 34 | rtd: 35 | curl -X POST https://readthedocs.org/build/yarg 36 | 37 | test: deps deps-test 38 | pytest 39 | 40 | tox: deps deps-test 41 | detox 42 | 43 | uninstall: 44 | pip uninstall yarg 45 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | yarg(1) -- A semi hard Cornish cheese, also queries PyPI 2 | ======================================================== 3 | 4 | .. image:: https://img.shields.io/travis/kura/yarg.svg?style=flat 5 | 6 | .. image:: https://img.shields.io/coveralls/kura/yarg.svg?style=flat 7 | 8 | Yarg is a PyPI client. 9 | 10 | .. code-block:: python 11 | 12 | >>> import yarg 13 | >>> package = yarg.get("yarg") 14 | >>> package.name 15 | u'yarg' 16 | >>> package.author 17 | Author(name=u'Kura', email=u'kura@kura.io') 18 | 19 | Full documentation is at . 20 | 21 | Yarg is released under the `MIT license 22 | `_. The `source code is on 23 | GitHub `_ and `issues are also tracked on 24 | GitHub `_. 25 | -------------------------------------------------------------------------------- /docs/source/_static/yarg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_static/yarg.png -------------------------------------------------------------------------------- /docs/source/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 |

Useful Links

2 | 8 | -------------------------------------------------------------------------------- /docs/source/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | Fork me on GitHub 2 | 5 | -------------------------------------------------------------------------------- /docs/source/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /docs/source/_themes/README: -------------------------------------------------------------------------------- 1 | Flask Sphinx Styles 2 | =================== 3 | 4 | This repository contains sphinx styles for Flask and Flask related 5 | projects. To use this style in your Sphinx documentation, follow 6 | this guide: 7 | 8 | 1. put this folder as _themes into your docs folder. Alternatively 9 | you can also use git submodules to check out the contents there. 10 | 2. add this to your conf.py: 11 | 12 | sys.path.append(os.path.abspath('_themes')) 13 | html_theme_path = ['_themes'] 14 | html_theme = 'flask' 15 | 16 | The following themes exist: 17 | 18 | - 'flask' - the standard flask documentation theme for large 19 | projects 20 | - 'flask_small' - small one-page theme. Intended to be used by 21 | very small addon libraries for flask. 22 | 23 | The following options exist for the flask_small theme: 24 | 25 | [options] 26 | index_logo = '' filename of a picture in _static 27 | to be used as replacement for the 28 | h1 in the index.rst file. 29 | index_logo_height = 120px height of the index logo 30 | github_fork = '' repository name on github for the 31 | "fork me" badge 32 | -------------------------------------------------------------------------------- /docs/source/_themes/jinja/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block relbar2 %}{% endblock %} 3 | {%- block footer %} 4 | 8 | {%- endblock %} 9 | -------------------------------------------------------------------------------- /docs/source/_themes/jinja/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/FiraMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_themes/jinja/static/FiraMono-Regular.woff -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/FiraSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_themes/jinja/static/FiraSans-Bold.woff -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/FiraSans-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_themes/jinja/static/FiraSans-Light.woff -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/FiraSans-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_themes/jinja/static/FiraSans-Regular.woff -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/FiraSans-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/docs/source/_themes/jinja/static/FiraSans-SemiBold.woff -------------------------------------------------------------------------------- /docs/source/_themes/jinja/static/jinja.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * jinja.css_t 3 | * ~~~~~~~~~~~ 4 | * 5 | * :copyright: Copyright 2011 by Armin Ronacher. 6 | * :license: Flask Design License, see LICENSE for details. 7 | */ 8 | 9 | {% set page_width = '940px' %} 10 | {% set sidebar_width = '220px' %} 11 | {% set font_family = 'FiraSans, Georgia, serif' %} 12 | {% set header_font_family = 'FiraSans, Georgia, serif' %} 13 | 14 | @import url("https://yarg.readthedocs.org/_static/basic.css"); 15 | 16 | @font-face { 17 | font-family: 'FiraSans'; 18 | font-style: normal; 19 | font-weight: 400; 20 | src: local('FiraSans'), local('FiraSans-Regular'), url(FiraSans-Regular.woff) format('woff'); 21 | } 22 | 23 | @font-face { 24 | font-family: 'FiraSans'; 25 | font-style: normal; 26 | font-weight: 600; 27 | src: local('FiraSans SemiBold'), local('FiraSans-SemiBold'), url(FiraSans-SemiBold.woff) format('woff'); 28 | } 29 | 30 | @font-face { 31 | font-family: 'FiraSans'; 32 | font-style: normal; 33 | font-weight: 700; 34 | src: local('FiraSans Bold'), local('FiraSans-Bold'), url(FiraSans-Bold.woff) format('woff'); 35 | } 36 | 37 | @font-face { 38 | font-family: 'FiraMono'; 39 | font-style: normal; 40 | font-weight: 400; 41 | src: local('FiraMono'), local('FiraMono-Regular'), url(FiraMono-Regular.woff) format('woff'); 42 | } 43 | 44 | /* -- page layout ----------------------------------------------------------- */ 45 | 46 | body { 47 | font-family: {{ font_family }}; 48 | font-size: 17px; 49 | background-color: white; 50 | color: #000; 51 | margin: 0; 52 | padding: 0; 53 | } 54 | 55 | div.document { 56 | width: {{ page_width }}; 57 | margin: 30px auto 0 auto; 58 | } 59 | 60 | div.documentwrapper { 61 | float: left; 62 | width: 100%; 63 | } 64 | 65 | div.bodywrapper { 66 | margin: 0 0 0 {{ sidebar_width }}; 67 | } 68 | 69 | div.sphinxsidebar { 70 | width: {{ sidebar_width }}; 71 | } 72 | 73 | hr { 74 | border: 1px solid #B1B4B6; 75 | } 76 | 77 | div.body { 78 | background-color: #ffffff; 79 | color: #3E4349; 80 | padding: 0 30px 0 30px; 81 | } 82 | 83 | img.floatingflask { 84 | padding: 0 0 10px 10px; 85 | float: right; 86 | } 87 | 88 | div.footer { 89 | width: {{ page_width }}; 90 | margin: 20px auto 30px auto; 91 | font-size: 14px; 92 | color: #888; 93 | text-align: right; 94 | } 95 | 96 | div.footer a { 97 | color: #888; 98 | } 99 | 100 | div.related { 101 | display: none; 102 | } 103 | 104 | div.sphinxsidebar a { 105 | color: #444; 106 | text-decoration: none; 107 | } 108 | 109 | div.sphinxsidebar { 110 | font-size: 15px; 111 | line-height: 1.5; 112 | } 113 | 114 | div.sphinxsidebarwrapper { 115 | padding: 18px 10px; 116 | } 117 | 118 | div.sphinxsidebarwrapper p.logo { 119 | padding: 0 0 20px 0; 120 | margin: 0; 121 | text-align: center; 122 | } 123 | 124 | div.sphinxsidebar h3, 125 | div.sphinxsidebar h4 { 126 | font-family: {{ font_family }}; 127 | color: #444; 128 | font-size: 24px; 129 | font-weight: normal; 130 | margin: 0 0 5px 0; 131 | padding: 0; 132 | } 133 | 134 | div.sphinxsidebar h4 { 135 | font-size: 20px; 136 | } 137 | 138 | div.sphinxsidebar h3 a { 139 | color: #444; 140 | } 141 | 142 | div.sphinxsidebar p.logo a, 143 | div.sphinxsidebar h3 a, 144 | div.sphinxsidebar p.logo a:hover, 145 | div.sphinxsidebar h3 a:hover { 146 | border: none; 147 | } 148 | 149 | div.sphinxsidebar p { 150 | color: #555; 151 | margin: 10px 0; 152 | } 153 | 154 | div.sphinxsidebar ul { 155 | margin: 10px 0; 156 | padding: 0; 157 | color: #000; 158 | } 159 | 160 | div.sphinxsidebar input { 161 | border: 1px solid #ccc; 162 | font-family: {{ font_family }}; 163 | font-size: 14px; 164 | } 165 | 166 | div.sphinxsidebar form.search input[name="q"] { 167 | width: 130px; 168 | } 169 | 170 | /* -- body styles ----------------------------------------------------------- */ 171 | 172 | a { 173 | color: #aa0000; 174 | text-decoration: underline; 175 | } 176 | 177 | a:hover { 178 | color: #dd0000; 179 | text-decoration: underline; 180 | } 181 | 182 | div.body h1, 183 | div.body h2, 184 | div.body h3, 185 | div.body h4, 186 | div.body h5, 187 | div.body h6 { 188 | font-family: {{ header_font_family }}; 189 | font-weight: 600; 190 | margin: 30px 0px 10px 0px; 191 | padding: 0; 192 | color: black; 193 | } 194 | 195 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 196 | div.body h2 { font-size: 180%; } 197 | div.body h3 { font-size: 150%; } 198 | div.body h4 { font-size: 130%; } 199 | div.body h5 { font-size: 100%; } 200 | div.body h6 { font-size: 100%; } 201 | 202 | a.headerlink { 203 | color: #ddd; 204 | padding: 0 4px; 205 | text-decoration: none; 206 | } 207 | 208 | a.headerlink:hover { 209 | color: #444; 210 | background: #eaeaea; 211 | } 212 | 213 | div.body p, div.body dd, div.body li { 214 | line-height: 1.4em; 215 | } 216 | 217 | div.admonition { 218 | background: #fafafa; 219 | margin: 20px -30px; 220 | padding: 10px 30px; 221 | border-top: 1px solid #ccc; 222 | border-bottom: 1px solid #ccc; 223 | } 224 | 225 | div.admonition tt.xref, div.admonition a tt { 226 | border-bottom: 1px solid #fafafa; 227 | } 228 | 229 | dd div.admonition { 230 | margin-left: -60px; 231 | padding-left: 60px; 232 | } 233 | 234 | div.admonition p.admonition-title { 235 | font-family: {{ font_family }}; 236 | font-weight: normal; 237 | font-size: 24px; 238 | margin: 0 0 10px 0; 239 | padding: 0; 240 | line-height: 1; 241 | } 242 | 243 | div.admonition p.last { 244 | margin-bottom: 0; 245 | } 246 | 247 | div.highlight { 248 | background-color: white; 249 | } 250 | 251 | dt:target, .highlight { 252 | background: #FAF3E8; 253 | } 254 | 255 | div.note { 256 | background-color: #eee; 257 | border: 1px solid #ccc; 258 | } 259 | 260 | div.seealso { 261 | background-color: #ffc; 262 | border: 1px solid #ff6; 263 | } 264 | 265 | div.topic { 266 | background-color: #eee; 267 | } 268 | 269 | p.admonition-title { 270 | display: inline; 271 | } 272 | 273 | p.admonition-title:after { 274 | content: ":"; 275 | } 276 | 277 | pre, tt { 278 | font-family: 'FiraMono', monospace; 279 | font-size: 0.85em; 280 | } 281 | 282 | img.screenshot { 283 | } 284 | 285 | tt.descname, tt.descclassname { 286 | font-size: 0.95em; 287 | } 288 | 289 | tt.descname { 290 | padding-right: 0.08em; 291 | } 292 | 293 | img.screenshot { 294 | -moz-box-shadow: 2px 2px 4px #eee; 295 | -webkit-box-shadow: 2px 2px 4px #eee; 296 | box-shadow: 2px 2px 4px #eee; 297 | } 298 | 299 | table.docutils { 300 | border: 1px solid #888; 301 | -moz-box-shadow: 2px 2px 4px #eee; 302 | -webkit-box-shadow: 2px 2px 4px #eee; 303 | box-shadow: 2px 2px 4px #eee; 304 | } 305 | 306 | table.docutils td, table.docutils th { 307 | border: 1px solid #888; 308 | padding: 0.25em 0.7em; 309 | } 310 | 311 | table.field-list, table.footnote { 312 | border: none; 313 | -moz-box-shadow: none; 314 | -webkit-box-shadow: none; 315 | box-shadow: none; 316 | } 317 | 318 | table.footnote { 319 | margin: 15px 0; 320 | width: 100%; 321 | border: 1px solid #eee; 322 | background: #fdfdfd; 323 | font-size: 0.9em; 324 | } 325 | 326 | table.footnote + table.footnote { 327 | margin-top: -15px; 328 | border-top: none; 329 | } 330 | 331 | table.field-list th { 332 | padding: 0 0.8em 0 0; 333 | } 334 | 335 | table.field-list td { 336 | padding: 0; 337 | } 338 | 339 | table.footnote td.label { 340 | width: 0px; 341 | padding: 0.3em 0 0.3em 0.5em; 342 | } 343 | 344 | table.footnote td { 345 | padding: 0.3em 0.5em; 346 | } 347 | 348 | dl { 349 | margin: 0; 350 | padding: 0; 351 | } 352 | 353 | dl dd { 354 | margin-left: 30px; 355 | } 356 | 357 | blockquote { 358 | margin: 0 0 0 30px; 359 | padding: 0; 360 | } 361 | 362 | ul, ol { 363 | margin: 10px 0 10px 30px; 364 | padding: 0; 365 | } 366 | 367 | pre { 368 | background: #eee; 369 | padding: 7px 30px; 370 | margin: 15px -30px; 371 | line-height: 1.3em; 372 | } 373 | 374 | dl pre, blockquote pre, li pre { 375 | margin-left: -60px; 376 | padding-left: 60px; 377 | } 378 | 379 | dl dl pre { 380 | margin-left: -90px; 381 | padding-left: 90px; 382 | } 383 | 384 | tt { 385 | background-color: #E8EFF0; 386 | color: #222; 387 | /* padding: 1px 2px; */ 388 | } 389 | 390 | tt.xref, a tt { 391 | background-color: #E8EFF0; 392 | border-bottom: 1px solid white; 393 | } 394 | 395 | a.reference { 396 | text-decoration: none; 397 | } 398 | 399 | a.footnote-reference { 400 | text-decoration: none; 401 | font-size: 0.7em; 402 | vertical-align: top; 403 | } 404 | 405 | a:hover tt { 406 | background: #EEE; 407 | } 408 | -------------------------------------------------------------------------------- /docs/source/_themes/jinja/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = jinja.css 4 | -------------------------------------------------------------------------------- /docs/source/api-rss.rst: -------------------------------------------------------------------------------- 1 | API - Package RSS Feeds 2 | ======================= 3 | 4 | .. module:: yarg 5 | 6 | Query Interface 7 | --------------- 8 | 9 | .. autofunction:: newest_packages 10 | 11 | .. autofunction:: latest_updated_packages 12 | 13 | Package Object 14 | -------------- 15 | 16 | .. autoclass:: yarg.parse.Package 17 | :inherited-members: 18 | :member-order: bysource 19 | -------------------------------------------------------------------------------- /docs/source/api-search.rst: -------------------------------------------------------------------------------- 1 | API - Package Lookup 2 | ==================== 3 | 4 | .. module:: yarg 5 | 6 | Query Interface 7 | --------------- 8 | 9 | .. autofunction:: get 10 | 11 | Package Object 12 | -------------- 13 | 14 | .. autoclass:: yarg.package.Package 15 | :inherited-members: 16 | :member-order: bysource 17 | 18 | Release Object 19 | -------------- 20 | 21 | .. autoclass:: yarg.release.Release 22 | :inherited-members: 23 | :member-order: bysource 24 | 25 | Exceptions and Errors 26 | --------------------- 27 | 28 | .. autoclass:: yarg.exceptions.HTTPError 29 | :inherited-members: 30 | :member-order: bysource 31 | -------------------------------------------------------------------------------- /docs/source/changelog.rst: -------------------------------------------------------------------------------- 1 | ../../CHANGES.rst -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # yarg documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Aug 8 11:10:17 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import datetime 16 | import os 17 | import sys 18 | 19 | from yarg.__about__ import __version__ 20 | 21 | # If extensions (or modules to document with autodoc) are in another directory, 22 | # add these directories to sys.path here. If the directory is relative to the 23 | # documentation root, use os.path.abspath to make it absolute, like shown here. 24 | #sys.path.insert(0, os.path.abspath('.')) 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.doctest', 37 | 'sphinx.ext.todo', 38 | 'sphinx.ext.coverage', 39 | 'sphinx.ext.viewcode', 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix of source filenames. 46 | source_suffix = '.rst' 47 | 48 | # The encoding of source files. 49 | #source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = u'yarg' 56 | copyright = u'{0}, Kura'.format(datetime.datetime.now().year) 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = __version__ 64 | # The full version, including alpha/beta/rc tags. 65 | release = __version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | #language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = [] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | #default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | #add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | #add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | #show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = 'sphinx' 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | #modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built documents. 103 | #keep_warnings = False 104 | 105 | 106 | # -- Options for HTML output ---------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | sys.path.append(os.path.abspath('_theme')) 111 | html_theme_path = ['_themes', ] 112 | html_theme = 'jinja' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | #html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | #html_theme_path = [] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | #html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | #html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | #html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | #html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['_static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | #html_extra_path = [] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | #html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | #html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | #html_file_suffix = None 188 | 189 | # Output file base name for HTML help builder. 190 | htmlhelp_basename = 'yargdoc' 191 | 192 | 193 | # -- Options for LaTeX output --------------------------------------------- 194 | 195 | latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | #'papersize': 'letterpaper', 198 | 199 | # The font size ('10pt', '11pt' or '12pt'). 200 | #'pointsize': '10pt', 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #'preamble': '', 204 | } 205 | 206 | # Grouping the document tree into LaTeX files. List of tuples 207 | # (source start file, target name, title, 208 | # author, documentclass [howto, manual, or own class]). 209 | latex_documents = [ 210 | ('index', 'yarg.tex', u'yarg Documentation', 211 | u'Kura', 'manual'), 212 | ] 213 | 214 | # The name of an image file (relative to this directory) to place at the top of 215 | # the title page. 216 | #latex_logo = None 217 | 218 | # For "manual" documents, if this is true, then toplevel headings are parts, 219 | # not chapters. 220 | #latex_use_parts = False 221 | 222 | # If true, show page references after internal links. 223 | #latex_show_pagerefs = False 224 | 225 | # If true, show URL addresses after external links. 226 | #latex_show_urls = False 227 | 228 | # Documents to append as an appendix to all manuals. 229 | #latex_appendices = [] 230 | 231 | # If false, no module index is generated. 232 | #latex_domain_indices = True 233 | 234 | 235 | # -- Options for manual page output --------------------------------------- 236 | 237 | # One entry per manual page. List of tuples 238 | # (source start file, name, description, authors, manual section). 239 | man_pages = [ 240 | ('index', 'yarg', u'yarg Documentation', 241 | [u'Kura'], 1) 242 | ] 243 | 244 | # If true, show URL addresses after external links. 245 | #man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ------------------------------------------- 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | texinfo_documents = [ 254 | ('index', 'yarg', u'yarg Documentation', 255 | u'Kura', 'yarg', 'One line description of project.', 256 | 'Miscellaneous'), 257 | ] 258 | 259 | # Documents to append as an appendix to all manuals. 260 | #texinfo_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | #texinfo_domain_indices = True 264 | 265 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 266 | #texinfo_show_urls = 'footnote' 267 | 268 | # If true, do not generate a @detailmenu in the "Top" node's menu. 269 | #texinfo_no_detailmenu = False 270 | 271 | html_sidebars = { 272 | 'index': ['sidebarlogo.html', 'sidebarintro.html', 'sourcelink.html', 273 | 'searchbox.html', ], 274 | '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 275 | 'sourcelink.html', 'searchbox.html', ] 276 | } 277 | -------------------------------------------------------------------------------- /docs/source/contributors.rst: -------------------------------------------------------------------------------- 1 | ../../CONTRIBUTORS.rst -------------------------------------------------------------------------------- /docs/source/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | === 3 | 4 | My package isn't being found 5 | ---------------------------- 6 | 7 | PyPI is cAsE sEnsItIvE, make sure you're looking for the package with 8 | the correct case. 9 | 10 | .. code-block:: python 11 | 12 | >>> yarg.get('Yarg') 13 | Traceback (most recent call last): 14 | File "", line 1, in 15 | File "yarg/client.py", line 52, in get 16 | reason=response.reason) 17 | yarg.exceptions.HTTPError: 404 Not Found 18 | 19 | >>> yarg.get('yarg') 20 | 21 | 22 | 23 | Why is my `docs` url empty? 24 | --------------------------- 25 | 26 | `docs_url` is only available when you're using `pythonhosted.org 27 | `_. 28 | 29 | Why does my `docs` url have an extra slash? 30 | ------------------------------------------- 31 | 32 | This an issue with PyPI itself, it returns an additional slash inside 33 | the URL to your pythonhosted docs. 34 | 35 | .. code-block:: python 36 | 37 | >>> yarg.get('yarg').docs 38 | u'http://pythonhosted.org//yarg' 39 | 40 | Why is my `bugtracker` url empty? 41 | --------------------------------- 42 | 43 | A: This does not seem to work when being set through setup.py, if you 44 | go to your package on PyPI and click edit, one of the available fields 45 | is called *bugtrack url*. 46 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Yarg documentation master file 2 | 3 | Yarg: A PyPI client 4 | =================== 5 | 6 | .. image:: https://img.shields.io/travis/kura/yarg.svg?style=flat 7 | 8 | .. image:: https://img.shields.io/coveralls/kura/yarg.svg?style=flat 9 | 10 | .. image:: https://pypip.in/version/yarg/badge.svg?style=flat 11 | 12 | .. image:: https://pypip.in/download/yarg/badge.svg?style=flat 13 | 14 | .. image:: https://pypip.in/py_versions/yarg/badge.svg?style=flat 15 | 16 | .. image:: https://pypip.in/implementation/yarg/badge.svg?style=flat 17 | 18 | .. image:: https://pypip.in/status/yarg/badge.svg?style=flat 19 | 20 | .. image:: https://pypip.in/wheel/yarg/badge.svg?style=flat 21 | 22 | .. image:: https://pypip.in/license/yarg/badge.svg?style=flat 23 | 24 | yarg(1) -- A semi hard Cornish cheese, also queries PyPI. 25 | Built on top of `requests 26 | `_, it's 27 | easy to use and makes sense. 28 | 29 | .. code-block:: python 30 | 31 | >>> import yarg 32 | >>> package = yarg.get("yarg") 33 | >>> package.name 34 | 'yarg' 35 | >>> package.author 36 | Author(name='Kura', email='kura@kura.io') 37 | 38 | Yarg is released under the `MIT license 39 | `_. The `source code is on 40 | GitHub `_ and `issues are also tracked on 41 | GitHub `_. 42 | 43 | Yarg in action 44 | -------------- 45 | 46 | - `Yarg is used extensively on pypip.in 47 | `_ 48 | - `Yarg being used on djangopackages.com 49 | `_ 50 | 51 | Yarg documentation 52 | ------------------ 53 | 54 | .. toctree:: 55 | :maxdepth: 2 56 | 57 | intro 58 | api-search 59 | api-rss 60 | testing 61 | 62 | Changelog 63 | --------- 64 | 65 | .. toctree:: 66 | :maxdepth: 2 67 | 68 | changelog 69 | 70 | Contributors 71 | ------------ 72 | 73 | .. toctree:: 74 | :maxdepth: 2 75 | 76 | contributors 77 | 78 | Help 79 | ---- 80 | 81 | .. toctree:: 82 | :maxdepth: 2 83 | 84 | faq 85 | -------------------------------------------------------------------------------- /docs/source/intro.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Prerequisites 5 | ------------- 6 | 7 | Yarg works with Python 2.6.x, 2.7.x, => 3.3, pypy and pypy3. 8 | 9 | Installation 10 | ------------ 11 | 12 | There are multiple ways to install Yarg. 13 | 14 | As source or as a wheel from PyPI using easy_install or pip: 15 | 16 | .. code-block:: bash 17 | 18 | easy_install yarg 19 | pip install yarg 20 | 21 | From the tarball release 22 | ------------------------ 23 | 24 | 1. Download the most recent tarball from the `PyPI download 25 | page `_ 26 | 2. Unpack the tarball 27 | 3. python setup.py install 28 | 29 | 30 | Getting started 31 | --------------- 32 | 33 | Search interface 34 | ~~~~~~~~~~~~~~~~ 35 | 36 | .. code-block:: python 37 | 38 | >>> import yarg 39 | >>> package = yarg.get("yarg") 40 | >>> package.name 41 | 'yarg' 42 | >>> package.author 43 | Author(name='Kura', email='kura@kura.io') 44 | 45 | :meth:`yarg.get` returns an instance of :class:`yarg.package.Package`. 46 | 47 | Newest packages interface 48 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 49 | 50 | .. code-block:: python 51 | 52 | >>> import yarg 53 | >>> packages = yarg.newest_packages() 54 | >>> packages 55 | [, , ] 56 | >>> packages[0].name 57 | 'yarg' 58 | >>> packages.url 59 | 'http://pypi.python.org/pypi/yarg 60 | 61 | :meth:`yarg.newest_packages` returns a list of :class:`yarg.parse.Package` 62 | objects. 63 | 64 | Updated packages interface 65 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | .. code-block:: python 68 | 69 | >>> import yarg 70 | >>> packages = yarg.latest_updated_packages() 71 | >>> packages 72 | [, , ] 73 | >>> packages[0].name 74 | 'yarg' 75 | >>> packages[0].version 76 | '0.1.2' 77 | >>> packages[0].url 78 | 'http://pypi.python.org/pypi/yarg/0.1.2 79 | 80 | :meth:`yarg.latest_updated_packages` returns a list of :class:`yarg.parse.Package` 81 | objects. 82 | -------------------------------------------------------------------------------- /docs/source/testing.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | Yarg uses the built-in `unittest` framework for Python and uses `pytest` as the 5 | test rig. 6 | 7 | Running the tests using the Makefile 8 | ------------------------------------ 9 | 10 | A target has been made available in the project's `Makefile` for running the 11 | test rig, it will install all requirements for testing and run the tests. 12 | 13 | .. code-block:: bash 14 | 15 | make test 16 | 17 | Running the tests without using the Makefile 18 | -------------------------------------------- 19 | 20 | .. code-block:: bash 21 | 22 | pip install -r requirements-test.txt 23 | pytest 24 | 25 | Running the tests with tox/detox 26 | -------------------------------- 27 | 28 | A `tox` configuration is also provided if you'd like to run the test rig 29 | against all the supported Python versions. 30 | 31 | You can do this via the `Makefile` target that will install all requirements 32 | and run the tests: 33 | 34 | .. code-block:: bash 35 | 36 | make tox 37 | 38 | Or you can do it manually: 39 | 40 | .. code-block:: bash 41 | 42 | pip install -r requirements.txt -r requirements-test.txt 43 | detox 44 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | -------------------------------------------------------------------------------- /requirements-pypi.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | twine 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | coveralls 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | import sys 5 | 6 | 7 | desc = "A semi hard Cornish cheese, also queries PyPI (PyPI client)" 8 | long_desc = open('README.rst').read() + "\n\n" + open('CHANGES.rst').read() 9 | long_desc = re.sub(r":[a-z]*:`", "`", long_desc) 10 | 11 | exec(open('yarg/__about__.py').read()) 12 | 13 | setup(name=__title__, 14 | version=__version__, 15 | url=__url__, 16 | author=__author__, 17 | author_email=__email__, 18 | maintainer=__author__, 19 | maintainer_email=__email__, 20 | description=desc, 21 | long_description=long_desc, 22 | license=__license__, 23 | platforms=['linux'], 24 | packages=find_packages(exclude=["tests"]), 25 | install_requires=['requests', ], 26 | requires=['requests', ], 27 | provides=[__title__, ], 28 | keywords=['pypi', 'client', 'packages'], 29 | classifiers=[ 30 | 'Development Status :: 4 - Beta', 31 | 'Intended Audience :: Developers', 32 | 'Natural Language :: English', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Programming Language :: Python', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.8', 37 | 'Programming Language :: Python :: 3.9', 38 | 'Programming Language :: Python :: 3.10', 39 | 'Programming Language :: Python :: 3.11', 40 | 'Programming Language :: Python :: 3.12', 41 | 'Programming Language :: Python :: Implementation :: CPython', 42 | 'Programming Language :: Python :: Implementation :: PyPy', 43 | 'Topic :: Software Development :: Libraries :: Python Modules', 44 | 'Topic :: System :: Archiving :: Packaging', 45 | ], 46 | zip_safe=True, 47 | ) 48 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kura/yarg/6378a9f4ab75acefcf166624082ad55eb484e04e/tests/__init__.py -------------------------------------------------------------------------------- /tests/newest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PyPI Newest Packages 6 | https://pypi.python.org/pypi 7 | Newest packages registered at the Python Package Index 8 | en 9 | 10 | gobble added to PyPI 11 | http://pypi.python.org/pypi/gobble 12 | http://pypi.python.org/pypi/gobble 13 | Automatic functional testing for CLI apps. 14 | 09 Aug 2014 06:57:42 GMT 15 | 16 | 17 | flask_autorest added to PyPI 18 | http://pypi.python.org/pypi/flask_autorest 19 | http://pypi.python.org/pypi/flask_autorest 20 | auto create restful apis for database, with the help of dataset. 21 | 09 Aug 2014 05:24:58 GMT 22 | 23 | 24 | ranrod added to PyPI 25 | http://pypi.python.org/pypi/ranrod 26 | http://pypi.python.org/pypi/ranrod 27 | download route53 hosted zones as local json files 28 | 09 Aug 2014 05:20:21 GMT 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "maintainer": "Kura", 4 | "docs_url": "http://yarg.readthedocs.org/", 5 | "maintainer_email": "kura@kura.io", 6 | "package_url": "http://pypi.python.org/pypi/yarg", 7 | "author": "Kura", 8 | "author_email": "kura@kura.io", 9 | "download_url": "https://github.com/kura/yarg/archive/0.0.0.tar.gz", 10 | "platform": "linux", 11 | "version": "0.0.15", 12 | "description": "This is the long description.", 13 | "release_url": "http://pypi.python.org/pypi/yarg/0.0.0", 14 | "downloads": { 15 | "last_month": 510000, 16 | "last_week": 72700, 17 | "last_day": 34001 18 | }, 19 | "classifiers": [ 20 | "Development Status :: 5 - Production/Stable", 21 | "Intended Audience :: Developers", 22 | "License :: OSI Approved :: MIT License", 23 | "Programming Language :: Python", 24 | "Programming Language :: Python :: 2.6", 25 | "Programming Language :: Python :: 2.7", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.1", 28 | "Programming Language :: Python :: 3.2", 29 | "Programming Language :: Python :: 3.3", 30 | "Programming Language :: Python :: Implementation :: CPython", 31 | "Programming Language :: Python :: Implementation :: PyPy" 32 | ], 33 | "name": "yarg", 34 | "bugtrack_url": "https://github.com/kura/yarg/issues", 35 | "license": "MIT", 36 | "summary": "This is the short summary.", 37 | "home_page": "https://kura.io/yarg/" 38 | }, 39 | "releases": { 40 | "0.0.0": [ 41 | { 42 | "has_sig": false, 43 | "upload_time": "2014-07-21T19:41:20", 44 | "comment_text": "", 45 | "python_version": "2.7", 46 | "url": "https://pypi.python.org/packages/2.7/y/yarg/yarg-0.0.0-py2.py3-none-any.whl", 47 | "md5_digest": "3e3098612677c34706de2e10476b3e50", 48 | "downloads": 749, 49 | "filename": "yarg-0.0.0-py2.py3-none-any.whl", 50 | "packagetype": "wheeeel", 51 | "size": 21576 52 | }, 53 | { 54 | "has_sig": false, 55 | "upload_time": "2014-07-21T19:40:55", 56 | "comment_text": "", 57 | "python_version": "source", 58 | "url": "https://pypi.python.org/packages/source/y/yarg/yarg-0.0.0.tar.gz", 59 | "md5_digest": "be198baa95536c1c9d17874428e3a0c6", 60 | "downloads": 834, 61 | "filename": "yarg-0.0.0.tar.gz", 62 | "packagetype": "egg", 63 | "size": 12377 64 | } 65 | ], 66 | "0.0.1": [], 67 | "0.0.15": [ 68 | { 69 | "has_sig": true, 70 | "upload_time": "2014-08-17T12:21:20", 71 | "comment_text": "", 72 | "python_version": "2.7", 73 | "url": "https://pypi.python.org/packages/2.7/y/yarg/yarg-0.0.15-py2.py3-none-any.whl", 74 | "md5_digest": "3e3098611177c34706de2e10476b3e51", 75 | "downloads": 742, 76 | "filename": "yarg-0.0.15-py2.py3-none-any.whl", 77 | "packagetype": "bdist_wheel", 78 | "size": 21597 79 | }, 80 | { 81 | "has_sig": true, 82 | "upload_time": "2014-08-17T12:21:49", 83 | "comment_text": "", 84 | "python_version": "source", 85 | "url": "https://pypi.python.org/packages/source/y/yarg/yarg-0.0.15.tar.gz", 86 | "md5_digest": "be198baa95116c1c9d17874428e3a0c7", 87 | "downloads": 824, 88 | "filename": "yarg-0.0.15.tar.gz", 89 | "packagetype": "sdist", 90 | "size": 12398 91 | } 92 | ], 93 | "0.0.2": [ 94 | { 95 | "has_sig": true, 96 | "upload_time": "2014-08-16T12:21:20", 97 | "comment_text": "", 98 | "python_version": "2.7", 99 | "url": "https://pypi.python.org/packages/2.7/y/yarg/yarg-0.0.2-py2.py3-none-any.whl", 100 | "md5_digest": "3e3098611177c34706de2e10476b3e50", 101 | "downloads": 742, 102 | "filename": "yarg-0.0.2-py2.py3-none-any.whl", 103 | "packagetype": "bdist_wheel", 104 | "size": 21596 105 | }, 106 | { 107 | "has_sig": true, 108 | "upload_time": "2014-08-16T12:21:49", 109 | "comment_text": "", 110 | "python_version": "source", 111 | "url": "https://pypi.python.org/packages/source/y/yarg/yarg-0.0.2.tar.gz", 112 | "md5_digest": "be198baa95116c1c9d17874428e3a0c6", 113 | "downloads": 824, 114 | "filename": "yarg-0.0.2.tar.gz", 115 | "packagetype": "sdist", 116 | "size": 12397 117 | } 118 | ] 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tests/package_no_homepage_bugtrack_one_release.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "maintainer": "Kura", 4 | "maintainer_email": "kura@kura.io", 5 | "package_url": "http://pypi.python.org/pypi/yarg", 6 | "author": "Kura", 7 | "author_email": "kura@kura.io", 8 | "download_url": "https://github.com/kura/yarg/archive/0.0.0.tar.gz", 9 | "platform": "linux", 10 | "version": "0.0.0", 11 | "description": "This is the long description.", 12 | "release_url": "http://pypi.python.org/pypi/yarg/0.0.0", 13 | "downloads": { 14 | "last_month": 510000, 15 | "last_week": 72700, 16 | "last_day": 34001 17 | }, 18 | "classifiers": [ 19 | "Development Status :: 5 - Production/Stable", 20 | "Intended Audience :: Developers", 21 | "License :: OSI Approved :: MIT License", 22 | "Programming Language :: Python", 23 | "Programming Language :: Python :: 2.6", 24 | "Programming Language :: Python :: 2.7", 25 | "Programming Language :: Python :: 3", 26 | "Programming Language :: Python :: 3.1", 27 | "Programming Language :: Python :: 3.2", 28 | "Programming Language :: Python :: 3.3", 29 | "Programming Language :: Python :: Implementation :: CPython", 30 | "Programming Language :: Python :: Implementation :: PyPy" 31 | ], 32 | "name": "yarg", 33 | "license": "MIT", 34 | "summary": "This is the short summary." 35 | }, 36 | "releases": { 37 | "0.0.0": [ 38 | { 39 | "has_sig": false, 40 | "upload_time": "2014-08-16T12:21:20", 41 | "comment_text": "", 42 | "python_version": "2.7", 43 | "url": "https://pypi.python.org/packages/2.7/y/yarg/yarg-0.0.0-py2.py3-none-any.whl", 44 | "md5_digest": "3e3098612677c34706de2e10476b3e50", 45 | "downloads": 742, 46 | "filename": "yarg-0.0.0-py2.py3-none-any.whl", 47 | "packagetype": "bdist_egg", 48 | "size": 21576 49 | }, 50 | { 51 | "has_sig": false, 52 | "upload_time": "2014-08-16T12:21:49", 53 | "comment_text": "", 54 | "python_version": "source", 55 | "url": "https://pypi.python.org/packages/source/y/yarg/yarg-0.0.0.tar.gz", 56 | "md5_digest": "be198baa95536c1c9d17874428e3a0c6", 57 | "downloads": 824, 58 | "filename": "yarg-0.0.0.tar.gz", 59 | "packagetype": "soooource", 60 | "size": 12377 61 | } 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | from unittest.mock import call, MagicMock, patch 5 | 6 | from yarg import get, HTTPError 7 | 8 | 9 | class GoodResponse(object): 10 | status_code = 200 11 | package = os.path.join(os.path.dirname(__file__), 12 | 'package.json') 13 | content = open(package).read() 14 | 15 | 16 | class BadResponse(object): 17 | status_code = 300 18 | reason = "Mocked" 19 | 20 | 21 | class TestClient(unittest.TestCase): 22 | 23 | @patch('requests.Session.get', return_value=BadResponse) 24 | def test_get(self, get_mock): 25 | # Python 2.6.... 26 | try: 27 | get("test") 28 | except HTTPError as e: 29 | self.assertEqual(300, e.status_code) 30 | self.assertEqual(e.status_code, e.errno) 31 | self.assertEqual(e.reason, e.message) 32 | 33 | @patch('requests.Session.get', return_value=GoodResponse) 34 | def test_end_slash(self, get_mock): 35 | get("test", pypi_server="https://mock.test.mock/test") 36 | self.assertEqual(call('https://mock.test.mock/test/test/json'), 37 | get_mock.call_args) 38 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from yarg import HTTPError 4 | 5 | 6 | class TestHTTPErrorWithReason(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.error = HTTPError(status_code=300, 10 | reason="Test") 11 | 12 | def test_repr(self): 13 | self.assertEqual('', 14 | self.error.__repr__()) 15 | 16 | def test_str(self): 17 | self.assertEqual('', 18 | self.error.__str__()) 19 | 20 | class TestHTTPErrorNoReason(unittest.TestCase): 21 | 22 | def setUp(self): 23 | self.error = HTTPError() 24 | 25 | def test_repr(self): 26 | self.assertEqual('', 27 | self.error.__repr__()) 28 | 29 | def test_str(self): 30 | self.assertEqual('', 31 | self.error.__str__()) 32 | -------------------------------------------------------------------------------- /tests/test_package.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | import json 3 | import os 4 | import unittest 5 | 6 | from yarg.package import json2package 7 | 8 | 9 | class TestPackage(unittest.TestCase): 10 | 11 | def setUp(self): 12 | package = os.path.join(os.path.dirname(__file__), 13 | 'package.json') 14 | self.json = json.loads(open(package).read()) 15 | self.package = json2package(open(package).read()) 16 | 17 | def test_repr(self): 18 | self.assertEqual(u'', self.package.__repr__()) 19 | 20 | def test_name(self): 21 | self.assertEqual(u'yarg', self.package.name) 22 | 23 | def test_pypi_url(self): 24 | self.assertEqual(u'http://pypi.python.org/pypi/yarg', 25 | self.package.pypi_url) 26 | 27 | def test_summary(self): 28 | self.assertEqual(u'This is the short summary.', self.package.summary) 29 | 30 | def test_description(self): 31 | self.assertEqual(u'This is the long description.', 32 | self.package.description) 33 | 34 | def test_homepage(self): 35 | self.assertEqual(u'https://kura.io/yarg/', 36 | self.package.homepage) 37 | 38 | def test_bugtracker(self): 39 | self.assertEqual(u'https://github.com/kura/yarg/issues', 40 | self.package.bugtracker) 41 | 42 | def test_docs(self): 43 | self.assertEqual(u'http://yarg.readthedocs.org/', 44 | self.package.docs) 45 | 46 | def test_author(self): 47 | author = namedtuple('Author', 'name email') 48 | self.assertEqual(author(name='Kura', email='kura@kura.io'), 49 | self.package.author) 50 | 51 | def test_maintainer(self): 52 | maintainer = namedtuple('Maintainer', 'name email') 53 | self.assertEqual(maintainer(name='Kura', email='kura@kura.io'), 54 | self.package.maintainer) 55 | 56 | def test_license(self): 57 | self.assertEqual(u'MIT', 58 | self.package.license) 59 | 60 | def test_license_from_classifiers(self): 61 | self.assertEqual(u'MIT License', 62 | self.package.license_from_classifiers) 63 | 64 | def test_downloads(self): 65 | downloads = namedtuple('Downloads', 'day week month') 66 | self.assertEqual(downloads(day=34001, week=72700, month=510000), 67 | self.package.downloads) 68 | 69 | def test_classifiers(self): 70 | self.assertEqual([u'Development Status :: 5 - Production/Stable', 71 | u'Intended Audience :: Developers', 72 | u'License :: OSI Approved :: MIT License', 73 | u'Programming Language :: Python', 74 | u'Programming Language :: Python :: 2.6', 75 | u'Programming Language :: Python :: 2.7', 76 | u'Programming Language :: Python :: 3', 77 | u'Programming Language :: Python :: 3.1', 78 | u'Programming Language :: Python :: 3.2', 79 | u'Programming Language :: Python :: 3.3', 80 | u'Programming Language :: Python :: Implementation :: CPython', 81 | u'Programming Language :: Python :: Implementation :: PyPy'], 82 | self.package.classifiers) 83 | 84 | def test_release_ids(self): 85 | self.assertEqual([u'0.0.0', u'0.0.2', u'0.0.15'], 86 | self.package.release_ids) 87 | 88 | def test_latest_release_id(self): 89 | self.assertEqual(u'0.0.15', self.package.latest_release_id) 90 | 91 | def test_has_wheel(self): 92 | self.assertEqual(True, self.package.has_wheel) 93 | 94 | def test_has_egg(self): 95 | self.assertEqual(False, self.package.has_egg) 96 | 97 | def test_has_source(self): 98 | self.assertEqual(True, self.package.has_source) 99 | 100 | def test_python_versions(self): 101 | self.assertEqual([u'2.6', u'2.7', u'3.1', u'3.2', u'3.3'], 102 | self.package.python_versions) 103 | 104 | def test_python_implementations(self): 105 | self.assertEqual([u'CPython', u'PyPy'], 106 | self.package.python_implementations) 107 | 108 | 109 | class TestPackageMissingData(unittest.TestCase): 110 | 111 | def setUp(self): 112 | package = os.path.join(os.path.dirname(__file__), 113 | 'package_no_homepage_bugtrack_one_release.json') 114 | self.json = json.loads(open(package).read()) 115 | self.package = json2package(open(package).read()) 116 | 117 | 118 | def test_homepage(self): 119 | self.assertEqual(None, self.package.homepage) 120 | 121 | def test_bugtracker(self): 122 | self.assertEqual(None, self.package.bugtracker) 123 | 124 | def test_docs(self): 125 | self.assertEqual(None, self.package.docs) 126 | 127 | def test_latest_release_id(self): 128 | self.assertEqual(u'0.0.0', self.package.latest_release_id) 129 | 130 | def test_has_wheel(self): 131 | self.assertEqual(False, self.package.has_wheel) 132 | 133 | def test_has_egg(self): 134 | self.assertEqual(True, self.package.has_egg) 135 | 136 | def test_has_source(self): 137 | self.assertEqual(False, self.package.has_source) 138 | -------------------------------------------------------------------------------- /tests/test_parse.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | import unittest 4 | 5 | from unittest.mock import call, MagicMock, patch 6 | 7 | from yarg import newest_packages, latest_updated_packages, HTTPError 8 | from yarg.parse import _get, Package 9 | 10 | 11 | class GoodNewestResponse(object): 12 | status_code = 200 13 | xml = os.path.join(os.path.dirname(__file__), 14 | 'newest.xml') 15 | content = open(xml).read() 16 | 17 | 18 | class GoodUpdatedResponse(object): 19 | status_code = 200 20 | xml = os.path.join(os.path.dirname(__file__), 21 | 'updated.xml') 22 | content = open(xml).read() 23 | 24 | 25 | class BadResponse(object): 26 | status_code = 300 27 | reason = "Mocked" 28 | 29 | 30 | class TestParse(unittest.TestCase): 31 | 32 | def setUp(self): 33 | self.newest = self.setup_newest() 34 | self.updated = self.setup_updated() 35 | 36 | def setup_newest(self): 37 | item1 = {'name': 'gobble', 38 | 'url': 'http://pypi.python.org/pypi/gobble', 39 | 'description': 'Automatic functional testing for CLI apps.', 40 | 'date': '09 Aug 2014 06:57:42 GMT'} 41 | item2 = {'name': 'flask_autorest', 42 | 'url': 'http://pypi.python.org/pypi/flask_autorest', 43 | 'description': 'auto create restful apis for database, with the help of dataset.', 44 | 'date': '09 Aug 2014 05:24:58 GMT'} 45 | item3 = {'name': 'ranrod', 46 | 'url': 'http://pypi.python.org/pypi/ranrod', 47 | 'description': 'download route53 hosted zones as local json files', 48 | 'date': '09 Aug 2014 05:20:21 GMT'} 49 | return [Package(item1), Package(item2), Package(item3)] 50 | 51 | def setup_updated(self): 52 | item1 = {'name': 'pycoin', 53 | 'version': '0.50', 54 | 'url': 'http://pypi.python.org/pypi/pycoin/0.50', 55 | 'description': 'Utilities for Bitcoin and altcoin addresses and transaction manipulation.', 56 | 'date': '09 Aug 2014 08:40:20 GMT'} 57 | item2 = {'name': 'django-signup', 58 | 'version': '0.6.0', 59 | 'url': 'http://pypi.python.org/pypi/django-signup/0.6.0', 60 | 'description': 'A user registration app for Django with support for custom user models', 61 | 'date': '09 Aug 2014 08:33:53 GMT'} 62 | item3 = {'name': 'pyADC', 63 | 'version': '0.1.3', 64 | 'url': 'http://pypi.python.org/pypi/pyADC/0.1.3', 65 | 'description': 'Python implementation of the ADC(S) Protocol for Direct Connect.', 66 | 'date': '09 Aug 2014 08:19:56 GMT'} 67 | return [Package(item1), Package(item2), Package(item3)] 68 | 69 | @patch('requests.get', return_value=BadResponse) 70 | def test_newest_packages_bad_get(self, get_mock): 71 | # Python 2.6.... 72 | try: 73 | newest_packages() 74 | except HTTPError as e: 75 | self.assertEqual(300, e.status_code) 76 | self.assertEqual(e.status_code, e.errno) 77 | self.assertEqual(e.reason, e.message) 78 | 79 | @patch('requests.get', return_value=BadResponse) 80 | def test_updated_packages_bad_get(self, get_mock): 81 | # Python 2.6.... 82 | try: 83 | latest_updated_packages() 84 | except HTTPError as e: 85 | self.assertEqual(300, e.status_code) 86 | self.assertEqual(e.status_code, e.errno) 87 | self.assertEqual(e.reason, e.message) 88 | 89 | @patch('requests.get', return_value=GoodNewestResponse) 90 | def test_newest_packages(self, get_mock): 91 | p = newest_packages() 92 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=packages_rss'), 93 | get_mock.call_args) 94 | self.assertEqual(self.newest[0].name, p[0].name) 95 | self.assertEqual(self.newest[1].name, p[1].name) 96 | self.assertEqual(self.newest[2].name, p[2].name) 97 | 98 | @patch('requests.get', return_value=GoodNewestResponse) 99 | def test_newest_package(self, get_mock): 100 | p = newest_packages() 101 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=packages_rss'), 102 | get_mock.call_args) 103 | self.assertEqual('gobble', p[0].name) 104 | self.assertEqual('http://pypi.python.org/pypi/gobble', p[0].url) 105 | self.assertEqual('Automatic functional testing for CLI apps.', 106 | p[0].description) 107 | self.assertEqual(datetime.strptime('09 Aug 2014 06:57:42 GMT', 108 | "%d %b %Y %H:%M:%S %Z"), 109 | p[0].date) 110 | 111 | @patch('requests.get', return_value=GoodNewestResponse) 112 | def test_newest_package_repr(self, get_mock): 113 | p = newest_packages() 114 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=packages_rss'), 115 | get_mock.call_args) 116 | self.assertEqual('', p[0].__repr__()) 117 | 118 | @patch('requests.get', return_value=GoodNewestResponse) 119 | def test_newest_package_version(self, get_mock): 120 | p = newest_packages() 121 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=packages_rss'), 122 | get_mock.call_args) 123 | self.assertEqual(None, p[0].version) 124 | 125 | @patch('requests.get', return_value=GoodUpdatedResponse) 126 | def test_updated_packages(self, get_mock): 127 | p = latest_updated_packages() 128 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=rss'), 129 | get_mock.call_args) 130 | self.assertEqual(self.updated[0].name, p[0].name) 131 | self.assertEqual(self.updated[0].version, p[0].version) 132 | self.assertEqual(self.updated[1].name, p[1].name) 133 | self.assertEqual(self.updated[1].version, p[1].version) 134 | self.assertEqual(self.updated[2].name, p[2].name) 135 | self.assertEqual(self.updated[2].version, p[2].version) 136 | 137 | @patch('requests.get', return_value=GoodUpdatedResponse) 138 | def test_updated_package(self, get_mock): 139 | p = latest_updated_packages() 140 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=rss'), 141 | get_mock.call_args) 142 | self.assertEqual('pycoin', p[0].name) 143 | self.assertEqual('0.50', p[0].version) 144 | self.assertEqual('http://pypi.python.org/pypi/pycoin/0.50', p[0].url) 145 | self.assertEqual('Utilities for Bitcoin and altcoin addresses and transaction manipulation.', 146 | p[0].description) 147 | self.assertEqual(datetime.strptime('09 Aug 2014 08:40:20 GMT', 148 | "%d %b %Y %H:%M:%S %Z"), 149 | p[0].date) 150 | 151 | @patch('requests.get', return_value=GoodUpdatedResponse) 152 | def test_updated_package_repr(self, get_mock): 153 | p = latest_updated_packages() 154 | self.assertEqual(call('https://pypi.python.org/pypi?%3Aaction=rss'), 155 | get_mock.call_args) 156 | self.assertEqual('', p[0].__repr__()) 157 | -------------------------------------------------------------------------------- /tests/test_release.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | import os 4 | import unittest 5 | 6 | from yarg.package import json2package 7 | from yarg.release import Release 8 | 9 | 10 | class TestRelease(unittest.TestCase): 11 | 12 | def setUp(self): 13 | package = os.path.join(os.path.dirname(__file__), 14 | 'package.json') 15 | self.json = json.loads(open(package).read()) 16 | self.package = json2package(open(package).read()) 17 | 18 | def test_release_ids(self): 19 | self.assertEqual([u'0.0.0', u'0.0.2', u'0.0.15'], 20 | self.package.release_ids) 21 | 22 | def test_release(self): 23 | release_id = '0.0.2' 24 | release = self.json['releases'][release_id] 25 | release_list = [Release(release_id, r) for r in release] 26 | self.assertEqual(release_list[0].md5_digest, 27 | self.package.release(release_id)[0].md5_digest) 28 | self.assertEqual(release_list[1].md5_digest, 29 | self.package.release(release_id)[1].md5_digest) 30 | self.assertEqual('3e3098611177c34706de2e10476b3e50', 31 | self.package.release(release_id)[0].md5_digest) 32 | self.assertEqual('be198baa95116c1c9d17874428e3a0c6', 33 | self.package.release(release_id)[1].md5_digest) 34 | 35 | def test_repr(self): 36 | release_id = '0.0.2' 37 | release = self.package.release(release_id)[0] 38 | self.assertEqual(u'', release.__repr__()) 39 | 40 | def test_release_id(self): 41 | release_id = '0.0.2' 42 | release = self.package.release(release_id)[0] 43 | self.assertEqual(release_id, release.release_id) 44 | 45 | def test_release_id(self): 46 | release_id = '0.0.3' 47 | release = self.package.release(release_id) 48 | self.assertEqual(None, release) 49 | 50 | def test_release_uploaded(self): 51 | release_id = '0.0.2' 52 | release = self.package.release(release_id)[0] 53 | self.assertEqual(datetime.strptime("2014-08-16T12:21:20", 54 | "%Y-%m-%dT%H:%M:%S"), 55 | release.uploaded) 56 | 57 | def test_release_python_version(self): 58 | release_id = '0.0.2' 59 | release = self.package.release(release_id)[0] 60 | self.assertEqual(u'2.7', release.python_version) 61 | 62 | def test_release_url(self): 63 | release_id = '0.0.2' 64 | release = self.package.release(release_id)[0] 65 | url = u'https://pypi.python.org/packages/2.7/y/yarg/yarg-0.0.2-py2.py3-none-any.whl' 66 | self.assertEqual(url, release.url) 67 | 68 | def test_release_md5(self): 69 | release_id = '0.0.2' 70 | release = self.package.release(release_id)[0] 71 | md5 = u'3e3098611177c34706de2e10476b3e50' 72 | self.assertEqual(md5, release.md5_digest) 73 | 74 | def test_release_filename(self): 75 | release_id = '0.0.2' 76 | release = self.package.release(release_id)[0] 77 | filename = u'yarg-0.0.2-py2.py3-none-any.whl' 78 | self.assertEqual(filename, release.filename) 79 | 80 | def test_release_size(self): 81 | release_id = '0.0.2' 82 | release = self.package.release(release_id)[0] 83 | size = 21596 84 | self.assertEqual(size, release.size) 85 | 86 | def test_release_unknown_package_type(self): 87 | release_id = '0.0.0' 88 | release = self.package.release(release_id)[0] 89 | self.assertEqual(u'wheeeel', release.package_type) 90 | 91 | def test_release_package_type(self): 92 | release_id = '0.0.2' 93 | release = self.package.release(release_id)[0] 94 | self.assertEqual(u'wheel', release.package_type) 95 | 96 | def test_release_has_sig(self): 97 | release_id = '0.0.2' 98 | release = self.package.release(release_id)[0] 99 | self.assertEqual(True, release.has_sig) 100 | 101 | def test_latest_release_id(self): 102 | self.assertEqual(u'0.0.15', self.package.latest_release_id) 103 | 104 | def test_latest_release(self): 105 | release_id = '0.0.15' 106 | release = self.json['releases'][release_id] 107 | release_list = [Release(release_id, r) for r in release] 108 | self.assertEqual(release_list[0].md5_digest, 109 | self.package.latest_release[0].md5_digest) 110 | self.assertEqual(release_list[1].md5_digest, 111 | self.package.latest_release[1].md5_digest) 112 | self.assertEqual('3e3098611177c34706de2e10476b3e51', 113 | self.package.latest_release[0].md5_digest) 114 | self.assertEqual('be198baa95116c1c9d17874428e3a0c7', 115 | self.package.latest_release[1].md5_digest) 116 | -------------------------------------------------------------------------------- /tests/updated.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PyPI Recent Updates 6 | https://pypi.python.org/pypi 7 | Recent updates to the Python Package Index 8 | en 9 | 10 | pycoin 0.50 11 | http://pypi.python.org/pypi/pycoin/0.50 12 | Utilities for Bitcoin and altcoin addresses and transaction manipulation. 13 | 09 Aug 2014 08:40:20 GMT 14 | 15 | 16 | django-signup 0.6.0 17 | http://pypi.python.org/pypi/django-signup/0.6.0 18 | A user registration app for Django with support for custom user models 19 | 09 Aug 2014 08:33:53 GMT 20 | 21 | 22 | pyADC 0.1.3 23 | http://pypi.python.org/pypi/pyADC/0.1.3 24 | Python implementation of the ADC(S) Protocol for Direct Connect. 25 | 09 Aug 2014 08:19:56 GMT 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38, pypy, pypy3, docs, flake8 3 | 4 | [testenv] 5 | deps = pytest 6 | requests 7 | commands = pytest 8 | 9 | [testenv:flake8] 10 | deps = flake8 11 | requests 12 | commands = flake8 yarg --show-source 13 | 14 | [testenv:docs] 15 | changedir = docs/source 16 | deps = sphinx 17 | requests 18 | commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 19 | 20 | [testenv:pypy] 21 | basepython = /opt/pypy/bin/pypy 22 | 23 | [testenv:pypy3] 24 | basepython = /opt/pypy3/bin/pypy 25 | -------------------------------------------------------------------------------- /yarg/__about__.py: -------------------------------------------------------------------------------- 1 | __title__ = 'yarg' 2 | __version__ = '0.1.10' 3 | __author__ = 'Kura' 4 | __email__ = 'kura@kura.io' 5 | __url__ = 'https://yarg.readthedocs.org/' 6 | __license__ = 'MIT' 7 | __copyright__ = 'Copyright 2014 Kura' 8 | -------------------------------------------------------------------------------- /yarg/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | """ 26 | yarg(1) -- A semi hard Cornish cheese, also queries PyPI 27 | ======================================================== 28 | 29 | Yarg is a PyPI client. 30 | 31 | >>> import yarg 32 | >>> 33 | >>> package = yarg.get("yarg") 34 | >>> package.name 35 | 'yarg' 36 | >>> package.author 37 | Author(name='Kura', email='kura@kura.io') 38 | >>> 39 | >>> yarg.newest_packages() 40 | [, , ] 41 | >>> 42 | >>> yarg.latest_updated_packages() 43 | [, , ] 44 | 45 | Full documentation is at . 46 | """ 47 | 48 | 49 | from .client import get 50 | from .exceptions import HTTPError 51 | from .package import json2package 52 | from .parse import (newest_packages, latest_updated_packages) 53 | 54 | 55 | __all__ = ['get', 'HTTPError', 'json2package', 'newest_packages', 56 | 'latest_updated_packages', ] 57 | -------------------------------------------------------------------------------- /yarg/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | 26 | import requests 27 | 28 | from .exceptions import HTTPError 29 | from .package import json2package 30 | 31 | 32 | session = requests.Session() 33 | 34 | 35 | def get(package_name, pypi_server="https://pypi.python.org/pypi/"): 36 | """ 37 | Construct a request to the PyPI server and returns an instance of 38 | :class:`yarg.package.Package`. 39 | 40 | :param package_name: case sensitive name of the package on the PyPI server. 41 | :param pypi_server: (option) URL to the PyPI server. 42 | 43 | >>> import yarg 44 | >>> package = yarg.get('yarg') 45 | 46 | """ 47 | if not pypi_server.endswith("/"): 48 | pypi_server = pypi_server + "/" 49 | response = session.get("{0}{1}/json".format(pypi_server, 50 | package_name)) 51 | if response.status_code >= 300: 52 | raise HTTPError(status_code=response.status_code, 53 | reason=response.reason) 54 | if hasattr(response.content, 'decode'): 55 | return json2package(response.content.decode()) 56 | else: 57 | return json2package(response.content) 58 | -------------------------------------------------------------------------------- /yarg/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | 26 | from requests.exceptions import HTTPError as RHTTPError 27 | 28 | 29 | class YargException(Exception): 30 | pass 31 | 32 | 33 | class HTTPError(YargException, RHTTPError): 34 | """ 35 | A catchall HTTPError exception to handle HTTP errors 36 | when using :meth:`yarg.get`. 37 | 38 | This exception is also loaded at :class:`yarg.HTTPError` 39 | for ease of access. 40 | 41 | :member: status_code 42 | """ 43 | 44 | def __init__(self, *args, **kwargs): 45 | for key, val in kwargs.items(): 46 | setattr(self, key, val) 47 | if hasattr(self, 'status_code'): 48 | setattr(self, 'errno', self.status_code) 49 | if hasattr(self, 'reason'): 50 | setattr(self, 'message', self.reason) 51 | 52 | def __str__(self): 53 | return self.__repr__() 54 | 55 | def __repr__(self): 56 | if hasattr(self, 'status_code') and hasattr(self, 'reason'): 57 | return "".format(self.status_code, self.reason) 58 | return "" 59 | -------------------------------------------------------------------------------- /yarg/package.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | 26 | try: 27 | import simplejson as json 28 | except ImportError: 29 | import json 30 | from collections import namedtuple 31 | import re 32 | 33 | from .release import Release 34 | 35 | 36 | class Package(object): 37 | """ 38 | A PyPI package. 39 | 40 | :param pypi_dict: A dictionary retrieved from the PyPI server. 41 | """ 42 | 43 | def __init__(self, pypi_dict): 44 | self._package = pypi_dict['info'] 45 | self._releases = pypi_dict['releases'] 46 | 47 | def __repr__(self): 48 | return "".format(self.name) 49 | 50 | @property 51 | def name(self): 52 | """ 53 | >>> package = yarg.get('yarg') 54 | >>> package.name 55 | 'yarg' 56 | """ 57 | return self._package['name'] 58 | 59 | @property 60 | def pypi_url(self): 61 | """ 62 | >>> package = yarg.get('yarg') 63 | >>> package.url 64 | 'https://pypi.python.org/pypi/yarg' 65 | """ 66 | return self._package['package_url'] 67 | 68 | @property 69 | def summary(self): 70 | """ 71 | >>> package = yarg.get('yarg') 72 | >>> package.summary 73 | 'Some random summary stuff' 74 | """ 75 | return self._package['summary'] 76 | 77 | @property 78 | def description(self): 79 | """ 80 | >>> package = yarg.get('yarg') 81 | >>> package.description 82 | 'A super long description, usually uploaded from the README' 83 | """ 84 | return self._package['description'] 85 | 86 | @property 87 | def homepage(self): 88 | """ 89 | >>> package = yarg.get('yarg') 90 | >>> package.homepage 91 | 'https://kura.io/yarg/' 92 | """ 93 | if ('home_page' not in self._package or 94 | self._package['home_page'] == ""): 95 | return None 96 | return self._package['home_page'] 97 | 98 | @property 99 | def bugtracker(self): 100 | """ 101 | >>> package = yarg.get('yarg') 102 | >>> package.bugtracker 103 | 'https://github.com/kura/yarg/issues' 104 | """ 105 | if ('bugtrack_url' not in self._package or 106 | self._package['bugtrack_url'] == ""): 107 | return None 108 | return self._package['bugtrack_url'] 109 | 110 | @property 111 | def docs(self): 112 | """ 113 | >>> package = yarg.get('yarg') 114 | >>> package.docs 115 | 'https://yarg.readthedocs.org/en/latest' 116 | """ 117 | if ('docs_url' not in self._package or 118 | self._package['docs_url'] == ""): 119 | return None 120 | return self._package['docs_url'] 121 | 122 | @property 123 | def author(self): 124 | """ 125 | >>> package = yarg.get('yarg') 126 | >>> package.author 127 | Author(name='Kura', email='kura@kura.io') 128 | """ 129 | author = namedtuple('Author', 'name email') 130 | return author(name=self._package['author'], 131 | email=self._package['author_email']) 132 | 133 | @property 134 | def maintainer(self): 135 | """ 136 | >>> package = yarg.get('yarg') 137 | >>> package.maintainer 138 | Maintainer(name='Kura', email='kura@kura.io') 139 | """ 140 | maintainer = namedtuple('Maintainer', 'name email') 141 | return maintainer(name=self._package['maintainer'], 142 | email=self._package['maintainer_email']) 143 | 144 | @property 145 | def license(self): 146 | """ 147 | >>> package = yarg.get('yarg') 148 | >>> package.license 149 | 'MIT' 150 | """ 151 | return self._package['license'] 152 | 153 | @property 154 | def license_from_classifiers(self): 155 | """ 156 | >>> package = yarg.get('yarg') 157 | >>> package.license_from_classifiers 158 | 'MIT License' 159 | """ 160 | if len(self.classifiers) > 0: 161 | for c in self.classifiers: 162 | if c.startswith("License"): 163 | return c.split(" :: ")[-1] 164 | 165 | @property 166 | def downloads(self): 167 | """ 168 | >>> package = yarg.get('yarg') 169 | >>> package.downloads 170 | Downloads(day=50100, week=367941, month=1601938) # I wish 171 | """ 172 | _downloads = self._package['downloads'] 173 | downloads = namedtuple('Downloads', 'day week month') 174 | return downloads(day=_downloads['last_day'], 175 | week=_downloads['last_week'], 176 | month=_downloads['last_month']) 177 | 178 | @property 179 | def classifiers(self): 180 | """ 181 | >>> package = yarg.get('yarg') 182 | >>> package.classifiers 183 | ['License :: OSI Approved :: MIT License', 184 | 'Programming Language :: Python :: 2.7', 185 | 'Programming Language :: Python :: 3.4'] 186 | """ 187 | return self._package['classifiers'] 188 | 189 | @property 190 | def python_versions(self): 191 | """ 192 | Returns a list of Python version strings that 193 | the package has listed in :attr:`yarg.Release.classifiers`. 194 | 195 | >>> package = yarg.get('yarg') 196 | >>> package.python_versions 197 | ['2.6', '2.7', '3.3', '3.4'] 198 | """ 199 | version_re = re.compile(r"""Programming Language \:\: """ 200 | """Python \:\: \d\.\d""") 201 | return [c.split(' :: ')[-1] for c in self.classifiers 202 | if version_re.match(c)] 203 | 204 | @property 205 | def python_implementations(self): 206 | """ 207 | Returns a list of Python implementation strings that 208 | the package has listed in :attr:`yarg.Release.classifiers`. 209 | 210 | >>> package = yarg.get('yarg') 211 | >>> package.python_implementations 212 | ['CPython', 'PyPy'] 213 | """ 214 | return [c.split(' :: ')[-1] for c in self.classifiers 215 | if c.startswith("""Programming Language :: """ 216 | """Python :: Implementation""")] 217 | 218 | @property 219 | def latest_release_id(self): 220 | """ 221 | >>> package = yarg.get('yarg') 222 | >>> package.latest_release_id 223 | '0.1.0' 224 | """ 225 | return self._package['version'] 226 | 227 | @property 228 | def latest_release(self): 229 | """ 230 | A list of :class:`yarg.release.Release` objects for each file in the 231 | latest release. 232 | 233 | >>> package = yarg.get('yarg') 234 | >>> package.latest_release 235 | [, ] 236 | """ 237 | release_id = self.latest_release_id 238 | return self.release(release_id) 239 | 240 | @property 241 | def has_wheel(self): 242 | """ 243 | Returns `True` if one of the :class:`yarg.release.Release` objects 244 | in the latest set of release files is `wheel` format. Returns 245 | `False` if not. 246 | 247 | >>> package = yarg.get('yarg') 248 | >>> package.has_wheel 249 | True 250 | """ 251 | for release in self.latest_release: 252 | if release.package_type in ('wheel', 'bdist_wheel'): 253 | return True 254 | return False 255 | 256 | @property 257 | def has_egg(self): 258 | """ 259 | Returns `True` if one of the :class:`yarg.release.Release` objects 260 | in the latest set of release files is `egg` format. Returns 261 | `False` if not. 262 | 263 | >>> package = yarg.get('yarg') 264 | >>> package.has_egg 265 | False 266 | """ 267 | for release in self.latest_release: 268 | if release.package_type in ('egg', 'bdist_egg'): 269 | return True 270 | return False 271 | 272 | @property 273 | def has_source(self): 274 | """ 275 | Returns `True` if one of the :class:`yarg.release.Release` objects 276 | in the latest set of release files is `source` format. Returns 277 | `False` if not. 278 | 279 | >>> package = yarg.get('yarg') 280 | >>> package.has_source 281 | True 282 | """ 283 | for release in self.latest_release: 284 | if release.package_type in ('source', 'sdist'): 285 | return True 286 | return False 287 | 288 | @property 289 | def release_ids(self): 290 | """ 291 | >>> package = yarg.get('yarg') 292 | >>> package.release_ids 293 | ['0.0.1', '0.0.5', '0.1.0'] 294 | """ 295 | r = [(k, self._releases[k][0]['upload_time']) 296 | for k in self._releases.keys() 297 | if len(self._releases[k]) > 0] 298 | return [k[0] for k in sorted(r, key=lambda k: k[1])] 299 | 300 | def release(self, release_id): 301 | """ 302 | A list of :class:`yarg.release.Release` objects for each file in a 303 | release. 304 | 305 | :param release_id: A pypi release id. 306 | 307 | >>> package = yarg.get('yarg') 308 | >>> last_release = yarg.releases[-1] 309 | >>> package.release(last_release) 310 | [, ] 311 | """ 312 | if release_id not in self.release_ids: 313 | return None 314 | return [Release(release_id, r) for r in self._releases[release_id]] 315 | 316 | 317 | def json2package(json_content): 318 | """ 319 | Returns a :class:`yarg.release.Release` object from JSON content from the 320 | PyPI server. 321 | 322 | :param json_content: JSON encoded content from the PyPI server. 323 | """ 324 | return Package(json.loads(json_content)) 325 | -------------------------------------------------------------------------------- /yarg/parse.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | from datetime import datetime 26 | import xml.etree.ElementTree 27 | 28 | import requests 29 | 30 | from .exceptions import HTTPError 31 | 32 | 33 | def _get(pypi_server): 34 | """ 35 | Query the PyPI RSS feed and return a list 36 | of XML items. 37 | """ 38 | response = requests.get(pypi_server) 39 | if response.status_code >= 300: 40 | raise HTTPError(status_code=response.status_code, 41 | reason=response.reason) 42 | if hasattr(response.content, 'decode'): 43 | tree = xml.etree.ElementTree.fromstring(response.content.decode()) 44 | else: 45 | tree = xml.etree.ElementTree.fromstring(response.content) 46 | channel = tree.find('channel') 47 | return channel.findall('item') 48 | 49 | 50 | def newest_packages( 51 | pypi_server="https://pypi.python.org/pypi?%3Aaction=packages_rss"): 52 | """ 53 | Constructs a request to the PyPI server and returns a list of 54 | :class:`yarg.parse.Package`. 55 | 56 | :param pypi_server: (option) URL to the PyPI server. 57 | 58 | >>> import yarg 59 | >>> yarg.newest_packages() 60 | [, , ] 61 | """ 62 | items = _get(pypi_server) 63 | i = [] 64 | for item in items: 65 | i_dict = {'name': item[0].text.split()[0], 66 | 'url': item[1].text, 67 | 'description': item[3].text, 68 | 'date': item[4].text} 69 | i.append(Package(i_dict)) 70 | return i 71 | 72 | 73 | def latest_updated_packages( 74 | pypi_server="https://pypi.python.org/pypi?%3Aaction=rss"): 75 | """ 76 | Constructs a request to the PyPI server and returns a list of 77 | :class:`yarg.parse.Package`. 78 | 79 | :param pypi_server: (option) URL to the PyPI server. 80 | 81 | >>> import yarg 82 | >>> yarg.latest_updated_packages() 83 | [, , ] 84 | """ 85 | items = _get(pypi_server) 86 | i = [] 87 | for item in items: 88 | name, version = item[0].text.split() 89 | i_dict = {'name': name, 90 | 'version': version, 91 | 'url': item[1].text, 92 | 'description': item[2].text, 93 | 'date': item[3].text} 94 | i.append(Package(i_dict)) 95 | return i 96 | 97 | 98 | class Package(object): 99 | """ 100 | A PyPI package generated from the RSS feed information. 101 | 102 | :param pypi_dict: A dictionary retrieved from the PyPI server. 103 | """ 104 | 105 | def __init__(self, pypi_dict): 106 | self._content = pypi_dict 107 | 108 | def __repr__(self): 109 | return "".format(self.name) 110 | 111 | @property 112 | def name(self): 113 | """ 114 | >>> package = yarg.newest_packages()[0] 115 | >>> package.name 116 | 'yarg' 117 | >>> package = yarg.latest_updated_packages()[0] 118 | >>> package.name 119 | 'yarg' 120 | """ 121 | return self._content['name'] 122 | 123 | @property 124 | def version(self): 125 | """ 126 | >>> package = yarg.newest_packages()[0] 127 | >>> package.name 128 | 'yarg' 129 | >>> package = yarg.latest_updated_packages()[0] 130 | >>> package.name 131 | 'yarg' 132 | """ 133 | if 'version' not in self._content: 134 | return None 135 | return self._content['version'] 136 | 137 | @property 138 | def url(self): 139 | """ 140 | This is only available for :meth:`yarg.latest_updated_packages`, for 141 | :meth:`yarg.newest_packages` will return `None` 142 | 143 | >>> package = yarg.latest_updated_packages()[0] 144 | >>> package.url 145 | 'http://pypi.python.org/pypi/yarg' 146 | """ 147 | return self._content['url'] 148 | 149 | @property 150 | def date(self): 151 | """ 152 | >>> package = yarg.newest_packages()[0] 153 | >>> package.date 154 | datetime.datetime(2014, 8, 9, 8, 40, 20) 155 | >>> package = yarg.latest_updated_packages()[0] 156 | >>> package.date 157 | datetime.datetime(2014, 8, 9, 8, 40, 20) 158 | """ 159 | return datetime.strptime(self._content['date'], 160 | "%d %b %Y %H:%M:%S %Z") 161 | 162 | @property 163 | def description(self): 164 | """ 165 | >>> package = yarg.newest_packages()[0] 166 | >>> package.description 167 | 'Some random summary stuff' 168 | >>> package = yarg.latest_updated_packages()[0] 169 | >>> package.description 170 | 'Some random summary stuff' 171 | """ 172 | return self._content['description'] 173 | -------------------------------------------------------------------------------- /yarg/release.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # (The MIT License) 4 | # 5 | # Copyright (c) 2014 Kura 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the 'Software'), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in 15 | # all copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | 26 | from datetime import datetime 27 | 28 | 29 | class Release(object): 30 | """ 31 | A release file from PyPI. 32 | 33 | :param release_id: A release id. 34 | :param pypi_dict: A dictionary of a release file. 35 | """ 36 | 37 | def __init__(self, release_id, pypi_dict): 38 | self._release = pypi_dict 39 | self._release['release_id'] = release_id 40 | 41 | def __repr__(self): 42 | return "".format(self.release_id) 43 | 44 | @property 45 | def release_id(self): 46 | """ 47 | >>> package = yarg.get('yarg') 48 | >>> v = "0.1.0" 49 | >>> r = package.release(v) 50 | >>> r[0].release_id 51 | '0.1.0' 52 | """ 53 | return self._release['release_id'] 54 | 55 | @property 56 | def uploaded(self): 57 | """ 58 | >>> package = yarg.get('yarg') 59 | >>> v = "0.1.0" 60 | >>> r = package.release(v) 61 | >>> r.uploaded 62 | datetime.datime(2014, 8, 7, 21, 26, 19) 63 | """ 64 | return datetime.strptime(self._release['upload_time'], 65 | '%Y-%m-%dT%H:%M:%S') 66 | 67 | @property 68 | def python_version(self): 69 | """ 70 | >>> package = yarg.get('yarg') 71 | >>> v = "0.1.0" 72 | >>> r = package.release(v) 73 | >>> r.python_version 74 | '2.7' 75 | """ 76 | return self._release['python_version'] 77 | 78 | @property 79 | def url(self): 80 | """ 81 | >>> package = yarg.get('yarg') 82 | >>> v = "0.1.0" 83 | >>> r = package.release(v) 84 | >>> r.url 85 | 'https://pypi.python.org/packages/2.7/y/yarg/yarg...' 86 | """ 87 | return self._release['url'] 88 | 89 | @property 90 | def md5_digest(self): 91 | """ 92 | >>> package = yarg.get('yarg') 93 | >>> v = "0.1.0" 94 | >>> r = package.release(v) 95 | >>> r.md5_digest 96 | 'bec88e1c1765ca6177360e8f37b44c5c' 97 | """ 98 | return self._release['md5_digest'] 99 | 100 | @property 101 | def filename(self): 102 | """ 103 | >>> package = yarg.get('yarg') 104 | >>> v = "0.1.0" 105 | >>> r = package.release(v) 106 | >>> r.filename 107 | 'yarg-0.1.0-py27-none-any.whl' 108 | """ 109 | return self._release['filename'] 110 | 111 | @property 112 | def size(self): 113 | """ 114 | >>> package = yarg.get('yarg') 115 | >>> v = "0.1.0" 116 | >>> r = package.release(v) 117 | >>> r.size 118 | 52941 119 | """ 120 | return self._release['size'] 121 | 122 | @property 123 | def package_type(self): 124 | """ 125 | >>> package = yarg.get('yarg') 126 | >>> v = "0.1.0" 127 | >>> r = package.release(v) 128 | >>> r.package_type 129 | 'wheel' 130 | """ 131 | mapping = {'bdist_egg': 'egg', 'bdist_wheel': 'wheel', 132 | 'sdist': 'source'} 133 | ptype = self._release['packagetype'] 134 | if ptype in mapping.keys(): 135 | return mapping[ptype] 136 | return ptype 137 | 138 | @property 139 | def has_sig(self): 140 | """ 141 | >>> package = yarg.get('yarg') 142 | >>> v = "0.1.0" 143 | >>> r = package.release(v) 144 | >>> r.has_sig 145 | True 146 | """ 147 | return self._release['has_sig'] 148 | --------------------------------------------------------------------------------