.
This commit is contained in:
@@ -1,93 +1,28 @@
|
|||||||
cmake_minimum_required(VERSION 2.8.3)
|
cmake_minimum_required(VERSION 2.8.3)
|
||||||
project(hos_cosgi_core)
|
project(hos_cosgi_core)
|
||||||
|
|
||||||
## Find catkin macros and libraries
|
|
||||||
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
|
||||||
## is used, also find other catkin packages
|
|
||||||
find_package(catkin REQUIRED COMPONENTS
|
find_package(catkin REQUIRED COMPONENTS
|
||||||
hos_celix
|
|
||||||
roscpp
|
roscpp
|
||||||
|
hos_celix
|
||||||
hos_cpp_utils
|
hos_cpp_utils
|
||||||
hos_deps
|
hos_deps
|
||||||
)
|
)
|
||||||
|
|
||||||
## System dependencies are found with CMake's conventions
|
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
find_package(APR REQUIRED)
|
find_package(APR REQUIRED)
|
||||||
|
find_package(libvariant REQUIRED)
|
||||||
|
|
||||||
|
|
||||||
## Uncomment this if the package has a setup.py. This macro ensures
|
|
||||||
## modules and global scripts declared therein get installed
|
|
||||||
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
|
|
||||||
# catkin_python_setup()
|
|
||||||
|
|
||||||
################################################
|
|
||||||
## Declare ROS messages, services and actions ##
|
|
||||||
################################################
|
|
||||||
|
|
||||||
## To declare and build messages, services or actions from within this
|
|
||||||
## package, follow these steps:
|
|
||||||
## * Let MSG_DEP_SET be the set of packages whose message types you use in
|
|
||||||
## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
|
|
||||||
## * In the file package.xml:
|
|
||||||
## * add a build_depend and a run_depend tag for each package in MSG_DEP_SET
|
|
||||||
## * If MSG_DEP_SET isn't empty the following dependencies might have been
|
|
||||||
## pulled in transitively but can be declared for certainty nonetheless:
|
|
||||||
## * add a build_depend tag for "message_generation"
|
|
||||||
## * add a run_depend tag for "message_runtime"
|
|
||||||
## * In this file (CMakeLists.txt):
|
|
||||||
## * add "message_generation" and every package in MSG_DEP_SET to
|
|
||||||
## find_package(catkin REQUIRED COMPONENTS ...)
|
|
||||||
## * add "message_runtime" and every package in MSG_DEP_SET to
|
|
||||||
## catkin_package(CATKIN_DEPENDS ...)
|
|
||||||
## * uncomment the add_*_files sections below as needed
|
|
||||||
## and list every .msg/.srv/.action file to be processed
|
|
||||||
## * uncomment the generate_messages entry below
|
|
||||||
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
|
|
||||||
|
|
||||||
## Generate messages in the 'msg' folder
|
|
||||||
# add_message_files(
|
|
||||||
# FILES
|
|
||||||
# Message1.msg
|
|
||||||
# Message2.msg
|
|
||||||
# )
|
|
||||||
|
|
||||||
## Generate services in the 'srv' folder
|
|
||||||
# add_service_files(
|
|
||||||
# FILES
|
|
||||||
# Service1.srv
|
|
||||||
# Service2.srv
|
|
||||||
# )
|
|
||||||
|
|
||||||
## Generate actions in the 'action' folder
|
|
||||||
# add_action_files(
|
|
||||||
# FILES
|
|
||||||
# Action1.action
|
|
||||||
# Action2.action
|
|
||||||
# )
|
|
||||||
|
|
||||||
## Generate added messages and services with any dependencies listed here
|
|
||||||
# generate_messages(
|
|
||||||
# DEPENDENCIES
|
|
||||||
# std_msgs # Or other packages containing msgs
|
|
||||||
# )
|
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
## catkin specific configuration ##
|
## catkin specific configuration ##
|
||||||
###################################
|
###################################
|
||||||
## The catkin_package macro generates cmake config files for your package
|
|
||||||
## Declare things to be passed to dependent projects
|
|
||||||
## INCLUDE_DIRS: uncomment this if you package contains header files
|
|
||||||
## LIBRARIES: libraries you create in this project that dependent projects also need
|
|
||||||
## CATKIN_DEPENDS: catkin_packages dependent projects also need
|
|
||||||
## DEPENDS: system dependencies of this project that dependent projects also need
|
|
||||||
catkin_package(
|
catkin_package(
|
||||||
INCLUDE_DIRS include/${PROJECT_NAME}/
|
INCLUDE_DIRS include/${PROJECT_NAME}/
|
||||||
# LIBRARIES hos_cosgi_launcher
|
|
||||||
CATKIN_DEPENDS hos_celix
|
CATKIN_DEPENDS hos_celix
|
||||||
# DEPENDS system_lib
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
###########
|
###########
|
||||||
## Build ##
|
## Build ##
|
||||||
###########
|
###########
|
||||||
@@ -96,70 +31,39 @@ add_definitions(
|
|||||||
-std=c++11
|
-std=c++11
|
||||||
)
|
)
|
||||||
|
|
||||||
## Specify additional locations of header files
|
|
||||||
## Your package locations should be listed before other locations
|
|
||||||
# include_directories(include)
|
|
||||||
include_directories(
|
include_directories(
|
||||||
|
include/${PROJECT_NAME}/
|
||||||
${catkin_INCLUDE_DIRS}
|
${catkin_INCLUDE_DIRS}
|
||||||
${APR_INCLUDE_DIR}
|
${APR_INCLUDE_DIR}
|
||||||
${APR_INCLUDE_DIR}/apr-1.0
|
${APR_INCLUDE_DIR}/apr-1.0
|
||||||
include/${PROJECT_NAME}/
|
${libvariant_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
add_executable(hos_cosgi_launcher
|
add_executable(hos_cosgi_launcher
|
||||||
src/launcher.cpp
|
src/launcher.cpp
|
||||||
src/hos/cosgi/service/core/core_serviceimpl.cpp
|
src/hos/cosgi/service/core/CoreServiceImpl.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(hos_cosgi_launcher
|
target_link_libraries(hos_cosgi_launcher
|
||||||
${catkin_LIBRARIES}
|
${catkin_LIBRARIES}
|
||||||
${APR_LIBRARIES}
|
${APR_LIBRARIES}
|
||||||
|
${libvariant_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
## Declare a cpp library
|
|
||||||
# add_library(hos_osgi_launcher
|
|
||||||
# src/${PROJECT_NAME}/hos_osgi_launcher.cpp
|
|
||||||
# )
|
|
||||||
|
|
||||||
## Declare a cpp executable
|
|
||||||
# add_executable(hos_osgi_launcher_node src/hos_osgi_launcher_node.cpp)
|
|
||||||
|
|
||||||
## Add cmake target dependencies of the executable/library
|
|
||||||
## as an example, message headers may need to be generated before nodes
|
|
||||||
# add_dependencies(hos_osgi_launcher_node hos_osgi_launcher_generate_messages_cpp)
|
|
||||||
|
|
||||||
## Specify libraries to link a library or executable target against
|
|
||||||
# target_link_libraries(hos_osgi_launcher_node
|
|
||||||
# ${catkin_LIBRARIES}
|
|
||||||
# )
|
|
||||||
|
|
||||||
#############
|
#############
|
||||||
## Install ##
|
## Install ##
|
||||||
#############
|
#############
|
||||||
|
|
||||||
# all install targets should use catkin DESTINATION variables
|
|
||||||
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
|
|
||||||
|
|
||||||
## Mark executable scripts (Python etc.) for installation
|
|
||||||
## in contrast to setup.py, you can choose the destination
|
|
||||||
# install(PROGRAMS
|
|
||||||
# scripts/my_python_script
|
|
||||||
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
|
||||||
# )
|
|
||||||
|
|
||||||
## Mark executables and/or libraries for installation
|
|
||||||
install(TARGETS hos_cosgi_launcher
|
install(TARGETS hos_cosgi_launcher
|
||||||
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||||
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||||
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||||
)
|
)
|
||||||
|
|
||||||
## Mark cpp header files for installation
|
|
||||||
install(DIRECTORY include/${PROJECT_NAME}/
|
install(DIRECTORY include/${PROJECT_NAME}/
|
||||||
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
|
DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
|
||||||
FILES_MATCHING PATTERN "*.h"
|
FILES_MATCHING PATTERN "*.h"
|
||||||
PATTERN ".svn" EXCLUDE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
||||||
@@ -167,26 +71,6 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc
|
|||||||
OPTIONAL
|
OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
## Mark other files for installation (e.g. launch and bag files, etc.)
|
|
||||||
# install(FILES
|
|
||||||
# # myfile1
|
|
||||||
# # myfile2
|
|
||||||
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
|
|
||||||
# )
|
|
||||||
|
|
||||||
#############
|
|
||||||
## Testing ##
|
|
||||||
#############
|
|
||||||
|
|
||||||
## Add gtest based cpp test target and link libraries
|
|
||||||
# catkin_add_gtest(${PROJECT_NAME}-test test/test_hos_osgi_launcher.cpp)
|
|
||||||
# if(TARGET ${PROJECT_NAME}-test)
|
|
||||||
# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
|
|
||||||
# endif()
|
|
||||||
|
|
||||||
## Add folders to be run by python nosetests
|
|
||||||
# catkin_add_nosetests(test)
|
|
||||||
|
|
||||||
|
|
||||||
###################
|
###################
|
||||||
## Documentation ##
|
## Documentation ##
|
||||||
@@ -200,3 +84,4 @@ if(DOXYGEN_FOUND)
|
|||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
)
|
)
|
||||||
endif(DOXYGEN_FOUND)
|
endif(DOXYGEN_FOUND)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Celix does not provide C++ ready headers, and seems to be under heavy development.
|
||||||
|
* Therefore we don't directly include celix but use this file als proxy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "celix/framework.h"
|
||||||
|
#include "celix/linked_list_iterator.h"
|
||||||
|
}
|
||||||
@@ -76,6 +76,15 @@ template <class... _Args> class Observable {};
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience template merging the Service class and the Observable template into a single template.
|
||||||
|
*/
|
||||||
|
template<class _Service, class... _Args> class ObservableService
|
||||||
|
: public virtual Service<_Service>,
|
||||||
|
public virtual Observable<_Args...> {};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of interface::Observable<_Event>.
|
* Implementation of interface::Observable<_Event>.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
|
|
||||||
|
|
||||||
#include <hos/cosgi/service/Interface.h>
|
#include <hos/cosgi/service/Interface.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
namespace ros { class NodeHandle; }
|
namespace ros { class NodeHandle; }
|
||||||
|
namespace hos { namespace utils { class SchemaLoader; } };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -24,24 +26,14 @@ namespace core {
|
|||||||
* Base class for events emitted by the CoreService.
|
* Base class for events emitted by the CoreService.
|
||||||
*/
|
*/
|
||||||
class CoreServiceEvent {
|
class CoreServiceEvent {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
typedef std::chrono::system_clock::time_point timestamp_t;
|
||||||
/**
|
|
||||||
* CoreService event types.
|
|
||||||
*/
|
|
||||||
enum class EventType : int { STARTUP_FINISHED = 1, SHUTDOWN = 2 };
|
enum class EventType : int { STARTUP_FINISHED = 1, SHUTDOWN = 2 };
|
||||||
|
|
||||||
/**
|
|
||||||
* Event type.
|
|
||||||
*/
|
|
||||||
const EventType eventType;
|
const EventType eventType;
|
||||||
|
const timestamp_t timestamp;
|
||||||
|
|
||||||
/**
|
CoreServiceEvent(EventType type, const timestamp_t timestamp = std::chrono::system_clock::now())
|
||||||
* Constructor.
|
: eventType(type), timestamp(timestamp) {}
|
||||||
*/
|
|
||||||
CoreServiceEvent(EventType type) : eventType(type) {}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -116,6 +108,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void requestShutdown() = 0;
|
virtual void requestShutdown() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the schema loader.
|
||||||
|
*
|
||||||
|
* \return schema loader.
|
||||||
|
*/
|
||||||
|
virtual utils::SchemaLoader& getSchemaLoader() = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+14
-17
@@ -1,34 +1,30 @@
|
|||||||
/*
|
|
||||||
* hos_core_service_impl.cpp
|
|
||||||
*
|
|
||||||
* Created on: 05.04.2015
|
|
||||||
* Author: matzman
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <ros/ros.h>
|
#include <ros/ros.h>
|
||||||
#include "core_serviceimpl.h"
|
#include "CoreServiceImpl.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Service Namespace
|
||||||
namespace hos {
|
namespace hos {
|
||||||
namespace cosgi{
|
namespace cosgi{
|
||||||
namespace service {
|
namespace service {
|
||||||
namespace core {
|
namespace core {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Service information
|
||||||
template<> const char* implementation::ServiceInfo<CoreServiceImpl>::name = service::core::CoreService::SERVICE_NAME;
|
template<> const char* implementation::ServiceInfo<CoreServiceImpl>::name = service::core::CoreService::SERVICE_NAME;
|
||||||
template<> const int implementation::ServiceInfo<CoreServiceImpl>::major = 0;
|
template<> const int implementation::ServiceInfo<CoreServiceImpl>::major = 1;
|
||||||
template<> const int implementation::ServiceInfo<CoreServiceImpl>::minor = 3;
|
template<> const int implementation::ServiceInfo<CoreServiceImpl>::minor = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CoreServiceImpl::CoreServiceImpl(ros::NodeHandle& publicNodeHandle, ros::NodeHandle& privateNodeHandle)
|
CoreServiceImpl::CoreServiceImpl(ros::NodeHandle& publicNodeHandle, ros::NodeHandle& privateNodeHandle)
|
||||||
: publicNodeHandle(publicNodeHandle), privateNodeHandle(privateNodeHandle) {}
|
: publicNodeHandle(publicNodeHandle), privateNodeHandle(privateNodeHandle) {}
|
||||||
|
|
||||||
|
|
||||||
ros::NodeHandle& CoreServiceImpl::getPublicNodeHandle() {
|
|
||||||
return publicNodeHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
ros::NodeHandle& CoreServiceImpl::getPrivateNodeHandle() {
|
|
||||||
return privateNodeHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void CoreServiceImpl::requestShutdown() {
|
void CoreServiceImpl::requestShutdown() {
|
||||||
ROS_INFO("Received shutdown request");
|
ROS_INFO("Received shutdown request");
|
||||||
@@ -36,6 +32,7 @@ void CoreServiceImpl::requestShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace service
|
} // namespace service
|
||||||
} // namespace cosgi
|
} // namespace cosgi
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <hos/cosgi/service/core/CoreService.h>
|
||||||
|
#include <hos/cosgi/service/Implementation.h>
|
||||||
|
#include <hos/utils/SchemaLoader.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Service Namespace
|
||||||
|
namespace hos {
|
||||||
|
namespace cosgi{
|
||||||
|
namespace service {
|
||||||
|
namespace core {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of CoreService.
|
||||||
|
*/
|
||||||
|
class CoreServiceImpl
|
||||||
|
: public virtual CoreService,
|
||||||
|
public virtual implementation::ObservableService<CoreServiceImpl, CoreServiceEvent> {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
ros::NodeHandle publicNodeHandle;
|
||||||
|
ros::NodeHandle privateNodeHandle;
|
||||||
|
std::unique_ptr<utils::SchemaLoader> schemaLoader;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*/
|
||||||
|
CoreServiceImpl(ros::NodeHandle& publicNodeHandle, ros::NodeHandle& privateNodeHandle);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
////////////// Public Service Implementations //////////////
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to a node handle with public namespace.
|
||||||
|
*
|
||||||
|
* \return public node handle
|
||||||
|
*/
|
||||||
|
virtual ros::NodeHandle& getPublicNodeHandle() override {
|
||||||
|
return publicNodeHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to a node handle with private namespace.
|
||||||
|
*
|
||||||
|
* \return private node handle
|
||||||
|
*/
|
||||||
|
virtual ros::NodeHandle& getPrivateNodeHandle() override {
|
||||||
|
return privateNodeHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request an application shutdown
|
||||||
|
*/
|
||||||
|
virtual void requestShutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the schema loader.
|
||||||
|
*
|
||||||
|
* \return schema loader.
|
||||||
|
*/
|
||||||
|
virtual utils::SchemaLoader& getSchemaLoader() override {
|
||||||
|
return *schemaLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace core
|
||||||
|
} // namespace service
|
||||||
|
} // namespace cosgi
|
||||||
|
} // namespace hos
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* hos_core_service_private.h
|
|
||||||
*
|
|
||||||
* Created on: 05.04.2015
|
|
||||||
* Author: matzman
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SRC_CORE_SERVICEIMPL_H_
|
|
||||||
#define SRC_CORE_SERVICEIMPL_H_
|
|
||||||
|
|
||||||
#include <hos/cosgi/service/core/CoreService.h>
|
|
||||||
#include <hos/cosgi/service/Implementation.h>
|
|
||||||
|
|
||||||
|
|
||||||
// Service Namespace
|
|
||||||
namespace hos {
|
|
||||||
namespace cosgi{
|
|
||||||
namespace service {
|
|
||||||
namespace core {
|
|
||||||
|
|
||||||
|
|
||||||
class CoreServiceImpl;
|
|
||||||
class CoreServiceImpl : public virtual CoreService, public virtual implementation::Service<CoreServiceImpl>, public virtual implementation::Observable<CoreServiceEvent> {
|
|
||||||
private:
|
|
||||||
ros::NodeHandle publicNodeHandle;
|
|
||||||
ros::NodeHandle privateNodeHandle;
|
|
||||||
|
|
||||||
public:
|
|
||||||
CoreServiceImpl(ros::NodeHandle& publicNodeHandle, ros::NodeHandle& privateNodeHandle);
|
|
||||||
|
|
||||||
virtual ros::NodeHandle& getPublicNodeHandle();
|
|
||||||
virtual ros::NodeHandle& getPrivateNodeHandle();
|
|
||||||
virtual void requestShutdown();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace core
|
|
||||||
} // namespace service
|
|
||||||
} // namespace cosgi
|
|
||||||
} // namespace hos
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* SRC_CORE_SERVICEIMPL_H_ */
|
|
||||||
@@ -1,53 +1,64 @@
|
|||||||
|
|
||||||
|
|
||||||
|
// std
|
||||||
|
#include <signal.h>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <exception>
|
||||||
|
// Boost
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
// ROS
|
||||||
#include "ros/ros.h"
|
#include "ros/ros.h"
|
||||||
|
// APR
|
||||||
#include <apr_general.h>
|
#include <apr_general.h>
|
||||||
#include <apr_strings.h>
|
#include <apr_strings.h>
|
||||||
|
// Celix
|
||||||
#include "hos/cosgi/service/core/core_serviceimpl.h"
|
#include <hos/cosgi/celix.h>
|
||||||
|
// CoreService
|
||||||
extern "C" { // celix does not provide C++ ready headers
|
#include "hos/cosgi/service/core/CoreServiceImpl.h"
|
||||||
#include "celix/framework.h"
|
|
||||||
#include "celix/linked_list_iterator.h"
|
|
||||||
}
|
|
||||||
#include <exception>
|
|
||||||
#include <sstream>
|
|
||||||
#include <boost/format.hpp>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace hos::cosgi::service::core;
|
using namespace hos::cosgi::service::core;
|
||||||
|
|
||||||
|
|
||||||
|
// forward declaration of signal handler
|
||||||
std::function<void(int)> signalHandler;
|
std::function<void(int)> signalHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hos_cosgi_launcher entry point.
|
||||||
|
*
|
||||||
|
* Command line argument parsing is handled by ROS.
|
||||||
|
*/
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
|
// Public Parameters
|
||||||
|
int loop_frequency = 100;
|
||||||
|
std::string autostart_bundles;
|
||||||
|
std::string config_properties;
|
||||||
|
|
||||||
|
// Internal State
|
||||||
bool apr_initialized = false;
|
bool apr_initialized = false;
|
||||||
apr_pool_t* memoryPool = nullptr;
|
apr_pool_t* memoryPool = nullptr;
|
||||||
framework_pt framework = nullptr;
|
framework_pt framework = nullptr;
|
||||||
bundle_pt fwBundle = nullptr;
|
bundle_pt fwBundle = nullptr;
|
||||||
properties_pt properties = nullptr;
|
properties_pt properties = nullptr;
|
||||||
|
|
||||||
int loop_frequency = 100;
|
|
||||||
std::string autostart_bundles;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Initialize ROS
|
// Initialize ROS
|
||||||
ros::init(argc, argv, "hos_osgi_launcher", ros::init_options::AnonymousName | ros::init_options::NoSigintHandler);
|
ros::init(argc, argv, "hos_osgi_launcher", ros::init_options::AnonymousName | ros::init_options::NoSigintHandler);
|
||||||
// Create public node-handle
|
|
||||||
ros::NodeHandle rosPublicNodeHandle;
|
ros::NodeHandle rosPublicNodeHandle;
|
||||||
// Create private node-handle
|
|
||||||
ros::NodeHandle rosPrivateNodeHandle("~");
|
ros::NodeHandle rosPrivateNodeHandle("~");
|
||||||
if (!ros::ok()) {
|
if (!ros::ok()) {
|
||||||
throw std::runtime_error(boost::format("Could not initialize ROS.").str());
|
throw std::runtime_error(boost::format("Could not initialize ROS.").str());
|
||||||
}
|
}
|
||||||
ROS_INFO("ROS initialized");
|
ROS_INFO("ROS initialized");
|
||||||
|
|
||||||
// Retrieve ROS parameter
|
// Retrieve public parameters
|
||||||
rosPrivateNodeHandle.getParam("loop_frequency", loop_frequency);
|
rosPrivateNodeHandle.getParam("loop_frequency", loop_frequency);
|
||||||
rosPrivateNodeHandle.getParam("autostart_bundles", autostart_bundles);
|
rosPrivateNodeHandle.getParam("autostart_bundles", autostart_bundles);
|
||||||
|
rosPrivateNodeHandle.getParam("config_properties", config_properties);
|
||||||
|
|
||||||
// Initialize APR and APR memory pool
|
// Initialize APR and APR memory pool
|
||||||
apr_status_t astatus = apr_initialize();
|
apr_status_t astatus = apr_initialize();
|
||||||
@@ -64,12 +75,10 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
// Create celix properties
|
// Create celix properties
|
||||||
properties = properties_create();
|
properties = properties_create();
|
||||||
|
|
||||||
//properties = properties_load((char*)"config.properties");
|
|
||||||
if (properties == nullptr) {
|
if (properties == nullptr) {
|
||||||
throw std::runtime_error(boost::format("Could not create celix properties.").str());
|
throw std::runtime_error(boost::format("Could not create celix properties.").str());
|
||||||
}
|
}
|
||||||
properties_store(properties, (char*)"config.properties", nullptr);
|
properties_store(properties, (char*)config_properties.c_str(), nullptr);
|
||||||
|
|
||||||
// Create and initialize celix framework
|
// Create and initialize celix framework
|
||||||
celix_status_t cstatus = framework_create(&framework, memoryPool, properties);
|
celix_status_t cstatus = framework_create(&framework, memoryPool, properties);
|
||||||
@@ -183,17 +192,23 @@ int main(int argc, char **argv) {
|
|||||||
hosCoreService.notifyObservers(&event);
|
hosCoreService.notifyObservers(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// main loop
|
// Install signal handler
|
||||||
ROS_INFO("Entering main loop");
|
|
||||||
signalHandler = [&](int signum) {
|
signalHandler = [&](int signum) {
|
||||||
|
std::cerr << "Caught SIGINT, exiting..." << std::endl;
|
||||||
hosCoreService.requestShutdown();
|
hosCoreService.requestShutdown();
|
||||||
};
|
};
|
||||||
signal(SIGINT, [](int signum) { signalHandler(signum); });
|
signal(SIGINT, [](int signum) { signalHandler(signum); });
|
||||||
|
// main loop
|
||||||
|
ROS_INFO("Entering main loop");
|
||||||
ros::Rate loop_rate(loop_frequency);
|
ros::Rate loop_rate(loop_frequency);
|
||||||
while (ros::ok()) {
|
while (ros::ok()) {
|
||||||
ros::spinOnce();
|
ros::spinOnce();
|
||||||
loop_rate.sleep();
|
loop_rate.sleep();
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
ShutdownEvent event;
|
||||||
|
hosCoreService.notifyObservers(&event);
|
||||||
|
}
|
||||||
ROS_INFO("Exiting main loop");
|
ROS_INFO("Exiting main loop");
|
||||||
|
|
||||||
} catch (std::exception &ex) {
|
} catch (std::exception &ex) {
|
||||||
|
|||||||
+63
-31
@@ -6,6 +6,7 @@
|
|||||||
#include <Variant/Variant.h>
|
#include <Variant/Variant.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <chrono>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -17,76 +18,106 @@ namespace cosgi {
|
|||||||
namespace service {
|
namespace service {
|
||||||
namespace devicerepository {
|
namespace devicerepository {
|
||||||
|
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class Device;
|
class Device;
|
||||||
|
|
||||||
using namespace ::libvariant;
|
|
||||||
|
using namespace libvariant;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for device events
|
* Base class for device events.
|
||||||
**/
|
*/
|
||||||
class DeviceEvent {
|
class DeviceEvent {
|
||||||
public:
|
public:
|
||||||
|
typedef std::chrono::system_clock::time_point timestamp_t;
|
||||||
enum class EventType : int { DEVICE_ADDED = 1, DEVICE_REMOVED = 2, STATUS_UPDATED = 3, COMMAND_RECEIVED = 4 };
|
enum class EventType : int { DEVICE_ADDED = 1, DEVICE_REMOVED = 2, STATUS_UPDATED = 3, COMMAND_RECEIVED = 4 };
|
||||||
const EventType eventType;
|
|
||||||
Device* const device;
|
Device* const device;
|
||||||
DeviceEvent(Device* device, EventType type) : device(device), eventType(type) {}
|
const timestamp_t timestamp;
|
||||||
|
const EventType eventType;
|
||||||
|
|
||||||
|
DeviceEvent(Device* device, EventType type, const timestamp_t timestamp)
|
||||||
|
: device(device), timestamp(timestamp), eventType(type) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device added
|
* Device added event.
|
||||||
**/
|
*/
|
||||||
class DeviceAddedEvent : public DeviceEvent {
|
class DeviceAddedEvent
|
||||||
|
: public DeviceEvent {
|
||||||
public:
|
public:
|
||||||
DeviceAddedEvent(Device* device) : DeviceEvent(device, DeviceEvent::EventType::DEVICE_ADDED) {}
|
DeviceAddedEvent(Device* device, const timestamp_t timestamp)
|
||||||
|
: DeviceEvent(device, DeviceEvent::EventType::DEVICE_ADDED, timestamp) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Device removed
|
|
||||||
**/
|
|
||||||
class DeviceRemovedEvent : public DeviceEvent {
|
|
||||||
public:
|
|
||||||
DeviceRemovedEvent(Device* device) : DeviceEvent(device, DeviceEvent::EventType::DEVICE_REMOVED) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device status message received
|
* Device removed event.
|
||||||
**/
|
*/
|
||||||
class DeviceStatusMsgEvent : public DeviceEvent {
|
class DeviceRemovedEvent
|
||||||
|
: public DeviceEvent {
|
||||||
public:
|
public:
|
||||||
DeviceStatusMsgEvent(Device* device, const Variant statusMsg)
|
DeviceRemovedEvent(Device* device, const timestamp_t timestamp)
|
||||||
: DeviceEvent(device, DeviceEvent::EventType::STATUS_UPDATED), statusMsg(statusMsg) {}
|
: DeviceEvent(device, DeviceEvent::EventType::DEVICE_REMOVED, timestamp) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device status message received event.
|
||||||
|
*/
|
||||||
|
class DeviceStatusMsgEvent
|
||||||
|
: public DeviceEvent {
|
||||||
|
public:
|
||||||
|
DeviceStatusMsgEvent(Device* device, const Variant statusMsg, const timestamp_t timestamp)
|
||||||
|
: DeviceEvent(device, DeviceEvent::EventType::STATUS_UPDATED, timestamp), statusMsg(statusMsg) {}
|
||||||
const Variant statusMsg;
|
const Variant statusMsg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device command message received
|
* Device command message received event.
|
||||||
**/
|
*/
|
||||||
class DeviceCmdMsgEvent : public DeviceEvent {
|
class DeviceCmdMsgEvent
|
||||||
|
: public DeviceEvent {
|
||||||
public:
|
public:
|
||||||
DeviceCmdMsgEvent(Device* device, const Variant cmdMsg)
|
DeviceCmdMsgEvent(Device* device, const Variant cmdMsg, const timestamp_t timestamp)
|
||||||
: DeviceEvent(device, DeviceEvent::EventType::COMMAND_RECEIVED), cmdMsg(cmdMsg) {}
|
: DeviceEvent(device, DeviceEvent::EventType::COMMAND_RECEIVED, timestamp), cmdMsg(cmdMsg) {}
|
||||||
const Variant cmdMsg;
|
const Variant cmdMsg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public interface of an device object
|
* Public COSGI interface of an device object.
|
||||||
**/
|
*/
|
||||||
class Device : public virtual interface::ObservableObject<DeviceEvent> {
|
class Device
|
||||||
|
: public virtual interface::ObservableObject<DeviceEvent> {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
typedef std::chrono::system_clock::time_point timestamp_t;
|
||||||
|
|
||||||
virtual const std::string& getDeviceID() = 0;
|
virtual const std::string& getDeviceID() = 0;
|
||||||
virtual const std::string& getDeviceType() = 0;
|
virtual const std::string& getDeviceType() = 0;
|
||||||
|
|
||||||
virtual const std::string& getCmdTopic() = 0;
|
virtual const std::string& getCmdTopic() = 0;
|
||||||
virtual const std::string& getStatusTopic() = 0;
|
virtual const std::string& getStatusTopic() = 0;
|
||||||
|
|
||||||
virtual const Variant& getCurrentState() = 0;
|
virtual const Variant& getCurrentState() = 0;
|
||||||
|
virtual const timestamp_t& getCurrentStateTimestamp() = 0;
|
||||||
|
|
||||||
virtual const Variant& getLastState() = 0;
|
virtual const Variant& getLastState() = 0;
|
||||||
|
virtual const timestamp_t& getLastStateTimestamp() = 0;
|
||||||
|
|
||||||
virtual void sendCommand(const Variant cmd) = 0;
|
virtual void sendCommand(const Variant cmd) = 0;
|
||||||
virtual bool stateChanged() = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +125,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Public Interface of the device repository service
|
* Public Interface of the device repository service
|
||||||
**/
|
**/
|
||||||
class DeviceRepositoryService : public virtual interface::ObservableService<DeviceEvent> {
|
class DeviceRepositoryService
|
||||||
|
: public virtual interface::ObservableService<DeviceEvent> {
|
||||||
public:
|
public:
|
||||||
static constexpr const char* SERVICE_NAME = "hos_devicerepository_service";
|
static constexpr const char* SERVICE_NAME = "hos_devicerepository_service";
|
||||||
|
|
||||||
|
|||||||
+13
-14
@@ -32,17 +32,17 @@ DeviceImpl::DeviceImpl(DeviceRepositoryServiceImpl* parent, std::string deviceID
|
|||||||
std::cout << "GenericDeviceImpl(" << deviceID << ", " << type << ", " << statusTopic << ", " << cmdTopic << ")" << std::endl;
|
std::cout << "GenericDeviceImpl(" << deviceID << ", " << type << ", " << statusTopic << ", " << cmdTopic << ")" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceImpl::handleStatusMsg(Variant& msg) {
|
void DeviceImpl::handleStatusMsg(Variant& msg, const timestamp_t& stamp) {
|
||||||
std::lock_guard<std::mutex> lock(objMutex);
|
std::lock_guard<std::mutex> lock(objMutex);
|
||||||
lastState = currentState.Copy();
|
lastState = currentState.Copy();
|
||||||
currentState.Merge(msg);
|
currentState.Merge(msg);
|
||||||
DeviceStatusMsgEvent event(this, msg);
|
DeviceStatusMsgEvent event(this, msg, stamp);
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceImpl::handleCommandMsg(Variant& msg) {
|
void DeviceImpl::handleCommandMsg(Variant& msg, const timestamp_t& stamp) {
|
||||||
std::lock_guard<std::mutex> lock(objMutex);
|
std::lock_guard<std::mutex> lock(objMutex);
|
||||||
DeviceCmdMsgEvent event(this, msg);
|
DeviceCmdMsgEvent event(this, msg, stamp);
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +51,6 @@ void DeviceImpl::sendCommand(const Variant cmd) {
|
|||||||
parent->sendDeviceCommand(this, cmd);
|
parent->sendDeviceCommand(this, cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceImpl::stateChanged() {
|
|
||||||
return currentState == lastState;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceRepositoryServiceImpl::setCoreService(service::core::CoreService* coreService) {
|
void DeviceRepositoryServiceImpl::setCoreService(service::core::CoreService* coreService) {
|
||||||
if (coreService && !this->coreService) {
|
if (coreService && !this->coreService) {
|
||||||
this->coreService = coreService;
|
this->coreService = coreService;
|
||||||
@@ -150,7 +146,7 @@ void DeviceRepositoryServiceImpl::addDevice(std::string name, XmlRpc::XmlRpcValu
|
|||||||
subscripedTopics[dev->getCmdTopic()] = entry2;
|
subscripedTopics[dev->getCmdTopic()] = entry2;
|
||||||
|
|
||||||
// send device added event
|
// send device added event
|
||||||
DeviceAddedEvent event(dev.get());
|
DeviceAddedEvent event(dev.get(), std::chrono::system_clock::now());
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +155,7 @@ void DeviceRepositoryServiceImpl::removeDevice(std::string name) {
|
|||||||
std::lock_guard<std::recursive_mutex> lock(serviceMutex);
|
std::lock_guard<std::recursive_mutex> lock(serviceMutex);
|
||||||
auto& dev = devices[name];
|
auto& dev = devices[name];
|
||||||
if (dev) {
|
if (dev) {
|
||||||
DeviceRemovedEvent event(dev.get());
|
DeviceRemovedEvent event(dev.get(), std::chrono::system_clock::now());
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
dev->notifyObservers(&event);
|
dev->notifyObservers(&event);
|
||||||
devices.erase(name);
|
devices.erase(name);
|
||||||
@@ -205,8 +201,9 @@ void DeviceRepositoryServiceImpl::statusMsgCallback(const hos_core_msgs::DeviceS
|
|||||||
if (!sr.Error()) {
|
if (!sr.Error()) {
|
||||||
std::shared_ptr<DeviceImpl> dev = std::static_pointer_cast<DeviceImpl>(devices[msg->device_id]);
|
std::shared_ptr<DeviceImpl> dev = std::static_pointer_cast<DeviceImpl>(devices[msg->device_id]);
|
||||||
if (dev) {
|
if (dev) {
|
||||||
dev->handleStatusMsg(m);
|
auto stamp = std::chrono::system_clock::now();
|
||||||
DeviceStatusMsgEvent event(dev.get(), m);
|
dev->handleStatusMsg(m, stamp);
|
||||||
|
DeviceStatusMsgEvent event(dev.get(), m, stamp);
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -219,12 +216,14 @@ void DeviceRepositoryServiceImpl::cmdMsgCallback(const hos_core_msgs::DeviceComm
|
|||||||
std::cout << "HOSDeviceRepositoryServiceImpl::cmdMsgCallback" << std::endl;
|
std::cout << "HOSDeviceRepositoryServiceImpl::cmdMsgCallback" << std::endl;
|
||||||
Variant s = schemaLoader->GetSchema(std::string("hos:schema://").append(msg->device_type).append("/command"));
|
Variant s = schemaLoader->GetSchema(std::string("hos:schema://").append(msg->device_type).append("/command"));
|
||||||
Variant m = Deserialize(msg->msg_body, SERIALIZE_JSON);
|
Variant m = Deserialize(msg->msg_body, SERIALIZE_JSON);
|
||||||
|
auto test = msg->stamp;
|
||||||
SchemaResult sr = SchemaValidate(s, m, schemaLoader.get());
|
SchemaResult sr = SchemaValidate(s, m, schemaLoader.get());
|
||||||
if (!sr.Error()) {
|
if (!sr.Error()) {
|
||||||
std::shared_ptr<DeviceImpl> dev = std::static_pointer_cast<DeviceImpl>(devices[msg->device_id]);
|
std::shared_ptr<DeviceImpl> dev = std::static_pointer_cast<DeviceImpl>(devices[msg->device_id]);
|
||||||
if (dev != nullptr) {
|
if (dev != nullptr) {
|
||||||
dev->handleCommandMsg(m);
|
auto stamp = std::chrono::system_clock::now();
|
||||||
DeviceCmdMsgEvent event(dev.get(), m);
|
dev->handleCommandMsg(m, stamp);
|
||||||
|
DeviceCmdMsgEvent event(dev.get(), m, stamp);
|
||||||
notifyObservers(&event);
|
notifyObservers(&event);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+6
-5
@@ -62,7 +62,9 @@ private:
|
|||||||
std::string statusTopic;
|
std::string statusTopic;
|
||||||
std::string cmdTopic;
|
std::string cmdTopic;
|
||||||
Variant currentState;
|
Variant currentState;
|
||||||
|
timestamp_t currentStamp;
|
||||||
Variant lastState;
|
Variant lastState;
|
||||||
|
timestamp_t lastStamp;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DeviceImpl(DeviceRepositoryServiceImpl* parent, std::string deviceID, std::string deviceType, std::string statusTopic, std::string cmdTopic);
|
DeviceImpl(DeviceRepositoryServiceImpl* parent, std::string deviceID, std::string deviceType, std::string statusTopic, std::string cmdTopic);
|
||||||
@@ -75,17 +77,16 @@ public:
|
|||||||
virtual const std::string& getStatusTopic() override { return statusTopic; }
|
virtual const std::string& getStatusTopic() override { return statusTopic; }
|
||||||
|
|
||||||
virtual const Variant& getCurrentState() override { return currentState; }
|
virtual const Variant& getCurrentState() override { return currentState; }
|
||||||
|
virtual const timestamp_t& getCurrentStateTimestamp() { return currentStamp; }
|
||||||
virtual const Variant& getLastState() override { return lastState; }
|
virtual const Variant& getLastState() override { return lastState; }
|
||||||
|
virtual const timestamp_t& getLastStateTimestamp() { return lastStamp; }
|
||||||
|
|
||||||
virtual void sendCommand(const Variant cmd) override;
|
virtual void sendCommand(const Variant cmd) override;
|
||||||
|
|
||||||
virtual bool stateChanged() override;
|
|
||||||
|
|
||||||
/* Internal Stuff */
|
/* Internal Stuff */
|
||||||
|
|
||||||
void handleStatusMsg(Variant& msg);
|
void handleStatusMsg(Variant& msg, const timestamp_t& stamp);
|
||||||
void handleCommandMsg(Variant& msg);
|
void handleCommandMsg(Variant& msg, const timestamp_t& stamp);
|
||||||
void sendDeviceCommand(Device* device, Variant& cmd);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,6 @@ find_library(libvariant_LIBRARY Variant
|
|||||||
PATHS $ENV{HOS_PREFIX}/libvariant
|
PATHS $ENV{HOS_PREFIX}/libvariant
|
||||||
PATH_SUFFIXES lib lib64
|
PATH_SUFFIXES lib lib64
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(libvariant_INCLUDE_DIRS "${libvariant_INCLUDE_DIR}")
|
||||||
|
set(libvariant_LIBRARIES "${libvariant_LIBRARY}")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ project(hos_dummy)
|
|||||||
## is used, also find other catkin packages
|
## is used, also find other catkin packages
|
||||||
find_package(catkin REQUIRED COMPONENTS
|
find_package(catkin REQUIRED COMPONENTS
|
||||||
hos_core_msgs
|
hos_core_msgs
|
||||||
hos_python_json
|
hos_python_utils
|
||||||
rospy
|
rospy
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ catkin_python_setup()
|
|||||||
catkin_package(
|
catkin_package(
|
||||||
# INCLUDE_DIRS include
|
# INCLUDE_DIRS include
|
||||||
# LIBRARIES hos_dummy
|
# LIBRARIES hos_dummy
|
||||||
CATKIN_DEPENDS hos_core_msgs hos_python_json rospy
|
CATKIN_DEPENDS hos_core_msgs hos_python_utils rospy
|
||||||
# DEPENDS system_lib
|
# DEPENDS system_lib
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -122,6 +122,7 @@ include_directories(
|
|||||||
#And this will install python nodes
|
#And this will install python nodes
|
||||||
catkin_install_python(PROGRAMS
|
catkin_install_python(PROGRAMS
|
||||||
nodes/hos_dummy_devices.py
|
nodes/hos_dummy_devices.py
|
||||||
|
nodes/hos_dummy_controller.py
|
||||||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
|
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION})
|
||||||
|
|
||||||
# all install targets should use catkin DESTINATION variables
|
# all install targets should use catkin DESTINATION variables
|
||||||
|
|||||||
@@ -41,10 +41,10 @@
|
|||||||
<!-- <test_depend>gtest</test_depend> -->
|
<!-- <test_depend>gtest</test_depend> -->
|
||||||
<buildtool_depend>catkin</buildtool_depend>
|
<buildtool_depend>catkin</buildtool_depend>
|
||||||
<build_depend>hos_core_msgs</build_depend>
|
<build_depend>hos_core_msgs</build_depend>
|
||||||
<build_depend>hos_python_json</build_depend>
|
<build_depend>hos_python_utils</build_depend>
|
||||||
<build_depend>rospy</build_depend>
|
<build_depend>rospy</build_depend>
|
||||||
<run_depend>hos_core_msgs</run_depend>
|
<run_depend>hos_core_msgs</run_depend>
|
||||||
<run_depend>hos_python_json</run_depend>
|
<run_depend>hos_python_utils</run_depend>
|
||||||
<run_depend>rospy</run_depend>
|
<run_depend>rospy</run_depend>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from hos_pyhue import pyhue
|
|||||||
from hos_core_msgs.msg import DeviceCommand
|
from hos_core_msgs.msg import DeviceCommand
|
||||||
from hos_core_msgs.msg import DeviceStatusUpdate
|
from hos_core_msgs.msg import DeviceStatusUpdate
|
||||||
from hos_core_msgs.msg import HosStatusUpdate
|
from hos_core_msgs.msg import HosStatusUpdate
|
||||||
from jsonspec_hos.reference.providers import HOSParamServerProvider
|
from hos.utils.schemaloader import SchemaLoader
|
||||||
from jsonspec.validators import load
|
from jsonspec.validators import load
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class HosDummyController(tk.Frame):
|
|||||||
rospy.set_param(key_base + "/cmd_topic", self.cmd_topic)
|
rospy.set_param(key_base + "/cmd_topic", self.cmd_topic)
|
||||||
print "Added dummy dimmer \"" + name + "\""
|
print "Added dummy dimmer \"" + name + "\""
|
||||||
# Create JSON schema provider
|
# Create JSON schema provider
|
||||||
self.schemaProvider = HOSParamServerProvider()
|
self.schemaProvider = SchemaLoader()
|
||||||
# Create message publisher
|
# Create message publisher
|
||||||
self.commandPublisher = rospy.Publisher(self.cmd_topic, DeviceCommand, queue_size=10)
|
self.commandPublisher = rospy.Publisher(self.cmd_topic, DeviceCommand, queue_size=10)
|
||||||
# Init GUI
|
# Init GUI
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from hos_pyhue import pyhue
|
|||||||
from hos_core_msgs.msg import DeviceCommand
|
from hos_core_msgs.msg import DeviceCommand
|
||||||
from hos_core_msgs.msg import DeviceStatusUpdate
|
from hos_core_msgs.msg import DeviceStatusUpdate
|
||||||
from hos_core_msgs.msg import HosStatusUpdate
|
from hos_core_msgs.msg import HosStatusUpdate
|
||||||
from jsonspec_hos.reference.providers import HOSParamServerProvider
|
from hos.utils.schemaloader import SchemaLoader
|
||||||
from jsonspec.validators import load
|
from jsonspec.validators import load
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class HosDummyDevices(tk.Frame):
|
|||||||
print "Added dummy dimmer \"" + name + "\""
|
print "Added dummy dimmer \"" + name + "\""
|
||||||
new_devices.append(name)
|
new_devices.append(name)
|
||||||
# Create JSON schema provider
|
# Create JSON schema provider
|
||||||
self.schemaProvider = HOSParamServerProvider()
|
self.schemaProvider = SchemaLoader()
|
||||||
# Create message publisher
|
# Create message publisher
|
||||||
self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10)
|
self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10)
|
||||||
self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10)
|
self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ project(hos_philips_hue)
|
|||||||
find_package(catkin REQUIRED COMPONENTS
|
find_package(catkin REQUIRED COMPONENTS
|
||||||
hos_core_msgs
|
hos_core_msgs
|
||||||
rospy
|
rospy
|
||||||
hos_python_json
|
hos_python_utils
|
||||||
)
|
)
|
||||||
|
|
||||||
## System dependencies are found with CMake's conventions
|
## System dependencies are found with CMake's conventions
|
||||||
@@ -82,7 +82,7 @@ catkin_python_setup()
|
|||||||
catkin_package(
|
catkin_package(
|
||||||
# INCLUDE_DIRS include
|
# INCLUDE_DIRS include
|
||||||
# LIBRARIES hos_philips_hue
|
# LIBRARIES hos_philips_hue
|
||||||
CATKIN_DEPENDS hos_core_msgs rospy hos_python_json
|
CATKIN_DEPENDS hos_core_msgs rospy hos_python_utils
|
||||||
# DEPENDS system_lib
|
# DEPENDS system_lib
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import rospy
|
|||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
from hos_core_msgs.msg import DeviceCommand
|
from hos_core_msgs.msg import DeviceCommand
|
||||||
from jsonspec_hos.reference.providers import HOSParamServerProvider
|
from hos.utils.schemaloader import SchemaLoader
|
||||||
from jsonspec.validators import load
|
from jsonspec.validators import load
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ if __name__ == '__main__':
|
|||||||
rospy.init_node('test', argv=sys.argv, anonymous=True)
|
rospy.init_node('test', argv=sys.argv, anonymous=True)
|
||||||
cmdPublisher = rospy.Publisher("/hos/devices/cmd", DeviceCommand, queue_size=10)
|
cmdPublisher = rospy.Publisher("/hos/devices/cmd", DeviceCommand, queue_size=10)
|
||||||
|
|
||||||
schemaProvider = HOSParamServerProvider()
|
schemaProvider = SchemaLoader()
|
||||||
stamp = rospy.get_rostime()
|
stamp = rospy.get_rostime()
|
||||||
|
|
||||||
msg = DeviceCommand()
|
msg = DeviceCommand()
|
||||||
|
|||||||
@@ -42,10 +42,10 @@
|
|||||||
<buildtool_depend>catkin</buildtool_depend>
|
<buildtool_depend>catkin</buildtool_depend>
|
||||||
<build_depend>hos_core_msgs</build_depend>
|
<build_depend>hos_core_msgs</build_depend>
|
||||||
<build_depend>rospy</build_depend>
|
<build_depend>rospy</build_depend>
|
||||||
<build_depend>hos_python_json</build_depend>
|
<build_depend>hos_python_utils</build_depend>
|
||||||
<run_depend>hos_core_msgs</run_depend>
|
<run_depend>hos_core_msgs</run_depend>
|
||||||
<run_depend>rospy</run_depend>
|
<run_depend>rospy</run_depend>
|
||||||
<run_depend>hos_python_json</run_depend>
|
<run_depend>hos_python_utils</run_depend>
|
||||||
|
|
||||||
|
|
||||||
<!-- The export tag contains other, unspecified, tags -->
|
<!-- The export tag contains other, unspecified, tags -->
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from hos_pyhue import pyhue
|
|||||||
from hos_core_msgs.msg import DeviceCommand
|
from hos_core_msgs.msg import DeviceCommand
|
||||||
from hos_core_msgs.msg import DeviceStatusUpdate
|
from hos_core_msgs.msg import DeviceStatusUpdate
|
||||||
from hos_core_msgs.msg import HosStatusUpdate
|
from hos_core_msgs.msg import HosStatusUpdate
|
||||||
from jsonspec_hos.reference.providers import HOSParamServerProvider
|
from hos.utils.schemaloader import SchemaLoader
|
||||||
from jsonspec.validators import load
|
from jsonspec.validators import load
|
||||||
from curses.has_key import has_key
|
from curses.has_key import has_key
|
||||||
from time import sleep
|
from time import sleep
|
||||||
@@ -55,7 +55,7 @@ class HueController(object):
|
|||||||
rospy.set_param(key_base + "/status_topic", self.status_topic)
|
rospy.set_param(key_base + "/status_topic", self.status_topic)
|
||||||
rospy.set_param(key_base + "/cmd_topic", self.cmd_topic)
|
rospy.set_param(key_base + "/cmd_topic", self.cmd_topic)
|
||||||
# Create JSON schema provider
|
# Create JSON schema provider
|
||||||
self.schemaProvider = HOSParamServerProvider()
|
self.schemaProvider = SchemaLoader()
|
||||||
# Create message publisher
|
# Create message publisher
|
||||||
self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10)
|
self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10)
|
||||||
self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10)
|
self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec
|
|
||||||
~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
@@ -1,508 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.cli
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
import sys
|
|
||||||
import pkg_resources
|
|
||||||
from functools import wraps
|
|
||||||
from jsonspec import driver
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
try:
|
|
||||||
from termcolor import colored
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
def colored(string, *args, **kwargs):
|
|
||||||
return string
|
|
||||||
|
|
||||||
|
|
||||||
def disable_logging(func):
|
|
||||||
return func
|
|
||||||
"""
|
|
||||||
Temporary disable logging.
|
|
||||||
"""
|
|
||||||
handler = logging.NullHandler()
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
logger = logging.getLogger()
|
|
||||||
logger.addHandler(handler)
|
|
||||||
resp = func(*args, **kwargs)
|
|
||||||
logger.removeHandler(handler)
|
|
||||||
return resp
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def format_output(func):
|
|
||||||
return func
|
|
||||||
"""
|
|
||||||
Format output.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(func)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
response = func(*args, **kwargs)
|
|
||||||
except Exception as error:
|
|
||||||
print(colored(error, 'red'), file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print(response)
|
|
||||||
sys.exit(0)
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def JSONStruct(string):
|
|
||||||
return driver.loads(string)
|
|
||||||
|
|
||||||
|
|
||||||
class JSONFile(argparse.FileType):
|
|
||||||
def __call__(self, string):
|
|
||||||
file = super(JSONFile, self).__call__(string)
|
|
||||||
return driver.load(file)
|
|
||||||
|
|
||||||
|
|
||||||
def document_arguments(parser):
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument('--document-json', type=JSONStruct, help='json structure', dest='document_json', metavar='<doc>')
|
|
||||||
group.add_argument('--document-file', type=JSONFile('r'), help='json filename', dest='document_file', metavar='<doc>')
|
|
||||||
|
|
||||||
|
|
||||||
def schema_arguments(parser):
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument('--schema-json', type=JSONStruct, help='json structure', dest='schema_json', metavar='<schema>')
|
|
||||||
group.add_argument('--schema-file', type=JSONFile('r'), help='json filename', dest='schema_file', metavar='<schema>')
|
|
||||||
|
|
||||||
|
|
||||||
def fragment_arguments(parser):
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument('--fragment-json', type=JSONStruct, help='json structure', dest='fragment_json', metavar='<fragment>')
|
|
||||||
group.add_argument('--fragment-file', type=JSONFile('r'), help='json filename', dest='fragment_file', metavar='<fragment>')
|
|
||||||
|
|
||||||
|
|
||||||
def indentation_arguments(parser):
|
|
||||||
parser.add_argument('--indent', type=int, help='return an indented json', metavar='<indentation>')
|
|
||||||
|
|
||||||
|
|
||||||
def pointer_arguments(parser):
|
|
||||||
parser.add_argument('pointer', type=str, help='json pointer', metavar='<pointer>')
|
|
||||||
|
|
||||||
|
|
||||||
def target_arguments(parser):
|
|
||||||
parser.add_argument('-t', '--target-pointer', help='target pointer', dest='target', metavar='<target>')
|
|
||||||
|
|
||||||
|
|
||||||
def parse_document(args):
|
|
||||||
document = None
|
|
||||||
if args.document_json:
|
|
||||||
document = args.document_json
|
|
||||||
elif args.document_file:
|
|
||||||
document = args.document_file
|
|
||||||
else:
|
|
||||||
mode = os.fstat(0).st_mode
|
|
||||||
if stat.S_ISFIFO(mode):
|
|
||||||
# cat doc.json | cmd
|
|
||||||
document = driver.load(sys.stdin)
|
|
||||||
elif stat.S_ISREG(mode):
|
|
||||||
# cmd < doc.json
|
|
||||||
document = driver.load(sys.stdin)
|
|
||||||
|
|
||||||
setattr(args, 'document', document)
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def parse_fragment(args):
|
|
||||||
setattr(args, 'fragment', args.fragment_json or args.fragment_file)
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def parse_pointer(args):
|
|
||||||
target = args.pointer
|
|
||||||
if target.startswith('#'):
|
|
||||||
target = target[1:]
|
|
||||||
setattr(args, 'pointer', target)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_schema(args):
|
|
||||||
setattr(args, 'schema', args.schema_json or args.schema_file)
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def parse_target(args):
|
|
||||||
target = args.target
|
|
||||||
if target.startswith('#'):
|
|
||||||
target = target[1:]
|
|
||||||
setattr(args, 'target', target)
|
|
||||||
if not target:
|
|
||||||
raise ValueError('target is required')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(object):
|
|
||||||
help = None
|
|
||||||
description = None
|
|
||||||
epilog = None
|
|
||||||
|
|
||||||
def __init__(self, parser=None):
|
|
||||||
self.parser = parser or argparse.ArgumentParser(description=self.description,
|
|
||||||
epilog=self.epilog)
|
|
||||||
self.parser.set_defaults(func=self)
|
|
||||||
self.arguments(self.parser)
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
return
|
|
||||||
|
|
||||||
def run(self, args=None):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def parse_args(self, args):
|
|
||||||
return self.parser.parse_args(args)
|
|
||||||
|
|
||||||
@disable_logging
|
|
||||||
@format_output
|
|
||||||
def __call__(self, args=None):
|
|
||||||
return self.run(args)
|
|
||||||
|
|
||||||
|
|
||||||
class AddCommand(Command):
|
|
||||||
"""Add a fragment to a json document.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1' --fragment-file=fragment.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'add fragment to a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
fragment_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
parse_fragment(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import add, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = add(args.document, args.pointer, args.fragment)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except Error as error:
|
|
||||||
raise Exception(error)
|
|
||||||
except ParseError as error:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class CheckCommand(Command):
|
|
||||||
"""Tests that a value at the target location is equal to a specified value.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1' --fragment-file=fragment.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'check member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
fragment_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
parse_fragment(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import check, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
if check(args.document, args.pointer, args.fragment):
|
|
||||||
return 'It validates'
|
|
||||||
else:
|
|
||||||
raise Exception('It does not validate')
|
|
||||||
except Error as error:
|
|
||||||
raise Exception('It does not validate')
|
|
||||||
except ParseError as error:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class CopyCommand(Command):
|
|
||||||
"""Copies the value at a specified location to the target location.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/1' --target='#/foo/2' --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1' --target='#/foo/2'
|
|
||||||
%(prog)s '#/foo/1' --target='#/foo/2' --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' --target='#/foo/2' < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'copy a member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
target_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
parse_target(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import copy, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = copy(args.document, args.target, args.pointer)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except Error as error:
|
|
||||||
raise Exception(error)
|
|
||||||
except ParseError as error:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractCommand(Command):
|
|
||||||
"""Extract a fragment from a json document.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/1' --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1'
|
|
||||||
%(prog)s '#/foo/1' --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'extract a member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
|
|
||||||
from jsonspec.pointer import extract
|
|
||||||
from jsonspec.pointer import ExtractError, ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = extract(args.document, args.pointer)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except ExtractError:
|
|
||||||
raise Exception(args)
|
|
||||||
raise Exception('{} does not match'.format(args.pointer))
|
|
||||||
except ParseError:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class MoveCommand(Command):
|
|
||||||
"""Removes the value at a specified location and adds it to the target location.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/2' --target='#/foo/1' --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/2' --target='#/foo/1'
|
|
||||||
%(prog)s '#/foo/2' --target='#/foo/1' --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/2' --target='#/foo/1' < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'move a member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
target_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_target(args)
|
|
||||||
parse_document(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import move, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = move(args.document, args.target, args.pointer)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except Error as error:
|
|
||||||
raise Exception(error)
|
|
||||||
except ParseError as error:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveCommand(Command):
|
|
||||||
"""Replace the value of pointer.
|
|
||||||
|
|
||||||
examples:
|
|
||||||
%(prog)s '#/foo/1' --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1'
|
|
||||||
%(prog)s '#/foo/1' --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' < doc.json
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'remove a member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import remove, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = remove(args.document, args.pointer)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except Error:
|
|
||||||
raise Exception('{} does not match'.format(args.pointer))
|
|
||||||
except ParseError:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class ReplaceCommand(Command):
|
|
||||||
"""Replace a fragment to a json document.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s '#/foo/1' --fragment-file=fragment.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json --document-file=doc.json
|
|
||||||
%(prog)s '#/foo/1' --fragment-file=fragment.json < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'replace a member of a document'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
pointer_arguments(parser)
|
|
||||||
document_arguments(parser)
|
|
||||||
fragment_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_pointer(args)
|
|
||||||
parse_document(args)
|
|
||||||
parse_fragment(args)
|
|
||||||
|
|
||||||
from jsonspec.operations import replace, Error
|
|
||||||
from jsonspec.pointer import ParseError
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = replace(args.document, args.pointer, args.fragment)
|
|
||||||
return driver.dumps(response, indent=args.indent)
|
|
||||||
except Error as error:
|
|
||||||
raise Exception(error)
|
|
||||||
except ParseError as error:
|
|
||||||
raise Exception('{} is not a valid pointer'.format(args.pointer))
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateCommand(Command):
|
|
||||||
"""Validate document against a schema.
|
|
||||||
|
|
||||||
examples::
|
|
||||||
|
|
||||||
%(prog)s --schema-file=schema.json --document-json='{"foo": ["bar", "baz"]}'
|
|
||||||
echo '{"foo": ["bar", "baz"]}' | %(prog)s --schema-file=schema.json
|
|
||||||
%(prog)s --schema-file=schema.json --document-file=doc.json
|
|
||||||
%(prog)s --schema-file=schema.json < doc.json
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = 'validate a document against a schema'
|
|
||||||
|
|
||||||
def arguments(self, parser):
|
|
||||||
document_arguments(parser)
|
|
||||||
schema_arguments(parser)
|
|
||||||
indentation_arguments(parser)
|
|
||||||
|
|
||||||
def run(self, args):
|
|
||||||
parse_document(args)
|
|
||||||
parse_schema(args)
|
|
||||||
|
|
||||||
from jsonspec.validators import load
|
|
||||||
from jsonspec.validators import ValidationError
|
|
||||||
|
|
||||||
try:
|
|
||||||
validated = load(args.schema).validate(args.document)
|
|
||||||
return driver.dumps(validated, indent=args.indent)
|
|
||||||
except ValidationError as error:
|
|
||||||
msg = 'document does not validate with schema.\n\n'
|
|
||||||
for pointer, reasons in error.flatten.items():
|
|
||||||
msg += ' {}\n'.format(pointer)
|
|
||||||
for reason in reasons:
|
|
||||||
msg += ' - reason {}\n'.format(reason)
|
|
||||||
msg += '\n'
|
|
||||||
raise Exception(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def get_parser():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(help='choose one of these actions',
|
|
||||||
dest='action',
|
|
||||||
metavar='<action>')
|
|
||||||
subparsers.required = True
|
|
||||||
cmds = []
|
|
||||||
for entrypoint in pkg_resources.iter_entry_points('jsonspec.cli.commands'):
|
|
||||||
logging.debug('loaded %s from %s', entrypoint, entrypoint.dist)
|
|
||||||
cmds.append((entrypoint.name, entrypoint.load()))
|
|
||||||
|
|
||||||
for name, command_class in sorted(cmds):
|
|
||||||
description, help, epilog = None, None, None
|
|
||||||
if command_class.__doc__:
|
|
||||||
description, _, epilog = command_class.__doc__.lstrip().partition('\n\n')
|
|
||||||
|
|
||||||
if description:
|
|
||||||
description = description.replace('\n', ' ')
|
|
||||||
|
|
||||||
if epilog:
|
|
||||||
epilog = dedent(epilog).replace(' ', ' ').replace('::\n\n', ':\n')
|
|
||||||
description = command_class.description or description
|
|
||||||
epilog = command_class.epilog or epilog
|
|
||||||
help = command_class.help or description
|
|
||||||
subparser = subparsers.add_parser(name,
|
|
||||||
help=help,
|
|
||||||
description=description,
|
|
||||||
epilog=epilog,
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
|
|
||||||
command = command_class(subparser)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig()
|
|
||||||
|
|
||||||
parser = get_parser()
|
|
||||||
args = parser.parse_args()
|
|
||||||
args.func(args)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.driver
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from simplejson import * # noqa
|
|
||||||
except ImportError:
|
|
||||||
from json import * # noqa
|
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
_load = load
|
|
||||||
_loads = loads
|
|
||||||
|
|
||||||
@wraps(_load)
|
|
||||||
def load(fp, *args, **kwargs):
|
|
||||||
return _load(fp, *args, **kwargs)
|
|
||||||
load.original = _load
|
|
||||||
|
|
||||||
|
|
||||||
@wraps(_loads)
|
|
||||||
def loads(s, *args, **kwargs):
|
|
||||||
# if not encoding:
|
|
||||||
# try:
|
|
||||||
# encoding = 'utf-16'
|
|
||||||
# return _loads(s, encoding, *args, **kwargs)
|
|
||||||
# except UnicodeDecodeError:
|
|
||||||
# encoding = 'utf-8'
|
|
||||||
# return _loads(s, encoding, *args, **kwargs)
|
|
||||||
return _loads(s, *args, **kwargs)
|
|
||||||
loads.original = _loads
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "A representation of an event",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"dtstart": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "Event starting time",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"dtend": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "Event ending time"
|
|
||||||
},
|
|
||||||
"summary": { "type": "string", "required": true },
|
|
||||||
"location": { "type": "string" },
|
|
||||||
"url": { "type": "string", "format": "uri" },
|
|
||||||
"duration": {
|
|
||||||
"format": "time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "Event duration"
|
|
||||||
},
|
|
||||||
"rdate": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": "string",
|
|
||||||
"description": "Recurrence date"
|
|
||||||
},
|
|
||||||
"rrule": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Recurrence rule"
|
|
||||||
},
|
|
||||||
"category": { "type": "string" },
|
|
||||||
"description": { "type": "string" },
|
|
||||||
"geo": { "$ref": "http: //json-schema.org/geo" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
|
||||||
"description": "A representation of a person, company, organization, or place",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"fn": {
|
|
||||||
"description": "Formatted Name",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"familyName": { "type": "string", "required": true },
|
|
||||||
"givenName": { "type": "string", "required": true },
|
|
||||||
"additionalName": { "type": "array", "items": { "type": "string" } },
|
|
||||||
"honorificPrefix": { "type": "array", "items": { "type": "string" } },
|
|
||||||
"honorificSuffix": { "type": "array", "items": { "type": "string" } },
|
|
||||||
"nickname": { "type": "string" },
|
|
||||||
"url": { "type": "string", "format": "uri" },
|
|
||||||
"email": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": { "type": "string" },
|
|
||||||
"value": { "type": "string", "format": "email" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tel": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": { "type": "string" },
|
|
||||||
"value": { "type": "string", "format": "phone" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"adr": { "$ref": "http://json-schema.org/address" },
|
|
||||||
"geo": { "$ref": "http://json-schema.org/geo" },
|
|
||||||
"tz": { "type": "string" },
|
|
||||||
"photo": { "type": "string" },
|
|
||||||
"logo": { "type": "string" },
|
|
||||||
"sound": { "type": "string" },
|
|
||||||
"bday": { "type": "string", "format": "date" },
|
|
||||||
"title": { "type": "string" },
|
|
||||||
"role": { "type": "string" },
|
|
||||||
"org": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"organizationName": { "type": "string" },
|
|
||||||
"organizationUnit": { "type": "string" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-03/hyper-schema#",
|
|
||||||
"extends": {"$ref": "http://json-schema.org/draft-03/schema#"},
|
|
||||||
"id": "http://json-schema.org/draft-03/hyper-schema#",
|
|
||||||
|
|
||||||
"properties": {
|
|
||||||
"links": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"$ref": "http://json-schema.org/draft-03/links#"}
|
|
||||||
},
|
|
||||||
"fragmentResolution": {
|
|
||||||
"type": "string",
|
|
||||||
"default": "slash-delimited"
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"readonly": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"contentEncoding": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pathStart": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"mediaType": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "media-type"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "{id}",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$ref}",
|
|
||||||
"rel": "full"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$schema}",
|
|
||||||
"rel": "describedby"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fragmentResolution": "slash-delimited"
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-03/hyper-schema#",
|
|
||||||
"id": "http://json-schema.org/draft-03/json-ref#",
|
|
||||||
"additionalItems": {"$ref": "#"},
|
|
||||||
"additionalProperties": {"$ref": "#"},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "{id}",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$ref}",
|
|
||||||
"rel": "full"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$schema}",
|
|
||||||
"rel": "describedby"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fragmentResolution": "dot-delimited"
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-03/hyper-schema#",
|
|
||||||
"id": "http://json-schema.org/draft-03/json-ref#",
|
|
||||||
"additionalItems": {"$ref": "#"},
|
|
||||||
"additionalProperties": {"$ref": "#"},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"href": "{id}",
|
|
||||||
"rel": "self"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$ref}",
|
|
||||||
"rel": "full"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"href": "{$schema}",
|
|
||||||
"rel": "describedby"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fragmentResolution": "dot-delimited"
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
|
||||||
"id": "http://json-schema.org/draft-03/schema#",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"type": ["string", "array"],
|
|
||||||
"items": {
|
|
||||||
"type": ["string", {"$ref": "#"}]
|
|
||||||
},
|
|
||||||
"uniqueItems": true,
|
|
||||||
"default": "any"
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {"$ref": "#"},
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"patternProperties": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {"$ref": "#"},
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": [{"$ref": "#"}, "boolean"],
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"items": {
|
|
||||||
"type": [{"$ref": "#"}, "array"],
|
|
||||||
"items": {"$ref": "#"},
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"additionalItems": {
|
|
||||||
"type": [{"$ref": "#"}, "boolean"],
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"required": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": ["string", "array", {"$ref": "#"}],
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"minimum": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"maximum": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"exclusiveMinimum": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"exclusiveMaximum": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"minItems": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"maxItems": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
},
|
|
||||||
"uniqueItems": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"pattern": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "regex"
|
|
||||||
},
|
|
||||||
"minLength": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0,
|
|
||||||
"default": 0
|
|
||||||
},
|
|
||||||
"maxLength": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"enum": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 1,
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"type": "any"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"format": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"divisibleBy": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"exclusiveMinimum": true
|
|
||||||
},
|
|
||||||
"disallow": {
|
|
||||||
"type": ["string", "array"],
|
|
||||||
"items": {
|
|
||||||
"type": ["string", {"$ref": "#"}]
|
|
||||||
},
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
"extends": {
|
|
||||||
"type": [{"$ref": "#"}, "array"],
|
|
||||||
"items": {"$ref": "#"},
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"$ref": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"$schema": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"exclusiveMinimum": "minimum",
|
|
||||||
"exclusiveMaximum": "maximum"
|
|
||||||
},
|
|
||||||
"default": {}
|
|
||||||
}
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-04/hyper-schema#",
|
|
||||||
"id": "http://json-schema.org/draft-04/hyper-schema#",
|
|
||||||
"title": "JSON Hyper-Schema",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "http://json-schema.org/draft-04/schema#"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"additionalItems": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"additionalProperties": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"items": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/schemaArray"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"patternProperties": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"allOf": {
|
|
||||||
"$ref": "#/definitions/schemaArray"
|
|
||||||
},
|
|
||||||
"anyOf": {
|
|
||||||
"$ref": "#/definitions/schemaArray"
|
|
||||||
},
|
|
||||||
"oneOf": {
|
|
||||||
"$ref": "#/definitions/schemaArray"
|
|
||||||
},
|
|
||||||
"not": {
|
|
||||||
"$ref": "#"
|
|
||||||
},
|
|
||||||
|
|
||||||
"links": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/linkDescription"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fragmentResolution": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"media": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"type": {
|
|
||||||
"description": "A media type, as described in RFC 2046",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"binaryEncoding": {
|
|
||||||
"description": "A content encoding scheme, as described in RFC 2045",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pathStart": {
|
|
||||||
"description": "Instances' URIs must start with this value for this schema to apply to them",
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"schemaArray": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linkDescription": {
|
|
||||||
"title": "Link Description Object",
|
|
||||||
"type": "object",
|
|
||||||
"required": [ "href", "rel" ],
|
|
||||||
"properties": {
|
|
||||||
"href": {
|
|
||||||
"description": "a URI template, as defined by RFC 6570, with the addition of the $, ( and ) characters for pre-processing",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"rel": {
|
|
||||||
"description": "relation to the target resource of the link",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"description": "a title for the link",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"targetSchema": {
|
|
||||||
"description": "JSON Schema describing the link target",
|
|
||||||
"$ref": "#"
|
|
||||||
},
|
|
||||||
"mediaType": {
|
|
||||||
"description": "media type (as defined by RFC 2046) describing the link target",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"method": {
|
|
||||||
"description": "method for requesting the target of the link (e.g. for HTTP this might be \"GET\" or \"DELETE\")",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"encType": {
|
|
||||||
"description": "The media type in which to submit data along with the request",
|
|
||||||
"type": "string",
|
|
||||||
"default": "application/json"
|
|
||||||
},
|
|
||||||
"schema": {
|
|
||||||
"description": "Schema describing the data to submit along with the request",
|
|
||||||
"$ref": "#"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"rel": "self",
|
|
||||||
"href": "{+id}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rel": "full",
|
|
||||||
"href": "{+($ref)}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"description": "Core schema meta-schema",
|
|
||||||
"definitions": {
|
|
||||||
"schemaArray": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 1,
|
|
||||||
"items": { "$ref": "#" }
|
|
||||||
},
|
|
||||||
"positiveInteger": {
|
|
||||||
"type": "integer",
|
|
||||||
"minimum": 0
|
|
||||||
},
|
|
||||||
"positiveIntegerDefault0": {
|
|
||||||
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
|
|
||||||
},
|
|
||||||
"simpleTypes": {
|
|
||||||
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
|
|
||||||
},
|
|
||||||
"stringArray": {
|
|
||||||
"type": "array",
|
|
||||||
"items": { "type": "string" },
|
|
||||||
"minItems": 1,
|
|
||||||
"uniqueItems": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"$schema": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "uri"
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"default": {},
|
|
||||||
"multipleOf": {
|
|
||||||
"type": "number",
|
|
||||||
"minimum": 0,
|
|
||||||
"exclusiveMinimum": true
|
|
||||||
},
|
|
||||||
"maximum": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"exclusiveMaximum": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"minimum": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"exclusiveMinimum": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"maxLength": { "$ref": "#/definitions/positiveInteger" },
|
|
||||||
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
|
||||||
"pattern": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "regex"
|
|
||||||
},
|
|
||||||
"additionalItems": {
|
|
||||||
"anyOf": [
|
|
||||||
{ "type": "boolean" },
|
|
||||||
{ "$ref": "#" }
|
|
||||||
],
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"items": {
|
|
||||||
"anyOf": [
|
|
||||||
{ "$ref": "#" },
|
|
||||||
{ "$ref": "#/definitions/schemaArray" }
|
|
||||||
],
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"maxItems": { "$ref": "#/definitions/positiveInteger" },
|
|
||||||
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
|
||||||
"uniqueItems": {
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false
|
|
||||||
},
|
|
||||||
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
|
|
||||||
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
|
|
||||||
"required": { "$ref": "#/definitions/stringArray" },
|
|
||||||
"additionalProperties": {
|
|
||||||
"anyOf": [
|
|
||||||
{ "type": "boolean" },
|
|
||||||
{ "$ref": "#" }
|
|
||||||
],
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": { "$ref": "#" },
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": { "$ref": "#" },
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"patternProperties": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": { "$ref": "#" },
|
|
||||||
"default": {}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"anyOf": [
|
|
||||||
{ "$ref": "#" },
|
|
||||||
{ "$ref": "#/definitions/stringArray" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enum": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 1,
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"anyOf": [
|
|
||||||
{ "$ref": "#/definitions/simpleTypes" },
|
|
||||||
{
|
|
||||||
"type": "array",
|
|
||||||
"items": { "$ref": "#/definitions/simpleTypes" },
|
|
||||||
"minItems": 1,
|
|
||||||
"uniqueItems": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"allOf": { "$ref": "#/definitions/schemaArray" },
|
|
||||||
"anyOf": { "$ref": "#/definitions/schemaArray" },
|
|
||||||
"oneOf": { "$ref": "#/definitions/schemaArray" },
|
|
||||||
"not": { "$ref": "#" }
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"exclusiveMaximum": [ "maximum" ],
|
|
||||||
"exclusiveMinimum": [ "minimum" ]
|
|
||||||
},
|
|
||||||
"default": {}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "A geographical coordinate",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"latitude": { "type": "number" },
|
|
||||||
"longitude": { "type": "number" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.operations
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['check', 'remove', 'add', 'replace', 'move', 'copy',
|
|
||||||
'Error', 'NonexistentTarget', 'Target']
|
|
||||||
|
|
||||||
from .exceptions import Error, NonexistentTarget
|
|
||||||
from .bases import Target
|
|
||||||
|
|
||||||
|
|
||||||
def check(doc, pointer, expected, raise_onerror=False):
|
|
||||||
"""Check if value exists into object.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:param expected: the expected value
|
|
||||||
:param raise_onerror: should raise on error?
|
|
||||||
:return: boolean
|
|
||||||
"""
|
|
||||||
return Target(doc).check(pointer, expected, raise_onerror)
|
|
||||||
|
|
||||||
|
|
||||||
def remove(doc, pointer):
|
|
||||||
"""Remove element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:return: the new object
|
|
||||||
"""
|
|
||||||
|
|
||||||
return Target(doc).remove(pointer).document
|
|
||||||
|
|
||||||
|
|
||||||
def add(doc, pointer, value):
|
|
||||||
"""Add element to sequence, member to mapping.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param pointer: the path to add in it
|
|
||||||
:param value: the new value
|
|
||||||
:return: the new object
|
|
||||||
"""
|
|
||||||
return Target(doc).add(pointer, value).document
|
|
||||||
|
|
||||||
|
|
||||||
def replace(doc, pointer, value):
|
|
||||||
"""Replace element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:param value: the new value
|
|
||||||
:return: the new object
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This operation is functionally identical to a "remove" operation for
|
|
||||||
a value, followed immediately by an "add" operation at the same
|
|
||||||
location with the replacement value.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return Target(doc).replace(pointer, value).document
|
|
||||||
|
|
||||||
|
|
||||||
def move(doc, dest, src):
|
|
||||||
"""Move element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param dest: the destination
|
|
||||||
:type dest: Pointer
|
|
||||||
:param src: the source
|
|
||||||
:type src: Pointer
|
|
||||||
:return: the new object
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
it delete then it add to the new location
|
|
||||||
soo the dest must refer to the middle object.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
return Target(doc).move(dest, src).document
|
|
||||||
|
|
||||||
|
|
||||||
def copy(doc, dest, src):
|
|
||||||
"""Copy element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param doc: the document base
|
|
||||||
:param dest: the destination
|
|
||||||
:type dest: Pointer
|
|
||||||
:param src: the source
|
|
||||||
:type src: Pointer
|
|
||||||
:return: the new object
|
|
||||||
"""
|
|
||||||
|
|
||||||
return Target(doc).copy(dest, src).document
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.operations.bases
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['Target']
|
|
||||||
|
|
||||||
from copy import deepcopy
|
|
||||||
import logging
|
|
||||||
from jsonspec.pointer import Pointer
|
|
||||||
from jsonspec.pointer import ExtractError, OutOfBounds, OutOfRange, LastElement
|
|
||||||
from .exceptions import Error, NonexistentTarget
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Target(object):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:ivar document: the document base
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, document):
|
|
||||||
self.document = document
|
|
||||||
|
|
||||||
def check(self, pointer, expected, raise_onerror=False):
|
|
||||||
"""Check if value exists into object.
|
|
||||||
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:param expected: the expected value
|
|
||||||
:param raise_onerror: should raise on error?
|
|
||||||
:return: boolean
|
|
||||||
"""
|
|
||||||
obj = self.document
|
|
||||||
for token in Pointer(pointer):
|
|
||||||
try:
|
|
||||||
obj = token.extract(obj, bypass_ref=True)
|
|
||||||
except ExtractError as error:
|
|
||||||
if raise_onerror:
|
|
||||||
raise Error(*error.args)
|
|
||||||
logger.exception(error)
|
|
||||||
return False
|
|
||||||
return obj == expected
|
|
||||||
|
|
||||||
def remove(self, pointer):
|
|
||||||
"""Remove element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:return: resolved document
|
|
||||||
:rtype: Target
|
|
||||||
"""
|
|
||||||
doc = deepcopy(self.document)
|
|
||||||
parent, obj = None, doc
|
|
||||||
try:
|
|
||||||
# fetching
|
|
||||||
for token in Pointer(pointer):
|
|
||||||
parent, obj = obj, token.extract(obj, bypass_ref=True)
|
|
||||||
|
|
||||||
# removing
|
|
||||||
if isinstance(parent, dict):
|
|
||||||
del parent[token]
|
|
||||||
|
|
||||||
if isinstance(parent, list):
|
|
||||||
parent.pop(int(token))
|
|
||||||
except Exception as error:
|
|
||||||
raise Error(*error.args)
|
|
||||||
|
|
||||||
return Target(doc)
|
|
||||||
|
|
||||||
def add(self, pointer, value):
|
|
||||||
"""Add element to sequence, member to mapping.
|
|
||||||
|
|
||||||
:param pointer: the path to add in it
|
|
||||||
:param value: the new value
|
|
||||||
:return: resolved document
|
|
||||||
:rtype: Target
|
|
||||||
|
|
||||||
|
|
||||||
The pointer must reference one of:
|
|
||||||
|
|
||||||
- The root of the target document - whereupon the specified value
|
|
||||||
becomes the entire content of the target document.
|
|
||||||
|
|
||||||
- A member to add to an existing mapping - whereupon the supplied
|
|
||||||
value is added to that mapping at the indicated location. If the
|
|
||||||
member already exists, it is replaced by the specified value.
|
|
||||||
|
|
||||||
- An element to add to an existing sequence - whereupon the supplied
|
|
||||||
value is added to the sequence at the indicated location.
|
|
||||||
Any elements at or above the specified index are shifted one
|
|
||||||
position to the right.
|
|
||||||
The specified index must no be greater than the number of elements
|
|
||||||
in the sequence.
|
|
||||||
If the "-" character is used to index the end of the sequence, this
|
|
||||||
has the effect of appending the value to the sequence.
|
|
||||||
|
|
||||||
"""
|
|
||||||
doc = deepcopy(self.document)
|
|
||||||
parent, obj = None, doc
|
|
||||||
try:
|
|
||||||
for token in Pointer(pointer):
|
|
||||||
parent, obj = obj, token.extract(obj, bypass_ref=True)
|
|
||||||
else:
|
|
||||||
if isinstance(parent, list):
|
|
||||||
raise OutOfRange(parent)
|
|
||||||
if isinstance(parent, dict):
|
|
||||||
raise OutOfBounds(parent)
|
|
||||||
raise Error('already setted')
|
|
||||||
except (OutOfBounds, OutOfRange, LastElement) as error:
|
|
||||||
if not token.last:
|
|
||||||
raise NonexistentTarget(obj)
|
|
||||||
value = deepcopy(value)
|
|
||||||
if isinstance(error, OutOfBounds):
|
|
||||||
error.obj[str(token)] = value
|
|
||||||
elif isinstance(error, OutOfRange):
|
|
||||||
error.obj.insert(int(token), value)
|
|
||||||
elif isinstance(error, LastElement):
|
|
||||||
error.obj.append(value)
|
|
||||||
|
|
||||||
return Target(doc)
|
|
||||||
|
|
||||||
def replace(self, pointer, value):
|
|
||||||
"""Replace element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param pointer: the path to search in
|
|
||||||
:param value: the new value
|
|
||||||
:return: resolved document
|
|
||||||
:rtype: Target
|
|
||||||
"""
|
|
||||||
doc = deepcopy(self.document)
|
|
||||||
parent, obj = None, doc
|
|
||||||
try:
|
|
||||||
# fetching
|
|
||||||
for token in Pointer(pointer):
|
|
||||||
parent, obj = obj, token.extract(obj, bypass_ref=True)
|
|
||||||
|
|
||||||
# replace
|
|
||||||
value = deepcopy(value)
|
|
||||||
if isinstance(parent, dict):
|
|
||||||
parent[token] = value
|
|
||||||
|
|
||||||
if isinstance(parent, list):
|
|
||||||
parent[int(token)] = value
|
|
||||||
except Exception as error:
|
|
||||||
raise Error(*error.args)
|
|
||||||
|
|
||||||
return Target(doc)
|
|
||||||
|
|
||||||
def move(self, dest, src):
|
|
||||||
"""Move element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param dest: the destination
|
|
||||||
:type dest: Pointer
|
|
||||||
:param src: the source
|
|
||||||
:type src: Pointer
|
|
||||||
:return: resolved document
|
|
||||||
:rtype: Target
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This operation is functionally identical to a "remove" operation on
|
|
||||||
the "from" location, followed immediately by an "add" operation at
|
|
||||||
the target location with the value that was just removed.
|
|
||||||
|
|
||||||
The "from" location MUST NOT be a proper prefix of the "path"
|
|
||||||
location; i.e., a location cannot be moved into one of its children
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
doc = deepcopy(self.document)
|
|
||||||
|
|
||||||
# delete
|
|
||||||
parent, fragment = None, doc
|
|
||||||
for token in Pointer(src):
|
|
||||||
parent, fragment = fragment, token.extract(fragment,
|
|
||||||
bypass_ref=True)
|
|
||||||
|
|
||||||
if isinstance(parent, dict):
|
|
||||||
del parent[token]
|
|
||||||
|
|
||||||
if isinstance(parent, list):
|
|
||||||
parent.pop(int(token))
|
|
||||||
|
|
||||||
# insert
|
|
||||||
return Target(doc).add(dest, fragment)
|
|
||||||
|
|
||||||
def copy(self, dest, src):
|
|
||||||
"""Copy element from sequence, member from mapping.
|
|
||||||
|
|
||||||
:param dest: the destination
|
|
||||||
:type dest: Pointer
|
|
||||||
:param src: the source
|
|
||||||
:type src: Pointer
|
|
||||||
:return: resolved document
|
|
||||||
:rtype: Target
|
|
||||||
"""
|
|
||||||
doc = fragment = deepcopy(self.document)
|
|
||||||
for token in Pointer(src):
|
|
||||||
fragment = token.extract(fragment, bypass_ref=True)
|
|
||||||
|
|
||||||
return Target(doc).add(dest, fragment)
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.operations.exceptions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['Error', 'NonexistentTarget']
|
|
||||||
|
|
||||||
|
|
||||||
class Error(LookupError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NonexistentTarget(Error):
|
|
||||||
"""Raised when trying to get a non existent target"""
|
|
||||||
pass
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.pointer
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
JSON Pointer defines a string syntax for identifying a specific value
|
|
||||||
within a JavaScript Object Notation (JSON) document.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, unicode_literals
|
|
||||||
|
|
||||||
__all__ = ['extract', 'stage', 'DocumentPointer', 'Pointer', 'PointerToken',
|
|
||||||
'ExtractError', 'RefError', 'LastElement', 'OutOfBounds', 'OutOfRange'] # noqa
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from .bases import DocumentPointer, Pointer, PointerToken
|
|
||||||
from .exceptions import ExtractError, RefError, LastElement, OutOfBounds, OutOfRange, WrongType, UnstagedError, ParseError # noqa
|
|
||||||
from .stages import stage
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def extract(obj, pointer, bypass_ref=False):
|
|
||||||
"""Extract member or element of obj according to pointer.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param pointer: the pointer
|
|
||||||
:type pointer: Pointer, str
|
|
||||||
:param bypass_ref: bypass JSON Reference event
|
|
||||||
:type bypass_ref: boolean
|
|
||||||
"""
|
|
||||||
|
|
||||||
return Pointer(pointer).extract(obj, bypass_ref)
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.pointer.bases
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['DocumentPointer', 'Pointer', 'PointerToken']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from abc import abstractmethod, ABCMeta
|
|
||||||
from six import add_metaclass, string_types
|
|
||||||
from .exceptions import ExtractError, RefError, LastElement, OutOfBounds, OutOfRange, WrongType, UnstagedError, ParseError # noqa
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentPointer(object):
|
|
||||||
"""Defines a document pointer
|
|
||||||
|
|
||||||
:ivar document: document name
|
|
||||||
:ivar pointer: pointer
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pointer):
|
|
||||||
"""
|
|
||||||
:param pointer: a string or DocumentPointer instance
|
|
||||||
"""
|
|
||||||
if isinstance(pointer, DocumentPointer):
|
|
||||||
document, path = pointer
|
|
||||||
elif '#' not in pointer:
|
|
||||||
logger.debug('# is missing %r', pointer)
|
|
||||||
document, path = pointer, ''
|
|
||||||
else:
|
|
||||||
document, path = pointer.split('#', 1)
|
|
||||||
self.document = document
|
|
||||||
self.pointer = Pointer(path)
|
|
||||||
|
|
||||||
def extract(self, obj, bypass_ref=False):
|
|
||||||
"""
|
|
||||||
Extract subelement from obj, according to pointer.
|
|
||||||
It assums that document is the object.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param bypass_ref: disable JSON Reference errors
|
|
||||||
"""
|
|
||||||
return self.pointer.extract(obj, bypass_ref)
|
|
||||||
|
|
||||||
def is_inner(self):
|
|
||||||
"""Tells if pointer refers to an inner document
|
|
||||||
"""
|
|
||||||
return self.document == ''
|
|
||||||
|
|
||||||
def endswith(self, txt):
|
|
||||||
"""used by os.path.join"""
|
|
||||||
return str(self).endswith(txt)
|
|
||||||
|
|
||||||
def __iadd__(self, txt):
|
|
||||||
"""append fragments"""
|
|
||||||
data = str(self) + txt
|
|
||||||
return DocumentPointer(data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""Return document and pointer.
|
|
||||||
"""
|
|
||||||
return iter([self.document, self.pointer])
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, string_types):
|
|
||||||
return other == self.__str__()
|
|
||||||
return super(Pointer, self).__eq__(other)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{}#{}'.format(self.document, self.pointer)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<DocumentPointer({})'.format(self)
|
|
||||||
|
|
||||||
|
|
||||||
class Pointer(object):
|
|
||||||
"""Defines a pointer
|
|
||||||
|
|
||||||
:ivar tokens: list of PointerToken
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pointer):
|
|
||||||
"""
|
|
||||||
:param pointer: a string or Pointer instance
|
|
||||||
"""
|
|
||||||
self.tokens = self.parse(pointer)
|
|
||||||
|
|
||||||
if self.tokens:
|
|
||||||
self.tokens[-1].last = True
|
|
||||||
|
|
||||||
def parse(self, pointer):
|
|
||||||
"""parse pointer into tokens"""
|
|
||||||
if isinstance(pointer, Pointer):
|
|
||||||
return pointer.tokens[:]
|
|
||||||
elif pointer == '':
|
|
||||||
return []
|
|
||||||
|
|
||||||
tokens = []
|
|
||||||
staged, _, children = pointer.partition('/')
|
|
||||||
if staged:
|
|
||||||
try:
|
|
||||||
token = StagesToken(staged)
|
|
||||||
token.last = False
|
|
||||||
tokens.append(token)
|
|
||||||
except ValueError:
|
|
||||||
raise ParseError('pointer must start with / or int', pointer)
|
|
||||||
|
|
||||||
if _:
|
|
||||||
for part in children.split('/'):
|
|
||||||
part = part.replace('~1', '/')
|
|
||||||
part = part.replace('~0', '~')
|
|
||||||
token = ChildToken(part)
|
|
||||||
token.last = False
|
|
||||||
tokens.append(token)
|
|
||||||
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def extract(self, obj, bypass_ref=False):
|
|
||||||
"""
|
|
||||||
Extract subelement from obj, according to tokens.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param bypass_ref: disable JSON Reference errors
|
|
||||||
"""
|
|
||||||
for token in self.tokens:
|
|
||||||
obj = token.extract(obj, bypass_ref)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
"""Walk thru tokens.
|
|
||||||
"""
|
|
||||||
return iter(self.tokens)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if isinstance(other, string_types):
|
|
||||||
return other == self.__str__()
|
|
||||||
return super(Pointer, self).__eq__(other)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
output = ''
|
|
||||||
for part in self.tokens:
|
|
||||||
if isinstance(part, StagesToken):
|
|
||||||
output += part
|
|
||||||
continue
|
|
||||||
part = part.replace('~', '~0')
|
|
||||||
part = part.replace('/', '~1')
|
|
||||||
output += '/' + part
|
|
||||||
return output
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<{}({!r})>'.format(self.__class__.__name__, self.__str__())
|
|
||||||
|
|
||||||
|
|
||||||
@add_metaclass(ABCMeta)
|
|
||||||
class PointerToken(str):
|
|
||||||
"""
|
|
||||||
A single token
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def extract(self, obj, bypass_ref=False):
|
|
||||||
"""
|
|
||||||
Extract parents or subelement from obj, according to current token.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param bypass_ref: disable JSON Reference errors
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class StagesToken(PointerToken):
|
|
||||||
"""
|
|
||||||
A parent token
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, value, *args, **kwargs):
|
|
||||||
value = str(value)
|
|
||||||
member = False
|
|
||||||
if value.endswith('#'):
|
|
||||||
value = value[:-1]
|
|
||||||
member = True
|
|
||||||
self.stages = int(value)
|
|
||||||
self.member = member
|
|
||||||
|
|
||||||
def extract(self, obj, bypass_ref=False):
|
|
||||||
"""
|
|
||||||
Extract parent of obj, according to current token.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param bypass_ref: not used
|
|
||||||
"""
|
|
||||||
for i in range(0, self.stages):
|
|
||||||
try:
|
|
||||||
obj = obj.parent_obj
|
|
||||||
except AttributeError:
|
|
||||||
raise UnstagedError(obj, '{!r} must be staged before '
|
|
||||||
'exploring its parents'.format(obj))
|
|
||||||
if self.member:
|
|
||||||
return obj.parent_member
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
class ChildToken(PointerToken):
|
|
||||||
"""
|
|
||||||
A child token
|
|
||||||
"""
|
|
||||||
def extract(self, obj, bypass_ref=False):
|
|
||||||
"""
|
|
||||||
Extract subelement from obj, according to current token.
|
|
||||||
|
|
||||||
:param obj: the object source
|
|
||||||
:param bypass_ref: disable JSON Reference errors
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
if not bypass_ref and '$ref' in obj:
|
|
||||||
raise RefError(obj, 'presence of a $ref member')
|
|
||||||
obj = self.extract_mapping(obj)
|
|
||||||
elif isinstance(obj, (list, tuple)):
|
|
||||||
obj = self.extract_sequence(obj)
|
|
||||||
else:
|
|
||||||
raise WrongType(obj, '{!r} does not apply '
|
|
||||||
'for {!r}'.format(str(self), obj))
|
|
||||||
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
if not bypass_ref and '$ref' in obj:
|
|
||||||
raise RefError(obj, 'presence of a $ref member')
|
|
||||||
return obj
|
|
||||||
except ExtractError as error:
|
|
||||||
logger.exception(error)
|
|
||||||
raise
|
|
||||||
except Exception as error:
|
|
||||||
logger.exception(error)
|
|
||||||
args = [arg for arg in error.args if arg not in (self, obj)]
|
|
||||||
raise ExtractError(obj, *args)
|
|
||||||
|
|
||||||
def extract_mapping(self, obj):
|
|
||||||
if self in obj:
|
|
||||||
return obj[self]
|
|
||||||
|
|
||||||
if self.isdigit():
|
|
||||||
key = int(self)
|
|
||||||
if key in obj:
|
|
||||||
return obj[key]
|
|
||||||
|
|
||||||
raise OutOfBounds(obj, 'member {!r} not found'.format(str(self)))
|
|
||||||
|
|
||||||
def extract_sequence(self, obj):
|
|
||||||
if self == '-':
|
|
||||||
raise LastElement(obj, 'last element is needed')
|
|
||||||
if not self.isdigit():
|
|
||||||
raise WrongType(obj, '{!r} does not apply '
|
|
||||||
'for sequence'.format(str(self)))
|
|
||||||
try:
|
|
||||||
return obj[int(self)]
|
|
||||||
except IndexError:
|
|
||||||
raise OutOfRange(obj, 'element {!r} not found'.format(str(self)))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<{}({!r})>'.format(self.__class__.__name__, str(self))
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.pointer.exceptions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['ExtractError', 'RefError', 'LastElement', 'OutOfBounds',
|
|
||||||
'OutOfRange', 'WrongType', 'UnstagedError']
|
|
||||||
|
|
||||||
|
|
||||||
class ParseError(ValueError):
|
|
||||||
"""Raised when pointer is not well formatted.
|
|
||||||
|
|
||||||
:ivar pointer: the faulty pointer
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, pointer, *args):
|
|
||||||
super(ParseError, self).__init__(*args)
|
|
||||||
self.pointer = pointer
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractError(Exception):
|
|
||||||
"""Raised for any errors.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, obj, *args):
|
|
||||||
super(ExtractError, self).__init__(*args)
|
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
|
|
||||||
class RefError(ExtractError):
|
|
||||||
"""Raised when encoutered a JSON Ref.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class WrongType(ExtractError, ValueError):
|
|
||||||
"""Raised when a member or a sequence is needed.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class OutOfBounds(ExtractError, KeyError):
|
|
||||||
"""Raised when a member of a mapping does not exists.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class OutOfRange(ExtractError, IndexError):
|
|
||||||
"""Raised when an element of a sequence does not exists.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class LastElement(ExtractError):
|
|
||||||
"""Raised when refers to the last element of a sequence.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class UnstagedError(ExtractError, ValueError):
|
|
||||||
"""Raised when obj is not staged.
|
|
||||||
|
|
||||||
:ivar obj: the object that raised this event
|
|
||||||
"""
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.pointer.stages
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Staged(object):
|
|
||||||
obj = None
|
|
||||||
parent_obj = None
|
|
||||||
parent_member = None
|
|
||||||
|
|
||||||
def __init__(self, obj, parent=None, member=None):
|
|
||||||
self.obj = obj
|
|
||||||
self.parent_obj = parent
|
|
||||||
self.parent_member = member
|
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
|
||||||
if name in ('obj', 'parent_obj', 'parent_member'):
|
|
||||||
return object.__getattribute__(self, name)
|
|
||||||
return getattr(object.__getattribute__(self, 'obj'), name)
|
|
||||||
|
|
||||||
def __delattr__(self, name):
|
|
||||||
delattr(object.__getattribute__(self, 'obj'), name)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
if name in ('obj', 'parent_obj', 'parent_member'):
|
|
||||||
object.__setattr__(self, name, value)
|
|
||||||
else:
|
|
||||||
setattr(object.__getattribute__(self, 'obj'), name, value)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return object.__getattribute__(self, 'obj').__iter__()
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
value = object.__getattribute__(self, 'obj').__getitem__(key)
|
|
||||||
return Staged(value, self, key)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return object.__getattribute__(self, 'obj').__len__()
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return object.__getattribute__(self, 'obj') == other
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return object.__getattribute__(self, 'obj').__str__()
|
|
||||||
|
|
||||||
|
|
||||||
def stage(obj, parent=None, member=None):
|
|
||||||
"""
|
|
||||||
Prepare obj to be staged.
|
|
||||||
|
|
||||||
This is almost used for relative JSON Pointers.
|
|
||||||
"""
|
|
||||||
obj = Staged(obj, parent, member)
|
|
||||||
|
|
||||||
if isinstance(obj, dict):
|
|
||||||
for key, value in obj.items():
|
|
||||||
stage(value, obj, key)
|
|
||||||
elif isinstance(obj, (list, tuple)):
|
|
||||||
for index, value in enumerate(obj):
|
|
||||||
stage(value, obj, index)
|
|
||||||
elif isinstance(obj, set):
|
|
||||||
for value in obj:
|
|
||||||
stage(value, obj, None)
|
|
||||||
|
|
||||||
return obj
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.reference
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
A JSON Reference is a JSON object, which contains a member named
|
|
||||||
"$ref", which has a JSON string value. Example:
|
|
||||||
|
|
||||||
{ "$ref": "http://example.com/example.json#/foo/bar" }
|
|
||||||
|
|
||||||
If a JSON value does not have these characteristics, then it SHOULD
|
|
||||||
NOT be interpreted as a JSON Reference.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import, print_function, unicode_literals
|
|
||||||
|
|
||||||
__all__ = ['resolve', 'Registry', 'LocalRegistry', 'NotFound', 'Forbidden']
|
|
||||||
|
|
||||||
from .bases import Registry, LocalRegistry
|
|
||||||
from .exceptions import NotFound, Forbidden
|
|
||||||
from jsonspec.pointer import DocumentPointer
|
|
||||||
|
|
||||||
|
|
||||||
def resolve(obj, pointer, registry=None):
|
|
||||||
"""resolve a local object
|
|
||||||
|
|
||||||
:param obj: the local object.
|
|
||||||
:param pointer: the pointer
|
|
||||||
:type pointer: DocumentPointer, str
|
|
||||||
:param registry: the registry.
|
|
||||||
It mays be omited if inner json references
|
|
||||||
document don't refer to other documents.
|
|
||||||
:type registry: Provider, dict
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Once pointer is extracted, it won't follow sub mapping /element!
|
|
||||||
For example, the value of::
|
|
||||||
|
|
||||||
value = resolve({
|
|
||||||
'foo': {'$ref': '#/bar'},
|
|
||||||
'bar': [{'$ref': '#/baz'}],
|
|
||||||
'baz': 'quux',
|
|
||||||
}, '#/foo')
|
|
||||||
|
|
||||||
is::
|
|
||||||
|
|
||||||
assert value == [{'$ref': '#/baz'}]
|
|
||||||
|
|
||||||
and not::
|
|
||||||
|
|
||||||
assert value == ['quux']
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
registry = LocalRegistry(obj, registry or {})
|
|
||||||
local = DocumentPointer(pointer)
|
|
||||||
|
|
||||||
if local.document:
|
|
||||||
registry[local.document] = obj
|
|
||||||
local.document = '<local>'
|
|
||||||
return registry.resolve(local)
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.reference.bases
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['LocalRegistry', 'Registry']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from .exceptions import NotFound, Forbidden
|
|
||||||
from .util import ref, MutableMapping, Mapping
|
|
||||||
from jsonspec.pointer import DocumentPointer
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(Mapping):
|
|
||||||
"""Defines a generic way to provide external documents"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Registry(Provider, MutableMapping):
|
|
||||||
"""Register all documents.
|
|
||||||
|
|
||||||
:ivar provider: all documents
|
|
||||||
:ivar provider: Provider, dict
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, provider=None):
|
|
||||||
self.provider = provider or {}
|
|
||||||
super(Registry, self).__init__()
|
|
||||||
|
|
||||||
def prototype(self, dp):
|
|
||||||
obj = self[dp.document]
|
|
||||||
return self[dp.document], LocalRegistry(obj, self)
|
|
||||||
|
|
||||||
def resolve(self, pointer):
|
|
||||||
"""Resolve from documents.
|
|
||||||
|
|
||||||
:param pointer: foo
|
|
||||||
:type pointer: DocumentPointer
|
|
||||||
"""
|
|
||||||
|
|
||||||
dp = DocumentPointer(pointer)
|
|
||||||
obj, fetcher = self.prototype(dp)
|
|
||||||
|
|
||||||
for token in dp.pointer:
|
|
||||||
obj = token.extract(obj, bypass_ref=True)
|
|
||||||
reference = ref(obj)
|
|
||||||
if reference:
|
|
||||||
obj = fetcher.resolve(reference)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __getitem__(self, uri):
|
|
||||||
try:
|
|
||||||
return self.provider[uri]
|
|
||||||
except KeyError:
|
|
||||||
raise NotFound('{!r} not registered'.format(uri))
|
|
||||||
|
|
||||||
def __setitem__(self, uri, obj):
|
|
||||||
self.provider[uri] = obj
|
|
||||||
|
|
||||||
def __delitem__(self, uri):
|
|
||||||
del self.provider[uri]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.provider)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.provider)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalRegistry(Registry):
|
|
||||||
"""Scoped registry to a local document.
|
|
||||||
|
|
||||||
:ivar doc: the local document
|
|
||||||
:ivar provider: all documents
|
|
||||||
:ivar provider: Provider, dict
|
|
||||||
:ivar key: current document identifier
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = '<local>'
|
|
||||||
|
|
||||||
def __init__(self, doc, provider=None):
|
|
||||||
self.doc = doc
|
|
||||||
self.provider = provider or {}
|
|
||||||
|
|
||||||
def prototype(self, dp):
|
|
||||||
if dp.is_inner():
|
|
||||||
return self.doc, self
|
|
||||||
else:
|
|
||||||
obj = self[dp.document]
|
|
||||||
return self[dp.document], LocalRegistry(obj, self)
|
|
||||||
|
|
||||||
def __getitem__(self, uri):
|
|
||||||
try:
|
|
||||||
return self.doc if uri == self.key else self.provider[uri]
|
|
||||||
except (NotFound, KeyError):
|
|
||||||
raise NotFound('{!r} not registered'.format(uri))
|
|
||||||
|
|
||||||
def __setitem__(self, uri, obj):
|
|
||||||
if uri == self.key:
|
|
||||||
raise Forbidden('setting {} is forbidden'.format(self.key))
|
|
||||||
if uri not in self.provider:
|
|
||||||
self.provider[uri] = obj
|
|
||||||
|
|
||||||
def __delitem__(self, uri):
|
|
||||||
if uri == self.key:
|
|
||||||
raise Forbidden('deleting {} is forbidden'.format(self.key))
|
|
||||||
del self.provider[uri]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(set(list(self.provider.keys()) + [self.key]))
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield self.key
|
|
||||||
for key in self.provider.keys():
|
|
||||||
yield key
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.reference.exceptions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class NotFound(Exception):
|
|
||||||
"""raises when a document is not found"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Forbidden(object):
|
|
||||||
"""raises when a trying to replace <local> document"""
|
|
||||||
pass
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.reference.providers
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Provider', 'FilesystemProvider', 'PkgProvider', 'SpecProvider']
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pkg_resources
|
|
||||||
from .bases import Provider
|
|
||||||
from .exceptions import NotFound
|
|
||||||
from .util import loop
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PkgProvider(Provider):
|
|
||||||
"""
|
|
||||||
Autoload providers declared into setuptools ``entry_points``.
|
|
||||||
|
|
||||||
For example, with this setup.cfg:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
jsonspec.reference.contributions =
|
|
||||||
spec = jsonspec.misc.providers:SpecProvider
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
namespace = 'jsonspec.reference.contributions'
|
|
||||||
|
|
||||||
def __init__(self, namespace=None, configuration=None):
|
|
||||||
self.namespace = namespace or self.namespace
|
|
||||||
self.configuration = configuration or {}
|
|
||||||
self.loaded = False
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
providers = {}
|
|
||||||
for entrypoint in pkg_resources.iter_entry_points(self.namespace):
|
|
||||||
kwargs = self.configuration.get(entrypoint.name, {})
|
|
||||||
providers[entrypoint.name] = entrypoint.load()(**kwargs)
|
|
||||||
logger.debug('loaded %s from %s', entrypoint, entrypoint.dist)
|
|
||||||
self.providers = providers
|
|
||||||
self.loaded = True
|
|
||||||
|
|
||||||
def __getitem__(self, uri):
|
|
||||||
if not self.loaded:
|
|
||||||
self.load()
|
|
||||||
|
|
||||||
for name, provider in self.providers.items():
|
|
||||||
try:
|
|
||||||
value = provider[uri]
|
|
||||||
logger.info('got %s from %s', uri, name)
|
|
||||||
return value
|
|
||||||
except (KeyError, NotFound):
|
|
||||||
pass
|
|
||||||
raise NotFound('no providers could return {!r}'.format(uri))
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class FilesystemProvider(Provider):
|
|
||||||
"""
|
|
||||||
Exposes json documents stored into filesystem.
|
|
||||||
|
|
||||||
for example, with ``prefix=my:pref:`` and ``directory=my/directory``,
|
|
||||||
this filesystem will be loaded as::
|
|
||||||
|
|
||||||
my/directory/
|
|
||||||
foo.json -> my:pref:foo#
|
|
||||||
bar.json -> my:pref:bar#
|
|
||||||
baz/
|
|
||||||
quux.json -> my:pref:baz/quux#
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, directory, prefix=None):
|
|
||||||
self.directory = directory
|
|
||||||
if not self.directory.endswith('/'):
|
|
||||||
self.directory += '/'
|
|
||||||
self.prefix = prefix or ''
|
|
||||||
self.loaded = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data(self):
|
|
||||||
if not self.loaded:
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
l = len(self.directory)
|
|
||||||
for filename in loop(self.directory, '*.json'):
|
|
||||||
spec = filename[l:-5]
|
|
||||||
with open(filename, 'r') as file:
|
|
||||||
schema = json.load(file)
|
|
||||||
data[spec] = schema
|
|
||||||
|
|
||||||
# set the fallbacks
|
|
||||||
for spec in sorted(data.keys(), reverse=True):
|
|
||||||
if spec.startswith('draft-'):
|
|
||||||
metaspec = spec.split('/', 1)[1]
|
|
||||||
if metaspec not in data:
|
|
||||||
data[metaspec] = data[spec]
|
|
||||||
|
|
||||||
self._data = data
|
|
||||||
self.loaded = True
|
|
||||||
return self._data
|
|
||||||
|
|
||||||
def __getitem__(self, uri):
|
|
||||||
spec = uri
|
|
||||||
if uri.startswith(self.prefix):
|
|
||||||
spec = uri[len(self.prefix):]
|
|
||||||
if spec.endswith('#'):
|
|
||||||
spec = spec[:-1]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.data[spec]
|
|
||||||
except (KeyError, UnboundLocalError):
|
|
||||||
raise NotFound(uri)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for spec in self.data.keys():
|
|
||||||
yield '{}{}#'.format(self.prefix, spec)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self.data.keys())
|
|
||||||
|
|
||||||
|
|
||||||
class SpecProvider(FilesystemProvider):
|
|
||||||
"""
|
|
||||||
Provides specs of http://json-schema.org/
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
from jsonspec.misc import __file__ as misc
|
|
||||||
base = os.path.realpath(os.path.dirname(misc))
|
|
||||||
src = os.path.join(base, 'schemas/')
|
|
||||||
prefix = 'http://json-schema.org/'
|
|
||||||
super(SpecProvider, self).__init__(src, prefix)
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyProvider(Provider):
|
|
||||||
def __init__(self, provider):
|
|
||||||
self.provider = provider
|
|
||||||
self.local = {}
|
|
||||||
|
|
||||||
def __getitem__(self, uri):
|
|
||||||
try:
|
|
||||||
return self.local[uri]
|
|
||||||
except KeyError:
|
|
||||||
return self.provider[uri]
|
|
||||||
|
|
||||||
def __setitem__(self, uri, schema):
|
|
||||||
self.local[uri] = schema
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
keys = set(self.local.keys())
|
|
||||||
keys.update(self.provider.keys())
|
|
||||||
for key in sorted(keys):
|
|
||||||
yield key
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
keys = set(self.local.keys())
|
|
||||||
keys.update(self.provider.keys())
|
|
||||||
return len(keys)
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.reference.util
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['ref', 'loop', 'Mapping', 'MutableMapping']
|
|
||||||
|
|
||||||
import fnmatch
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
def ref(obj):
|
|
||||||
"""Extracts $ref of object."""
|
|
||||||
try:
|
|
||||||
return obj['$ref']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def loop(directory, pattern='*'):
|
|
||||||
regex = fnmatch.translate(pattern)
|
|
||||||
matcher = re.compile(regex)
|
|
||||||
|
|
||||||
def _loop(directory):
|
|
||||||
for file in os.listdir(directory):
|
|
||||||
path = os.path.join(directory, file)
|
|
||||||
if os.path.isfile(path) and matcher.match(path):
|
|
||||||
yield path
|
|
||||||
elif os.path.isdir(path):
|
|
||||||
for subpath in loop(path):
|
|
||||||
yield subpath
|
|
||||||
return _loop(directory)
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
# py3
|
|
||||||
from collections.abc import MutableMapping, Mapping
|
|
||||||
except ImportError:
|
|
||||||
# py2
|
|
||||||
from collections import MutableMapping, Mapping
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators
|
|
||||||
~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['load', 'register', 'Factory', 'Context',
|
|
||||||
'Validator', 'ReferenceValidator',
|
|
||||||
'Draft03Validator', 'Draft04Validator',
|
|
||||||
'CompilationError', 'ReferenceError', 'ValidationError']
|
|
||||||
|
|
||||||
from .bases import Validator, ReferenceValidator
|
|
||||||
from .exceptions import CompilationError, ReferenceError, ValidationError
|
|
||||||
from .factorize import register, Factory, Context
|
|
||||||
from . import draft04 # noqa
|
|
||||||
from . import draft03 # noqa
|
|
||||||
from .draft03 import Draft03Validator # noqa
|
|
||||||
from .draft04 import Draft04Validator # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def load(schema, uri=None, spec=None, provider=None):
|
|
||||||
"""Scaffold a validator against a schema.
|
|
||||||
|
|
||||||
:param schema: the schema to compile into a Validator
|
|
||||||
:type schema: Mapping
|
|
||||||
:param uri: the uri of the schema.
|
|
||||||
it may be ignored in case of not cross
|
|
||||||
referencing.
|
|
||||||
:type uri: Pointer, str
|
|
||||||
:param spec: fallback to this spec if the schema does not provides ts own
|
|
||||||
:type spec: str
|
|
||||||
:param provider: the other schemas, in case of cross
|
|
||||||
referencing
|
|
||||||
:type provider: Mapping, Provider...
|
|
||||||
"""
|
|
||||||
factory = Factory(provider, spec)
|
|
||||||
return factory(schema, uri or '#')
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.bases
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['ValidationError', 'Validator', 'ReferenceValidator']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from abc import abstractmethod, ABCMeta
|
|
||||||
from six import add_metaclass
|
|
||||||
from jsonspec.pointer import DocumentPointer
|
|
||||||
from .exceptions import ValidationError
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@add_metaclass(ABCMeta)
|
|
||||||
class Validator(object):
|
|
||||||
"""
|
|
||||||
The mother of Validators.
|
|
||||||
"""
|
|
||||||
#: indicates current uri
|
|
||||||
uri = None
|
|
||||||
|
|
||||||
default = None
|
|
||||||
|
|
||||||
def __init__(self, **attrs):
|
|
||||||
self.uri = attrs.pop('uri', None)
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def has_default(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def is_optional(self):
|
|
||||||
"""
|
|
||||||
Indicates if the instance must be defined.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def validate(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Validate object.
|
|
||||||
|
|
||||||
:param obj: the object to validate
|
|
||||||
:param pointer: the object pointer
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __call__(self, obj, pointer=None):
|
|
||||||
"""shortcut for validate()"""
|
|
||||||
return self.validate(obj, pointer)
|
|
||||||
|
|
||||||
|
|
||||||
class ReferenceValidator(Validator):
|
|
||||||
"""
|
|
||||||
Reference a validator to his pointer.
|
|
||||||
|
|
||||||
:ivar pointer: the pointer to the validator
|
|
||||||
:ivar context: the context object
|
|
||||||
:ivar default: return the default validator
|
|
||||||
:ivar validator: return the lazy loaded validator
|
|
||||||
|
|
||||||
>>> validator = ReferenceValidator('http://json-schema.org/geo#', context)
|
|
||||||
>>> assert validator({
|
|
||||||
>>> 'latitude': 0.0124,
|
|
||||||
>>> 'longitude': 1.2345
|
|
||||||
>>> })
|
|
||||||
"""
|
|
||||||
def __init__(self, pointer, context):
|
|
||||||
super(ReferenceValidator, self).__init__()
|
|
||||||
self.pointer = DocumentPointer(pointer)
|
|
||||||
self.context = context
|
|
||||||
self.uri = str(self.pointer)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def validator(self):
|
|
||||||
if not hasattr(self, '_validator'):
|
|
||||||
self._validator = self.context.resolve(self.pointer)
|
|
||||||
return self._validator
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return self.validator.has_default()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def default(self):
|
|
||||||
return self.validator.default
|
|
||||||
|
|
||||||
def is_optional(self):
|
|
||||||
return self.validator.is_optional()
|
|
||||||
|
|
||||||
def validate(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Validate object against validator.
|
|
||||||
|
|
||||||
:param obj: the object to validate
|
|
||||||
:param pointer: the object pointer
|
|
||||||
"""
|
|
||||||
return self.validator.validate(obj, pointer)
|
|
||||||
@@ -1,666 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.draft03
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements JSON Schema draft03.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['compile', 'Draft03Validator']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os.path
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
from decimal import Decimal
|
|
||||||
from six import integer_types, string_types
|
|
||||||
from six.moves.urllib.parse import urljoin
|
|
||||||
from .bases import ReferenceValidator, Validator
|
|
||||||
from .exceptions import CompilationError
|
|
||||||
from .factorize import register
|
|
||||||
from jsonspec.validators.exceptions import ValidationError
|
|
||||||
from jsonspec.validators.util import uncamel
|
|
||||||
from jsonspec.validators.pointer_util import pointer_join
|
|
||||||
from jsonspec import driver as json
|
|
||||||
|
|
||||||
sequence_types = (list, set, tuple)
|
|
||||||
number_types = (integer_types, float, Decimal)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@register(spec='http://json-schema.org/draft-03/schema#')
|
|
||||||
def compile(schema, pointer, context, scope=None):
|
|
||||||
"""
|
|
||||||
Compiles schema with `JSON Schema`_ draft-03.
|
|
||||||
|
|
||||||
:param schema: obj to compile
|
|
||||||
:type schema: Mapping
|
|
||||||
:param pointer: uri of the schema
|
|
||||||
:type pointer: Pointer, str
|
|
||||||
:param context: context of this schema
|
|
||||||
:type context: Context
|
|
||||||
|
|
||||||
.. _`JSON Schema`: http://json-schema.org
|
|
||||||
"""
|
|
||||||
|
|
||||||
schm = deepcopy(schema)
|
|
||||||
|
|
||||||
scope = urljoin(scope or str(pointer), schm.pop('id', None))
|
|
||||||
|
|
||||||
if '$ref' in schema:
|
|
||||||
return ReferenceValidator(urljoin(scope, schema['$ref']), context)
|
|
||||||
|
|
||||||
attrs = {}
|
|
||||||
|
|
||||||
if 'additionalItems' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'additionalItems')
|
|
||||||
attrs['additional_items'] = schm.pop('additionalItems')
|
|
||||||
if isinstance(attrs['additional_items'], dict):
|
|
||||||
compiled = compile(attrs['additional_items'],
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
attrs['additional_items'] = compiled
|
|
||||||
elif not isinstance(attrs['additional_items'], bool):
|
|
||||||
raise CompilationError('wrong type for {}'.format('additional_items'), schema) # noqa
|
|
||||||
|
|
||||||
if 'additionalProperties' in schm:
|
|
||||||
attrs['additional_properties'] = schm.pop('additionalProperties')
|
|
||||||
if isinstance(attrs['additional_properties'], dict):
|
|
||||||
subpointer = os.path.join(pointer, 'additionalProperties')
|
|
||||||
value = attrs['additional_properties']
|
|
||||||
attrs['additional_properties'] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif not isinstance(attrs['additional_properties'], bool):
|
|
||||||
raise CompilationError('additionalProperties must be an object or boolean', schema) # noqa
|
|
||||||
|
|
||||||
if 'dependencies' in schm:
|
|
||||||
attrs['dependencies'] = schm.pop('dependencies')
|
|
||||||
if not isinstance(attrs['dependencies'], dict):
|
|
||||||
raise CompilationError('dependencies must be an object', schema)
|
|
||||||
for key, value in attrs['dependencies'].items():
|
|
||||||
if isinstance(value, dict):
|
|
||||||
subpointer = os.path.join(pointer, 'dependencies', key)
|
|
||||||
attrs['dependencies'][key] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif isinstance(value, sequence_types):
|
|
||||||
continue
|
|
||||||
elif not isinstance(value, string_types):
|
|
||||||
raise CompilationError('dependencies must be an array, object or string', schema) # noqa
|
|
||||||
|
|
||||||
if 'disallow' in schm:
|
|
||||||
attrs['disallow'] = schm.pop('disallow')
|
|
||||||
if isinstance(attrs['disallow'], sequence_types):
|
|
||||||
for index, value in enumerate(attrs['disallow']):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
subpointer = os.path.join(pointer, 'disallow', str(index))
|
|
||||||
attrs['disallow'][index] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif not isinstance(value, string_types):
|
|
||||||
raise CompilationError('disallow must be an object or string', schema) # noqa
|
|
||||||
elif not isinstance(attrs['disallow'], string_types):
|
|
||||||
raise CompilationError('disallow must be an array or string', schema) # noqa
|
|
||||||
|
|
||||||
if 'divisibleBy' in schm:
|
|
||||||
attrs['divisible_by'] = schm.pop('divisibleBy')
|
|
||||||
if not isinstance(attrs['divisible_by'], number_types):
|
|
||||||
raise CompilationError('divisibleBy must be a number', schema)
|
|
||||||
|
|
||||||
if 'enum' in schm:
|
|
||||||
attrs['enum'] = schm.pop('enum')
|
|
||||||
if not isinstance(attrs['enum'], sequence_types):
|
|
||||||
raise CompilationError('enum must be a sequence', schema)
|
|
||||||
|
|
||||||
if 'exclusiveMaximum' in schm:
|
|
||||||
attrs['exclusive_maximum'] = schm.pop('exclusiveMaximum')
|
|
||||||
if not isinstance(attrs['exclusive_maximum'], bool):
|
|
||||||
raise CompilationError('exclusiveMaximum must be a boolean', schema) # noqa
|
|
||||||
|
|
||||||
if 'exclusiveMinimum' in schm:
|
|
||||||
attrs['exclusive_minimum'] = schm.pop('exclusiveMinimum')
|
|
||||||
if not isinstance(attrs['exclusive_minimum'], bool):
|
|
||||||
raise CompilationError('exclusiveMinimum must be a boolean', schema) # noqa
|
|
||||||
|
|
||||||
if 'extends' in schm:
|
|
||||||
attrs['extends'] = schm.pop('extends')
|
|
||||||
subpointer = os.path.join(pointer, 'extends')
|
|
||||||
if isinstance(attrs['extends'], dict):
|
|
||||||
attrs['extends'] = compile(attrs['extends'],
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif isinstance(attrs['extends'], sequence_types):
|
|
||||||
for index, value in enumerate(attrs['extends']):
|
|
||||||
attrs['extends'][index] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
else:
|
|
||||||
raise CompilationError('extends must be an object or array', schema) # noqa
|
|
||||||
|
|
||||||
if 'format' in schm:
|
|
||||||
attrs['format'] = schm.pop('format')
|
|
||||||
if not isinstance(attrs['format'], string_types):
|
|
||||||
raise CompilationError('format must be a string', schema)
|
|
||||||
|
|
||||||
if 'items' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'items')
|
|
||||||
attrs['items'] = schm.pop('items')
|
|
||||||
if isinstance(attrs['items'], (list, tuple)):
|
|
||||||
# each value must be a json schema
|
|
||||||
attrs['items'] = [compile(element, subpointer, context, scope) for element in attrs['items']] # noqa
|
|
||||||
elif isinstance(attrs['items'], dict):
|
|
||||||
# value must be a json schema
|
|
||||||
attrs['items'] = compile(attrs['items'], subpointer, context, scope) # noqa
|
|
||||||
else:
|
|
||||||
# should be a boolean
|
|
||||||
raise CompilationError('wrong type for {}'.format('items'), schema) # noqa
|
|
||||||
|
|
||||||
if 'maximum' in schm:
|
|
||||||
attrs['maximum'] = schm.pop('maximum')
|
|
||||||
if not isinstance(attrs['maximum'], number_types):
|
|
||||||
raise CompilationError('enum must be an integer', schema)
|
|
||||||
|
|
||||||
if 'maxItems' in schm:
|
|
||||||
attrs['max_items'] = schm.pop('maxItems')
|
|
||||||
if not isinstance(attrs['max_items'], integer_types):
|
|
||||||
raise CompilationError('maxItems must be an integer', schema)
|
|
||||||
|
|
||||||
if 'maxLength' in schm:
|
|
||||||
attrs['max_length'] = schm.pop('maxLength')
|
|
||||||
if not isinstance(attrs['max_length'], integer_types):
|
|
||||||
raise CompilationError('maxLength must be integer', schema)
|
|
||||||
|
|
||||||
if 'minimum' in schm:
|
|
||||||
attrs['minimum'] = schm.pop('minimum')
|
|
||||||
if not isinstance(attrs['minimum'], number_types):
|
|
||||||
raise CompilationError('enum must be a number', schema)
|
|
||||||
|
|
||||||
if 'minItems' in schm:
|
|
||||||
attrs['min_items'] = schm.pop('minItems')
|
|
||||||
if not isinstance(attrs['min_items'], integer_types):
|
|
||||||
raise CompilationError('minItems must be an integer', schema)
|
|
||||||
|
|
||||||
if 'minLength' in schm:
|
|
||||||
attrs['min_length'] = schm.pop('minLength')
|
|
||||||
if not isinstance(attrs['min_length'], integer_types):
|
|
||||||
raise CompilationError('minLength must be integer', schema)
|
|
||||||
|
|
||||||
if 'pattern' in schm:
|
|
||||||
attrs['pattern'] = schm.pop('pattern')
|
|
||||||
if not isinstance(attrs['pattern'], string_types):
|
|
||||||
raise CompilationError('pattern must be a string', schema)
|
|
||||||
|
|
||||||
if 'patternProperties' in schm:
|
|
||||||
attrs['pattern_properties'] = schm.pop('patternProperties')
|
|
||||||
if not isinstance(attrs['pattern_properties'], dict):
|
|
||||||
raise CompilationError('patternProperties must be an object', schema) # noqa
|
|
||||||
for name, value in attrs['pattern_properties'].items():
|
|
||||||
subpointer = os.path.join(pointer, 'patternProperties', name)
|
|
||||||
attrs['pattern_properties'][name] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
|
|
||||||
if 'properties' in schm:
|
|
||||||
attrs['properties'] = schm.pop('properties')
|
|
||||||
if not isinstance(attrs['properties'], dict):
|
|
||||||
raise CompilationError('properties must be an object', schema)
|
|
||||||
for name, value in attrs['properties'].items():
|
|
||||||
subpointer = os.path.join(pointer, 'properties', name)
|
|
||||||
attrs['properties'][name] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
|
|
||||||
if 'required' in schm:
|
|
||||||
attrs['required'] = schm.pop('required')
|
|
||||||
if not isinstance(attrs['required'], bool):
|
|
||||||
raise CompilationError('required must be a boolean', schema)
|
|
||||||
|
|
||||||
if 'type' in schm:
|
|
||||||
attrs['type'] = schm.pop('type')
|
|
||||||
if isinstance(attrs['type'], sequence_types):
|
|
||||||
for index, value in enumerate(attrs['type']):
|
|
||||||
if isinstance(value, dict):
|
|
||||||
subpointer = os.path.join(pointer, 'type', str(index))
|
|
||||||
attrs['type'][index] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif not isinstance(value, string_types):
|
|
||||||
raise CompilationError('type must be an object or string', schema) # noqa
|
|
||||||
elif not isinstance(attrs['type'], string_types):
|
|
||||||
raise CompilationError('type must be an array or string', schema) # noqa
|
|
||||||
|
|
||||||
if 'uniqueItems' in schm:
|
|
||||||
attrs['unique_items'] = schm.pop('uniqueItems')
|
|
||||||
if not isinstance(attrs['unique_items'], bool):
|
|
||||||
raise CompilationError('type must be boolean', schema)
|
|
||||||
|
|
||||||
return Draft03Validator(attrs, scope, context.formats)
|
|
||||||
|
|
||||||
|
|
||||||
class Draft03Validator(Validator):
|
|
||||||
"""
|
|
||||||
Implements `JSON Schema`_ draft-03 validation.
|
|
||||||
|
|
||||||
:ivar attrs: attributes to validate against
|
|
||||||
:ivar uri: uri of the current validator
|
|
||||||
:ivar formats: mapping of available formats
|
|
||||||
|
|
||||||
>>> validator = Draft03Validator({'min_length': 4})
|
|
||||||
>>> assert validator('this is sparta')
|
|
||||||
|
|
||||||
.. _`JSON Schema`: http://json-schema.org
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, attrs, uri=None, formats=None):
|
|
||||||
attrs = {uncamel(k): v for k, v in attrs.items()}
|
|
||||||
|
|
||||||
self.attrs = attrs
|
|
||||||
self.attrs.setdefault('additional_items', True)
|
|
||||||
self.attrs.setdefault('pattern_properties', {})
|
|
||||||
self.attrs.setdefault('exclusive_maximum', False)
|
|
||||||
self.attrs.setdefault('exclusive_minimum', False)
|
|
||||||
self.attrs.setdefault('additional_properties', True)
|
|
||||||
self.attrs.setdefault('properties', {})
|
|
||||||
self.uri = uri
|
|
||||||
self.formats = formats or {}
|
|
||||||
self.default = self.attrs.get('default', None)
|
|
||||||
self.fail_fast = True
|
|
||||||
self.errors = []
|
|
||||||
|
|
||||||
def is_array(self, obj):
|
|
||||||
return isinstance(obj, sequence_types)
|
|
||||||
|
|
||||||
def is_boolean(self, obj):
|
|
||||||
return isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_integer(self, obj):
|
|
||||||
return isinstance(obj, integer_types) and not isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_null(self, obj):
|
|
||||||
return obj is None
|
|
||||||
|
|
||||||
def is_number(self, obj):
|
|
||||||
return isinstance(obj, number_types) and not isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_object(self, obj):
|
|
||||||
return isinstance(obj, dict)
|
|
||||||
|
|
||||||
def is_string(self, obj):
|
|
||||||
return isinstance(obj, string_types)
|
|
||||||
|
|
||||||
def validate(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Validate object against validator
|
|
||||||
|
|
||||||
:param obj: the object to validate
|
|
||||||
"""
|
|
||||||
|
|
||||||
pointer = pointer or '#'
|
|
||||||
|
|
||||||
validator = deepcopy(self)
|
|
||||||
validator.errors = []
|
|
||||||
validator.fail_fast = False
|
|
||||||
|
|
||||||
obj = deepcopy(obj)
|
|
||||||
obj = validator.validate_enum(obj, pointer)
|
|
||||||
obj = validator.validate_type(obj, pointer)
|
|
||||||
obj = validator.validate_disallow(obj, pointer)
|
|
||||||
obj = validator.validate_extends(obj, pointer)
|
|
||||||
|
|
||||||
if validator.is_array(obj):
|
|
||||||
obj = validator.validate_max_items(obj, pointer)
|
|
||||||
obj = validator.validate_min_items(obj, pointer)
|
|
||||||
obj = validator.validate_items(obj, pointer)
|
|
||||||
obj = validator.validate_unique_items(obj, pointer)
|
|
||||||
|
|
||||||
if validator.is_number(obj):
|
|
||||||
obj = validator.validate_maximum(obj, pointer)
|
|
||||||
obj = validator.validate_minimum(obj, pointer)
|
|
||||||
obj = validator.validate_divisible_by(obj, pointer)
|
|
||||||
|
|
||||||
if validator.is_object(obj):
|
|
||||||
obj = validator.validate_dependencies(obj, pointer)
|
|
||||||
obj = validator.validate_properties(obj, pointer)
|
|
||||||
|
|
||||||
if validator.is_string(obj):
|
|
||||||
obj = validator.validate_max_length(obj, pointer)
|
|
||||||
obj = validator.validate_min_length(obj, pointer)
|
|
||||||
obj = validator.validate_pattern(obj, pointer)
|
|
||||||
obj = validator.validate_format(obj, pointer)
|
|
||||||
|
|
||||||
if validator.errors:
|
|
||||||
raise ValidationError('multiple errors',
|
|
||||||
obj,
|
|
||||||
errors=validator.errors)
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_dependencies(self, obj, pointer=None):
|
|
||||||
if 'dependencies' in self.attrs:
|
|
||||||
missings = set()
|
|
||||||
for name, dependencies in self.attrs['dependencies'].items():
|
|
||||||
if name not in obj:
|
|
||||||
continue
|
|
||||||
if isinstance(dependencies, Validator):
|
|
||||||
obj[name] = dependencies(obj)
|
|
||||||
elif isinstance(dependencies, sequence_types):
|
|
||||||
for d in dependencies:
|
|
||||||
if d not in obj:
|
|
||||||
missings.add(d)
|
|
||||||
elif dependencies not in obj:
|
|
||||||
missings.add(dependencies)
|
|
||||||
if missings:
|
|
||||||
missings = sorted(missings)
|
|
||||||
self.fail('Missing properties', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_disallow(self, obj, pointer=None):
|
|
||||||
if 'disallow' in self.attrs:
|
|
||||||
disallows = self.attrs['disallow']
|
|
||||||
if not isinstance(disallows, sequence_types):
|
|
||||||
disallows = [disallows]
|
|
||||||
disallowed = 0
|
|
||||||
for type in disallows:
|
|
||||||
try:
|
|
||||||
if isinstance(type, Validator):
|
|
||||||
type(obj)
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "any":
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "array" and self.is_array(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "boolean" and self.is_boolean(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "integer" and self.is_integer(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "null" and self.is_null(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "number" and self.is_number(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "object" and self.is_object(obj):
|
|
||||||
disallowed += 1
|
|
||||||
elif type == "string" and self.is_string(obj):
|
|
||||||
disallowed += 1
|
|
||||||
except ValidationError:
|
|
||||||
# let it, it may be good
|
|
||||||
pass
|
|
||||||
if disallowed:
|
|
||||||
self.fail('Wrong type', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_divisible_by(self, obj, pointer=None):
|
|
||||||
if 'divisible_by' in self.attrs:
|
|
||||||
factor = Decimal(str(self.attrs['divisible_by']))
|
|
||||||
orig = Decimal(str(obj))
|
|
||||||
if orig % factor != 0:
|
|
||||||
self.fail('Not a multiple of {}', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_enum(self, obj, pointer=None):
|
|
||||||
if 'enum' in self.attrs:
|
|
||||||
if not obj in self.attrs['enum']:
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_extends(self, obj, pointer=None):
|
|
||||||
if 'extends' in self.attrs:
|
|
||||||
extends = self.attrs['extends']
|
|
||||||
if not isinstance(extends, sequence_types):
|
|
||||||
extends = [extends]
|
|
||||||
for type in extends:
|
|
||||||
obj = type(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_format(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
================= ============
|
|
||||||
Expected draft03 Alias of
|
|
||||||
----------------- ------------
|
|
||||||
color css.color
|
|
||||||
date-time utc.datetime
|
|
||||||
date utc.date
|
|
||||||
time utc.time
|
|
||||||
utc-millisec utc.millisec
|
|
||||||
regex regex
|
|
||||||
style css.style
|
|
||||||
phone phone
|
|
||||||
uri uri
|
|
||||||
email email
|
|
||||||
ip-address ipv4
|
|
||||||
ipv6 ipv6
|
|
||||||
host-name hostname
|
|
||||||
================= ============
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'format' in self.attrs:
|
|
||||||
substituted = {
|
|
||||||
'color': 'css.color',
|
|
||||||
'date-time': 'utc.datetime',
|
|
||||||
'date': 'utc.date',
|
|
||||||
'time': 'utc.time',
|
|
||||||
'utc-millisec': 'utc.millisec',
|
|
||||||
'regex': 'regex',
|
|
||||||
'style': 'css.style',
|
|
||||||
'phone': 'phone',
|
|
||||||
'uri': 'uri',
|
|
||||||
'email': 'email',
|
|
||||||
'ip-address': 'ipv4',
|
|
||||||
'ipv6': 'ipv6',
|
|
||||||
'host-name': 'hostname',
|
|
||||||
}.get(self.attrs['format'], self.attrs['format'])
|
|
||||||
logger.debug('use %s', substituted)
|
|
||||||
return self.formats[substituted](obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_items(self, obj, pointer=None):
|
|
||||||
if 'items' in self.attrs:
|
|
||||||
items = self.attrs['items']
|
|
||||||
if isinstance(items, Validator):
|
|
||||||
validator = items
|
|
||||||
for index, element in enumerate(obj):
|
|
||||||
with self.catch_fail():
|
|
||||||
obj[index] = validator(element, pointer_join(pointer, index)) # noqa
|
|
||||||
return obj
|
|
||||||
elif isinstance(items, (list, tuple)):
|
|
||||||
additionals = self.attrs['additional_items']
|
|
||||||
validators = items
|
|
||||||
for index, element in enumerate(obj):
|
|
||||||
with self.catch_fail():
|
|
||||||
try:
|
|
||||||
validator = validators[index]
|
|
||||||
except IndexError:
|
|
||||||
if additionals is True:
|
|
||||||
return obj
|
|
||||||
elif additionals is False:
|
|
||||||
self.fail('Additional elements are forbidden',
|
|
||||||
obj,
|
|
||||||
pointer_join(pointer, index))
|
|
||||||
continue
|
|
||||||
validator = additionals
|
|
||||||
obj[index] = validator(element, pointer_join(pointer, index)) # noqa
|
|
||||||
return obj
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(items)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_max_items(self, obj, pointer=None):
|
|
||||||
if 'max_items' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count > self.attrs['max_items']:
|
|
||||||
self.fail('Too many items', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_max_length(self, obj, pointer=None):
|
|
||||||
if 'max_length' in self.attrs:
|
|
||||||
length = len(obj)
|
|
||||||
if length > self.attrs['max_length']:
|
|
||||||
self.fail('Too long', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_maximum(self, obj, pointer=None):
|
|
||||||
if 'maximum' in self.attrs:
|
|
||||||
if obj > self.attrs['maximum']:
|
|
||||||
self.fail('Too big number', obj, pointer)
|
|
||||||
if self.attrs['exclusive_maximum'] and obj == self.attrs['maximum']: # noqa
|
|
||||||
self.fail('Too big number', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_min_items(self, obj, pointer=None):
|
|
||||||
if 'min_items' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count < self.attrs['min_items']:
|
|
||||||
self.fail('Too few items', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_min_length(self, obj, pointer=None):
|
|
||||||
if 'min_length' in self.attrs:
|
|
||||||
length = len(obj)
|
|
||||||
if length < self.attrs['min_length']:
|
|
||||||
self.fail('Too short', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_minimum(self, obj, pointer=None):
|
|
||||||
if 'minimum' in self.attrs:
|
|
||||||
if obj < self.attrs['minimum']:
|
|
||||||
self.fail('Too low number', obj, pointer)
|
|
||||||
if self.attrs['exclusive_minimum'] and obj == self.attrs['minimum']: # noqa
|
|
||||||
self.fail('Too low number',
|
|
||||||
obj,
|
|
||||||
pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_pattern(self, obj, pointer=None):
|
|
||||||
if 'pattern' in self.attrs:
|
|
||||||
regex = re.compile(self.attrs['pattern'])
|
|
||||||
if not regex.search(obj):
|
|
||||||
self.fail('Does not match pattern', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_properties(self, obj, pointer=None):
|
|
||||||
validated = set()
|
|
||||||
pending = set(obj.keys())
|
|
||||||
|
|
||||||
for name, validator in self.attrs['properties'].items():
|
|
||||||
if name in obj:
|
|
||||||
with self.catch_fail():
|
|
||||||
pending.discard(name)
|
|
||||||
obj[name] = validator(obj[name], pointer_join(pointer, name)) # noqa
|
|
||||||
validated.add(name)
|
|
||||||
elif not validator.is_optional():
|
|
||||||
self.fail('Required property', obj, pointer)
|
|
||||||
|
|
||||||
for pattern, validator in self.attrs['pattern_properties'].items():
|
|
||||||
regex = re.compile(pattern)
|
|
||||||
for name, value in obj.items():
|
|
||||||
if regex.search(name):
|
|
||||||
with self.catch_fail():
|
|
||||||
pending.discard(name)
|
|
||||||
obj[name] = validator(obj[name], pointer_join(pointer, name)) # noqa
|
|
||||||
validated.add(name)
|
|
||||||
|
|
||||||
if not pending:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
if self.attrs['additional_properties'] is True:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
if self.attrs['additional_properties'] is False:
|
|
||||||
if len(obj) > len(validated):
|
|
||||||
self.fail('Additional properties are forbidden', obj, pointer) # noqa
|
|
||||||
return obj
|
|
||||||
|
|
||||||
validator = self.attrs['additional_properties']
|
|
||||||
for name, value in obj.items():
|
|
||||||
if name not in validated:
|
|
||||||
obj[name] = validator(value, pointer_join(pointer, name))
|
|
||||||
validated.add(name)
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_type(self, obj, pointer=None):
|
|
||||||
if 'type' in self.attrs:
|
|
||||||
types = self.attrs['type']
|
|
||||||
if not isinstance(types, sequence_types):
|
|
||||||
types = [types]
|
|
||||||
for type in types:
|
|
||||||
try:
|
|
||||||
if isinstance(type, Validator):
|
|
||||||
return type(obj)
|
|
||||||
elif type == "any":
|
|
||||||
return obj
|
|
||||||
elif type == "array" and self.is_array(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "boolean" and self.is_boolean(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "integer" and self.is_integer(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "null" and self.is_null(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "number" and self.is_number(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "object" and self.is_object(obj):
|
|
||||||
return obj
|
|
||||||
elif type == "string" and self.is_string(obj):
|
|
||||||
return obj
|
|
||||||
except ValidationError:
|
|
||||||
# let it, it may be good
|
|
||||||
pass
|
|
||||||
self.fail('Wrong type', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_unique_items(self, obj, pointer=None):
|
|
||||||
if self.attrs.get('unique_items'):
|
|
||||||
if len(obj) > len(set(json.dumps(element) for element in obj)):
|
|
||||||
self.fail('Elements must be unique', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
"""docstring for has_default"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_optional(self):
|
|
||||||
"""
|
|
||||||
True by default.
|
|
||||||
"""
|
|
||||||
return not self.attrs.get('required', False)
|
|
||||||
|
|
||||||
def fail(self, reason, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Called when validation fails.
|
|
||||||
"""
|
|
||||||
pointer = pointer_join(pointer)
|
|
||||||
err = ValidationError(reason, obj, pointer)
|
|
||||||
if self.fail_fast:
|
|
||||||
raise err
|
|
||||||
else:
|
|
||||||
self.errors.append(err)
|
|
||||||
return err
|
|
||||||
|
|
||||||
def catch_fail(self):
|
|
||||||
return FailCatcher(self)
|
|
||||||
|
|
||||||
|
|
||||||
class FailCatcher(object):
|
|
||||||
def __init__(self, validator):
|
|
||||||
self.validator = validator
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, type, value, tb):
|
|
||||||
if isinstance(value, ValidationError) and not self.validator.fail_fast:
|
|
||||||
self.validator.errors.append(value)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,678 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.draft04
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements JSON Schema draft04.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['compile', 'Draft04Validator']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os.path
|
|
||||||
import re
|
|
||||||
from copy import deepcopy
|
|
||||||
from decimal import Decimal
|
|
||||||
from six import integer_types, string_types
|
|
||||||
from six.moves.urllib.parse import urljoin
|
|
||||||
from .bases import ReferenceValidator, Validator
|
|
||||||
from .exceptions import CompilationError
|
|
||||||
from .factorize import register
|
|
||||||
from jsonspec.validators.exceptions import ValidationError
|
|
||||||
from jsonspec.validators.util import uncamel
|
|
||||||
from jsonspec.validators.pointer_util import pointer_join
|
|
||||||
from jsonspec import driver as json
|
|
||||||
|
|
||||||
sequence_types = (list, set, tuple)
|
|
||||||
number_types = (integer_types, float, Decimal)
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@register(spec='http://json-schema.org/draft-04/schema#')
|
|
||||||
def compile(schema, pointer, context, scope=None):
|
|
||||||
"""
|
|
||||||
Compiles schema with `JSON Schema`_ draft-04.
|
|
||||||
|
|
||||||
:param schema: obj to compile
|
|
||||||
:type schema: Mapping
|
|
||||||
:param pointer: uri of the schema
|
|
||||||
:type pointer: Pointer, str
|
|
||||||
:param context: context of this schema
|
|
||||||
:type context: Context
|
|
||||||
|
|
||||||
.. _`JSON Schema`: http://json-schema.org
|
|
||||||
"""
|
|
||||||
|
|
||||||
schm = deepcopy(schema)
|
|
||||||
|
|
||||||
scope = urljoin(scope or str(pointer), schm.pop('id', None))
|
|
||||||
|
|
||||||
if '$ref' in schema:
|
|
||||||
return ReferenceValidator(urljoin(scope, schema['$ref']), context)
|
|
||||||
|
|
||||||
attrs = {}
|
|
||||||
|
|
||||||
if 'additionalItems' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'additionalItems')
|
|
||||||
attrs['additional_items'] = schm.pop('additionalItems')
|
|
||||||
if isinstance(attrs['additional_items'], dict):
|
|
||||||
compiled = compile(attrs['additional_items'],
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
attrs['additional_items'] = compiled
|
|
||||||
elif not isinstance(attrs['additional_items'], bool):
|
|
||||||
raise CompilationError('wrong type for {}'.format('additional_items'), schema) # noqa
|
|
||||||
|
|
||||||
if 'additionalProperties' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'additionalProperties')
|
|
||||||
attrs['additional_properties'] = schm.pop('additionalProperties')
|
|
||||||
if isinstance(attrs['additional_properties'], dict):
|
|
||||||
compiled = compile(attrs['additional_properties'],
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
attrs['additional_properties'] = compiled
|
|
||||||
elif not isinstance(attrs['additional_properties'], bool):
|
|
||||||
raise CompilationError('wrong type for {}'.format('additional_properties'), schema) # noqa
|
|
||||||
|
|
||||||
if 'allOf' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'allOf')
|
|
||||||
attrs['all_of'] = schm.pop('allOf')
|
|
||||||
if isinstance(attrs['all_of'], (list, tuple)):
|
|
||||||
attrs['all_of'] = [compile(element, subpointer, context, scope) for element in attrs['all_of']] # noqa
|
|
||||||
else:
|
|
||||||
# should be a boolean
|
|
||||||
raise CompilationError('wrong type for {}'.format('allOf'), schema) # noqa
|
|
||||||
|
|
||||||
if 'anyOf' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'anyOf')
|
|
||||||
attrs['any_of'] = schm.pop('anyOf')
|
|
||||||
if isinstance(attrs['any_of'], (list, tuple)):
|
|
||||||
attrs['any_of'] = [compile(element, subpointer, context, scope) for element in attrs['any_of']] # noqa
|
|
||||||
else:
|
|
||||||
# should be a boolean
|
|
||||||
raise CompilationError('wrong type for {}'.format('anyOf'), schema) # noqa
|
|
||||||
|
|
||||||
if 'default' in schm:
|
|
||||||
attrs['default'] = schm.pop('default')
|
|
||||||
|
|
||||||
if 'dependencies' in schm:
|
|
||||||
attrs['dependencies'] = schm.pop('dependencies')
|
|
||||||
if not isinstance(attrs['dependencies'], dict):
|
|
||||||
raise CompilationError('dependencies must be an object', schema)
|
|
||||||
for key, value in attrs['dependencies'].items():
|
|
||||||
if isinstance(value, dict):
|
|
||||||
subpointer = os.path.join(pointer, 'dependencies', key)
|
|
||||||
attrs['dependencies'][key] = compile(value,
|
|
||||||
subpointer,
|
|
||||||
context,
|
|
||||||
scope)
|
|
||||||
elif not isinstance(value, sequence_types):
|
|
||||||
raise CompilationError('dependencies must be an array or object', schema) # noqa
|
|
||||||
|
|
||||||
if 'enum' in schm:
|
|
||||||
attrs['enum'] = schm.pop('enum')
|
|
||||||
if not isinstance(attrs['enum'], sequence_types):
|
|
||||||
raise CompilationError('enum must be a sequence', schema)
|
|
||||||
|
|
||||||
if 'exclusiveMaximum' in schm:
|
|
||||||
attrs['exclusive_maximum'] = schm.pop('exclusiveMaximum')
|
|
||||||
if not isinstance(attrs['exclusive_maximum'], bool):
|
|
||||||
raise CompilationError('exclusiveMaximum must be a boolean', schema)
|
|
||||||
|
|
||||||
if 'exclusiveMinimum' in schm:
|
|
||||||
attrs['exclusive_minimum'] = schm.pop('exclusiveMinimum')
|
|
||||||
if not isinstance(attrs['exclusive_minimum'], bool):
|
|
||||||
raise CompilationError('exclusiveMinimum must be a boolean', schema)
|
|
||||||
|
|
||||||
if 'format' in schm:
|
|
||||||
attrs['format'] = schm.pop('format')
|
|
||||||
if not isinstance(attrs['format'], string_types):
|
|
||||||
raise CompilationError('format must be a string', schema)
|
|
||||||
|
|
||||||
if 'items' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'items')
|
|
||||||
attrs['items'] = schm.pop('items')
|
|
||||||
if isinstance(attrs['items'], (list, tuple)):
|
|
||||||
# each value must be a json schema
|
|
||||||
attrs['items'] = [compile(element, subpointer, context, scope) for element in attrs['items']] # noqa
|
|
||||||
elif isinstance(attrs['items'], dict):
|
|
||||||
# value must be a json schema
|
|
||||||
attrs['items'] = compile(attrs['items'], subpointer, context, scope) # noqa
|
|
||||||
else:
|
|
||||||
# should be a boolean
|
|
||||||
raise CompilationError('wrong type for {}'.format('items'), schema) # noqa
|
|
||||||
|
|
||||||
if 'maximum' in schm:
|
|
||||||
attrs['maximum'] = schm.pop('maximum')
|
|
||||||
if not isinstance(attrs['maximum'], number_types):
|
|
||||||
raise CompilationError('maximum must be a number', schema)
|
|
||||||
|
|
||||||
if 'maxItems' in schm:
|
|
||||||
attrs['max_items'] = schm.pop('maxItems')
|
|
||||||
if not isinstance(attrs['max_items'], integer_types):
|
|
||||||
raise CompilationError('maxItems must be integer', schema)
|
|
||||||
|
|
||||||
if 'maxLength' in schm:
|
|
||||||
attrs['max_length'] = schm.pop('maxLength')
|
|
||||||
if not isinstance(attrs['max_length'], integer_types):
|
|
||||||
raise CompilationError('maxLength must be integer', schema)
|
|
||||||
|
|
||||||
if 'maxProperties' in schm:
|
|
||||||
attrs['max_properties'] = schm.pop('maxProperties')
|
|
||||||
if not isinstance(attrs['max_properties'], integer_types):
|
|
||||||
raise CompilationError('maxProperties must be integer', schema)
|
|
||||||
|
|
||||||
if 'minimum' in schm:
|
|
||||||
attrs['minimum'] = schm.pop('minimum')
|
|
||||||
if not isinstance(attrs['minimum'], number_types):
|
|
||||||
raise CompilationError('minimum must be a number', schema)
|
|
||||||
|
|
||||||
if 'minItems' in schm:
|
|
||||||
attrs['min_items'] = schm.pop('minItems')
|
|
||||||
if not isinstance(attrs['min_items'], integer_types):
|
|
||||||
raise CompilationError('minItems must be integer', schema)
|
|
||||||
|
|
||||||
if 'minLength' in schm:
|
|
||||||
attrs['min_length'] = schm.pop('minLength')
|
|
||||||
if not isinstance(attrs['min_length'], integer_types):
|
|
||||||
raise CompilationError('minLength must be integer', schema)
|
|
||||||
|
|
||||||
if 'minProperties' in schm:
|
|
||||||
attrs['min_properties'] = schm.pop('minProperties')
|
|
||||||
if not isinstance(attrs['min_properties'], integer_types):
|
|
||||||
raise CompilationError('minProperties must be integer', schema)
|
|
||||||
|
|
||||||
if 'multipleOf' in schm:
|
|
||||||
attrs['multiple_of'] = schm.pop('multipleOf')
|
|
||||||
if not isinstance(attrs['multiple_of'], number_types):
|
|
||||||
raise CompilationError('multipleOf must be a number', schema)
|
|
||||||
|
|
||||||
if 'not' in schm:
|
|
||||||
attrs['not'] = schm.pop('not')
|
|
||||||
if not isinstance(attrs['not'], dict):
|
|
||||||
raise CompilationError('not must be an object', schema)
|
|
||||||
subpointer = os.path.join(pointer, 'not')
|
|
||||||
attrs['not'] = compile(attrs['not'], subpointer, context, scope)
|
|
||||||
|
|
||||||
if 'oneOf' in schm:
|
|
||||||
subpointer = os.path.join(pointer, 'oneOf')
|
|
||||||
attrs['one_of'] = schm.pop('oneOf')
|
|
||||||
if isinstance(attrs['one_of'], (list, tuple)):
|
|
||||||
# each value must be a json schema
|
|
||||||
attrs['one_of'] = [compile(element, subpointer, context, scope) for element in attrs['one_of']] # noqa
|
|
||||||
else:
|
|
||||||
# should be a boolean
|
|
||||||
raise CompilationError('wrong type for {}'.format('oneOf'), schema)
|
|
||||||
|
|
||||||
if 'pattern' in schm:
|
|
||||||
attrs['pattern'] = schm.pop('pattern')
|
|
||||||
if not isinstance(attrs['pattern'], string_types):
|
|
||||||
raise CompilationError('pattern must be a string', schema)
|
|
||||||
|
|
||||||
if 'properties' in schm:
|
|
||||||
attrs['properties'] = schm.pop('properties')
|
|
||||||
if not isinstance(attrs['properties'], dict):
|
|
||||||
raise CompilationError('properties must be an object', schema)
|
|
||||||
for subname, subschema in attrs['properties'].items():
|
|
||||||
subpointer = pointer_join(pointer, subname)
|
|
||||||
compiled = compile(subschema, subpointer, context, scope)
|
|
||||||
attrs['properties'][subname] = compiled
|
|
||||||
|
|
||||||
if 'patternProperties' in schm:
|
|
||||||
attrs['pattern_properties'] = schm.pop('patternProperties')
|
|
||||||
if not isinstance(attrs['pattern_properties'], dict):
|
|
||||||
raise CompilationError('patternProperties must be an object', schema)
|
|
||||||
for subname, subschema in attrs['pattern_properties'].items():
|
|
||||||
subpointer = os.path.join(pointer, 'patternProperties', subname)
|
|
||||||
compiled = compile(subschema, subpointer, context, scope)
|
|
||||||
attrs['pattern_properties'][subname] = compiled
|
|
||||||
|
|
||||||
if 'required' in schm:
|
|
||||||
attrs['required'] = schm.pop('required')
|
|
||||||
if not isinstance(attrs['required'], list):
|
|
||||||
raise CompilationError('required must be a list', schema)
|
|
||||||
if len(attrs['required']) < 1:
|
|
||||||
raise CompilationError('required cannot be empty', schema)
|
|
||||||
|
|
||||||
if 'type' in schm:
|
|
||||||
attrs['type'] = schm.pop('type')
|
|
||||||
if isinstance(attrs['type'], string_types):
|
|
||||||
attrs['type'] = [attrs['type']]
|
|
||||||
elif not isinstance(attrs['type'], sequence_types):
|
|
||||||
raise CompilationError('type must be string or sequence', schema)
|
|
||||||
|
|
||||||
if 'uniqueItems' in schm:
|
|
||||||
attrs['unique_items'] = schm.pop('uniqueItems')
|
|
||||||
if not isinstance(attrs['unique_items'], bool):
|
|
||||||
raise CompilationError('type must be boolean', schema)
|
|
||||||
|
|
||||||
return Draft04Validator(attrs, str(pointer), context.formats)
|
|
||||||
|
|
||||||
|
|
||||||
class Draft04Validator(Validator):
|
|
||||||
"""
|
|
||||||
Implements `JSON Schema`_ draft-04 validation.
|
|
||||||
|
|
||||||
:ivar attrs: attributes to validate against
|
|
||||||
:ivar uri: uri of the current validator
|
|
||||||
:ivar formats: mapping of available formats
|
|
||||||
|
|
||||||
>>> validator = Draft04Validator({'min_length': 4})
|
|
||||||
>>> assert validator('this is sparta')
|
|
||||||
|
|
||||||
.. _`JSON Schema`: http://json-schema.org
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, attrs, uri=None, formats=None):
|
|
||||||
attrs = {uncamel(k): v for k, v in attrs.items()}
|
|
||||||
|
|
||||||
self.formats = formats or {}
|
|
||||||
self.attrs = attrs
|
|
||||||
self.attrs.setdefault('additional_items', True)
|
|
||||||
self.attrs.setdefault('additional_properties', True)
|
|
||||||
self.attrs.setdefault('exclusive_maximum', False),
|
|
||||||
self.attrs.setdefault('exclusive_minimum', False),
|
|
||||||
self.attrs.setdefault('pattern_properties', {})
|
|
||||||
self.attrs.setdefault('properties', {})
|
|
||||||
self.uri = uri
|
|
||||||
self.default = self.attrs.get('default', None)
|
|
||||||
self.fail_fast = True
|
|
||||||
self.errors = []
|
|
||||||
|
|
||||||
def validate(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Validate object against validator
|
|
||||||
|
|
||||||
:param obj: the object to validate
|
|
||||||
"""
|
|
||||||
|
|
||||||
pointer = pointer or '#'
|
|
||||||
|
|
||||||
validator = deepcopy(self)
|
|
||||||
validator.errors = []
|
|
||||||
validator.fail_fast = False
|
|
||||||
|
|
||||||
obj = deepcopy(obj)
|
|
||||||
obj = validator.validate_enum(obj, pointer)
|
|
||||||
obj = validator.validate_type(obj, pointer)
|
|
||||||
obj = validator.validate_not(obj, pointer)
|
|
||||||
obj = validator.validate_all_of(obj, pointer)
|
|
||||||
obj = validator.validate_any_of(obj, pointer)
|
|
||||||
obj = validator.validate_one_of(obj, pointer)
|
|
||||||
|
|
||||||
if self.is_array(obj):
|
|
||||||
obj = validator.validate_items(obj, pointer)
|
|
||||||
obj = validator.validate_max_items(obj, pointer)
|
|
||||||
obj = validator.validate_min_items(obj, pointer)
|
|
||||||
obj = validator.validate_unique_items(obj, pointer)
|
|
||||||
elif self.is_number(obj):
|
|
||||||
obj = validator.validate_maximum(obj, pointer)
|
|
||||||
obj = validator.validate_minimum(obj, pointer)
|
|
||||||
obj = validator.validate_multiple_of(obj, pointer)
|
|
||||||
elif self.is_object(obj):
|
|
||||||
obj = validator.validate_required(obj, pointer)
|
|
||||||
obj = validator.validate_max_properties(obj, pointer)
|
|
||||||
obj = validator.validate_min_properties(obj, pointer)
|
|
||||||
obj = validator.validate_dependencies(obj, pointer)
|
|
||||||
obj = validator.validate_properties(obj, pointer)
|
|
||||||
obj = validator.validate_default_properties(obj, pointer)
|
|
||||||
elif self.is_string(obj):
|
|
||||||
obj = validator.validate_max_length(obj, pointer)
|
|
||||||
obj = validator.validate_min_length(obj, pointer)
|
|
||||||
obj = validator.validate_pattern(obj, pointer)
|
|
||||||
obj = validator.validate_format(obj, pointer)
|
|
||||||
|
|
||||||
if validator.errors:
|
|
||||||
print(validator.errors)
|
|
||||||
raise ValidationError('multiple errors',
|
|
||||||
obj,
|
|
||||||
errors=validator.errors)
|
|
||||||
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def is_array(self, obj):
|
|
||||||
return isinstance(obj, sequence_types)
|
|
||||||
|
|
||||||
def is_boolean(self, obj):
|
|
||||||
return isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_integer(self, obj):
|
|
||||||
return isinstance(obj, integer_types) and not isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_number(self, obj):
|
|
||||||
return isinstance(obj, number_types) and not isinstance(obj, bool)
|
|
||||||
|
|
||||||
def is_object(self, obj):
|
|
||||||
return isinstance(obj, dict)
|
|
||||||
|
|
||||||
def is_string(self, obj):
|
|
||||||
return isinstance(obj, string_types)
|
|
||||||
|
|
||||||
def has_default(self):
|
|
||||||
return 'default' in self.attrs
|
|
||||||
|
|
||||||
def validate_all_of(self, obj, pointer=None):
|
|
||||||
for validator in self.attrs.get('all_of', []):
|
|
||||||
obj = validator(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_any_of(self, obj, pointer=None):
|
|
||||||
if 'any_of' in self.attrs:
|
|
||||||
for validator in self.attrs['any_of']:
|
|
||||||
try:
|
|
||||||
obj = validator(obj)
|
|
||||||
return obj
|
|
||||||
except ValidationError:
|
|
||||||
pass
|
|
||||||
self.fail('Not in any_of', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_default_properties(self, obj, pointer=None):
|
|
||||||
# Reinject defaults from properties.
|
|
||||||
for name, validator in self.attrs.get('properties', {}).items():
|
|
||||||
if name not in obj and validator.has_default():
|
|
||||||
obj[name] = deepcopy(validator.default)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_dependencies(self, obj, pointer=None):
|
|
||||||
for key, dependencies in self.attrs.get('dependencies', {}).items():
|
|
||||||
if key in obj:
|
|
||||||
if isinstance(dependencies, sequence_types):
|
|
||||||
for dep in set(dependencies) - set(obj.keys()):
|
|
||||||
self.fail('Missing property', obj, pointer)
|
|
||||||
else:
|
|
||||||
dependencies(obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_enum(self, obj, pointer=None):
|
|
||||||
if 'enum' in self.attrs:
|
|
||||||
if obj not in self.attrs['enum']:
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_format(self, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
================= ============
|
|
||||||
Expected draft04 Alias of
|
|
||||||
----------------- ------------
|
|
||||||
date-time rfc3339.datetime
|
|
||||||
email email
|
|
||||||
hostname hostname
|
|
||||||
ipv4 ipv4
|
|
||||||
ipv6 ipv6
|
|
||||||
uri uri
|
|
||||||
================= ============
|
|
||||||
|
|
||||||
"""
|
|
||||||
if 'format' in self.attrs:
|
|
||||||
substituted = {
|
|
||||||
'date-time': 'rfc3339.datetime',
|
|
||||||
'email': 'email',
|
|
||||||
'hostname': 'hostname',
|
|
||||||
'ipv4': 'ipv4',
|
|
||||||
'ipv6': 'ipv6',
|
|
||||||
'uri': 'uri',
|
|
||||||
}.get(self.attrs['format'], self.attrs['format'])
|
|
||||||
logger.debug('use %s', substituted)
|
|
||||||
try:
|
|
||||||
return self.formats[substituted](obj)
|
|
||||||
except ValidationError as error:
|
|
||||||
logger.error(error)
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_items(self, obj, pointer=None):
|
|
||||||
if 'items' in self.attrs:
|
|
||||||
items = self.attrs['items']
|
|
||||||
if isinstance(items, Validator):
|
|
||||||
validator = items
|
|
||||||
for index, element in enumerate(obj):
|
|
||||||
with self.catch_fail():
|
|
||||||
obj[index] = validator(element, pointer_join(pointer, index)) # noqa
|
|
||||||
return obj
|
|
||||||
elif isinstance(items, (list, tuple)):
|
|
||||||
additionals = self.attrs['additional_items']
|
|
||||||
validators = items
|
|
||||||
for index, element in enumerate(obj):
|
|
||||||
with self.catch_fail():
|
|
||||||
try:
|
|
||||||
validator = validators[index]
|
|
||||||
except IndexError:
|
|
||||||
if additionals is True:
|
|
||||||
return obj
|
|
||||||
elif additionals is False:
|
|
||||||
self.fail('Forbidden value',
|
|
||||||
obj,
|
|
||||||
pointer=pointer_join(self.uri, index))
|
|
||||||
continue
|
|
||||||
validator = additionals
|
|
||||||
obj[index] = validator(element, pointer_join(pointer, index)) # noqa
|
|
||||||
return obj
|
|
||||||
else:
|
|
||||||
raise NotImplementedError(items)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_maximum(self, obj, pointer=None):
|
|
||||||
if 'maximum' in self.attrs:
|
|
||||||
m = self.attrs['maximum']
|
|
||||||
if obj < m:
|
|
||||||
return obj
|
|
||||||
exclusive = self.attrs['exclusive_maximum']
|
|
||||||
if not exclusive and (obj == m):
|
|
||||||
return obj
|
|
||||||
self.fail('Exceeded maximum', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_max_items(self, obj, pointer=None):
|
|
||||||
if 'max_items' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count > self.attrs['max_items']:
|
|
||||||
self.fail('Too many elements', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_max_length(self, obj, pointer=None):
|
|
||||||
if 'max_length' in self.attrs:
|
|
||||||
length = len(obj)
|
|
||||||
if length > self.attrs['max_length']:
|
|
||||||
self.fail('Too long', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_max_properties(self, obj, pointer=None):
|
|
||||||
if 'max_properties' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count > self.attrs['max_properties']:
|
|
||||||
self.fail('Too many properties', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_minimum(self, obj, pointer=None):
|
|
||||||
if 'minimum' in self.attrs:
|
|
||||||
m = self.attrs['minimum']
|
|
||||||
if obj > m:
|
|
||||||
return obj
|
|
||||||
exclusive = self.attrs['exclusive_minimum']
|
|
||||||
if not exclusive and (obj == m):
|
|
||||||
return obj
|
|
||||||
self.fail('Too small', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_min_items(self, obj, pointer=None):
|
|
||||||
if 'min_items' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count < self.attrs['min_items']:
|
|
||||||
self.fail('Too few elements', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_min_length(self, obj, pointer=None):
|
|
||||||
if 'min_length' in self.attrs:
|
|
||||||
length = len(obj)
|
|
||||||
if length < self.attrs['min_length']:
|
|
||||||
self.fail('Too short', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_min_properties(self, obj, pointer=None):
|
|
||||||
if 'min_properties' in self.attrs:
|
|
||||||
count = len(obj)
|
|
||||||
if count < self.attrs['min_properties']:
|
|
||||||
self.fail('Too few properties', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_multiple_of(self, obj, pointer=None):
|
|
||||||
if 'multiple_of' in self.attrs:
|
|
||||||
factor = Decimal(str(self.attrs['multiple_of']))
|
|
||||||
orig = Decimal(str(obj))
|
|
||||||
if orig % factor != 0:
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_not(self, obj, pointer=None):
|
|
||||||
if 'not' in self.attrs:
|
|
||||||
try:
|
|
||||||
validator = self.attrs['not']
|
|
||||||
validator(obj)
|
|
||||||
except ValidationError:
|
|
||||||
return obj
|
|
||||||
else:
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_one_of(self, obj, pointer=None):
|
|
||||||
if 'one_of' in self.attrs:
|
|
||||||
validated = 0
|
|
||||||
for validator in self.attrs['one_of']:
|
|
||||||
try:
|
|
||||||
validated_obj = validator(obj)
|
|
||||||
validated += 1
|
|
||||||
except ValidationError:
|
|
||||||
pass
|
|
||||||
if not validated:
|
|
||||||
self.fail('Validates noone', obj)
|
|
||||||
if validated == 1:
|
|
||||||
return validated_obj
|
|
||||||
else:
|
|
||||||
self.fail('Validates more than once', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_pattern(self, obj, pointer=None):
|
|
||||||
if 'pattern' in self.attrs:
|
|
||||||
pattern = self.attrs['pattern']
|
|
||||||
if re.search(pattern, obj):
|
|
||||||
return obj
|
|
||||||
self.fail('Forbidden value', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_properties(self, obj, pointer=None):
|
|
||||||
validated = set()
|
|
||||||
pending = set(obj.keys())
|
|
||||||
response = {}
|
|
||||||
|
|
||||||
if not obj:
|
|
||||||
return response
|
|
||||||
|
|
||||||
for name, validator in self.attrs['properties'].items():
|
|
||||||
if name in obj:
|
|
||||||
with self.catch_fail():
|
|
||||||
pending.discard(name)
|
|
||||||
obj[name] = validator(obj[name], pointer_join(pointer, name)) # noqa
|
|
||||||
validated.add(name)
|
|
||||||
|
|
||||||
for pattern, validator in self.attrs['pattern_properties'].items():
|
|
||||||
for name in sorted(obj.keys()):
|
|
||||||
if re.search(pattern, name):
|
|
||||||
with self.catch_fail():
|
|
||||||
pending.discard(name)
|
|
||||||
obj[name] = validator(obj[name], pointer_join(pointer, name)) # noqa
|
|
||||||
validated.add(name)
|
|
||||||
|
|
||||||
if not pending:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
additionals = self.attrs['additional_properties']
|
|
||||||
if additionals is True:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
if additionals is False:
|
|
||||||
self.fail('Forbidden additional properties', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
validator = additionals
|
|
||||||
for name in sorted(pending):
|
|
||||||
obj[name] = validator(obj.pop(name), pointer_join(pointer, name)) # noqa
|
|
||||||
validated.add(name)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_required(self, obj, pointer=None):
|
|
||||||
if 'required' in self.attrs:
|
|
||||||
for name in self.attrs['required']:
|
|
||||||
if name not in obj:
|
|
||||||
self.fail('Missing property', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_type(self, obj, pointer=None):
|
|
||||||
if 'type' in self.attrs:
|
|
||||||
types = self.attrs['type']
|
|
||||||
if isinstance(types, string_types):
|
|
||||||
types = [types]
|
|
||||||
|
|
||||||
for t in types:
|
|
||||||
if t == 'array' and self.is_array(obj):
|
|
||||||
return obj
|
|
||||||
if t == 'boolean' and self.is_boolean(obj):
|
|
||||||
return obj
|
|
||||||
if t == 'integer' and self.is_integer(obj):
|
|
||||||
return obj
|
|
||||||
if t == 'number' and self.is_number(obj):
|
|
||||||
return obj
|
|
||||||
if t == 'null' and obj is None:
|
|
||||||
return obj
|
|
||||||
if t == 'object' and self.is_object(obj):
|
|
||||||
return obj
|
|
||||||
if t == 'string' and self.is_string(obj):
|
|
||||||
return obj
|
|
||||||
|
|
||||||
self.fail('Wrong type', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def validate_unique_items(self, obj, pointer=None):
|
|
||||||
if self.attrs.get('unique_items'):
|
|
||||||
if len(obj) > len(set(json.dumps(element) for element in obj)):
|
|
||||||
self.fail('Elements must be unique', obj, pointer)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def is_optional(self):
|
|
||||||
"""
|
|
||||||
Returns True, beceause it is meaningless in draft04.
|
|
||||||
"""
|
|
||||||
logger.warn('asking for is_optional')
|
|
||||||
return True
|
|
||||||
|
|
||||||
def fail(self, reason, obj, pointer=None):
|
|
||||||
"""
|
|
||||||
Called when validation fails.
|
|
||||||
"""
|
|
||||||
pointer = pointer_join(pointer)
|
|
||||||
err = ValidationError(reason, obj, pointer)
|
|
||||||
if self.fail_fast:
|
|
||||||
raise err
|
|
||||||
else:
|
|
||||||
self.errors.append(err)
|
|
||||||
return err
|
|
||||||
|
|
||||||
def catch_fail(self):
|
|
||||||
return FailCatcher(self)
|
|
||||||
|
|
||||||
|
|
||||||
class FailCatcher(object):
|
|
||||||
def __init__(self, validator):
|
|
||||||
self.validator = validator
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, type, value, tb):
|
|
||||||
if isinstance(value, ValidationError) and not self.validator.fail_fast:
|
|
||||||
self.validator.errors.append(value)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.exceptions
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['CompilationError', 'ReferenceError', 'ValidationError']
|
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
|
|
||||||
class CompilationError(Exception):
|
|
||||||
"""Raised while schema parsing"""
|
|
||||||
def __init__(self, message, schema):
|
|
||||||
super(CompilationError, self).__init__(message, schema)
|
|
||||||
self.schema = schema
|
|
||||||
|
|
||||||
|
|
||||||
class ReferenceError(Exception):
|
|
||||||
"""Raised while reference error"""
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(ReferenceError, self).__init__(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(ValueError):
|
|
||||||
"""Raised when validation fails"""
|
|
||||||
def __init__(self, reason, obj=None, pointer=None, errors=None):
|
|
||||||
"""
|
|
||||||
:param reason: the reason failing
|
|
||||||
:param obj: the obj that fails
|
|
||||||
:param errors: sub errors, if they exists
|
|
||||||
"""
|
|
||||||
super(ValidationError, self).__init__(reason, obj)
|
|
||||||
self.obj = obj
|
|
||||||
self.pointer = pointer
|
|
||||||
|
|
||||||
self.errors = set()
|
|
||||||
if isinstance(errors, (list, tuple, set)):
|
|
||||||
self.errors.update(errors)
|
|
||||||
elif isinstance(errors, Exception):
|
|
||||||
self.errors.add(errors)
|
|
||||||
|
|
||||||
def flatten(self):
|
|
||||||
"""
|
|
||||||
Flatten nested errors.
|
|
||||||
|
|
||||||
{pointer: reasons}
|
|
||||||
"""
|
|
||||||
return flatten(self)
|
|
||||||
|
|
||||||
|
|
||||||
def flatten(error):
|
|
||||||
def iter_it(src):
|
|
||||||
if isinstance(src, (list, set, tuple)):
|
|
||||||
for error in src:
|
|
||||||
for pointer, reason in iter_it(error):
|
|
||||||
yield pointer, reason
|
|
||||||
if isinstance(src, ValidationError):
|
|
||||||
if src.errors:
|
|
||||||
for pointer, reason in iter_it(src.errors):
|
|
||||||
yield pointer, reason
|
|
||||||
if src.pointer:
|
|
||||||
yield src.pointer, src.args[0]
|
|
||||||
|
|
||||||
data = defaultdict(set)
|
|
||||||
for pointer, reason in iter_it(error):
|
|
||||||
data[pointer].add(reason)
|
|
||||||
return dict(data)
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.factorize
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
__all__ = ['Context', 'Factory', 'register']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from functools import partial
|
|
||||||
from jsonspec.pointer import DocumentPointer
|
|
||||||
from jsonspec.pointer.exceptions import ExtractError
|
|
||||||
from jsonspec.reference import LocalRegistry
|
|
||||||
from .exceptions import CompilationError
|
|
||||||
from .formats import FormatRegistry
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Context(object):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:ivar factory: global factory
|
|
||||||
:ivar registry: the current registry
|
|
||||||
:ivar spec: the current spec
|
|
||||||
:ivar formats: the current formats exposed
|
|
||||||
"""
|
|
||||||
def __init__(self, factory, registry, spec=None, formats=None):
|
|
||||||
self.factory = factory
|
|
||||||
self.registry = registry
|
|
||||||
self.spec = spec
|
|
||||||
self.formats = formats
|
|
||||||
|
|
||||||
def __call__(self, schema, pointer):
|
|
||||||
return self.factory(schema, pointer, self.spec)
|
|
||||||
|
|
||||||
def resolve(self, pointer):
|
|
||||||
try:
|
|
||||||
dp = DocumentPointer(pointer)
|
|
||||||
if dp.is_inner():
|
|
||||||
logger.debug('resolve inner %s', pointer)
|
|
||||||
return self.factory.local(self.registry.resolve(pointer),
|
|
||||||
pointer,
|
|
||||||
self.registry,
|
|
||||||
self.spec)
|
|
||||||
|
|
||||||
logger.debug('resolve outside %s', pointer)
|
|
||||||
return self.factory(self.registry.resolve(pointer),
|
|
||||||
pointer,
|
|
||||||
self.spec)
|
|
||||||
except ExtractError as error:
|
|
||||||
raise CompilationError({}, error)
|
|
||||||
|
|
||||||
|
|
||||||
class Factory(object):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:ivar provider: global registry
|
|
||||||
:ivar spec: default spec
|
|
||||||
"""
|
|
||||||
|
|
||||||
spec = 'http://json-schema.org/draft-04/schema#'
|
|
||||||
compilers = {}
|
|
||||||
|
|
||||||
def __init__(self, provider=None, spec=None, formats=None):
|
|
||||||
self.provider = provider or {}
|
|
||||||
self.spec = spec or self.spec
|
|
||||||
if not isinstance(formats, FormatRegistry):
|
|
||||||
formats = FormatRegistry(formats)
|
|
||||||
self.formats = formats
|
|
||||||
|
|
||||||
def __call__(self, schema, pointer, spec=None):
|
|
||||||
try:
|
|
||||||
spec = schema.get('$schema', spec or self.spec)
|
|
||||||
compiler = self.compilers[spec]
|
|
||||||
except KeyError:
|
|
||||||
raise CompilationError('{!r} not registered'.format(spec), schema)
|
|
||||||
|
|
||||||
registry = LocalRegistry(schema, self.provider)
|
|
||||||
local = DocumentPointer(pointer)
|
|
||||||
|
|
||||||
if local.document:
|
|
||||||
registry[local.document] = schema
|
|
||||||
local.document = '<local>'
|
|
||||||
context = Context(self, registry, spec, self.formats)
|
|
||||||
return compiler(schema, pointer, context)
|
|
||||||
|
|
||||||
def local(self, schema, pointer, registry, spec=None):
|
|
||||||
try:
|
|
||||||
spec = schema.get('$schema', spec or self.spec)
|
|
||||||
compiler = self.compilers[spec]
|
|
||||||
except KeyError:
|
|
||||||
raise CompilationError('{!r} not registered'.format(spec))
|
|
||||||
|
|
||||||
context = Context(self, registry, spec, self.formats)
|
|
||||||
return compiler(schema, pointer, context)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, spec, compiler):
|
|
||||||
cls.compilers[spec] = compiler
|
|
||||||
return compiler
|
|
||||||
|
|
||||||
|
|
||||||
def register(compiler=None, spec=None):
|
|
||||||
"""
|
|
||||||
Expose compiler to factory.
|
|
||||||
|
|
||||||
:param compiler: the callable to expose
|
|
||||||
:type compiler: callable
|
|
||||||
:param spec: name of the spec
|
|
||||||
:type spec: str
|
|
||||||
|
|
||||||
It can be used as a decorator::
|
|
||||||
|
|
||||||
@register(spec='my:first:spec')
|
|
||||||
def my_compiler(schema, pointer, context):
|
|
||||||
return Validator(schema)
|
|
||||||
|
|
||||||
or as a function::
|
|
||||||
|
|
||||||
def my_compiler(schema, pointer, context):
|
|
||||||
return Validator(schema)
|
|
||||||
|
|
||||||
register(my_compiler, 'my:second:spec')
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not spec:
|
|
||||||
raise CompilationError('Spec is required')
|
|
||||||
if not compiler:
|
|
||||||
return partial(register, spec=spec)
|
|
||||||
return Factory.register(spec, compiler)
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.formats
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = ['register', 'FormatRegistry']
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from functools import partial
|
|
||||||
from pkg_resources import iter_entry_points, DistributionNotFound
|
|
||||||
from .exceptions import CompilationError
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class FormatRegistry(object):
|
|
||||||
"""
|
|
||||||
Declare callables that must validate strings.
|
|
||||||
|
|
||||||
Callables are be injected in two ways:
|
|
||||||
|
|
||||||
- Using the instance register method:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
registry = FormatRegistry()
|
|
||||||
registry.register('foo', bar)
|
|
||||||
|
|
||||||
- Using the class register method:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
FormatRegistry.register('foo', bar)
|
|
||||||
registry = FormatRegistry()
|
|
||||||
assert 'foo' in registry
|
|
||||||
|
|
||||||
- Every callables declared into setuptools ``entry_points``
|
|
||||||
are automatically loaded.
|
|
||||||
|
|
||||||
For example, with this setup.cfg:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
jsonspec.validators.formats =
|
|
||||||
date-time = jsonspec.validators.util:validate_datetime
|
|
||||||
email = jsonspec.validators.util:validate_email
|
|
||||||
hostname = jsonspec.validators.util:validate_hostname
|
|
||||||
ipv4 = jsonspec.validators.util:validate_ipv4
|
|
||||||
ipv6 = jsonspec.validators.util:validate_ipv6
|
|
||||||
uri = jsonspec.validators.util:validate_uri
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
registry = FormatRegistry()
|
|
||||||
assert 'date-time' in registry
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
namespace = 'jsonspec.validators.formats'
|
|
||||||
custom = {}
|
|
||||||
|
|
||||||
def __init__(self, data=None, namespace=None):
|
|
||||||
self.custom = data or self.custom
|
|
||||||
self.loaded = {}
|
|
||||||
self.fallback = {}
|
|
||||||
self.namespace = namespace or self.namespace
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
if name in self.custom:
|
|
||||||
return self.custom[name]
|
|
||||||
if name in self.loaded:
|
|
||||||
return self.loaded[name]
|
|
||||||
if name in self.fallback:
|
|
||||||
return self.fallback[name]
|
|
||||||
return self.load(name)
|
|
||||||
|
|
||||||
def __contains__(self, name):
|
|
||||||
return name in self.custom or name in self.loaded
|
|
||||||
|
|
||||||
def load(self, name):
|
|
||||||
error = None
|
|
||||||
|
|
||||||
for entrypoint in iter_entry_points(self.namespace):
|
|
||||||
try:
|
|
||||||
if entrypoint.name == name:
|
|
||||||
self.loaded[name] = entrypoint.load()
|
|
||||||
return self.loaded[name]
|
|
||||||
except DistributionNotFound as error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if error:
|
|
||||||
logger.warn('Unable to load %s: %s is missing', name, error)
|
|
||||||
else:
|
|
||||||
logger.warn('%s is not defined', name)
|
|
||||||
|
|
||||||
def fallback(obj):
|
|
||||||
logger.info('Unable to validate %s: %s is missing', name, error)
|
|
||||||
return obj
|
|
||||||
fallback.__doc__ = 'fallback for {!r} validation'.format(name)
|
|
||||||
self.fallback[name] = fallback
|
|
||||||
return self.fallback[name]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, name, func):
|
|
||||||
cls.custom[name] = func
|
|
||||||
|
|
||||||
|
|
||||||
def register(func=None, name=None):
|
|
||||||
"""
|
|
||||||
Expose compiler to factory.
|
|
||||||
|
|
||||||
:param func: the callable to expose
|
|
||||||
:type func: callable
|
|
||||||
:param name: name of format
|
|
||||||
:type name: str
|
|
||||||
|
|
||||||
It can be used as a decorator::
|
|
||||||
|
|
||||||
@register(name='my:validator')
|
|
||||||
def my_validator(obj):
|
|
||||||
if obj is True:
|
|
||||||
return obj
|
|
||||||
raise ValidationError('obj is not true')
|
|
||||||
|
|
||||||
or as a function::
|
|
||||||
|
|
||||||
def my_validator(obj):
|
|
||||||
if obj is True:
|
|
||||||
return obj
|
|
||||||
raise ValidationError('obj is not true')
|
|
||||||
|
|
||||||
@register(name='my:validator')
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not name:
|
|
||||||
raise CompilationError('Name is required')
|
|
||||||
if not func:
|
|
||||||
return partial(register, name=name)
|
|
||||||
return FormatRegistry.register(name, func)
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.pointer_util
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
|
|
||||||
def pointer_join(pre, *parts):
|
|
||||||
resp = str(pre or '#')
|
|
||||||
if resp == '#/':
|
|
||||||
resp == '#'
|
|
||||||
for part in parts:
|
|
||||||
if not resp.endswith('/'):
|
|
||||||
resp += '/'
|
|
||||||
resp += str(part)
|
|
||||||
if resp == '#':
|
|
||||||
resp = '#/'
|
|
||||||
return resp
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
"""
|
|
||||||
jsonspec.validators.util
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
__all__ = []
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
from copy import deepcopy
|
|
||||||
from decimal import Decimal
|
|
||||||
from datetime import tzinfo, timedelta, datetime, date
|
|
||||||
from six import text_type
|
|
||||||
from six import integer_types
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
from .exceptions import ValidationError
|
|
||||||
|
|
||||||
number_types = (integer_types, float, Decimal)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
HOSTNAME_TOKENS = re.compile('(?!-)[a-z\d-]{1,63}(?<!-)$', re.IGNORECASE)
|
|
||||||
HOSTNAME_LAST_TOKEN = re.compile('[a-z]+$', re.IGNORECASE)
|
|
||||||
EMAIL = re.compile('[^@]+@[^@]+\.[^@]+')
|
|
||||||
|
|
||||||
CSS_COLORS = set([
|
|
||||||
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige',
|
|
||||||
'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown',
|
|
||||||
'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral',
|
|
||||||
'cornflowerblue', 'cornsilk', 'crimson', 'darkblue', 'darkcyan',
|
|
||||||
'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta',
|
|
||||||
'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon',
|
|
||||||
'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise',
|
|
||||||
'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue',
|
|
||||||
'feldspar', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia',
|
|
||||||
'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green',
|
|
||||||
'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory',
|
|
||||||
'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
|
|
||||||
'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow',
|
|
||||||
'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen',
|
|
||||||
'lightskyblue', 'lightslateblue', 'lightslategray', 'lightsteelblue',
|
|
||||||
'lightyellow', 'lime', 'limegreen', 'linen', 'maroon', 'mediumaquamarine',
|
|
||||||
'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen',
|
|
||||||
'mediumslateblue', 'mediumspringgreen', 'mediumturquoise',
|
|
||||||
'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
|
|
||||||
'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange',
|
|
||||||
'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise',
|
|
||||||
'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum',
|
|
||||||
'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown',
|
|
||||||
'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver',
|
|
||||||
'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue',
|
|
||||||
'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violetred', 'wheat',
|
|
||||||
'white', 'whitesmoke', 'yellow', 'yellowgreen'])
|
|
||||||
|
|
||||||
|
|
||||||
def uncamel(name):
|
|
||||||
"""converts camelcase to underscore
|
|
||||||
>>> uncamel('fooBar')
|
|
||||||
'foo_bar'
|
|
||||||
>>> uncamel('FooBar')
|
|
||||||
'foo_bar'
|
|
||||||
>>> uncamel('_fooBar')
|
|
||||||
'_foo_bar'
|
|
||||||
>>> uncamel('_FooBar')
|
|
||||||
'__foo_bar'
|
|
||||||
"""
|
|
||||||
response, name = name[0].lower(), name[1:]
|
|
||||||
for n in name:
|
|
||||||
if n.isupper():
|
|
||||||
response += '_' + n.lower()
|
|
||||||
else:
|
|
||||||
response += n
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class offset(tzinfo):
|
|
||||||
def __init__(self, value):
|
|
||||||
self.value = value
|
|
||||||
# super(tzinfo, self).__init__()
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
hours, minutes = self.value.split(':', 1)
|
|
||||||
return timedelta(hours=int(hours), minutes=int(minutes))
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return '{}'.format(self.value)
|
|
||||||
|
|
||||||
|
|
||||||
def rfc3339_to_datetime(data):
|
|
||||||
"""convert a rfc3339 date representation into a Python datetime"""
|
|
||||||
try:
|
|
||||||
ts = time.strptime(data, '%Y-%m-%d')
|
|
||||||
return date(*ts[:3])
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
dt, _, tz = data.partition('Z')
|
|
||||||
if tz:
|
|
||||||
tz = offset(tz)
|
|
||||||
else:
|
|
||||||
tz = offset('00:00')
|
|
||||||
if '.' in dt and dt.rsplit('.', 1)[-1].isdigit():
|
|
||||||
ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S.%f')
|
|
||||||
else:
|
|
||||||
ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S')
|
|
||||||
return datetime(*ts[:6], tzinfo=tz)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError('date-time {!r} is not a valid rfc3339 date representation'.format(data)) # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def validate_css_color(obj):
|
|
||||||
color = obj.lower()
|
|
||||||
if len(color) == 7 and re.match('^#[0-9a-f]{6}$', color):
|
|
||||||
return obj
|
|
||||||
elif len(color) == 4 and re.match('^#[0-9a-f]{3}$', color):
|
|
||||||
return obj
|
|
||||||
elif color not in CSS_COLORS:
|
|
||||||
raise ValidationError('Not a css color {!r}'.format(obj))
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_rfc3339_datetime(obj):
|
|
||||||
try:
|
|
||||||
rfc3339_to_datetime(obj)
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('{!r} is not a valid datetime', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_utc_datetime(obj):
|
|
||||||
if not obj.endswith('Z'):
|
|
||||||
raise ValidationError('{!r} is not a valid datetime', obj)
|
|
||||||
obj = obj[:-1]
|
|
||||||
if '.' in obj:
|
|
||||||
obj, milli = obj.split('.', 1)
|
|
||||||
if not milli.isdigit():
|
|
||||||
raise ValidationError('{!r} is not a valid datetime', obj)
|
|
||||||
|
|
||||||
try:
|
|
||||||
time.strptime(obj, '%Y-%m-%dT%H:%M:%S')
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('{!r} is not a valid datetime', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_utc_date(obj):
|
|
||||||
try:
|
|
||||||
time.strptime(obj, '%Y-%m-%d')
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('{!r} is not a valid date', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_utc_time(obj):
|
|
||||||
try:
|
|
||||||
time.strptime(obj, '%H:%M:%S')
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('{!r} is not a valid time', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_utc_millisec(obj):
|
|
||||||
try:
|
|
||||||
if not isinstance(obj, number_types):
|
|
||||||
raise TypeError
|
|
||||||
datetime.utcfromtimestamp(obj/1000)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise ValidationError('{!r} is not a valid utc millis', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_email(obj):
|
|
||||||
if not EMAIL.match(obj):
|
|
||||||
raise ValidationError('{!r} is not defined')
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_hostname(obj):
|
|
||||||
try:
|
|
||||||
host = deepcopy(obj)
|
|
||||||
if len(host) > 255:
|
|
||||||
raise ValueError
|
|
||||||
if host[-1] == '.':
|
|
||||||
host = host[:-1]
|
|
||||||
tokens = host.split('.')
|
|
||||||
if not all(HOSTNAME_TOKENS.match(x) for x in tokens):
|
|
||||||
raise ValueError
|
|
||||||
if not HOSTNAME_LAST_TOKEN.search(tokens[-1]):
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
raise ValidationError('{!r} is not a valid hostname'.format(obj))
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv4(obj):
|
|
||||||
try:
|
|
||||||
import ipaddress
|
|
||||||
obj = text_type(obj)
|
|
||||||
ipaddress.IPv4Address(obj)
|
|
||||||
except ImportError:
|
|
||||||
raise ValidationError('IPv4 relies on ipaddress package', obj)
|
|
||||||
except (ipaddress.AddressValueError, ipaddress.NetmaskValueError):
|
|
||||||
raise ValidationError('{!r} does not appear to '
|
|
||||||
'be an IPv4 address'.format(obj))
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv6(obj):
|
|
||||||
try:
|
|
||||||
import ipaddress
|
|
||||||
obj = text_type(obj)
|
|
||||||
ipaddress.IPv6Address(obj)
|
|
||||||
except ImportError:
|
|
||||||
raise ValidationError('IPv6 relies on ipaddress package', obj)
|
|
||||||
except (ipaddress.AddressValueError, ipaddress.NetmaskValueError):
|
|
||||||
raise ValidationError('{!r} does not appear to '
|
|
||||||
'be an IPv6 address'.format(obj))
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_regex(obj):
|
|
||||||
# TODO implement ECMA 262 regex
|
|
||||||
import re
|
|
||||||
try:
|
|
||||||
re.compile(obj)
|
|
||||||
except:
|
|
||||||
raise ValidationError('Not a regex', obj)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def validate_uri(obj):
|
|
||||||
try:
|
|
||||||
if ':' not in obj:
|
|
||||||
raise ValueError('missing scheme')
|
|
||||||
urlparse(obj)
|
|
||||||
except Exception as error:
|
|
||||||
logger.exception(error)
|
|
||||||
raise ValidationError('{!r} is not an uri'.format(obj))
|
|
||||||
return obj
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
cmake_minimum_required(VERSION 2.8.3)
|
cmake_minimum_required(VERSION 2.8.3)
|
||||||
project(hos_python_json)
|
project(hos_python_utils)
|
||||||
|
|
||||||
## Find catkin macros and libraries
|
## Find catkin macros and libraries
|
||||||
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<package>
|
<package>
|
||||||
<name>hos_python_json</name>
|
<name>hos_python_utils</name>
|
||||||
<version>0.0.0</version>
|
<version>0.0.0</version>
|
||||||
<description>The hos_python_json package</description>
|
<description>The hos_python_json package</description>
|
||||||
|
|
||||||
+1
-2
@@ -7,7 +7,7 @@ import json
|
|||||||
from jsonspec.reference.bases import Provider
|
from jsonspec.reference.bases import Provider
|
||||||
|
|
||||||
|
|
||||||
class HOSParamServerProvider(Provider):
|
class SchemaLoader(Provider):
|
||||||
|
|
||||||
def __init__(self, ros_param_ns = "/hos/types/repository", prefix = "hos:schema://"):
|
def __init__(self, ros_param_ns = "/hos/types/repository", prefix = "hos:schema://"):
|
||||||
self.ros_param_ns = ros_param_ns
|
self.ros_param_ns = ros_param_ns
|
||||||
@@ -37,7 +37,6 @@ class HOSParamServerProvider(Provider):
|
|||||||
else:
|
else:
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
|
|
||||||
# Provider inherits from Mapping, therefore we need to implement __iter__ and __len__,
|
# Provider inherits from Mapping, therefore we need to implement __iter__ and __len__,
|
||||||
# They don't really fit our use case but we need to return at least one item here to avoid problems with jsonspec.
|
# They don't really fit our use case but we need to return at least one item here to avoid problems with jsonspec.
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
@@ -4,7 +4,7 @@ from distutils.core import setup
|
|||||||
from catkin_pkg.python_setup import generate_distutils_setup
|
from catkin_pkg.python_setup import generate_distutils_setup
|
||||||
|
|
||||||
package_info = generate_distutils_setup(
|
package_info = generate_distutils_setup(
|
||||||
packages=['jsonspec', 'jsonspec_hos'],
|
packages=['hos'],
|
||||||
package_dir={'': 'python'},
|
package_dir={'': 'python'},
|
||||||
)
|
)
|
||||||
|
|
||||||
Reference in New Issue
Block a user