Extended Usage

To parse a RAML file, include ramlfications in your project and call the parse function:

>>> import ramlfications
>>> RAML_FILE = "/path/to/my-api.raml"
>>> api = ramlfications.parse(RAML_FILE)

Configuration

Perhaps your API supports response codes beyond what IETF supports (default for this parser). Or maybe you implemented your own authentication scheme that your API uses I hope not!.

Example configuration file:

[main]
validate = True

[custom]
append = True
resp_codes = 420, 421, 422
auth_schemes = oauth_3_0, oauth_4_0
media_types = application/vnd.github.v3, foo/bar
protocols = FTP
raml_versions = 0.8

Feed the configuration into the parse function like so:

>>> import ramlfications
>>> RAML_FILE = "/path/to/my-api.raml"
>>> CONFIG_FILE = "/path/to/my-config.ini"
>>> api = ramlfications.parse(RAML_FILE, CONFIG_FILE)

RAML Root Section

In following the RAML Spec’s Root Section definition, here is how you can access the following attributes:

The Basics

>>> api.title
'My Other Foo API'
>>>
>>> api.version
v2
>>> api.base_uri
'https://{domainName}.foo.com/v2'
>>> api.base_uri_parameters
[<URIParameter(name='domainName')>]
>>>
>>> api.protocols
['HTTPS']

API Documentation

>>> api.documentation
[<Documentation(title='The Foo API Docs')>]
>>> doc = api.documentation[0]
>>> doc.title
'The Foo API Docs'

Docs written in the RAML file should be written using Markdown. This also applies to any description parameter.

With ramlfications, documentation content and descriptions can either be viewed raw, or in parsed HTML.

>>> doc.content
'Welcome to the _Foo API_ specification. For more information about\nhow to use the API, check out [developer site](https://developer.foo.com).\n'
>>>
>>> doc.content.html
u'<p>Welcome to the <em>Foo API</em> specification. For more information about\nhow to use the API, check out <a href="https://developer.foo.com">developer site</a>.</p>\n'

Check out API Definition for full definition of RootNode and its associated attributes and objects.

Security Schemes

RAML supports OAuth 1, OAuth 2, Basic & Digest, and any authentication scheme self-defined with an x-{other} header.

To parse auth schemes:

>>> api.security_schemes
[<SecurityScheme(name='oauth_2_0')>]
>>> oauth2 = api.security_schemes[0]
>>> oauth2.name
'oauth_2_0'
>>> oauth2.type
'OAuth 2.0'
>>> oauth2.description
'Foo supports OAuth 2.0 for authenticating all API requests.\n'
>>> oauth2.description.html
u'<p>Foo supports OAuth 2.0 for authenticating all API requests.</p>\n'

And its related Headers and Responses:

>>> oauth2.described_by
{'headers': [<Header(name='Authorization')>], 'responses': [<Response(code='401')>, <Response(code='403')>]}
>>> first_header = oauth2.described_by['headers'][0]
>>> first_header
<HeaderParameter(name='Authorization')>
>>> first_header.name
'Authorization'
>>> first_headers.description
'Used to send a valid OAuth 2 access token.\n'
>>> first_headers.description.html
u'<p>Used to send a valid OAuth 2 access token.</p>\n'
>>> resps = oauth2.described_by['responses']
>>> resps
[<Response(code='401')>, <Response(code='403')>]
>>> resp[0].code
401
>>> resp[0].description.raw
'Bad or expired token. This can happen if the user revoked a token or\nthe access token has expired. You should re-authenticate the user.\n'

Authentication settings (available for OAuth1, OAuth2, and any x-header that includes “settings” in the RAML definition).

>>> oauth2.settings.scopes
['foo-read-private', 'foo-modify-public',..., 'user-read-email-address']
>>> oauth2.settings.access_token_uri
'https://accounts.foo.com/api/token'
>>> oauth2.settings.authorization_grants
['code', 'token']
>>> oauth2.settings.authorization_uri
'https://accounts.foo.com/authorize'

Check out API Definition for full definition of SecuritySchemes, Header, Response and their associated attributes and objects.

Schemas

The RAML specification allows the ability to define schemas that can be used anywhere within the API definition. One may define a schema within the RAML file itself, or in another, separate file (local or over HTTP/S). ramlfications supports json and xsd filetimes in addition to parsing RAML.

See Non-RAML Parsing for more information about how ramlfications handles json and xsd formats.

Traits & Resource Types

Traits & resource types help when API definitions get a bit repetitive. More information can be found in the RAML spec for resource types and traits.

Resource Types

>>> api.resource_types
[<ResourceTypeNode(name='collection')>, <ResourceTypeNode(name='member')>]
>>> collection = api.resource_types[0]
>>> collection.name
'collection'
>>> collection.description
'The collection of <<resourcePathName>>'
>>> collection.usage
'This resourceType should be used for any collection of items'
>>> collection.method
'get'
>>> get.optional
False

Traits

>>> api.traits
[<TraitNode(name='filtered')>, <TraitNode(name='paged')>]
>>> paged = api.traits[1]
>>> paged.query_params
[<QueryParameter(name='offset')>, <QueryParameter(name='limit')>]
>>> paged.query_params[0].name
'offset'
>>> paged.query_params[0].description
'The index of the first track to return'

Mapping of Properties and Elements from Traits & Resource Types to Resources

When a resource has a trait and/or type assigned to it, or a resource type has another resource type or a trait assigned to it, it inherits its properties.

Also, the RAML Spec allows for parameters within Traits and ResourceTypes, denoted by double brackets within the Trait/ResourceType definition, e.g. <<parameter>>. After the parsing of the API definition, the appropriate parameters are filled in for the respective resource.

For example, a simplified RAML file:

#%RAML 0.8
title: Example API - Mapped Traits
version: v1
resourceTypes:
  - searchableCollection:
      get:
        queryParameters:
          <<queryParamName>>:
            description: |
              Return <<resourcePathName>> that have their <<queryParamName>>
              matching the given value
          <<fallbackParamName>>:
            description: |
              If no values match the value given for <<queryParamName>>,
              use <<fallbackParamName>> instead
  - collection:
      usage: This resourceType should be used for any collection of items
      description: The collection of <<resourcePathName>>
      get:
        description: Get all <<resourcePathName>>, optionally filtered
      post:
        description: Create a new <<resourcePathName | !singularize>>
traits:
  - secured:
      description: A secured method
      queryParameters:
        <<tokenName>>:
          description: A valid <<tokenName>> is required
  - paged:
      queryParameters:
        numPages:
          description: The number of pages to return, not to exceed <<maxPages>>
/books:
  type: { searchableCollection: { queryParamName: title, fallbackParamName: digest_all_fields } }
  get:
    is: [ secured: { tokenName: access_token }, paged: { maxPages: 10 } ]

When parsed, the Python notation would look like this:

>>> RAML_FILE = "/path/to/foo-api.raml"
>>> api = parse(RAML_FILE)
# accessing API-supported resource types
>>> api.resource_types
[<ResourceTypeNode(method='GET', name='searchableCollection')>,
<ResourceTypeNode(method='POST', name='collection')>,
<ResourceTypeNode(method='GET', name='collection')>]
>>> api.resource_types[0].query_params
[<QueryParameter(name='<<queryParamName>>')>,
<QueryParameter(name='<<fallbackParamName>>')>]
>>> api.resource_types[0].query_params[0].description
Return <<resourcePathName>> that have their <<queryParamName>> matching the given value
# accessing API-supported traits
>>> api.traits
[<TraitNode(name='secured')>, <TraitNode(name='paged')>]
>>> api.traits[0].query_params
[<QueryParameter(name='numPages')>]
>>> api.traits[0].query_params[0].description
The number of pages to return, not to exceed <<maxPages>>
# accessing a single resource
>>> books = api.resources[0]
>>> books
<ResourceNode(method='GET', path='/books')>
>>> books.type
{'searchableCollection': {'fallbackParamName': 'digest_all_fields', 'queryParamName': 'title'}}
>>> books.traits
[<TraitNode(name='secured')>, <TraitNode(name='paged')>]
>>> books.query_params
[<QueryParameter(name='title')>, <QueryParameter(name='digest_all_fields')>,
<QueryParameter(name='access_token')>, <QueryParameter(name='numPages')>]
>>> books.query_params[0].description
Return books that have their title matching the given value
>>> books.query_params[3].description
The number of pages to return, not to exceed 10

Resource types can also contain optional properties. Currently, ramlfications only supports the method parameter to be optional, but broadening that to all properties within a defined resource type and trait is coming soon.

Below are a few examples of applying a resource type that has a require method, and an optional method.

Example 1

A required method in a resource type with that method not explicitly defined/included in resource:

#%RAML 0.8
---
title: Example API
baseUri: http://example.com
version: v1
resourceTypes:
  - inheritgetmethod:
      description: get-method resource type example
      usage: Some sort of usage description
      get:
        description: This description should be inherited when applied to resources
        headers:
          X-Inherited-Header:
            description: This header should be inherited
/a-get-resource:
  description: Resource inherits from inheritedgetmethod
  type: inheritgetmethod
>>> len(api.resources)
1
>>> res = api.resources[0]
>>> res.name
'/a-get-resource'
>>> res.type
'inheritedgetmethod'
>>> res.method  # inherits the required method from its resource type
'get'
>>> # also inherits all of it's other properties, if defined
>>> res.description.raw
'This description should be inherited when applied to resources'
>>> res.headers
[Header(display_name='X-Inherited-Header')]

Example 2

Similar to the example above, a required method in a resource type where not explicitly defined in the resource, and the resource has another method defined (really confusing to explain, just check out the example):

#%RAML 0.8
---
title: Example API
baseUri: http://example.com
version: v1
resourceTypes:
  - inheritgetmethod:
      description: get-method resource type example
      usage: Some sort of usage description
      get:
        description: This description should be inherited when applied to resources
        headers:
          X-Inherited-Header:
            description: This header should be inherited
/a-resource:
  description: Resource inherits from inheritedgetmethod
  type: inheritgetmethod
  post:
    description: Post some foobar
>>> len(api.resources)
2
>>> first = api.resources[0]
>>> first.name
'/a-resource'
>>> first.type
'inheritedgetmethod'
>>> first.method  # inherits the required method from its resource type
'get'
>>> second = api.resources[1]
>>> second.name
'/a-resources'
>>> second.method
'post'

Example 3

Inheriting an optional resource type method:

#%RAML 0.8
---
title: Example API
baseUri: http://example.com
version: v1
resourceTypes:
  - inheritgetoptionalmethod:
      description: optional get-method resource type example
      usage: Some sort of usage description
      get?:
        description: This description should be inherited when applied to resources with get methods
        headers:
          X-Optional-Inherited-Header:
            description: This header should be inherited when resource has get method
/a-resource:
  description: Resource inherits from inheritoptionalmethod
  type: inheritgetoptionalmethod
  get:
    headers:
      X-Explicit-Header:
        description: This is a header in addition to what is inherited
>>> len(api.resources)
1
>>> res = api.resources[0]
>>> res.name
'/a-resource'
>>> res.method
'get'
>>> res.headers
[Header(display_name='X-Optional-Inherited-Header'), Header(display_name='X-Explicit-Header')]

Example 4

Let’s combine all permutations!:

#%RAML 0.8
---
title: Example API
baseUri: http://example.com
version: v1
resourceTypes:
  - inheritgetmethod:
      description: get-method resource type example
      usage: Some sort of usage description
      get:
        description: This description should be inherited when applied to resources
        headers:
          X-Inherited-Header:
            description: This header should be inherited
  - inheritgetoptionalmethod:
      description: optional get-method resource type example
      usage: Some sort of usage description
      get?:
        description: This description should be inherited when applied to resources with get methods
        headers:
          X-Optional-Inherited-Header:
            description: This header should be inherited when resource has get method
/a-resource:
  description: Resource inherits from inheritoptionalmethod
  type: inheritgetoptionalmethod
  get:
    headers:
      X-Explicit-Header:
        description: This is a header in addition to what is inherited
/another-resource:
  type: inheritgetmethod
  description: This resource should also inherit the Resource Type's get method properties
  post:
    description: post some more foobar
>>> len(api.resources)
3
>>> first = api.resources[0]
>>> first.name
'/a-resource'
>>> first.method
'get'
>>> second = api.resources[1]
>>> second.name
'/another-resource'
>>> second.method
'post'
>>> third = api.resources[2]
>>> third.method
'get'

Example 5

Last thing to note is that properties from the inherited Resource Type will not overwrite properties of the resource if they are explicitly defined under the resource:

#%RAML 0.8
---
title: Example API
baseUri: http://example.com
version: v1
resourceTypes:
  - inheritgetmethod:
      description: get-method resource type example
      usage: Some sort of usage description
      get:
        description: This description should *NOT* be inherited when applied to resources
        headers:
          X-Inherited-Header:
            description: This header should be inherited
/a-get-resource:
  description: Resource inherits from inheritedgetmethod
  type: inheritgetmethod
  get:
    description: This description will actually be used
>>> len(api.resources)
1
>>> res = api.resources[0]
>>> res.name
'/a-get-resource'
>>> res.type
'inheritedgetmethod'
>>> # inherited types will not overwrite properties if explicitly defined in resource
>>> res.description.raw
'This description will actually be used'
>>> res.headers
[Header(display_name='X-Inherited-Header')]

Check out API Definition for full definition of traits and resources, and its associated attributes and objects.

Resources

“Resources” are defined in the RAML Spec’s Resource Section and is a relative URI (relative to the base_uri and, if nested, relative to its parent URI).

For example, Foo API defines /foo/bar as a resource (a “top-level resource” to be exact). It also defines /{id} under /foo/bar, making /{id} a nested resource, relative to /foo/bar. The relative path would be /foo/bar/{id}, and the absolute URI path would be https://api.foo.com/v2/foo/bar/{id}.

>>> api.resources
[<Resource(method='GET', path='/foo')>,..., <Resource(method='DELETE', path='/foo/bar/{id}')>]
>>>
>>> foo_bar = resources[-1]
>>> foo_bar.name
'/{id}'
>>> foo_bar.description
'[Delete a foo bar](https://developer.foo.com/api/delete-foo-bar/)\n'
>>> foo_bar.description.html
u'<p><a href="https://developer.foo.com/api/delete-foo-bar/">Delete a foo bar</a></p>\n'
>>> foo_bar.display_name
'foo bar'
>>> foo_bar.method
'delete'
>>> foo_bar.path
'/foo/bar/{id}'
>>> foo_bar.absolute_uri
'https://api.foo.com/v2/foo/bar/{id}'
>>> foo_bar.uri_params
[<URIParameter(name='id')>]
>>>
>>> uri_param = foo_bar.uri_params[0]
>>> uri_param.required
True
>>> uri_param.type
'string'
>>> uri_param.example
'f0ob@r1D'
>>> foo_bar.parent
<Resource(method='GET', path='/foo/bar/')>

Note

The uri_params and base_uri_params on the api object (RootNode) and a resource object (ResourceNode) will always preserve order according to the absolute URI.

Check out API Definition for full definition of what is available for a resource object, and its associated attributes and objects.

Non-RAML Parsing

JSON

For json filetypes, ramlfications will also parse $ref keywords and bring in the referenced objects according to both Draft 3 and Draft 4 JSON Schema definition.

The following $ref values are supported:

Internal fragments

RAML File:

#%RAML 0.8
title: Sample API Demo - JSON Includes
version: v1
schemas:
    - json: !include includes/ref_internal_fragment.json
baseUri: http://json.example.com
/foo:
  displayName: foo resource

includes/ref_internal_fragment.json file:

{
  "references": {
    "internal": "yes"
  },
  "name": "foo",
  "is_this_internal?": [{"$ref": "#/references/internal"}]
}

ramlfications would produce the following:

>>> RAML_FILE = "api.raml"
>>> api = parse(RAML_FILE)
>>> api.schemas
[{'json': {u'is_this_internal?': [u'yes'],
u'name': u'foo',
u'references': {u'internal': u'yes'}}}]

Local file with & without fragments

Parsing references to local files via relative or absolute filepaths work fine, as well as prepending the URI with file:, e.g. file:local.schema.json.

RAML File that includes a JSON file under schemas:

#%RAML 0.8
title: Sample API Demo - JSON Includes
version: v1
schemas:
    - jsonexample: !include local.schema.json
baseUri: http://json.example.com
/foo:
  displayName: foo resource

The included local.schema.json file that refers to another JSON file via a relative filepath and a fragment:

{
  "$schema":"http://json-schema.org/draft-03/schema",
  "type": "object",
  "properties": {
    "album_type": {
      "type": "string",
      "description": "The type of the album: one of 'album', 'single', or 'compilation'."
    },
    "artists": {
        "type": "array",
        "description": "The artists of the album. Each artist object includes a link in href to more detailed information about the artist.",
        "items": [{ "$ref": "artist.schema.json#properties" }]
    }
  }
}

The referred artist.schema.json file:

{
  "$schema": "http://json-schema.org/draft-03/schema",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of the artist."
    },
    "popularity": {
      "type": "integer",
      "description": "The popularity of the artist. The value will be between 0 and 100, with 100 being the most popular. The artist's popularity is calculated from the popularity of all the artist's tracks."
    },
    "type": {
      "type": "string",
      "description": "The object type: 'artist'"
    },
    "uri": {
      "type": "string",
      "description": "The Spotify URI for the artist."
    }
  }
}

Finally, ramlfications would produce the following (pretty printed for readability):

>>> RAML_FILE = "api.raml"
>>> api = parse(RAML_FILE)
>>> api.schemas
[{'jsonexample': {
  u'$schema': u'http://json-schema.org/draft-03/schema',
  u'properties': {
    u'album_type': {
      u'description': u"The type of the album: one of 'album', 'single', or 'compilation'.",
      u'type': u'string'
    },
    u'artists': {
      u'description': u'The artists of the album. Each artist object includes a link in href to more detailed information about the artist.',
      u'items': [{
        u'popularity': {
          u'type': u'integer',
          u'description': u"The popularity of the artist. The value will be between 0 and 100, with 100 being the most popular. The artist's popularity is calculated from the popularity of all the artist's tracks."
        },
        u'type': {
          u'type': u'string',
          u'description': u"The object type: 'artist'"
        },
        u'name': {
          u'type': u'string',
          u'description': u'The name of the artist.'
        },
        u'uri': {
          u'type': u'string',
          u'description': u'The Spotify URI for the artist.'
        }
      }],
    u'type': u'array'
    }
  },
  u'type': u'object'
  }
}]

Remote file with & without fragments (over HTTP or HTTPS only)

RAML file:

#%RAML 0.8
title: Sample API Demo - JSON Includes
version: v1
schemas:
    - json: !include local.schema.json
baseUri: http://json.example.com
/foo:
  displayName: foo resource

The included local.schema.json file (that’s local) that refers to another JSON file remotely:

{
    "$schema": "http://json-schema.org/draft-03/schema",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "The name of the artist."
        },
        "images": {
            "type": "array",
            "description": "Images associated with artist",
            "items": [{"$ref": "https://example.com/data#properties"}]
        }
    }
}

The remote file found on https://example.com/data#properties:

{
  "$schema": "http://json-schema.org/draft-03/schema",
  "type": "object",
  "properties": {
    "height": {
      "type": "integer",
      "description": "The image height in pixels. If unknown: null or not returned."
    },
    "url": {
      "type": "string",
      "description": "The source URL of the image."
    },
    "width": {
      "type": "integer",
      "description": "The image width in pixels. If unknown: null or not returned."
    }
  }
}

Finally, ramlfications would produce the following (pretty printed for readability):

>>> RAML_FILE = "api.raml"
>>> api = parse(RAML_FILE)
>>> api.schemas
[{'jsonexample': {
  u'$schema': u'http://json-schema.org/draft-03/schema',
  u'properties': {
    u'name': {
      u'type': u'string',
      u'description': u'The name of the artist.'
    },
    u'images': {
      u'type': 'array',
      u'description': 'Images associated with artist',
      u'items': [{
        u'height': {
          u'type': u'integer',
          u'description': u'The image height in pixels. If unknown: null or not returned.'
        },
        u'url': {
          u'type': u'string',
          u'description': u'The source URL of the image.'
        },
        u'width': {
          u'type': u'integer',
          u'description': u'The image width in pixels. If unknown: null or not returned.'
        }
      }]
    }
  }
}}]

XML

Documentation (and improved functionality) coming soon!