{ "info": { "author": "Ben Hoyt", "author_email": "benhoyt@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP" ], "description": "\n=========\ncdnupload\n=========\n\n\nIntroduction\n============\n\ncdnupload uploads your website\u2019s static files to a CDN with a content-based hash in the filenames, giving great caching while avoiding versioning issues. cdnupload is:\n\n* Fast and simple to integrate\n* Helps you follow web best practices: `use a CDN <#why-should-i-use-a-cdn>`_, good Cache-Control headers, versioned filenames\n* Works with web apps written in any language\n* Written in Python (runs on Python 2 and 3)\n\nThe tool helps you follow performance best practices by including a content-based hash in each asset filename.\n\nDeploying is really fast too: only files that have actually changed will be uploaded (with a new hash).\n\ncdnupload is **trivial to install**::\n\n $ pip install cdnupload\n\nIt's **simple to use**::\n\n $ cdnupload /website/static s3://static-bucket --key-map=statics.json\n uploading script.js to script_8f3283c6342816f7.js\n uploading style.css to style_abcdef0123456789.css\n writing key map JSON to statics.json\n\nAnd it's **easy to integrate** in most languages, for example Python::\n\n import json, settings\n\n def init_server():\n with open('statics.json') as f:\n settings.statics = json.load(f)\n\n def static_url(path):\n return '//mycdn.com/' + settings.statics[path]\n\n\nInstallation\n============\n\ncdnupload is a Python package which runs under Python 3.4+ as well as Python 2.7. To install it `from PyPI `_ as a command-line script and in the global Python environment, simply type::\n\n pip install cdnupload\n\nIf you are using a specific version of Python or want to install it in a virtual Python environment, activate the virtual environment first, then run the ``pip install``.\n\nAdditionally, if you\u2019ll be using Amazon S3 as a destination, you\u2019ll need to install the boto3 package to interact with Amazon AWS. To install boto3, type the following (in your virtual environment if you\u2019re using one)::\n\n pip install boto3\n\nAfter cdnupload is installed, you can run the command-line script simply by typing ``cdnupload``. Or, if you need to run it against a specific Python interpreter, run the script as a module with ``python -m``, like so::\n\n /path/to/my/python -m cdnupload\n\n\nOverview\n========\n\ncdnupload is primarily a **command-line tool** that uploads your site\u2019s static files to a CDN (well, really the CDN\u2019s origin server). It optionally generates a JSON \u201ckey mapping\u201d that maps file paths to destination keys. A destination key is a file path with a hash in it based on the file\u2019s contents. This allows you to set up the CDN to cache your static files aggressively, with an essentially infinite expiry time (max age).\n\n(For a brief introduction to what a CDN is and why you might want to use one, `see the CDN section of this document. <#why-should-i-use-a-cdn>`_)\n\nWhen you upload statics, you specify a source directory and a destination directory (or Amazon S3 URL or other origin pseudo-URL). For example, you can upload all the static files from the ``/website/static`` directory to ``static-bucket``, and output the key mapping to the file ``statics.json`` using the following command::\n\n cdnupload /website/static s3://static-bucket --key-map=statics.json\n\nThe uploader will walk the source directory tree, query the destination S3 bucket (or directory), and upload any files that are missing. For example, if you have one JavaScript file and two CSS files, the output of the tool might look something like this::\n\n uploading script.js to script_0beec7b5ea3f0fdb.js\n uploading style.css to style_62cdb7020ff920e5.css\n uploading mobile.css to mobile_bbe960a25ea311d2.css\n writing key map JSON to statics.json\n\nIf you modify mobile.css and then run it again, you\u2019ll see that it only uploads the changed files::\n\n uploading mobile.css to mobile_6b369e490de120a9.css\n writing key map JSON to statics.json\n\nIt doesn\u2019t delete unused files on the destination directory automatically (as the currently-deployed website is probably still using them). To do that, you need to use the delete action::\n\n cdnupload /website/static s3://static-bucket --action=delete\n\nHere\u2019s what the output might be after the above uploads::\n\n deleting mobile_bbe960a25ea311d2.css\n\nThere are many `command-line options <#command-line-usage>`_ to control what files to upload, change the destination parameters, etc. And you can use the `Python API`_ directly if you need advanced features or if you need to add another destination \u201cprovider\u201d.\n\nYou\u2019ll also need to **integrate with your web server** so that your web application knows the hash mapping and can output the correct static URLs. That can be as simple as a ``static_url`` template function that uses the key map JSON to convert from a file path to the destination key. See details in the `web server integration section below. <#web-server-integration>`_\n\n\nWhy should I use a CDN?\n=======================\n\n*If you\u2019re not sure what a CDN is, or if you\u2019re wondering why you should use one, this section is for you.*\n\n.. image:: https://raw.githubusercontent.com/benhoyt/cdnupload/master/images/cdn.png\n :alt: From Wikimedia under Creative Commons (NCDN_-_CDN.png)\n :align: center\n\nCDN stands for Content Delivery Network, which is a service that serves your static files -- heavily cached, on servers around the world that are close to your users.\n\nSo if someone from New Jersey requests ``https://mycdn.com/style.css``, the CDN will almost certainly have a cached version in an East Coast or even a local New Jersey data center, and will serve that up to the user faster than you can say \u201cHTTP/2\u201d.\n\nIf the CDN doesn\u2019t have a cached version of the file, it will in turn request it from the origin server (where the files are hosted). If you\u2019re using something like Amazon S3 as your origin server, that request will be quick too, and the user will still get the file in good time. From then on, the CDN will serve the cached version.\n\nBecause the files are heavily cached (ideally with long expiry times), you need to include version numbers in the filenames. cdnupload does this by appending to the filename a 16-character hash based on the file\u2019s contents. For example, ``style.css`` might become ``style_abcdef0123456789.css``, and then ``style_a0b1c2d3e4f56789.css`` in the next revision.\n\nOn one `website `_ we run, we saw our **static file load time drop from 1500ms to 220ms** when we starting using cdnupload with the Amazon Cloudfront CDN.\n\nSo you should use a CDN if your site gets a good amount of traffic, and you need good performance from various locations around the world. You probably *don\u2019t* need to use a CDN if you have a small personal site.\n\nUsing the `Amazon CloudFront `_ CDN together with `Amazon S3 `_ as an origin server is a great place to start -- like other AWS products, you only pay for the bytes you use, and there\u2019s no monthly fee.\n\n\nCommand-line usage\n==================\n\nThe format of the cdnupload command line is::\n\n cdnupload [options] source destination [dest_args]\n\nWhere ``options`` are short or long command line options (``-s`` or ``--long``). You can mix these freely with the positional arguments if you want.\n\nSource\n------\n\n``source`` is the source directory of your static files, for example ``/website/static``. Use the optional ``--include`` and ``--exclude`` arguments, and other arguments described below, to control exactly which files are uploaded.\n\nDestination and dest-args\n-------------------------\n\n``destination`` is the destination directory to upload to, or an ``s3://static-bucket/prefix`` path for uploading to Amazon S3.\n\nYou can also specify a custom scheme for the destination (the ``scheme://`` part of the URL), and cdnupload will try to import a module named ``cdnupload_scheme`` (which must be on the PYTHONPATH) and use that module\u2019s ``Destination`` class along with the ``dest_args`` to create the destination instance.\n\nFor example, if you create your own uploader for Google Cloud Storage, you might use the prefix ``gcs://`` and name your module ``cdnupload_gcs``. Then you could use ``gcs://my/path`` as a destination, and cdnupload would instantiate the destination instance using ``cdnupload_gcs.Destination('gcs://bucket', **dest_args)``.\n\nSee the `custom destination`_ section for more details about custom ``Destination`` subclasses.\n\n``dest_args`` are destination-specific arguments passed as keyword arguments to the ``Destination`` class (for example, for ``s3://`` destinations, useful dest args might be ``max-age=86400`` or ``region-name=us-west-2``). Note that hyphens in dest args are converted to underscores, so ``region-name=us-west-2`` becomes ``region_name='us-west-2'``.\n\nFor help on destination-specific args, use the ``dest-help`` action. For example, to show S3-specific destination args::\n\n cdnupload source s3:// --action=dest-help\n\nCommon arguments\n----------------\n\n -h, --help\n Show help about these command-line options and exit.\n\n -a ACTION, --action ACTION\n Specify action to perform (the default is to upload):\n\n * ``upload``: Upload files from the source to the destination (but only if they\u2019re not already on the destination).\n * ``delete``: Delete unused files at the destination (files no longer present at the source). Be careful with deleting, and use ``--dry-run`` to test first!\n * ``dest-help``: Show help and available destination arguments for the given Destination class.\n\n -d, --dry-run\n Show what the script would upload or delete instead of actually doing it. This option is recommended before running with ``--action=delete``, to ensure you\u2019re not deleting more than you expect.\n\n -e PATTERN, --exclude PATTERN\n Exclude source files if their relative path matches the given pattern (according to globbing rules as per Python\u2019s ``fnmatch``). For example, ``*.txt`` to exclude all text files, or ``__pycache__/*`` to exclude everything under the *pycache* directory. This option may be specified multiple times to exclude more than one pattern.\n\n Excludes take precedence over includes, so you can do ``--include=*.txt`` but then exclude a specific text file with ``--exclude=docs/README.txt``.\n\n -f, --force\n If uploading, force all files to be uploaded even if destination files already exist (useful, for example, when updating headers on Amazon S3).\n\n If deleting, allow the delete to occur even if all files on the destination would be deleted (the default is to prevent that to avoid ``rm -rf`` style mistakes).\n\n -i PATTERN, --include PATTERN\n If specified, only include source files if their relative path matches the given pattern (according to globbing rules as per Python\u2019s ``fnmatch``). For example, ``*.png`` to include all PNG images, or ``images/*`` to include everything under the *images* directory. This option may be specified multiple times to include more than one pattern.\n\n Excludes take precedence over includes, so you can do ``--include=*.txt`` but then exclude a specific text file with ``--exclude=docs/README.txt``.\n\n -k FILENAME, --key-map FILENAME\n Write key mapping to given file as JSON (but only after successful upload or delete). This file can be used by your web server to produce full CDN URLs for your static files.\n\n Keys in the JSON object are the original paths (relative to the source root), and values in the object are the destination paths (relative to the destination root). For example, the JSON might look like ``{\"script.js\": \"script_0beec7b5ea3f0fdb.js\", ...}``.\n\n -l LEVEL, --log-level LEVEL\n Set the verbosity of the log output. The level must be one of:\n\n * ``debug``: Most detailed output. Log even files that the script would skip uploading.\n * ``verbose``: Verbose output. Log when the script starts, finishes, and when uploads and deletes occur (or would occur if doing a ``--dry-run``).\n * ``default``: Default level of log output. Only log when and if the script actually uploads or deletes files (no start or finish logs). If there\u2019s nothing to do, don\u2019t log anything.\n * ``error``: Only log errors.\n * ``off``: Turn all logging off completely.\n\n -v, --version\n Show cdnupload\u2019s version number and exit.\n\nLess common arguments\n---------------------\n\n --continue-on-errors\n Continue after upload or delete errors. The script will still log the errors, and it will also return a nonzero exit code if there is at least one error. The default is to stop on the first error.\n --dot-names\n Include source files and directories that start with ``.`` (dot). The default is to skip any files or directories that start with a dot.\n --follow-symlinks\n Follow symbolic links to directories when walking the source tree. The default is to skip any symbolic links to directories.\n --hash-length N\n Set the number of hexadecimal characters of the content hash to use for destination key. The default is 16.\n --ignore-walk-errors\n Ignore errors when walking the source tree (for example, permissions errors on a directory), except for an error when listing the source root directory.\n\n\nWeb server integration\n======================\n\nIn addition to using the command line script to upload files, you\u2019ll need to modify your web server so it knows how to generate the static URLs including the content-based hash in the filename.\n\nThe recommended way to do this is to use the key mapping JSON, which is written out by the ``--key-map`` command line argument when you upload your statics. You can load this into a key-value dictionary when your server starts up, and then generate a static URL simply by looking up the relative path of a static file in this dictionary.\n\nEven though the keys in the JSON are relative file paths, they\u2019re normalized to always use ``/`` (forward slash) as the directory separator, even on Windows. This is so consumers of the mapping can look up files directly in the mapping with a consistent path separator.\n\nBelow is a simple example of loading the key mapping in your web server startup (call ``init_server()`` on startup) and then defining a function to generate full static URLs for use in your HTML templates. This example is written in Python, but you can use any language that can parse JSON and look something up in a map::\n\n import json\n import settings\n\n def init_server():\n settings.cdn_base_url = 'https://mycdn.com/'\n with open('statics.json') as f:\n settings.statics = json.load(f)\n\n def static_url(rel_path):\n \"\"\"Convert relative static path to full static URL (including hash)\"\"\"\n return settings.cdn_base_url + settings.statics[rel_path]\n\nAnd then in your HTML templates, just reference a static file using the ``static_url`` function (referenced here as a Jinja2 template filter)::\n\n \n\nIf your web server is in fact written in Python, you can also ``import cdnupload`` directly and use ``cdnupload.FileSource`` with the same parameters as the upload command line. This will build the key mapping at server startup time, and may simplify the deployment process a little::\n\n import cdnupload\n import settings\n\n def init_server():\n settings.cdn_base_url = 'https://mycdn.com/'\n source = cdnupload.FileSource(settings.static_dir)\n settings.static_paths = source.build_key_map()\n\nIf you have huge numbers of static files, this is not recommended, as it does have to re-hash all the files when the server starts up. So for larger sites it\u2019s best to produce the key map JSON and copy that to your app servers as part of your deployment process.\n\n\nStatic URLs in CSS\n==================\n\nIf you reference static files in your CSS (for example, background images with ``url(...)`` expressions), you\u2019ll need to either remove them from your CSS and generate them in an inline ``\n \n\nHowever, for larger-scale sites where the CSS references a lot of static images, this quickly becomes hard to manage. In that case, you\u2019ll want to use a tool like `PostCSS `_ to rewrite static URLs in your CSS to cdnupload URLs via the key mapping. There\u2019s a PostCSS plugin called `postcss-url `_ that you can use to rewrite URLs with a custom transform function.\n\nThe CSS rewriting should be integrated into your build or deployment process, as the PostCSS rule will need access to the JSON key mapping that the uploader wrote out.\n\n\nPython API\n==========\n\ncdnupload is a Python command-line script, but it\u2019s also a Python module you can import and extend if you need to customize it or hook into advanced features. It works on both Python 3.4+ and Python 2.7.\n\nCustom destination\n------------------\n\nThe most likely reason you\u2019ll need to extend cdnupload is to write a custom ``Destination`` subclass (if the built-in file or Amazon S3 destinations don\u2019t work for you).\n\nFor example, if you\u2019re using a CDN that connects to an origin server called \u201cMy Origin\u201d, you might write a custom subclass for uploading to your origin. You\u2019ll need to subclass ``cdnupload.Destination`` and implement an initalizer as well as the ``__str__``, ``walk_keys``, ``upload``, and ``delete`` methods::\n\n import cdnupload\n import myorigin\n\n class Destination(cdnupload.Destination):\n def __init__(self, url, foo='FOO', bar=None):\n \"\"\"Initialize destination instance with given \"My Origin\" URL\n (which should be in form my://server/path).\n \"\"\"\n self.url = url\n self.conn = myorigin.Connection(url, foo=foo, bar=bar)\n\n def __str__(self):\n \"\"\"Return a human-readable string for this destination.\"\"\"\n return self.url\n\n def walk_keys(self):\n \"\"\"Yield keys (files) that are currently on the destination.\"\"\"\n for file in self.conn.get_files():\n yield file.name\n\n def upload(self, key, source, rel_path):\n \"\"\"Upload a single file from source at rel_path to destination\n at given key. Normally this function will use the with statement\n \"with source.open(rel_path)\" to open the source file object.\n \"\"\"\n with source.open(rel_path) as source_file:\n self.conn.upload_file_obj(source_file, key)\n\n def delete(self, key):\n \"\"\"Delete a single file on destination at given key.\"\"\"\n self.conn.delete_file(key)\n\nTo use this custom destination, save your custom code to ``cdnupload_my.py`` and ensure the file is somewhere on your PYTHONPATH. Then if you run the cdnupload command-line tool with a destination starting with scheme ``my://``, it will automatically import ``cdnupload_my`` and look for a class called ``Destination``, passing the ``my://server/path`` URL and any additional destination arguments to your initializer.\n\nNote that when the command-line tool passes additional dest_args to a custom destination, it always passes them as strings (or a list of strings if a dest arg is specified more than once). So if you need an integer or other type, you\u2019ll need to convert it in your ``__init__`` method.\n\nUpload and delete\n-----------------\n\nThe top-level functions ``upload()`` and ``delete()`` drive cdnupload. You can create your own command-line entry point if you want to hook into cdnupload\u2019s Python API. For example, you could make a ``myupload.py`` script as follows::\n\n import cdnupload\n import hashlib\n\n source = cdnupload.FileSource('/path/to/my/statics',\n hash_class=hashlib.md5)\n destination = cdnupload.S3Destination('s3://bucket/path')\n cdnupload.upload(source, destination)\n\nHere we\u2019re doing some light customization of ``FileSource``\u2019s hashing behaviour (changing it from SHA-1 to MD5) and then performing an upload.\n\nThe ``upload()`` function uploads files from a source to a destination, but only if they\u2019re missing at the destination (according to ``destination.walk_keys``).\n\nThe ``delete()`` function deletes files from the destination if they\u2019re no longer present at the source (according to ``source.build_key_map``).\n\nBoth ``upload`` and ``delete`` take the same set of arguments:\n\n* ``source``: the source object; either a ``FileSource`` instance (or object that implements the same interface), or a string in which case it gets converted to a source via ``FileSource(source)``\n* ``destination``: the destination object; either an instance of a concrete ``Destination`` subclass, or a string in which case it gets converted to a destination via ``FileDestination(destination)``\n* ``force=False``: if True, same as specifying the ``--force`` command line option\n* ``dry_run=False``: if True, same as specifying the ``--dry-run`` command line option\n* ``continue_on_errors=False``: if True, same as specifying the ``--continue-on-errors`` command line option\n\nBoth functions return a ``Result`` namedtuple, which has the following attributes:\n\n* ``source_key_map``: the source path to destination key mapping, the same dict returned by ``source.build_key_map()``\n* ``destination_keys``: a set containing the destination keys, as returned by ``destination.walk_keys()``\n* ``num_scanned``: total number of files scanned (source files when uploading, or destination keys when deleting)\n* ``num_processed``: number of files processed (actually uploaded or deleted)\n* ``num_errors``: number of errors (useful when ``continue_on_errors`` is true)\n\nCustom source\n-------------\n\nYou can also customize the source of the files. There\u2019s currently only one source class, ``FileSource``, which reads files from the filesystem and produces file hashes. You can pass options to the ``FileSource`` initializer to control which files it includes or excludes, as well as how it hashes their contents to produce the content-based hash.\n\nThe ``dot_names``, ``include``, ``exclude``, ``ignore_walk_errors``, ``follow_symlinks``, and ``hash_length`` arguments correspond directly to the ``--dot-names``, ``--include``, ``--exclude``, ``--ignore-walk-errors``, ``--follow-symlinks``, and ``--hash-length`` command line options.\n\nAdditionally, you can customize ``FileSource`` further with the ``hash_chunk_size`` and ``hash_class`` arguments. The file is read in ``hash_chunk_size``-byte blocks when being hashed, and ``hash_class`` is instantiated to generate the hashes (must have a hashlib-style signature).\n\nOr you can subclass ``FileSource`` if you want to customize advanced behaviour. For example, you could override ``FileSource.hash_file()``\u2019s handling of text and binary files to treat all files as binary::\n\n from cdnupload import FileSource\n\n class BinarySource(FileSource):\n def hash_file(self, rel_path):\n return FileSource.hash_file(self, rel_path, is_text=False)\n\nTo use a subclassed ``FileSource``, you\u2019ll need to call the ``upload()`` and ``delete()`` functions with your instance directly from Python. It\u2019s not currently possibly to use a subclassed source via the cdnupload command line script.\n\nLogging\n-------\n\ncdnupload functions use standard Python logging to log all operations. The name of the logger is ``cdnupload``, and you can control log output format and verbosity (log level) using the Python logging functions.\n\nFor example, to log all errors but turn debug-level logging on only for cdnupload logs, you could do this::\n\n import logging\n\n logging.basicConfig(level=logging.ERROR)\n logging.getLogger('cdnupload').setLevel(logging.DEBUG)\n\n\nContributing\n============\n\nIf you find a bug in cdnupload, please open an issue with the following information:\n\n* Full error messages or tracebacks\n* The cdnupload version, Python version, and operating system type and version\n* Steps or a test case that reproduces the issue (ideally)\n\nIf you have a feature request, documentation fix, or other suggestion, open an issue and we\u2019ll discuss!\n\nSee also `CONTRIBUTING.md `_ in the cdnupload source tree.\n\n\nLicense\n=======\n\ncdnupload is licensed under a permissive MIT license: see `LICENSE.txt `_ for details.\n\nNote that prior to August 2017 it was licensed under an AGPL plus commercial license combination, but now it's completely free.\n\n\nAbout the author\n================\n\ncdnupload is written and maintained by Ben Hoyt: a `software developer `_, `Python contributor `_, and general all-round software geek. For more info, see his personal website at `benhoyt.com `_.\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/benhoyt/cdnupload", "keywords": "", "license": "MIT License", "maintainer": "", "maintainer_email": "", "name": "cdnupload", "package_url": "https://pypi.org/project/cdnupload/", "platform": "", "project_url": "https://pypi.org/project/cdnupload/", "project_urls": { "Homepage": "https://github.com/benhoyt/cdnupload" }, "release_url": "https://pypi.org/project/cdnupload/1.0.4/", "requires_dist": null, "requires_python": "", "summary": "Upload static files from given source directory to destination directory or Amazon S3 bucket, with content-based hash in filenames for versioning.", "version": "1.0.4" }, "last_serial": 3086737, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "74f1fc8e67b3df8b40334bbe42137bca", "sha256": "918734ad4a25c3bcb1cf38b778d2bd80a862342ab8d674b3cb5d0d9282b92a9f" }, "downloads": -1, "filename": "cdnupload-1.0.0.tar.gz", "has_sig": false, "md5_digest": "74f1fc8e67b3df8b40334bbe42137bca", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32572, "upload_time": "2017-04-06T12:39:54", "url": "https://files.pythonhosted.org/packages/d4/6e/895fb7ffe3404768e556002e7683e0eece69bf3157b5caf0f759841fc7db/cdnupload-1.0.0.tar.gz" } ], "1.0.1": [ { "comment_text": "", "digests": { "md5": "f9853cf389828f804fa148b70e6d93fd", "sha256": "10e16771136b0c58245d4d4af8cae97536cd75fb27b47849d4f99ff6d94c9bfe" }, "downloads": -1, "filename": "cdnupload-1.0.1.tar.gz", "has_sig": false, "md5_digest": "f9853cf389828f804fa148b70e6d93fd", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32628, "upload_time": "2017-04-08T16:09:52", "url": "https://files.pythonhosted.org/packages/0d/da/dbad9a9897f816d7ed2c7fc18815cfe5451bb2a6c0570720cbd59b625e59/cdnupload-1.0.1.tar.gz" } ], "1.0.2": [ { "comment_text": "", "digests": { "md5": "fd5368519706071cc950700357f31bf1", "sha256": "5241ef288725490fe812e6f05098e9bceef5d01654f8025c5f43cdf36aebe4c4" }, "downloads": -1, "filename": "cdnupload-1.0.2.tar.gz", "has_sig": false, "md5_digest": "fd5368519706071cc950700357f31bf1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32702, "upload_time": "2017-04-08T17:34:18", "url": "https://files.pythonhosted.org/packages/b0/a5/30b04e713de3ef11a1abfc05f688e9537513a0c8dc17671f8360d637c230/cdnupload-1.0.2.tar.gz" } ], "1.0.3": [ { "comment_text": "", "digests": { "md5": "1c9b6daf28e21ecd433cc451d43d1c63", "sha256": "fb69020cbeae65a99bc6b81c96780321ca13239a398f3693b6ef5587ecdde0e0" }, "downloads": -1, "filename": "cdnupload-1.0.3.tar.gz", "has_sig": false, "md5_digest": "1c9b6daf28e21ecd433cc451d43d1c63", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 32672, "upload_time": "2017-04-12T16:43:13", "url": "https://files.pythonhosted.org/packages/79/11/5b108c97ae3c0cc1eb7c7df919ca7b20fcb692ba1b690b4e93c1c64a6c78/cdnupload-1.0.3.tar.gz" } ], "1.0.4": [ { "comment_text": "", "digests": { "md5": "f2ef6a0f68c44af7420bdf2e19c3f225", "sha256": "e1b6c7dfaef3c7968ffeecb82e3665f32c0ec175fc79fab2a018ffd6e57f56df" }, "downloads": -1, "filename": "cdnupload-1.0.4.tar.gz", "has_sig": false, "md5_digest": "f2ef6a0f68c44af7420bdf2e19c3f225", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33824, "upload_time": "2017-08-10T12:40:54", "url": "https://files.pythonhosted.org/packages/be/0c/79a6f9ba704590c2a3e0fb295d717e7ec611195278d9eebe16bb933d0f66/cdnupload-1.0.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "f2ef6a0f68c44af7420bdf2e19c3f225", "sha256": "e1b6c7dfaef3c7968ffeecb82e3665f32c0ec175fc79fab2a018ffd6e57f56df" }, "downloads": -1, "filename": "cdnupload-1.0.4.tar.gz", "has_sig": false, "md5_digest": "f2ef6a0f68c44af7420bdf2e19c3f225", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33824, "upload_time": "2017-08-10T12:40:54", "url": "https://files.pythonhosted.org/packages/be/0c/79a6f9ba704590c2a3e0fb295d717e7ec611195278d9eebe16bb933d0f66/cdnupload-1.0.4.tar.gz" } ] }