Browse Source

Initial commit

Gu1 11 years ago
commit
c76fd79064

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+*~
+*.pyc
+.*.swp

+ 23 - 0
LICENSE

@@ -0,0 +1,23 @@
+
+Copyright (c) FFDN
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met: 
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 4 - 0
MANIFEST.in

@@ -0,0 +1,4 @@
+include LICENSE
+include README
+recursive-include ispformat/schema *
+recursive-include ispformat/specs *

+ 1 - 0
README

@@ -0,0 +1 @@
+TODO

+ 1 - 0
ispformat/__init__.py

@@ -0,0 +1 @@
+__version__ = '0.1'

+ 18 - 0
ispformat/bin/isp-format-validator

@@ -0,0 +1,18 @@
+#!/usr/bin/env python2
+# -*- coding: utf-8 -*-
+
+import sys
+import json
+from ispformat.validator import validate_isp
+
+if len(sys.argv) == 2:
+    j=json.load(open(sys.argv[1]))
+    errs=list(validate_isp(j))
+    if errs:
+        for e in errs:
+            print e
+    else:
+        print "All clear"
+else:
+    print "Usage: validator.py file-name.json"
+

+ 17 - 0
ispformat/schema/0.1/address.json

@@ -0,0 +1,17 @@
+{
+    "description": "An Address following the convention of http://microformats.org/wiki/hcard",
+    "type": "object",
+    "properties": {
+        "post-office-box": { "type": "string" },
+        "extended-address": { "type": "string" },
+        "street-address": { "type": "string" },
+        "locality":{ "type": "string", "required": true },
+        "region": { "type": "string", "required": true },
+        "postal-code": { "type": "string" },
+        "country-name": { "type": "string", "required": true}
+    },
+    "dependencies": {
+        "post-office-box": "street-address",
+        "extended-address": "street-address"
+    }
+}

+ 8 - 0
ispformat/schema/0.1/geo.json

@@ -0,0 +1,8 @@
+{
+    "description": "A geographical coordinate",
+    "type": "object",
+    "properties": {
+        "latitude": { "type": "number" },
+        "longitude": { "type": "number" }
+    }
+}

+ 8 - 0
ispformat/schema/0.1/geojson/bbox.json

@@ -0,0 +1,8 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/bbox.json#",
+    "description": "A bounding box as defined by GeoJSON",
+    "FIXME": "unenforceable constraint: even number of elements in array",
+    "type": "array",
+    "items": { "type": "number" }
+}

+ 54 - 0
ispformat/schema/0.1/geojson/crs.json

@@ -0,0 +1,54 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "crs",
+    "description": "a Coordinate Reference System object",
+    "type": [ "object", "null" ],
+    "required": [ "type", "properties" ],
+    "properties": {
+        "type": { "type": "string" },
+        "properties": { "type": "object" }
+    },
+    "additionalProperties": false,
+    "oneOf": [
+        { "$ref": "#/definitions/namedCrs" },
+        { "$ref": "#/definitions/linkedCrs" }
+    ],
+    "definitions": {
+        "namedCrs": {
+            "properties": {
+                "type": { "enum": [ "name" ] },
+                "properties": {
+                    "required": [ "name" ],
+                    "additionalProperties": false,
+                    "properties": {
+                        "name": {
+                            "type": "string",
+                            "FIXME": "semantic validation necessary"
+                        }
+                    }
+                }
+            }
+        },
+        "linkObject": {
+            "type": "object",
+            "required": [ "href" ],
+            "properties": {
+                "href": {
+                    "type": "string",
+                    "format": "uri",
+                    "FIXME": "spec says \"dereferenceable\", cannot enforce that"
+                },
+                "type": {
+                    "type": "string",
+                    "description": "Suggested values: proj4, ogjwkt, esriwkt"
+                }
+            }
+        },
+        "linkedCRS": {
+            "properties": {
+                "type": { "enum": [ "link" ] },
+                "properties": { "$ref": "#/definitions/linkedObject" }
+            }
+        }
+    }
+}

+ 61 - 0
ispformat/schema/0.1/geojson/geojson.json

@@ -0,0 +1,61 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/geojson.json#",
+    "title": "Geo JSON object",
+    "description": "Schema for a Geo JSON object",
+    "type": "object",
+    "required": [ "type" ],
+    "properties": {
+        "crs": { "$ref": "http://json-schema.org/geojson/crs.json#" },
+        "bbox": { "$ref": "http://json-schema.org/geojson/bbox.json#" }
+    },
+    "oneOf": [
+        { "$ref": "http://json-schema.org/geojson/geometry.json#" },
+        { "$ref": "#/definitions/geometryCollection" },
+        { "$ref": "#/definitions/feature" },
+        { "$ref": "#/definitions/featureCollection" }
+    ],
+    "definitions": {
+        "geometryCollection": {
+            "title": "GeometryCollection",
+            "description": "A collection of geometry objects",
+            "required": [ "geometries" ],
+            "properties": {
+                "type": { "enum": [ "GeometryCollection" ] },
+                "geometries": {
+                    "type": "array",
+                    "items": { "$ref": "http://json-schema.org/geojson/geometry.json#" }
+                }
+            }
+        },
+        "feature": {
+            "title": "Feature",
+            "description": "A Geo JSON feature object",
+            "required": [ "geometry", "properties" ],
+            "properties": {
+                "type": { "enum": [ "Feature" ] },
+                "geometry": {
+                    "oneOf": [
+                        { "type": "null" },
+                        { "$ref": "http://json-schema.org/geojson/geometry.json#" }
+                    ]
+                },
+                "properties": { "type": [ "object", "null" ] },
+                "id": { "FIXME": "may be there, type not known (string? number?)" }
+            }
+        },
+        "featureCollection": {
+            "title": "FeatureCollection",
+            "description": "A Geo JSON feature collection",
+            "required": [ "features" ],
+            "properties": {
+                "type": { "enum": [ "FeatureCollection" ] },
+                "features": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/feature" }
+                }
+            }
+        }
+    }
+}
+

+ 91 - 0
ispformat/schema/0.1/geojson/geometry.json

@@ -0,0 +1,91 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://json-schema.org/geojson/geometry.json#",
+    "title": "geometry",
+    "description": "One geometry as defined by GeoJSON",
+    "type": "object",
+    "required": [ "type", "coordinates" ],
+    "oneOf": [
+        {
+            "title": "Point",
+            "properties": {
+                "type": { "enum": [ "Point" ] },
+                "coordinates": { "$ref": "#/definitions/position" }
+            }
+        },
+        {
+            "title": "MultiPoint",
+            "properties": {
+                "type": { "enum": [ "MultiPoint" ] },
+                "coordinates": { "$ref": "#/definitions/positionArray" }
+            }
+        },
+        {
+            "title": "LineString",
+            "properties": {
+                "type": { "enum": [ "LineString" ] },
+                "coordinates": { "$ref": "#/definitions/lineString" }
+            }
+        },
+        {
+            "title": "MultiLineString",
+            "properties": {
+                "type": { "enum": [ "MultiLineString" ] },
+                "coordinates": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/lineString" }
+                }
+            }
+        },
+        {
+            "title": "Polygon",
+            "properties": {
+                "type": { "enum": [ "Polygon" ] },
+                "coordinates": { "$ref": "#/definitions/polygon" }
+            }
+        },
+        {
+            "title": "MultiPolygon",
+            "properties": {
+                "type": { "enum": [ "MultiPolygon" ] },
+                "coordinates": {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/polygon" }
+                }
+            }
+        }
+    ],
+    "definitions": {
+        "position": {
+            "description": "A single position",
+            "type": "array",
+            "minItems": 2,
+            "items": [ { "type": "number" }, { "type": "number" } ],
+            "additionalItems": false
+        },
+        "positionArray": {
+            "description": "An array of positions",
+            "type": "array",
+            "items": { "$ref": "#/definitions/position" }
+        },
+        "lineString": {
+            "description": "An array of two or more positions",
+            "allOf": [
+                { "$ref": "#/definitions/positionArray" },
+                { "minItems": 2 }
+            ]
+        },
+        "linearRing": {
+            "description": "An array of four positions where the first equals the last",
+            "allOf": [
+                { "$ref": "#/definitions/positionArray" },
+                { "minItems": 4 }
+            ]
+        },
+        "polygon": {
+            "description": "An array of linear rings",
+            "type": "array",
+            "items": { "$ref": "#/definitions/linearRing" }
+        }
+    }
+}

+ 120 - 0
ispformat/schema/0.1/isp.json

@@ -0,0 +1,120 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "id": "http://ffdn.org/ispschema/draft-01/schema#",
+    "title": "ISP",
+    "description": "Object describing a local ISP",
+    "type": "object",
+    "required": ["name", "email", "memberCount", "subscriberCount", "coveredAreas", "version"],
+
+    "properties": {
+        "name": {
+            "description": "The ISP's name",
+            "type": "string"
+        },
+        "shortname": {
+            "description": "Shorter name",
+            "type": "string",
+            "maxLength": 15
+        },
+        "description": {
+            "description": "Short text describing the project",
+            "type": "string"
+        },
+        "logoURL": {
+            "description": "HTTP(S) URL of the ISP's logo",
+            "type": "string",
+            "format": "uri"
+        },
+        "website": {
+            "description": "URL to the official website",
+            "type": "string",
+            "format": "uri"
+        },
+        "otherWebsites": {
+            "description": "Additional websites hosted by the ISP (wiki, etherpad, ..)",
+            "type": "object",
+            "patternProperties": {
+                "^.+$": { "type": "string", "format": "uri" }
+            },
+            "additionalProperties": false
+        },
+        "email": {
+            "description": "Contact email address",
+            "type": "string",
+            "format": "email"
+        },
+        "mainMailingList": {
+            "description": "Main public mailing-list",
+            "type": "string",
+            "format": "email"
+        },
+        "creationDate": {
+            "description": "Date of creation for legal structure, in ISO8601 format",
+            "type": "string"
+        },
+        "ffdnMemberSince": {
+            "description": "Date at wich the ISP joined the Federation, in ISO8601 format",
+            "type": "string"
+        },
+        "progressStatus": {
+            "description": "Progression status of the ISP",
+            "type": "integer",
+            "minimum": 1,
+            "maximum": 7
+        },
+        "memberCount": {
+            "description": "Number of members",
+            "type": "integer",
+            "minimum": 0
+        },
+        "subscriberCount": {
+            "description": "Number of subscribers to an internet access",
+            "type": "integer",
+            "minimum": 0
+        },
+        "chatrooms": {
+            "description": "URIs to the various chat-rooms",
+            "type": "array",
+            "items": { "type": "string", "format": "uri" },
+            "uniqueItems": true
+        },
+        "registeredOffice": {
+            "description": "address of the registered office (siège social)",
+            "$ref": "http://json-schema.org/address"
+        },
+        "coordinates": {
+            "description": "Coordinates of the registered office",
+            "$ref": "http://json-schema.org/geo"
+        },
+        "coveredAreas": {
+            "description": "Array of covered area in GeoJSON format",
+            "type": "array",
+            "items": { "$ref": "#/definitions/coveredArea" }
+        },
+        "version": {
+            "description": "Version of the specification implemented",
+            "type": "number",
+            "enum": [0.1]
+        }
+    },
+
+    "definitions": {
+        "coveredArea": {
+            "type": "object",
+            "required": ["name", "technologies"],
+            "properties": {
+                "name": {
+                    "type": "string"
+                },
+                "technologies": {
+                    "type": "array",
+                    "items": { "enum": [ "ftth", "dsl", "wifi" ] }
+                },
+                "area": {
+                    "$ref": "http://json-schema.org/geojson/geojson.json#"
+                }
+            },
+            "additionalProperties": false
+        }
+    }
+}

+ 34 - 0
ispformat/schema/__init__.py

@@ -0,0 +1,34 @@
+import json
+import os
+
+path = os.path.dirname(__file__)
+
+def load_schema(version, name='isp'):
+    """
+    Load a schema from ./``version``/``name``.json and return it.
+
+    """
+    schemapath = os.path.join(path, str(version), '%s.json'%(name,))
+    with open(schemapath) as f:
+        return json.load(f)
+
+
+versions = {
+    0.1: load_schema('0.1')
+}
+
+latest = versions[0.1]
+
+def deps_for_version(version):
+    return {
+        'http://json-schema.org/geo': load_schema(version, 'geo'),
+        'http://json-schema.org/address': load_schema(version, 'address'),
+        'http://json-schema.org/geojson/geojson.json#': load_schema(version, 'geojson/geojson'),
+        'http://json-schema.org/geojson/geometry.json#': load_schema(version, 'geojson/geometry'),
+        'http://json-schema.org/geojson/bbox.json#': load_schema(version, 'geojson/bbox'),
+        'http://json-schema.org/geojson/crs.json#': load_schema(version, 'geojson/crs'),
+    }
+
+
+
+__all__ = ['path', 'versions', 'latest']

+ 74 - 0
ispformat/specs/0.1/isp-schema-spec.rst

@@ -0,0 +1,74 @@
+==========
+ISP format
+==========
+------------------------------------------------
+Schema specification & implementation guidelines
+------------------------------------------------
+
+:Editor:
+    Gu1 <gu1@cafai.fr>
+:Authors:
+    FFDN diyisp schema working group
+:Revision:
+    draft-1 (0.1)
+:Date:
+    October 2013
+
+
+**Abstract:**
+ The ISP format is a file format created and supported by the FDN Federation (FFDN) in order to help ISPs sharing its value to document and publish relevent informations regarding their status and progress in a common format.
+
+
+.. contents:: **Table of Contents**
+
+
+
+1. Introduction
+===============
+
+The ISP format is meant to allow an ISP to share relevant data and informations, such as its number of subscribers, the URL of its websites, chatrooms, etc... It was mostly created with small/diy/non-profit ISPs in mind, such as those in the FFDN.
+
+1.1. Definitions
+----------------
+* The ISP format itself is based on the JSON data format. The JavaScript Object Notation (JSON) format and its basic types are defined in [RFC4627]_.
+* The ISP format is mostly specified as a JSON Schema file, a JSON-based format for defining the structure of JSON data. JSON Schema is defined in the following IETF draft [json-schema-04]_.
+* HTTP... [RFC2616]_.
+* The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119]_.
+
+
+2. JSON Schema definition of the format
+=======================================
+.. include:: schema/0.1/isp.json
+    :literal:
+
+
+3. Implementating the format
+============================
+
+An ISP implementing the ISP format MUST serve the file as a valid HTTP ressource named isp.json placed at the root (/) of its HTTP server.
+
+The ressource SHOULD be served on the ISP's main website (e.g. http://www.isp.org/isp.json). If that is not possible for some reason, the ISP MUST serve it on a subdomain (e.g. http://api.isp.org/isp.json).
+
+The ressource SHOULD be served with a MIME type of "application/json".
+
+The ressource SHOULD be reachable over HTTPS ([RFC2818]_).
+
+The ressource MUST be served over HTTP version 1.1. Moreover, if the ressource is dynamically generated by a script, the implementer MUST ensure HTTP 1.1 caching features are properly implemented.
+
+
+3. References
+=============
+
+.. [RFC4627] Crockford, D., "The application/json Media Type for JavaScript Object Notation (JSON)",
+             `RFC 4627 <http://tools.ietf.org/html/rfc4627>`_, July 2006.
+.. [json-schema-04] "JSON Schema: core definitions and terminology",
+                    `draft-zyp-json-schema-04 <http://tools.ietf.org/html/draft-zyp-json-schema-04>`_, January 31, 2013.
+.. [RFC2616] Fielding, R., Gettys, J. Mogul, J., Frystyk, H., Masinter, L., Leach P., and T. Berners-Lee,
+             "Hypertext Transfer Protocol -- HTTP/1.1", `RFC 2616 <http://tools.ietf.org/html/rfc2616>`_, June 1999.
+.. [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", `RFC 2119 <http://tools.ietf.org/html/rfc2119>`_, March 1997.
+.. [RFC2818] Rescorla, E., "HTTP Over TLS", `RFC 2818 <https://tools.ietf.org/html/rfc2818>`_, May 2000.
+
+
+Appendix A. ISPs Examples
+=========================
+TODO

+ 8 - 0
ispformat/specs/__init__.py

@@ -0,0 +1,8 @@
+
+import os
+
+path = os.path.dirname(__file__)
+
+versions = {
+    0.1: os.path.join(path, '0.1', 'isp-schema-spec.rst')
+}

+ 2 - 0
ispformat/validator/__init__.py

@@ -0,0 +1,2 @@
+
+from ispformat.validator.schemavalidator import *

+ 69 - 0
ispformat/validator/schemavalidator.py

@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+import ispformat.schema as _schema
+from jsonschema import Draft4Validator, RefResolver, draft4_format_checker
+from jsonschema.exceptions import RefResolutionError, SchemaError, ValidationError
+import json
+import os.path
+from urlparse import urlsplit
+
+
+class MyRefResolver(RefResolver):
+    def resolve_remote(self, uri):
+        # Prevent remote resolving
+        raise RefResolutionError("LOL NOPE")
+
+
+def validate_isp(jdict):
+    """
+    Validate a json-object against the isp json-schema
+    """
+    if not 'version' in jdict:
+        raise ValidationError(u'version is a required property')
+    try:
+        schema=_schema.versions.get(jdict['version'])
+    except (AttributeError, TypeError):
+        raise ValidationError(u'version %r unsupported'%jdict['version'])
+
+    v=Draft4Validator(
+        schema,
+        resolver=MyRefResolver.from_schema(schema, store=_schema.deps_for_version(jdict['version'])),
+        format_checker=draft4_format_checker,
+    )
+
+    for err in v.iter_errors(jdict):
+        yield err
+
+    def is_valid_url(u):
+        try:
+            pu=urlsplit(u)
+        except:
+            return False
+        if pu.scheme not in ('', 'http', 'https'):
+            return False
+        if not pu.netloc:
+            return False
+        return True
+
+    if 'website' in jdict and not is_valid_url(jdict['website']):
+        yield ValidationError(u'%r must be an absolute HTTP URL'%u'website',
+                              instance=jdict[u'website'], schema=schema[u'properties'][u'website'],
+                              path=[u'website'], schema_path=[u'properties', u'website', u'description'],
+                              validator=u'validate_url', validator_value=jdict['website'])
+
+    if 'logoURL' in jdict and not is_valid_url(jdict['logoURL']):
+        yield ValidationError(u'%r must be an absolute HTTP URL'%u'logoURL',
+                              instance=jdict[u'logoURL'], schema=schema[u'properties'][u'logoURL'],
+                              path=[u'logoURL'], schema_path=[u'properties', u'logoURL', u'description'],
+                              validator=u'validate_url', validator_value=jdict['logoURL'])
+
+    sch=schema[u'properties'][u'otherWebsites'][u'patternProperties'][u'^.+$']
+    for name, url in jdict.get('otherWebsites', {}).iteritems():
+        if is_valid_url(url):
+            continue
+        yield ValidationError(u'%r must be an absolute HTTP URL'%name,
+                              instance=url, schema=sch, path=[u'otherWebsite', name],
+                              schema_path=[u'properties', u'otherWebsites', u'patternProperties', u'^.+$', 'description'],
+                              validator=u'validate_url', validator_value=url)
+
+

+ 39 - 0
setup.py

@@ -0,0 +1,39 @@
+import os
+from setuptools import setup
+
+
+PKG_NAME = 'ispformat'
+VERSION = __import__(PKG_NAME).__version__
+
+README = open(os.path.join(os.path.dirname(__file__), 'README')).read()
+
+# allow setup.py to be run from any path
+os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
+
+setup(
+    name = 'isp-format',
+    version = VERSION,
+    packages = ['ispformat.validator', 'ispformat.schema', 'ispformat.specs'],
+    include_package_data = True,
+    scripts = ['ispformat/bin/isp-format-validator'],
+    license = '2-clause BSD License',
+    description = 'Tools and specification related to FFDN\'s ISP format',
+    long_description = README,
+    url = 'http://www.ffdn.org/',
+    author = 'Gu1',
+    author_email = 'gu1@cafai.fr',
+    classifiers = [
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Topic :: Internet :: WWW/HTTP',
+    ],
+    install_requires=[
+        'jsonschema',
+    ],
+    zip_safe = False,
+)