Wednesday, February 22, 2017

Back up:
https://semaphoreci.com/community/tutorials/testing-python-applications-with-pytest

Testing Python Applications with Pytest

Pytest stands out among Python testing tools due to its ease of use. This tutorial will get you started with using pytest to test your next Python project.
Brought to you by

Semaphore

Introduction

Testing applications has become a standard skill set required for any competent developer today. The Python community embraces testing, and even the Python standard library has good inbuilt tools to support testing. In the larger Python ecosystem, there are a lot of testing tools. Pytest stands out among them due to its ease of use and its ability to handle increasingly complex testing needs.
This tutorial will demonstrate how to write tests for Python code with pytest, and how to utilize it to cater for a wide range of testing scenarios.

Prerequisites

This tutorial uses Python 3, and we will be working inside a virtualenv.
Fortunately for us, Python 3 has inbuilt support for creating virtual environments.
To create and activate a virtual environment for this project, let's run the following commands:
mkdir pytest_project
cd pytest_project
python3 -m venv pytest-env
This creates a virtual environment called pytest-env in our working directory.
To begin using the virtualenv, we need to activate it as follows:
source pytest-env/bin/activate
As long as the virtualenv is active, any packages we install will be installed in our virtual environment, rather than in the global Python installation.
To get started, let's install pytest in our virtualenv.
pip install pytest

Basic Pytest Usage

We will start with a simple test. Pytest expects our tests to be located in files whose names begin with test_ or end with _test.py. Let's create a file called test_capitalize.py, and inside it we will write a function called capital_case which should take a string as its argument, and should return a capitalized version of the string. We will also write a test, test_capital_case to ensure that the function does what it says. We prefix our test function names with test_, since this is what pytest expects our test functions to be named.
# test_capitalize.py

def capital_case(x):
    return x.capitalize()

def test_capital_case():
    assert capital_case('semaphore') == 'Semaphore'
The immediately noticeable thing is that pytest uses a plain assert statement, which is much easier to remember and use compared to the numerous assertSomething functions found in unittest.
To run the test, execute the pytest command:
pytest
We should see that our first test passes.
A keen reader will notice that our function could lead to a bug. It does not check the type of the argument to ensure that it is a string. Therefore, if we passed in a number as the argument to the function, it would raise an exception.
We would like to handle this case in our function by raising a custom exception with a friendly error message to the user.
Let's try to capture this in our test:
# test_capitalize.py

import pytest

def test_capital_case():
    assert capital_case('semaphore') == 'Semaphore'

def test_raises_exception_on_non_string_arguments():
    with pytest.raises(TypeError):
        capital_case(9)
The major addition here is the pytest.raises helper, which asserts that our function should raise a TypeError in case the argument passed is not a string.
Running the tests at this point should fail with the following error:
def capital_case(x):
>       return x.capitalize()
E       AttributeError: 'int' object has no attribute 'capitalize'
Since we've verified that we have not handled such a case, we can go ahead and fix it.
In our capital_case function, we should check that the argument passed is a string or a string subclass before calling the capitalize function. If it is not, we should raise a TypeError with a custom error message.
# test_capitalize.py

def capital_case(x):
    if not isinstance(x, str):
        raise TypeError('Please provide a string argument')
      return x.capitalize()
When we rerun our tests, they should be passing once again.

Using Pytest Fixtures

In the following sections, we will explore some more advanced pytest features. To do this, we will need a small project to work with.
We will be writing a wallet application that enables its users to add or spend money in the wallet. It will be modeled as a class with two instance methods: spend_cash and add_cash.
We'll get started by writing our tests first. Create a file called test_wallet.py in the working directory, and add the following contents:
# test_wallet.py

import pytest
from wallet import Wallet, InsufficientAmount


def test_default_initial_amount():
    wallet = Wallet()
    assert wallet.balance == 0

def test_setting_initial_amount():
    wallet = Wallet(100)
    assert wallet.balance == 100

def test_wallet_add_cash():
    wallet = Wallet(10)
    wallet.add_cash(90)
    assert wallet.balance == 100

def test_wallet_spend_cash():
    wallet = Wallet(20)
    wallet.spend_cash(10)
    assert wallet.balance == 10

def test_wallet_spend_cash_raises_exception_on_insufficient_amount():
    wallet = Wallet()
    with pytest.raises(InsufficientAmount):
        wallet.spend_cash(100)
First things first, we import the Wallet class and the InsufficientAmount exception that we expect to raise when the user tries to spend more cash than they have in their wallet.
When we initialize the Wallet class, we expect it to have a default balance of 0. However, when we initialize the class with a value, that value should be set as the wallet's initial balance.
Moving on to the methods we plan to implement, we test that the add_cash method correctly increments the balance with the added amount. On the other hand, we are also ensuring that the spend_cash method reduces the balance by the spent amount, and that we can't spend more cash than we have in the wallet. If we try to do so, an InsufficientAmount exception should be raised.
Running the tests at this point should fail, since we have not created our Wallet class yet. We'll proceed with creating it. Create a file called wallet.py, and we will add our Wallet implementation in it. The file should look as follows:
# wallet.py

class InsufficientAmount(Exception):
    pass


class Wallet(object):

    def __init__(self, initial_amount=0):
        self.balance = initial_amount

    def spend_cash(self, amount):
        if self.balance < amount:
            raise InsufficientAmount('Not enough available to spend {}'.format(amount))
        self.balance -= amount

    def add_cash(self, amount):
        self.balance += amount
First of all, we define our custom exception, InsufficientAmount, which will be raised when we try to spend more money than we have in the wallet. The Wallet class then follows. The constructor accepts an initial amount, which defaults to 0 if not provided. The initial amount is then set as the balance.
In the spend_cash method, we first check that we have a sufficient balance. If the balance is lower than the amount we intend to spend, we raise the InsufficientAmount exception with a friendly error message.
The implementation of add_cash then follows, which simply adds the provided amount to the current wallet balance.
Once we have this in place, we can rerun our tests, and they should be passing.
pytest -q test_wallet.py

.....
5 passed in 0.01 seconds

Refactoring our Tests with Fixtures

You may have noticed some repetition in the way we initialized the class in each test. This is where pytest fixtures come in. They help us set up some helper code that should run before any tests are executed, and are perfect for setting up resources that are needed by the tests.
Fixture functions are created by marking them with the @pytest.fixture decorator. Test functions that require fixtures should accept them as arguments. For example, for a test to receive a fixture called wallet, it should have an argument with the fixture name, i.e. wallet.
Let's see how this works in practice. We will refactor our previous tests to use test fixtures where appropriate.
# test_wallet.py

import pytest
from wallet import Wallet, InsufficientAmount

@pytest.fixture
def empty_wallet():
    '''Returns a Wallet instance with a zero balance'''
    return Wallet()

@pytest.fixture
def wallet():
    '''Returns a Wallet instance with a balance of 20'''
    return Wallet(20)

def test_default_initial_amount(empty_wallet):
    assert empty_wallet.balance == 0

def test_setting_initial_amount(wallet):
    assert wallet.balance == 20

def test_wallet_add_cash(wallet):
    wallet.add_cash(80)
    assert wallet.balance == 100

def test_wallet_spend_cash(wallet):
    wallet.spend_cash(10)
    assert wallet.balance == 10

def test_wallet_spend_cash_raises_exception_on_insufficient_amount(empty_wallet):
    with pytest.raises(InsufficientAmount):
        empty_wallet.spend_cash(100)
In our refactored tests, we can see that we have reduced the amount of boilerplate code by making use of fixtures.
We define two fixture functions,wallet and empty_wallet, which will be responsible for initializing the Wallet class in tests where it is needed, with different values.
For the first test function, we make use of the empty_wallet fixture, which provided a wallet instance with a balance of 0 to the test.
The next three tests receive a wallet instance initialized with a balance of 20. Finally, the last test receives the empty_wallet fixture. The tests can then make use of the fixture as if it was created inside the test function, as in the tests we had before.
Rerun the tests to confirm that everything works.
Utilizing fixtures helps us de-duplicate our code. If you notice a case where a piece of code is used repeatedly in a number of tests, that might be a good candidate to use as a fixture.

Some Pointers on Test Fixtures

Here are some pointers on using test fixtures:
  • Each test is provided with a newly-initialized Wallet instance, and not one that has been used in another test.
  • It is a good practice to add docstrings for your fixtures. To see all the available fixtures, run the following command:
pytest --fixtures
This lists out some inbuilt pytest fixtures, as well as our custom fixtures. The docstrings will appear as the descriptions of the fixtures.
wallet
    Returns a Wallet instance with a balance of 20
empty_wallet
    Returns a Wallet instance with a zero balance

Parametrized Test Functions

Having tested the individual methods in the Wallet class, the next step we should take is to test various combinations of these methods. This is to answer questions such as "If I have an initial balance of 30, and spend 20, then add 100, and later on spend 50, how much should the balance be?"
As you can imagine, writing out those steps in the tests would be tedious, and pytest provides quite a delightful solution: Parametrized test functions
To capture a scenario like the one above, we can write a test:
# test_wallet.py

@pytest.mark.parametrize("earned,spent,expected", [
    (30, 10, 20),
    (20, 2, 18),
])
def test_transactions(earned, spent, expected):
    my_wallet = Wallet()
    my_wallet.add_cash(earned)
    my_wallet.spend_cash(spent)
    assert my_wallet.balance == expected
This enables us to test different scenarios, all in one function. We make use of the @pytest.mark.parametrize decorator, where we can specify the names of the arguments that will be passed to the test function, and a list of arguments corresponding to the names.
The test function marked with the decorator will then be run once for each set of parameters.
For example, the test will be run the first time with the earned parameter set to 30, spent set to 10, and expected set to 20. The second time the test is run, the parameters will take the second set of arguments. We can then use these parameters in our test function.
This elegantly helps us capture the scenario:
  • My wallet initially has 0,
  • I add 30 units of cash to the wallet,
  • I spend 10 units of cash, and
  • I should have 20 units of cash remaining after the two transactions.
This is quite a succinct way to test different combinations of values without writing a lot of repeated code.

Combining Test Fixtures and Parametrized Test Functions

To make our tests less repetitive, we can go further and combine test fixtures and parametrize test functions. To demonstrate this, let's replace the wallet initialization code with a test fixture as we did before. The end result will be:
# test_wallet.py

@pytest.fixture
def my_wallet():
    '''Returns a Wallet instance with a zero balance'''
    return Wallet()

@pytest.mark.parametrize("earned,spent,expected", [
    (30, 10, 20),
    (20, 2, 18),
])
def test_transactions(my_wallet, earned, spent, expected):
    my_wallet.add_cash(earned)
    my_wallet.spend_cash(spent)
    assert my_wallet.balance == expected
We will create a new fixture called my_wallet that is exactly the same as the empty_wallet fixture we used before. It returns a wallet instance with a balance of 0. To use both the fixture and the parametrized functions in the test, we include the fixture as the first argument, and the parameters as the rest of the arguments.
The transactions will then be performed on the wallet instance provided by the fixture.
You can try out this pattern further, e.g. with the wallet instance with a non-empty balance and with other different combinations of the earned and spent amounts.

Continuous Testing on Semaphore CI

Next, let's add continuous testing to our application using SemaphoreCI to ensure that we don't break our code when we make new changes.
Make sure you've committed everything on Git, and push your repository to GitHub or Bitbucket, which will enable Semaphore to fetch your code. Next, sign up for a free Semaphore account, if you don't have one already. Once you've confirmed your email, it's time to create a new project.
Follow these steps to add the project to Semaphore:
  1. Once you're logged into Semaphore, navigate to your list of projects and click the "Add New Project" button:
    Add New Project Screen
  2. Next, select the account where you wish to add the new project.
    Select Account Screen
  3. Select the repository that holds the code you'd like to build:
    Select Repository Screen
  4. Select the branch you would like to build. The master branch is the default.
    Select branch
  5. Configure your project as shown below:
    Project Configuration
  6. Once your build has run, you should see a successful build that should look something like this: Successful Build
In a few simple steps, we've set up continuous testing.

Summary

We hope that this article has given you a solid introduction to pytest, which is one of the most popular testing tools in the Python ecosystem. It's extremely easy to get started with using it, and it can handle most of what you need from a testing tool.
You can check out the complete code on GitHub.

Tuesday, February 14, 2017

Reloading python packages on your command line.

A really quick post to bring readers attention to python package called imp

What is it?
Let's say you are on your command line and you already loaded some library... now, you need to change that library and for changes to take effect. How would you do it? Simple...

import imp

imp.reload(sample)

That's all to it! No need to close the prompt shell anymore.

Wednesday, February 8, 2017

Working with Tar Files in Python

1. Introduction
"Tar" is an archiving format that has become rather popular in the opensource world. In essence, it takes several files and bundles them into onefile. Originally, the tar format was made for tape archives, hence the name;today it is often used for distributing source code or for making backups ofdata. Most Linux distributions have tools in the standard installation forcreating and unpacking tar files.Python's standard library comes with a module which makes creating andextracting tar files very simple. Examples of when individuals might want suchfunctionality include programming a custom backup script or a script to createa snapshot of other personal projects

2. Tutorial
This is a basic tutorial designed to teach three things: how to add filesto an archive, how to retrieve information on files in the archive, and how to extract files from the archive.

Adding Files
To begin, import the tarfile module. Then, create what is called a"TarFile Object". This is an object with special functions for interacting withthe tar file. In this case, we are opening the file "archive.tar.gz". Note thatthe mode is "w:gz", which opens the file fo writing and with gzip compression. As usual, "w" not preserve previous contents of the file. If the tarfilealready exists, use "a" to append files to the end of the archive (n.b.: youcannot use append with a compressed archive - there is no such mode as "a:gz").

Create a TarFile Object
>>> import tarfile
>>> tar = tarfile.open("archive.tar.gz", "w:gz")
>>> tar<tarfile.TarFile object at 0x2af77c060990>

Adding files to the archive is very simple. If you want the file to have adifferent name in the archive, use the arcname option.

Adding a File to the Archive
>>> tar.add("file.txt")
>>> tar.add("file.txt", arcname="new.txt")

Adding directories works in the same way. Note that by default a directory will be added recursively: every file and folder under it will be included.This behavior can be changed by setting recursive to False.

Adding a Directory to the Archive
>>> tar.add("docs/")
>>> tar.add("financial/", recursive=False)

As with normal file objects, always be sure to close a TarFile Object.

Close the TarFile Object
>>> tar.close()

File Information
The tarfile module includes the ability to retrieve information about theindividual contents of a tar file. Each item is accessed as a "TarInfo Object".For example, getmembers() will return a list of all TarInfo objects in a tarfile:

Listing TarInfo Objects
>>> import tarfile
>>> tar = tarfile.open("archive.tar.gz", "r:gz")
>>> members = tar.getmembers()
>>> members[<TarInfo 'text.txt' at 0x2b0b73e46a90>, <TarInfo 'text2.txt' at0x2b0b73e46ad0>]

Each TarInfo object has several methods associated with it.

TarInfo information
>>> members[0].name'text.txt'
>>> members[0].isfile()True

Extracting Files
Extracting the contents is a very simple process. To extract the entiretar file, simple use extractall(). This will extract the file to the currentworking directory. Optionally, a path may be specified to have the tar extractelsewhere.

Extracting an entire tar file
>>> import tarfile
>>> tar = tarfile.open("archive.tar.gz", "r:gz")
>>> tar.extractall()
>>> tar.extractall("/tmp/")

If only specific files need to be extracted, use extract()

Extracting a single file from a tar file
>>> import tarfile
>>> tar = tarfile.open("archive.tar.gz", "r:gz")
>>> tar.extract("text.txt")

You should be aware that there is at least onesecurity concernto takeinto account when extracting tar files. Namely, a tar can be designed tooverwrite files outside of the current working directory (/etc/passwd, forexample). Never extract a tar as the root user if you do not trust it

3. Examples
Archiving Select Files from a Directory
>>> import os
>>> import tar
>>> filewhitelist = ['.odt', '.pdf']
>>> contents = os.listdir(os.getcwd())
>>> tar = tarfile.open('backup.tar.gz', 'w:gz')
>>> for item in contents:
>>>    if item[-4:] in whitelist:
>>>       tar.add(item)
>>> tar.close()

4. Extending
Removing Files
The tarfile module does not contain any function to remove an item from anarchive. It is presumed that this is because of the nature of tape drives,which were not designed to move back and forth (considerthis postto thePython tutor mailing list). Nevertheless, other programs for creating tararchives do have a delete feature.The following code uses the popular GNU tar programs that comes with mostLinux distributions. Their documentation of the "--delete" flag can be readhere; note that they warn not to use it on an actual tape drive. The relianceon an external program obviously makes the code far less portable, but it issuitable for personal scripts.

Removing an Item from a Tar

>>> import subprocess
>>> def remove(archive, unwanted):
>>>     external = subprocess.getoutput("tar --version")
>>>     if external[:13] != "tar (GNU tar)":
>>>         raise Exception("err: need GNU tar to delete individual files.")
>>>     command = 'tar --delete --file="{0}" "{1}"'.format(archive, unwanted)
>>>     output = subprocess.getstatusoutput(command)[0]
>>>     return output

Wednesday, February 1, 2017

I couldn't find a kernel matching Python 2. Please select a kernel

while trying to run
jupyter notebook &

i received rather cryptic message when I tried to view my notebook in jupiter:
"I couldn't find a kernel matching Python 2. Please select a kernel"

I ran following command to find out that indeed, I have no kernels available
jupyter kernelspec list

After installing ipykernel through anaconda I was back in business
conda install ipykernel

$ jupyter kernelspec list
Available kernels:
  python2    conda/lib/python2.7/site-packages/ipykernel/resources


and you have to restart the notebook of course...

Tuesday, January 31, 2017

Creating dummy variables in pandas

During my work when I need to leverage some ML techniques and I have input and output data with the output column containing categories, I often time need to split the output column into several columns with yes or no values.
Let me explain... So for instance you are involved in logistical regression calculation and your output is set of categories like 1, 2, 3, 4... if you use logistical regression to predict the value, it can come up with 1.4... which is really not a valid category.
In this case, you need to break down output column into several with yes/no values, this way, logistical regression would work by predicting either yes or no.

So how do you do it?
Pandas!

First import pandas
>>> import pandas as pd

Then let's say you have following data.
>>> my_data = {'name': ['John Doe', 'Jane Doe', 'Mike Roth', 'Mark Wagner', 'David Scott'],
...         'title': ['developer', 'manager', 'developer', 'manager', 'developer']}

View it again...
>>> my_data
{'name': ['John Doe', 'Jane Doe', 'Mike Roth', 'Mark Wagner', 'David Scott'], 'title': ['developer', 'manager', 'developer', 'manager', 'developer']}

Convert it to dataframe object
>>> df = pd.DataFrame(my_data, columns=['name','title'])

>>> df.head()
          name      title
0     John Doe  developer
1     Jane Doe    manager
2    Mike Roth  developer
3  Mark Wagner    manager
4  David Scott  developer

use get_dummies panda method to break it out accordingly
>>> df_title = pd.get_dummies(df['title'])

merge it to the original dataframe
>>> df = pd.concat([df,df_title],axis=1)

Tada... that's our result
>>> df.head()
          name      title  developer  manager
0     John Doe  developer        1.0      0.0
1     Jane Doe    manager        0.0      1.0
2    Mike Roth  developer        1.0      0.0
3  Mark Wagner    manager        0.0      1.0
4  David Scott  developer        1.0      0.0

Friday, January 27, 2017

Information hiding

It looks like the concept of information hiding is familiar to a lot of developers, but from what I have seen it is viewed as more of “hide the complex logic that I don’t care to see from me and just give me the object to work with”

While this is certainly true, this is not the only definition or implementation of information hiding…

Here is an example of how to make information hiding work for you when you have something that can change in the future and make it possible for you to localize the changes instead of refactoring entire code base.

Let’s say that you are writing some type of program that created new records and add them into the database. Each record would have their own id that would be generated by your program.

A trivial approach is to get max record number in the database, increase it by one and then add a new record. This would certainly work, but here is a first glimpse of where information hiding can help… Instead of incrementing the id inside of the block of code that adds new record create a new method that would generate new id. This way, if in the future you would want to change the logic, for instance, due to security concerns you don’t have your ids to be sequential and you want to make them random. Also, you might want to reuse deleted ids. All of this logic would be in one place! No need to change this logic throughout your code base!

Well, that was a simple one… but here is another example! Let’s say that you no longer allowed to have numerical ids… that means that if you have snippets of code that compare objects simply on theirs id with assumptions that they are numeric, this code would be broken.

So to change the id generation, that’s not a problem. You can do that inside of one method.

Now, to make your code even more robust, you have to implement comparable in your class that would make type of id irrelevant outside of your class. All you have to do is to be able to specify logic (numeric, alphanumeric, etc.) inside your one class and now you can have as many comparisons throughout your code that would never have to change! All thanks to information hiding that now would hide what type of id you have and how it is compared.

Another example of where information hiding should be used are for methods that throw exceptions. For example, let's say that you use same class as before and need to get the id. The method that retrieves the id, shall not throw any low level exceptions like EOFException. This would expose the underlying implementation of your method... instead create higher level exception like DataNotFound that still in line with your domain object and provides little to its user in terms of its concrete implementation.

Hope you enjoyed this and like always ping me with questions or feedback!


Wednesday, January 25, 2017

How to test a REST api from command line with curl

From http://www.codingpedia.org/ama/how-to-test-a-rest-api-from-command-line-with-curl/

How to test a REST api from command line with curl

If you want to quickly test your REST api from the command line, you can use curl. In this post I will present how to execute GET, POST, PUT, HEAD, DELETE HTTP Requests against a REST API. For the purpose of this blog post I will be using the REST api developed in my post Tutorial – REST API design and implementation in Java with Jersey and Spring

1. Introduction

If in the first part of the blog post I will do a brief introduction to curl and what it can do (HTTP requests with options), in the second part I will “translate” the SOAPui test suite developed for the REST API tutorial to curl requests.

1.1. What is curl?

 Curl is a command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMTP, SMTPS, Telnet and TFTP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, HTTP/2, cookies, user+password authentication (Basic, Digest, NTLM, Negotiate, Kerberos…), file transfer resume, proxy tunneling and more.[1]
As mentioned, I will be using curl to simulate HEAD, GET, POST, PUT and DELETE request calls to the REST API.

1.2. HEAD requests

If you want to check if a resource is serviceable, what kind of headers it provides and other useful meta-information written in response headers, without having to transport the entire content, you can make a HEAD request.  Let’s say I want to see what I would GET when requesting a Podcast resource. I would issue the following HEAD request with curl:
Request
curl -I http://localhost:8888/demo-rest-jersey-spring/podcasts/1
OR
curl -i -X HEAD http://localhost:8888/demo-rest-jersey-spring/podcasts/1
Curl options 
  • -i, --include – include protocol headers in the output (H/F)
  • -X, --request – specify request  COMMAND (GET, PUT, DELETE…)  to use
Response
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0   631    0     0    0     0      0      0 --:--:--  0:00:05 --:--:--     0
HTTP/1.1 200 OK
Date: Tue, 25 Nov 2014 12:54:56 GMT
Server: Jetty(9.0.7.v20131107)
Access-Control-Allow-Headers: X-extra-header
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Allow: OPTIONS
Content-Type: application/xml
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Vary: Accept-Encoding
Content-Length: 631
Note the following headers
  • Access-Control-Allow-Headers: Content-Type
  • Access-Control-Allow-Methods: GET, POST, DELETE, PUT and
  • Access-Control-Allow-Origin: *
in the response.
They’ve been added to support Cross-Origing Resource Sharing (CORS). You can find more about that in my post How to add CORS support on the server side in Java with Jersey.
What I find a little bit intriguing is the response header Content-Type: application/xml, because I would have expected it to be application/json, since in the resource method defined with Jersey this should have taken precedence:
@GET
@Path("{id}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response getPodcastById(@PathParam("id") Long id, @QueryParam("detailed") boolean detailed)
        throws IOException, AppException {
    Podcast podcastById = podcastService.getPodcastById(id);
    return Response.status(200)
            .entity(podcastById, detailed ? new Annotation[]{PodcastDetailedView.Factory.get()} : new Annotation[0])
            .header("Access-Control-Allow-Headers", "X-extra-header")
            .allow("OPTIONS").build();
}

1.3. GET request

Executing curl with no parameters on a URL (resource) will execute a GET.
Request
curl http://localhost:8888/demo-rest-jersey-spring/podcasts/1
Response
<?xml version="1.0" encoding="UTF-8"?>
<podcast>
   <id>1</id>
   <title>- The Naked Scientists Podcast - Stripping Down Science</title>
   <linkOnPodcastpedia>http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science</linkOnPodcastpedia>
   <feed>feed_placeholder</feed>
   <description>The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.</description>
   <insertionDate>2014-10-29T10:46:02.00+0100</insertionDate>
</podcast>
Note that as expected from the HEAD request we get an xml document. Anyway we can force a JSON response by adding a header line to our curl request, setting the Accept HTTP header to application/json:
curl --header "Accept:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/1
Curl options 
  • -H, --header – customer header to pass to the server
curl -H "Accept:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/1
Response
{
  "id": 1,
  "title": "- The Naked Scientists Podcast - Stripping Down Science",
  "linkOnPodcastpedia": "http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
  "feed": "feed_placeholder",
  "description": "The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.",
  "insertionDate": "2014-10-29T10:46:02.00+0100"
}
If you want to have it displayed prettier, you can use the following command, provided you have Python installed on your machine.
Request
curl -H "Accept:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/1 | python -m json.tool
Response
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   758  100   758    0     0   6954      0 --:--:-- --:--:-- --:--:--  6954
[
    {
        "description": "The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home.",
        "feed": "feed_placeholder",
        "id": 1,
        "insertionDate": "2014-10-29T10:46:02.00+0100",
        "linkOnPodcastpedia": "http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science",
        "title": "- The Naked Scientists Podcast - Stripping Down Science"
    },
    {
        "description": "Quarks & Co: Das Wissenschaftsmagazin",
        "feed": "http://podcast.wdr.de/quarks.xml",
        "id": 2,
        "insert
        ionDate": "2014-10-29T10:46:13.00+0100",
        "linkOnPodcastpedia": "http://www.podcastpedia.org/quarks",
        "title": "Quarks & Co - zum Mitnehmen"
    }
]

1.4. Curl request with multiple headers

As you’ve found out in my latest post, How to compress responses in Java REST API with GZip and Jersey, all the responses provided by the REST api are being compressed with GZip. This happens only if the client “suggests” that it accepts such encoding, by setting the following header Accept-encoding:gzip.
Request
curl -v -H "Accept:application/json" -H "Accept-encoding:gzip" http://localhost:8888/demo-rest-jersey-spring/podcasts/
Curl options 
  • -v, --verbose – make the operation more talkative
To achieve that you need to simply add another -H option with the corresponding value. Of course in this case you would get some unreadable characters in the content, if you do not redirect the response to a file:
* Adding handle: conn: 0x28ddd80
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x28ddd80) send_pipe: 1, recv_pipe: 0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* About to connect() to proxy vldn680 port 19001 (#0)
*   Trying 10.32.142.80...
* Connected to vldn680 (10.32.142.80) port 19001 (#0)
> GET http://localhost:8888/demo-rest-jersey-spring/podcasts/ HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8888
> Proxy-Connection: Keep-Alive
> Accept:application/json
> Accept-encoding:gzip
>
< HTTP/1.1 200 OK
< Date: Tue, 25 Nov 2014 16:17:02 GMT
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Encoding: gzip
< Content-Length: 413
< Via: 1.1 vldn680:8888
<
{ [data not shown]
100   413  100   413    0     0   2647      0 --:--:-- --:--:-- --:--:--  2647▒QKo▒0▒+▒▒g▒▒R▒+{▒V▒Pe▒▒؊c▒▒      n▒▒▒▒fæHH▒"▒▒g▒/?2▒eM▒gl▒a▒d
▒{=`7▒EÏ–▒▒c▒ZM

n8▒i▒▒▒}H▒▒i1▒3g▒▒▒▒▒   ;▒E▒0O▒n▒R*▒g/E▒▒n=▒▒▒▒)▒U▒▒▒lÕª▒Φ▒h▒6▒▒▒_>w▒▒-▒▒:▒▒▒!▒Bb▒Z▒▒tO▒N@'= |▒▒C▒f▒▒loØ ▒,T▒▒A▒4▒▒:▒l+<▒▒▒▒P▒3▒▒A▒lR
▒u▒a▒͓9hO        #▒▒h▒i▒gq▒▒$▒▒|Ň        ▒▒▒08>#▒0b!▒▒'▒G▒^▒Iﺬ.TU▒▒▒z▒\▒i^]e▒▒▒▒2▒▒▒֯▒▒?▒:/▒m▒▒▒▒▒Y▒h▒▒▒_ä¶™V▒+R▒WT▒0▒?f{▒▒▒▒&▒l▒▒Sk▒iÔ½~▒▒▒▒▒▒n▒▒▒▒_V]į▒
* Connection #0 to host vldn680 left intact

2. SOAPui test suite translated to curl requests

As mentioned, in this second part I will map to curl requests the SOAPui test suite presented here.

2.1. Create podcast(s) resource

2.1.1. Delete all podcasts (preparation step)

Request
curl -i -X DELETE http://localhost:8888/demo-rest-jersey-spring/podcasts/
Response
HTTP/1.1 204 No Content
Date: Tue, 25 Nov 2014 14:10:17 GMT
Server: Jetty(9.0.7.v20131107)
Content-Type: text/html
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Vary: Accept-Encoding
Via: 1.1 vldn680:8888
Content-Length: 0

2.1.2. POST new podcast without feed – 400 (BAD_REQUEST)

Request
curl -i -X POST -H "Content-Type:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/ -d '{"title":"- The Naked Scientists Podcast - Stripping Down Science-new-title2","linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science","description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."}'
Response
HTTP/1.1 400 Bad Request
Date: Tue, 25 Nov 2014 15:12:11 GMT
Server: Jetty(9.0.7.v20131107)
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Vary: Accept-Encoding
Content-Length: 271
Via: 1.1 vldn680:8888
Connection: close

{"status":400,"code":400,"message":"Provided data not sufficient for insertion","link":"http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/","developerMessage":"Please verify that the feed is properly generated/set"}

2.1.3. POST new podcast correctly – 201 (CREATED)

Request
curl -i -X POST -H "Content-Type:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/ -d '{"title":"- The Naked Scientists Podcast - Stripping Down Science","linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science","feed":"feed_placeholder","description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."}'
Response
HTTP/1.1 201 Created
Location: http://localhost:8888/demo-rest-jersey-spring/podcasts/2
Content-Type: text/html
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Vary: Accept-Encoding
Content-Length: 60
Server: Jetty(9.0.7.v20131107)

A new podcast has been created AT THE LOCATION you specified

2.1.4. POST same podcast as before to receive – 409 (CONFLICT)

Request
curl -i -X POST -H "Content-Type:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/ -d '{"title":"- The Naked Scientists Podcast - Stripping Down Science","linkOnPodcastpedia":"http://www.podcastpedia.org/podcasts/792/-The-Naked-Scientists-Podcast-Stripping-Down-Science","feed":"feed_placeholder","description":"The Naked Scientists flagship science show brings you a lighthearted look at the latest scientific breakthroughs, interviews with the world top scientists, answers to your science questions and science experiments to try at home."}'
Response
HTTP/1.1 409 Conflict
Date: Tue, 25 Nov 2014 15:58:39 GMT
Server: Jetty(9.0.7.v20131107)
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Vary: Accept-Encoding
Content-Length: 300

{"status":409,"code":409,"message":"Podcast with feed already existing in the database with the id 1","link":"http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/","developerMessage":"Please verify that the feed and title are properly generated"}

2.1.5. PUT new podcast at location – 201 (CREATED)

Request
curl -i -X PUT -H "Content-Type:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/2 -d '{"id":2,"title":"Quarks & Co - zum Mitnehmen","linkOnPodcastpedia":"http://www.podcastpedia.org/quarks","feed":"http://podcast.wdr.de/quarks.xml","description":"Quarks & Co: Das Wissenschaftsmagazin"}'
Response
HTTP/1.1 201 Created
Location: http://localhost:8888/demo-rest-jersey-spring/podcasts/2
Content-Type: text/html
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
Vary: Accept-Encoding
Content-Length: 60
Server: Jetty(9.0.7.v20131107)

A new podcast has been created AT THE LOCATION you specified

2.2. Read podcast resource

2.2.1. GET new inserted podcast – 200 (OK)

Request
curl -v -H "Accept:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts/1 | python -m json.tool
Response
< HTTP/1.1 200 OK
< Access-Control-Allow-Headers: X-extra-header
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Allow: OPTIONS
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Vary: Accept-Encoding
< Content-Length: 192
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
{ [data not shown]
* STATE: PERFORM => DONE handle 0x600056180; line 1626 (connection #0)
100   192  100   192    0     0   2766      0 --:--:-- --:--:-- --:--:--  3254
* Connection #0 to host localhost left intact
* Expire cleared
{
    "feed": "http://podcast.wdr.de/quarks.xml",
    "id": 1,
    "insertionDate": "2014-06-05T22:35:34.00+0200",
    "linkOnPodcastpedia": "http://www.podcastpedia.org/quarks",
    "title": "Quarks & Co - zum Mitnehmen"
}

2.2.2. GET podcasts sorted by insertion date DESC – 200 (OK)

Request
curl -v -H "Accept:application/json" http://localhost:8888/demo-rest-jersey-spring/podcasts?orderByInsertionDate=DESC | python -m json.tool
Response
< HTTP/1.1 200 OK
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 419
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
  0   419    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0{ [data not shown]
* STATE: PERFORM => DONE handle 0x600056180; line 1626 (connection #0)
100   419  100   419    0     0   6044      0 --:--:-- --:--:-- --:--:--  6983
* Connection #0 to host localhost left intact
* Expire cleared
[
    {
        "feed": "http://podcast.wdr.de/quarks.xml",
        "id": 1,
        "insertionDate": "2014-06-05T22:35:34.00+0200",
        "linkOnPodcastpedia": "http://www.podcastpedia.org/quarks",
        "title": "Quarks & Co - zum Mitnehmen"
    },
    {
        "feed": "http://www.dayintechhistory.com/feed/podcast-2",
        "id": 2,
        "insertionDate": "2014-06-05T22:35:34.00+0200",
        "linkOnPodcastpedia": "http://www.podcastpedia.org/podcasts/766/Day-in-Tech-History",
        "title": "Day in Tech History"
    }
]

2.3. Update podcast resource

2.3.1. PUT not “complete” podcast for FULL update – 400 (BAD_REQUEST)

Request
curl -v -H "Content-Type:application/json" -X PUT http://localhost:8888/demo-rest-jersey-spring/podcasts/2 -d '{"id":2, "title":"Quarks & Co - zum Mitnehmen","linkOnPodcastpedia":"http://www.podcastpedia.org/quarks","feed":"http://podcast.wdr.de/quarks.xml"}'
Response
< HTTP/1.1 400 Bad Request
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 290
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
* STATE: PERFORM => DONE handle 0x600056180; line 1626 (connection #0)
* Connection #0 to host localhost left intact
* Expire cleared
{"status":400,"code":400,"message":"Please specify all properties for Full UPDATE","link":"http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/","developerMessage":"required properties - id, title, feed, lnkOnPodcastpedia, description"}

2.3.2. PUT podcast for FULL update – 200 (OK)

Request
$ curl -v -H "Content-Type:application/json" -X PUT http://localhost:8888/demo-rest-jersey-spring/podcasts/2 -d '{"id":2, "title":"Quarks & Co - zum Mitnehmen","linkOnPodcastpedia":"http://www.podcastpedia.org/quarks","feed":"http://podcast.wdr.de/quarks.xml", "description":"Quarks & Co: Das Wissenschaftsmagazin"}'
Response
< HTTP/1.1 200 OK
< Location: http://localhost:8888/demo-rest-jersey-spring/podcasts/2
< Content-Type: text/html
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 86
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
* Connection #0 to host localhost left intact
* Expire cleared
The podcast you specified has been fully updated created AT THE LOCATION you specified

2.3.3. POST (partial update) for not existent podcast – 404 (NOT_FOUND)

Request
$ curl -v -H "Content-Type:application/json" -X POST http://localhost:8888/demo-rest-jersey-spring/podcasts/3 -d '{"title":"Quarks & Co - zum Mitnehmen - GREAT PODCAST"}' | python -m json.tool
Response
< HTTP/1.1 404 Not Found
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 306
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
{ [data not shown]
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
100   361  100   306  100    55   9069   1630 --:--:-- --:--:-- --:--:-- 13304
* Connection #0 to host localhost left intact
* Expire cleared
{
    "code": 404,
    "developerMessage": "Please verify existence of data in the database for the id - 3",
    "link": "http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/",
    "message": "The resource you are trying to update does not exist in the database",
    "status": 404
}

2.3.4. POST (partial update) podcast – 200 (OK)

Request
$ curl -v -H "Content-Type:application/json" -X POST http://localhost:8888/demo-rest-jersey-spring/podcasts/2 -d '{"title":"Quarks & Co - zum Mitnehmen - GREAT PODCAST"}'
Response
< HTTP/1.1 200 OK
< Content-Type: text/html
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 55
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
* Connection #0 to host localhost left intact
* Expire cleared
The podcast you specified has been successfully updated

2.4. DELETE resource

2.4.1. DELETE second inserted podcast – 204 (NO_CONTENT)

Request
$ curl -v -X DELETE http://localhost:8888/demo-rest-jersey-spring/podcasts/2
Response
< HTTP/1.1 204 No Content
< Content-Type: text/html
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
* Excess found in a non pipelined read: excess = 42 url = /demo-rest-jersey-spring/podcasts/2 (zero-length body)
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
* Connection #0 to host localhost left intact
* Expire cleared

2.4.2. GET deleted podcast – 404 (NOT_FOUND)

Request
curl -v http://localhost:8888/demo-rest-jersey-spring/podcasts/2 | python -m json.tool
Response
< HTTP/1.1 404 Not Found
< Content-Type: application/json
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 306
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
{ [data not shown]
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
100   306  100   306    0     0   8916      0 --:--:-- --:--:-- --:--:-- 13304
* Connection #0 to host localhost left intact
* Expire cleared
{
    "code": 404,
    "developerMessage": "Verify the existence of the podcast with the id 2 in the database",
    "link": "http://www.codingpedia.org/ama/tutorial-rest-api-design-and-implementation-in-java-with-jersey-and-spring/",
    "message": "The podcast you requested with id 2 was not found in the database",
    "status": 404
}

2.5. Bonus operations

2.5.1. Add podcast from application form urlencoded

Request
curl -v --data-urlencode "title=Day in Tech History" --data-urlencode "linkOnPodcastpedia=http://www.podcastpedia.org/podcasts/766/Day-in-Tech-History" --data-urlencode "feed=http://www.dayintechhistory.com/feed/podcast"
Response
< HTTP/1.1 201 Created
< Location: http://localhost:8888/demo-rest-jersey-spring/podcasts/null
< Content-Type: text/html
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, DELETE, PUT
< Access-Control-Allow-Headers: X-Requested-With, Content-Type, X-Codingpedia
< Vary: Accept-Encoding
< Content-Length: 81
* Server Jetty(9.0.7.v20131107) is not blacklisted
< Server: Jetty(9.0.7.v20131107)
<
* STATE: PERFORM =&gt; DONE handle 0x600056180; line 1626 (connection #0)
* Connection #0 to host localhost left intact
* Expire cleared
A new podcast/resource has been created at /demo-rest-jersey-spring/podcasts/null