Skip to content

Commit

Permalink
Updated client for pasted.tech
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Carter <kevin.carter@rackspace.com>
  • Loading branch information
cloudnull committed Nov 14, 2018
1 parent f1d5ec0 commit b9ad197
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 174 deletions.
76 changes: 76 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
*.pyc
build/
dist/
doc/build/
deploy-guide/build/

# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip

# Logs and databases #
######################
*.log
*.sql
*.sqlite
logs/*

# OS generated files #
######################
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
.idea
.tox
*.sublime*
*.egg-info
Icon?
ehthumbs.db
Thumbs.db
.eggs
.coverage
*.retry

# User driven backup files #
############################
*.bak
*.swp

# Generated by pbr while building docs
######################################
AUTHORS
ChangeLog

# Files created by releasenotes build
releasenotes/build

# Vagrant artifacts
.vagrant

# run playbooks tests
playbooks/root-include-playbook.yml
playbooks/include-playbook.yml*
playbooks/logs

# This file is generated by doc/source/scenario_table_gen.py
# So we should ignore any contributor changes to it.
doc/source/user/aio/scenario-table-gen.html
51 changes: 31 additions & 20 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
===============
pasteraw-client
===============
=============
pasted-client
=============

Pipe `stdin` directly to a raw pastebin. Get a URL back. Go be productive.
Pipe `STDIN` or upload files to a raw paste. Get a URL back. Go be productive.

By default, `pasteraw` uses a hosted pastebin service, `pasteraw.com
<http://pasteraw.com/>`_. You can also deploy `your own instance
<https://github.com/dolph/pasteraw>`_ of the service and use it instead.
By default, `pasted` uses a hosted paste service, `pasted.tech
<http://pasted.tech/>`_. You can also deploy `your own instance
<https://github.com/cloudnull/pasted>`_ of the service and use it instead.

Installation
------------

.. image:: https://img.shields.io/pypi/v/pasteraw.svg
:target: https://pypi.python.org/pypi/pasteraw
.. image:: https://img.shields.io/badge/pasted-stable-brightgreen.svg
:target: https://pypi.python.org/pypi/pasted-client

From PyPi::

$ pip install pasteraw
$ pip install pasted-client

Command line usage
------------------
Expand All @@ -26,30 +26,41 @@ Given a file::
$ cat somefile
Lorem ipsum.

Pipe the file to `pasteraw` and get back a URL to a raw paste of that file::

$ cat somefile | pasteraw
http://cdn.pasteraw.com/9lvwkwgrgji5gbhjygxgaqcfx3hefpb
Pipe the file to `pasted` and get back a URL to a raw paste of that file::

$ cat somefile | pasted
http://pasted.com/89001a7fbbe57e6921a91b2ba166fa98e1579cd2.raw


Do whatever you want with the URL. Curl it, email it, whatever::

$ curl http://cdn.pasteraw.com/9lvwkwgrgji5gbhjygxgaqcfx3hefpb
$ curl http://pasted.tech/89001a7fbbe57e6921a91b2ba166fa98e1579cd2.raw
Lorem ipsum.


You can also paste multiple files without a pipe::

$ pasted /path/to/file1 /path/to/file2 /path/to/file3
https://pasted.tech/pastes/294b43b2cec9919063be1a3b49e8722648424779.raw
https://pasted.tech/pastes/3c56f1d7f112e09002627d24b82446431df5039a.raw
https://pasted.tech/pastes/f9372ce11a7370c54135f3c708131de123caf90f.raw


Python library usage
--------------------

To use `pasteraw.com <http://pasteraw.com/>`_::
To use `pasted.tech <http://pasted.tech/>`_::

>>> c = pasteraw.Client()
>>> c = pasted.Client()
>>> url = c.create_paste('Lorem ipsum.')
>>> print(url)
http://cdn.pasteraw.com/9lvwkwgrgji5gbhjygxgaqcfx3hefpb
http://pasted.tech/89001a7fbbe57e6921a91b2ba166fa98e1579cd2.raw

Alternatively, if you're using your own deployment of `pasteraw
<https://github.com/dolph/pasteraw>`_, pass your own API endpoint to the
<https://github.com/cloudnull/pasted>`_, pass your own API endpoint to the
client::

>>> c = pasteraw.Client('http://pasteraw.example.com/api/v1')
>>> c = pasteraw.Client('http://pasted.example.com/api/pastes')

Usage is otherwise identical to using `pasteraw.com <http://pasteraw.com/>`_.
Usage is otherwise identical to using `pasted.tech <http://pasted.tech/>`_.
181 changes: 181 additions & 0 deletions pasted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import argparse
import functools
import json
import logging
import os
import sys
import time

import requests


LOG_FORMAT = '%(levelname)s: %(message)s'
logging.basicConfig(format=LOG_FORMAT)
LOG = logging.getLogger(__name__)

ENDPOINT = 'https://pasted.tech/api/pastes'


class Error(Exception):
pass


class MaxLengthExceeded(Error):
pass


class UnexpectedError(Error):
pass


class UrlNotFound(Error):
pass


def retry(ExceptionToCheck, tries=5, delay=1, backoff=2):
"""Retry calling the decorated function using an exponential backoff.
:param ExceptionToCheck: the exception to check. may be a tuple of
exceptions to check
:type ExceptionToCheck: Exception or tuple
:param tries: number of times to try (not retry) before giving up
:type tries: int
:param delay: initial delay between retries in seconds
:type delay: int
:param backoff: backoff multiplier e.g. value of 2 will double the
delay each retry
:type backoff: int
"""
def deco_retry(f):
@functools.wraps(f)
def f_retry(*args, **kwargs):
mtries, mdelay = tries, delay
while mtries > 1:
try:
return f(*args, **kwargs)
except ExceptionToCheck:
time.sleep(mdelay)
mtries -= 1
mdelay *= backoff
return f(*args, **kwargs)
return f_retry
return deco_retry


class Client(object):
"""A client library for pasteraw.
To use pasteraw.com:
>>> c = pasted.Client()
>>> url = c.create_paste('Lorem ipsum.')
>>> print(url)
http://cdn.pasteraw.com/9lvwkwgrgji5gbhjygxgaqcfx3hefpb
If you're using your own pasteraw deployment, pass your own API endpoint to
the client:
>>> c = pasteraw.Client('http://pasted.example.com/api/pastes')
"""

def __init__(self, endpoint=None):
"""Initialize a pasteraw client for the given endpoint (optional)."""
self.endpoint = endpoint or ENDPOINT
LOG.debug('Endpoint: %s', ENDPOINT)

@retry(UrlNotFound)
def _validate_paste(self, url):
r = requests.get(url)
if r.status_code != 200:
print('Its likely the server is busy or the object store backend'
' is not responding as quick as we would like. Rest assured'
' your pasted content has been written given the POST'
' returned a URL. Please wait a couple of minutes for the'
' content to be rendered by the site.')
print('URL: {}'.format(url))
raise UrlNotFound(url)
else:
return url

def create_paste(self, content):
"""Create a raw paste of the given content.
Returns a URL to the paste, or raises a ``pasteraw.Error`` if something
tragic happens instead.
"""
content_length = len(content)
LOG.debug('Content-Length: %d', content_length)

r = requests.post(
self.endpoint,
data=json.dumps({'content': content}),
headers={'content-type': 'application/json'}
)

if r.status_code == 201:
return self._validate_paste(r.text)
elif r.status_code == 302:
return r.headers['Location']
elif r.status_code == 413:
raise MaxLengthExceeded('%d bytes' % len(content))
else:
print(r.text)
raise UnexpectedError(r.headers)


def main(args):
LOG.debug('File-Count: %d', len(args.files))

client = Client(args.endpoint)
if args.files:
for arg_file in args.files:
if os.path.isfile(arg_file):
LOG.debug('Content-Length: %s', arg_file)
with open(arg_file) as f:
url = client.create_paste(f.read())
print(url)
else:
url = client.create_paste(''.join(sys.stdin.readlines()))
print(url)


def cli():
parser = argparse.ArgumentParser(
prog='pasted-client',
description='Pipe `STDIN` or upload files to a raw paste. Get a URL back.'
' Go be productive.')
parser.add_argument(
'files', metavar='file', nargs='*',
help='one or more file names')
parser.add_argument(
'--endpoint', default=ENDPOINT,
help=argparse.SUPPRESS)
parser.add_argument(
'--debug', action='store_true',
help=argparse.SUPPRESS)
args = parser.parse_args()

if args.debug:
LOG.setLevel(logging.DEBUG)
else:
LOG.setLevel(logging.WARN)

main(args)


if __name__ == '__main__':
cli()
Loading

0 comments on commit b9ad197

Please sign in to comment.