From feff4efc356590b4d170a726c3a6f8bb0bf8fddd Mon Sep 17 00:00:00 2001 From: matzman Date: Fri, 22 May 2015 01:46:40 +0200 Subject: [PATCH] . --- src/hos_cosgi_core/CMakeLists.txt | 135 +--- .../include/hos_cosgi_core/hos/cosgi/celix.h | 12 + .../hos/cosgi/service/Implementation.h | 9 + .../hos/cosgi/service/core/CoreService.h | 27 +- ...re_serviceimpl.cpp => CoreServiceImpl.cpp} | 31 +- .../hos/cosgi/service/core/CoreServiceImpl.h | 85 +++ .../hos/cosgi/service/core/core_serviceimpl.h | 42 -- src/hos_cosgi_core/src/launcher.cpp | 61 +- .../devicerepository_service.h | 94 ++- .../devicerepository_serviceimpl.cpp | 27 +- .../devicerepository_serviceimpl.h | 11 +- .../hos_cpp_utils/hos/utils/SchemaLoader.h | 2 +- .../cmake/Modules/Findlibhttpserver.cmake | 2 +- .../cmake/Modules/Findlibvariant.cmake | 3 + src/hos_dummy/CMakeLists.txt | 5 +- src/hos_dummy/package.xml | 4 +- .../python/hos_dummy/HosDummyController.py | 4 +- .../python/hos_dummy/HosDummyDevices.py | 4 +- src/hos_philips_hue/CMakeLists.txt | 4 +- src/hos_philips_hue/nodes/test.py | 4 +- src/hos_philips_hue/package.xml | 4 +- .../python/hos_huecontroller/huecontroller.py | 4 +- .../python/jsonspec/__init__.py | 5 - .../python/jsonspec/cli/__init__.py | 508 ------------- src/hos_python_json/python/jsonspec/driver.py | 35 - .../python/jsonspec/misc/__init__.py | 0 .../python/jsonspec/misc/schemas/address.json | 17 - .../jsonspec/misc/schemas/calendar.json | 37 - .../jsonspec/misc/schemas/draft-03/card.json | 48 -- .../misc/schemas/draft-03/hyper-schema.json | 50 -- .../misc/schemas/draft-03/json-ref.json | 21 - .../jsonspec/misc/schemas/draft-03/links.json | 21 - .../misc/schemas/draft-03/schema.json | 142 ---- .../misc/schemas/draft-04/hyper-schema.json | 168 ----- .../misc/schemas/draft-04/schema.json | 150 ---- .../python/jsonspec/misc/schemas/geo.json | 8 - .../python/jsonspec/operations/__init__.py | 98 --- .../python/jsonspec/operations/bases.py | 202 ------ .../python/jsonspec/operations/exceptions.py | 17 - .../python/jsonspec/pointer/__init__.py | 33 - .../python/jsonspec/pointer/bases.py | 264 ------- .../python/jsonspec/pointer/exceptions.py | 72 -- .../python/jsonspec/pointer/stages.py | 67 -- .../python/jsonspec/reference/__init__.py | 62 -- .../python/jsonspec/reference/bases.py | 119 --- .../python/jsonspec/reference/exceptions.py | 15 - .../python/jsonspec/reference/providers.py | 172 ----- .../python/jsonspec/reference/util.py | 42 -- .../python/jsonspec/validators/__init__.py | 37 - .../python/jsonspec/validators/bases.py | 104 --- .../python/jsonspec/validators/draft03.py | 666 ----------------- .../python/jsonspec/validators/draft04.py | 678 ------------------ .../python/jsonspec/validators/exceptions.py | 70 -- .../python/jsonspec/validators/factorize.py | 131 ---- .../python/jsonspec/validators/formats.py | 143 ---- .../jsonspec/validators/pointer_util.py | 20 - .../python/jsonspec/validators/util.py | 244 ------- .../CMakeLists.txt | 2 +- .../package.xml | 2 +- .../python/hos}/__init__.py | 0 .../python/hos/utils}/__init__.py | 0 .../python/hos/utils/schemaloader.py} | 5 +- .../setup.py | 2 +- 63 files changed, 290 insertions(+), 4761 deletions(-) create mode 100644 src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/celix.h rename src/hos_cosgi_core/src/hos/cosgi/service/core/{core_serviceimpl.cpp => CoreServiceImpl.cpp} (69%) create mode 100644 src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.h delete mode 100644 src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.h delete mode 100644 src/hos_python_json/python/jsonspec/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/cli/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/driver.py delete mode 100644 src/hos_python_json/python/jsonspec/misc/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/address.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/calendar.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-03/card.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-03/hyper-schema.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-03/json-ref.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-03/links.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-03/schema.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-04/hyper-schema.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/draft-04/schema.json delete mode 100644 src/hos_python_json/python/jsonspec/misc/schemas/geo.json delete mode 100644 src/hos_python_json/python/jsonspec/operations/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/operations/bases.py delete mode 100644 src/hos_python_json/python/jsonspec/operations/exceptions.py delete mode 100644 src/hos_python_json/python/jsonspec/pointer/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/pointer/bases.py delete mode 100644 src/hos_python_json/python/jsonspec/pointer/exceptions.py delete mode 100644 src/hos_python_json/python/jsonspec/pointer/stages.py delete mode 100644 src/hos_python_json/python/jsonspec/reference/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/reference/bases.py delete mode 100644 src/hos_python_json/python/jsonspec/reference/exceptions.py delete mode 100644 src/hos_python_json/python/jsonspec/reference/providers.py delete mode 100644 src/hos_python_json/python/jsonspec/reference/util.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/__init__.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/bases.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/draft03.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/draft04.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/exceptions.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/factorize.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/formats.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/pointer_util.py delete mode 100644 src/hos_python_json/python/jsonspec/validators/util.py rename src/{hos_python_json => hos_python_utils}/CMakeLists.txt (99%) rename src/{hos_python_json => hos_python_utils}/package.xml (98%) rename src/{hos_python_json/python/jsonspec_hos => hos_python_utils/python/hos}/__init__.py (100%) rename src/{hos_python_json/python/jsonspec_hos/reference => hos_python_utils/python/hos/utils}/__init__.py (100%) rename src/{hos_python_json/python/jsonspec_hos/reference/providers.py => hos_python_utils/python/hos/utils/schemaloader.py} (95%) rename src/{hos_python_json => hos_python_utils}/setup.py (84%) diff --git a/src/hos_cosgi_core/CMakeLists.txt b/src/hos_cosgi_core/CMakeLists.txt index d1d44ee6..ca4535ea 100644 --- a/src/hos_cosgi_core/CMakeLists.txt +++ b/src/hos_cosgi_core/CMakeLists.txt @@ -1,93 +1,28 @@ cmake_minimum_required(VERSION 2.8.3) 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 - hos_celix roscpp + hos_celix hos_cpp_utils hos_deps ) -## System dependencies are found with CMake's conventions find_package(Boost 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 ## ################################### -## 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( INCLUDE_DIRS include/${PROJECT_NAME}/ -# LIBRARIES hos_cosgi_launcher CATKIN_DEPENDS hos_celix -# DEPENDS system_lib ) + ########### ## Build ## ########### @@ -96,70 +31,39 @@ add_definitions( -std=c++11 ) -## Specify additional locations of header files -## Your package locations should be listed before other locations -# include_directories(include) include_directories( + include/${PROJECT_NAME}/ ${catkin_INCLUDE_DIRS} ${APR_INCLUDE_DIR} ${APR_INCLUDE_DIR}/apr-1.0 - include/${PROJECT_NAME}/ + ${libvariant_INCLUDE_DIRS} ) - add_executable(hos_cosgi_launcher src/launcher.cpp - src/hos/cosgi/service/core/core_serviceimpl.cpp + src/hos/cosgi/service/core/CoreServiceImpl.cpp ) + target_link_libraries(hos_cosgi_launcher ${catkin_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 ## ############# -# 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 ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} ) -## Mark cpp header files for installation install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} FILES_MATCHING PATTERN "*.h" - PATTERN ".svn" EXCLUDE ) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc @@ -167,26 +71,6 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc 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 ## @@ -200,3 +84,4 @@ if(DOXYGEN_FOUND) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) endif(DOXYGEN_FOUND) + diff --git a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/celix.h b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/celix.h new file mode 100644 index 00000000..a4488d87 --- /dev/null +++ b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/celix.h @@ -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" +} diff --git a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h index 32916b7b..408d09fc 100644 --- a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h +++ b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/Implementation.h @@ -76,6 +76,15 @@ template class Observable {}; +/** + * Convenience template merging the Service class and the Observable template into a single template. + */ +template class ObservableService + : public virtual Service<_Service>, + public virtual Observable<_Args...> {}; + + + /** * Implementation of interface::Observable<_Event>. * diff --git a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/core/CoreService.h b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/core/CoreService.h index e432d21a..32a5aacb 100644 --- a/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/core/CoreService.h +++ b/src/hos_cosgi_core/include/hos_cosgi_core/hos/cosgi/service/core/CoreService.h @@ -4,11 +4,13 @@ #include +#include // Forward declarations namespace ros { class NodeHandle; } +namespace hos { namespace utils { class SchemaLoader; } }; @@ -24,24 +26,14 @@ namespace core { * Base class for events emitted by the CoreService. */ class CoreServiceEvent { - public: - - /** - * CoreService event types. - */ + typedef std::chrono::system_clock::time_point timestamp_t; enum class EventType : int { STARTUP_FINISHED = 1, SHUTDOWN = 2 }; - - /** - * Event type. - */ const EventType eventType; + const timestamp_t timestamp; - /** - * Constructor. - */ - CoreServiceEvent(EventType type) : eventType(type) {} - + CoreServiceEvent(EventType type, const timestamp_t timestamp = std::chrono::system_clock::now()) + : eventType(type), timestamp(timestamp) {} }; @@ -116,6 +108,13 @@ public: */ virtual void requestShutdown() = 0; + /** + * Returns a reference to the schema loader. + * + * \return schema loader. + */ + virtual utils::SchemaLoader& getSchemaLoader() = 0; + }; diff --git a/src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.cpp b/src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.cpp similarity index 69% rename from src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.cpp rename to src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.cpp index 693b95df..63d01f6e 100644 --- a/src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.cpp +++ b/src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.cpp @@ -1,34 +1,30 @@ -/* - * hos_core_service_impl.cpp - * - * Created on: 05.04.2015 - * Author: matzman - */ + + #include -#include "core_serviceimpl.h" +#include "CoreServiceImpl.h" + + +// Service Namespace namespace hos { namespace cosgi{ namespace service { namespace core { + + +// Service information template<> const char* implementation::ServiceInfo::name = service::core::CoreService::SERVICE_NAME; -template<> const int implementation::ServiceInfo::major = 0; -template<> const int implementation::ServiceInfo::minor = 3; +template<> const int implementation::ServiceInfo::major = 1; +template<> const int implementation::ServiceInfo::minor = 0; + + CoreServiceImpl::CoreServiceImpl(ros::NodeHandle& publicNodeHandle, ros::NodeHandle& privateNodeHandle) : publicNodeHandle(publicNodeHandle), privateNodeHandle(privateNodeHandle) {} -ros::NodeHandle& CoreServiceImpl::getPublicNodeHandle() { - return publicNodeHandle; -} - -ros::NodeHandle& CoreServiceImpl::getPrivateNodeHandle() { - return privateNodeHandle; -} - void CoreServiceImpl::requestShutdown() { ROS_INFO("Received shutdown request"); @@ -36,6 +32,7 @@ void CoreServiceImpl::requestShutdown() { } + } // namespace core } // namespace service } // namespace cosgi diff --git a/src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.h b/src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.h new file mode 100644 index 00000000..98cd697d --- /dev/null +++ b/src/hos_cosgi_core/src/hos/cosgi/service/core/CoreServiceImpl.h @@ -0,0 +1,85 @@ + +#pragma once + + + +#include +#include +#include +#include + + + +// Service Namespace +namespace hos { +namespace cosgi{ +namespace service { +namespace core { + + + +/** + * Implementation of CoreService. + */ +class CoreServiceImpl + : public virtual CoreService, + public virtual implementation::ObservableService { + +private: + + ros::NodeHandle publicNodeHandle; + ros::NodeHandle privateNodeHandle; + std::unique_ptr 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 + diff --git a/src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.h b/src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.h deleted file mode 100644 index 026c1063..00000000 --- a/src/hos_cosgi_core/src/hos/cosgi/service/core/core_serviceimpl.h +++ /dev/null @@ -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 -#include - - -// Service Namespace -namespace hos { -namespace cosgi{ -namespace service { -namespace core { - - -class CoreServiceImpl; -class CoreServiceImpl : public virtual CoreService, public virtual implementation::Service, public virtual implementation::Observable { -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_ */ diff --git a/src/hos_cosgi_core/src/launcher.cpp b/src/hos_cosgi_core/src/launcher.cpp index 44342618..0161c88f 100644 --- a/src/hos_cosgi_core/src/launcher.cpp +++ b/src/hos_cosgi_core/src/launcher.cpp @@ -1,53 +1,64 @@ + +// std +#include #include +#include +#include +#include +// Boost +#include +// ROS #include "ros/ros.h" +// APR #include #include - -#include "hos/cosgi/service/core/core_serviceimpl.h" - -extern "C" { // celix does not provide C++ ready headers - #include "celix/framework.h" - #include "celix/linked_list_iterator.h" -} -#include -#include -#include -#include -#include +// Celix +#include +// CoreService +#include "hos/cosgi/service/core/CoreServiceImpl.h" using namespace hos::cosgi::service::core; +// forward declaration of signal handler std::function signalHandler; + +/** + * hos_cosgi_launcher entry point. + * + * Command line argument parsing is handled by ROS. + */ 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; apr_pool_t* memoryPool = nullptr; framework_pt framework = nullptr; bundle_pt fwBundle = nullptr; properties_pt properties = nullptr; - int loop_frequency = 100; - std::string autostart_bundles; - try { // Initialize ROS ros::init(argc, argv, "hos_osgi_launcher", ros::init_options::AnonymousName | ros::init_options::NoSigintHandler); - // Create public node-handle ros::NodeHandle rosPublicNodeHandle; - // Create private node-handle ros::NodeHandle rosPrivateNodeHandle("~"); if (!ros::ok()) { throw std::runtime_error(boost::format("Could not initialize ROS.").str()); } ROS_INFO("ROS initialized"); - // Retrieve ROS parameter + // Retrieve public parameters rosPrivateNodeHandle.getParam("loop_frequency", loop_frequency); rosPrivateNodeHandle.getParam("autostart_bundles", autostart_bundles); + rosPrivateNodeHandle.getParam("config_properties", config_properties); // Initialize APR and APR memory pool apr_status_t astatus = apr_initialize(); @@ -64,12 +75,10 @@ int main(int argc, char **argv) { // Create celix properties properties = properties_create(); - - //properties = properties_load((char*)"config.properties"); if (properties == nullptr) { 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 celix_status_t cstatus = framework_create(&framework, memoryPool, properties); @@ -183,17 +192,23 @@ int main(int argc, char **argv) { hosCoreService.notifyObservers(&event); } - // main loop - ROS_INFO("Entering main loop"); + // Install signal handler signalHandler = [&](int signum) { + std::cerr << "Caught SIGINT, exiting..." << std::endl; hosCoreService.requestShutdown(); }; signal(SIGINT, [](int signum) { signalHandler(signum); }); + // main loop + ROS_INFO("Entering main loop"); ros::Rate loop_rate(loop_frequency); while (ros::ok()) { ros::spinOnce(); loop_rate.sleep(); } + { + ShutdownEvent event; + hosCoreService.notifyObservers(&event); + } ROS_INFO("Exiting main loop"); } catch (std::exception &ex) { diff --git a/src/hos_cosgi_devicerepository/include/hos_cosgi_devicerepository/hos/cosgi/service/devicerepository/devicerepository_service.h b/src/hos_cosgi_devicerepository/include/hos_cosgi_devicerepository/hos/cosgi/service/devicerepository/devicerepository_service.h index 382fb621..20b25f8e 100644 --- a/src/hos_cosgi_devicerepository/include/hos_cosgi_devicerepository/hos/cosgi/service/devicerepository/devicerepository_service.h +++ b/src/hos_cosgi_devicerepository/include/hos_cosgi_devicerepository/hos/cosgi/service/devicerepository/devicerepository_service.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -17,76 +18,106 @@ namespace cosgi { namespace service { namespace devicerepository { + // Forward declarations class Device; -using namespace ::libvariant; + +using namespace libvariant; + /** - * Base class for device events - **/ + * Base class for device events. + */ class DeviceEvent { 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 }; - const EventType eventType; 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 - **/ -class DeviceAddedEvent : public DeviceEvent { + * Device added event. + */ +class DeviceAddedEvent + : public DeviceEvent { 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 - **/ -class DeviceStatusMsgEvent : public DeviceEvent { + * Device removed event. + */ +class DeviceRemovedEvent + : public DeviceEvent { public: - DeviceStatusMsgEvent(Device* device, const Variant statusMsg) - : DeviceEvent(device, DeviceEvent::EventType::STATUS_UPDATED), statusMsg(statusMsg) {} + DeviceRemovedEvent(Device* device, const timestamp_t timestamp) + : 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; }; + + /** - * Device command message received - **/ -class DeviceCmdMsgEvent : public DeviceEvent { + * Device command message received event. + */ +class DeviceCmdMsgEvent + : public DeviceEvent { public: - DeviceCmdMsgEvent(Device* device, const Variant cmdMsg) - : DeviceEvent(device, DeviceEvent::EventType::COMMAND_RECEIVED), cmdMsg(cmdMsg) {} + DeviceCmdMsgEvent(Device* device, const Variant cmdMsg, const timestamp_t timestamp) + : DeviceEvent(device, DeviceEvent::EventType::COMMAND_RECEIVED, timestamp), cmdMsg(cmdMsg) {} const Variant cmdMsg; }; /** - * Public interface of an device object - **/ -class Device : public virtual interface::ObservableObject { + * Public COSGI interface of an device object. + */ +class Device + : public virtual interface::ObservableObject { + public: + + typedef std::chrono::system_clock::time_point timestamp_t; + virtual const std::string& getDeviceID() = 0; virtual const std::string& getDeviceType() = 0; + virtual const std::string& getCmdTopic() = 0; virtual const std::string& getStatusTopic() = 0; virtual const Variant& getCurrentState() = 0; + virtual const timestamp_t& getCurrentStateTimestamp() = 0; + virtual const Variant& getLastState() = 0; + virtual const timestamp_t& getLastStateTimestamp() = 0; virtual void sendCommand(const Variant cmd) = 0; - virtual bool stateChanged() = 0; + }; @@ -94,7 +125,8 @@ public: /** * Public Interface of the device repository service **/ -class DeviceRepositoryService : public virtual interface::ObservableService { +class DeviceRepositoryService + : public virtual interface::ObservableService { public: static constexpr const char* SERVICE_NAME = "hos_devicerepository_service"; diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.cpp b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.cpp index acff5891..b0abdb61 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.cpp +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.cpp @@ -32,17 +32,17 @@ DeviceImpl::DeviceImpl(DeviceRepositoryServiceImpl* parent, std::string deviceID 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 lock(objMutex); lastState = currentState.Copy(); currentState.Merge(msg); - DeviceStatusMsgEvent event(this, msg); + DeviceStatusMsgEvent event(this, msg, stamp); notifyObservers(&event); } -void DeviceImpl::handleCommandMsg(Variant& msg) { +void DeviceImpl::handleCommandMsg(Variant& msg, const timestamp_t& stamp) { std::lock_guard lock(objMutex); - DeviceCmdMsgEvent event(this, msg); + DeviceCmdMsgEvent event(this, msg, stamp); notifyObservers(&event); } @@ -51,10 +51,6 @@ void DeviceImpl::sendCommand(const Variant cmd) { parent->sendDeviceCommand(this, cmd); } -bool DeviceImpl::stateChanged() { - return currentState == lastState; -} - void DeviceRepositoryServiceImpl::setCoreService(service::core::CoreService* coreService) { if (coreService && !this->coreService) { this->coreService = coreService; @@ -150,7 +146,7 @@ void DeviceRepositoryServiceImpl::addDevice(std::string name, XmlRpc::XmlRpcValu subscripedTopics[dev->getCmdTopic()] = entry2; // send device added event - DeviceAddedEvent event(dev.get()); + DeviceAddedEvent event(dev.get(), std::chrono::system_clock::now()); notifyObservers(&event); } @@ -159,7 +155,7 @@ void DeviceRepositoryServiceImpl::removeDevice(std::string name) { std::lock_guard lock(serviceMutex); auto& dev = devices[name]; if (dev) { - DeviceRemovedEvent event(dev.get()); + DeviceRemovedEvent event(dev.get(), std::chrono::system_clock::now()); notifyObservers(&event); dev->notifyObservers(&event); devices.erase(name); @@ -205,8 +201,9 @@ void DeviceRepositoryServiceImpl::statusMsgCallback(const hos_core_msgs::DeviceS if (!sr.Error()) { std::shared_ptr dev = std::static_pointer_cast(devices[msg->device_id]); if (dev) { - dev->handleStatusMsg(m); - DeviceStatusMsgEvent event(dev.get(), m); + auto stamp = std::chrono::system_clock::now(); + dev->handleStatusMsg(m, stamp); + DeviceStatusMsgEvent event(dev.get(), m, stamp); notifyObservers(&event); } } else { @@ -219,12 +216,14 @@ void DeviceRepositoryServiceImpl::cmdMsgCallback(const hos_core_msgs::DeviceComm std::cout << "HOSDeviceRepositoryServiceImpl::cmdMsgCallback" << std::endl; Variant s = schemaLoader->GetSchema(std::string("hos:schema://").append(msg->device_type).append("/command")); Variant m = Deserialize(msg->msg_body, SERIALIZE_JSON); + auto test = msg->stamp; SchemaResult sr = SchemaValidate(s, m, schemaLoader.get()); if (!sr.Error()) { std::shared_ptr dev = std::static_pointer_cast(devices[msg->device_id]); if (dev != nullptr) { - dev->handleCommandMsg(m); - DeviceCmdMsgEvent event(dev.get(), m); + auto stamp = std::chrono::system_clock::now(); + dev->handleCommandMsg(m, stamp); + DeviceCmdMsgEvent event(dev.get(), m, stamp); notifyObservers(&event); } } else { diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.h index 8be740b7..15d0d6eb 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.h +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/devicerepository/devicerepository_serviceimpl.h @@ -62,7 +62,9 @@ private: std::string statusTopic; std::string cmdTopic; Variant currentState; + timestamp_t currentStamp; Variant lastState; + timestamp_t lastStamp; public: 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 Variant& getCurrentState() override { return currentState; } + virtual const timestamp_t& getCurrentStateTimestamp() { return currentStamp; } virtual const Variant& getLastState() override { return lastState; } + virtual const timestamp_t& getLastStateTimestamp() { return lastStamp; } virtual void sendCommand(const Variant cmd) override; - virtual bool stateChanged() override; - /* Internal Stuff */ - void handleStatusMsg(Variant& msg); - void handleCommandMsg(Variant& msg); - void sendDeviceCommand(Device* device, Variant& cmd); + void handleStatusMsg(Variant& msg, const timestamp_t& stamp); + void handleCommandMsg(Variant& msg, const timestamp_t& stamp); }; diff --git a/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/SchemaLoader.h b/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/SchemaLoader.h index e57878ce..535f3121 100644 --- a/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/SchemaLoader.h +++ b/src/hos_cpp_utils/include/hos_cpp_utils/hos/utils/SchemaLoader.h @@ -13,7 +13,7 @@ namespace hos { namespace utils { - + class SchemaLoader: public libvariant::SchemaLoader { private: std::string ros_param_ns; diff --git a/src/hos_deps/cmake/Modules/Findlibhttpserver.cmake b/src/hos_deps/cmake/Modules/Findlibhttpserver.cmake index 7410f168..1e6a01c9 100644 --- a/src/hos_deps/cmake/Modules/Findlibhttpserver.cmake +++ b/src/hos_deps/cmake/Modules/Findlibhttpserver.cmake @@ -20,4 +20,4 @@ find_library(libmicrohttpd_LIBRARY microhttpd ) set(libhttpserver_INCLUDE_DIRS "${libhttpserver_INCLUDE_DIR}" "${libmicrohttpd_INCLUDE_DIR}") -set(libhttpserver_LIBRARIES "${libhttpserver_LIBRARY}" "${libmicrohttpd_LIBRARY}") \ No newline at end of file +set(libhttpserver_LIBRARIES "${libhttpserver_LIBRARY}" "${libmicrohttpd_LIBRARY}") diff --git a/src/hos_deps/cmake/Modules/Findlibvariant.cmake b/src/hos_deps/cmake/Modules/Findlibvariant.cmake index a2b0f0c8..3122a7d6 100644 --- a/src/hos_deps/cmake/Modules/Findlibvariant.cmake +++ b/src/hos_deps/cmake/Modules/Findlibvariant.cmake @@ -8,3 +8,6 @@ find_library(libvariant_LIBRARY Variant PATHS $ENV{HOS_PREFIX}/libvariant PATH_SUFFIXES lib lib64 ) + +set(libvariant_INCLUDE_DIRS "${libvariant_INCLUDE_DIR}") +set(libvariant_LIBRARIES "${libvariant_LIBRARY}") diff --git a/src/hos_dummy/CMakeLists.txt b/src/hos_dummy/CMakeLists.txt index 506f8913..561abd51 100644 --- a/src/hos_dummy/CMakeLists.txt +++ b/src/hos_dummy/CMakeLists.txt @@ -6,7 +6,7 @@ project(hos_dummy) ## is used, also find other catkin packages find_package(catkin REQUIRED COMPONENTS hos_core_msgs - hos_python_json + hos_python_utils rospy ) @@ -82,7 +82,7 @@ catkin_python_setup() catkin_package( # INCLUDE_DIRS include # LIBRARIES hos_dummy - CATKIN_DEPENDS hos_core_msgs hos_python_json rospy + CATKIN_DEPENDS hos_core_msgs hos_python_utils rospy # DEPENDS system_lib ) @@ -122,6 +122,7 @@ include_directories( #And this will install python nodes catkin_install_python(PROGRAMS nodes/hos_dummy_devices.py + nodes/hos_dummy_controller.py DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) # all install targets should use catkin DESTINATION variables diff --git a/src/hos_dummy/package.xml b/src/hos_dummy/package.xml index da87f5f0..2d42097d 100644 --- a/src/hos_dummy/package.xml +++ b/src/hos_dummy/package.xml @@ -41,10 +41,10 @@ catkin hos_core_msgs - hos_python_json + hos_python_utils rospy hos_core_msgs - hos_python_json + hos_python_utils rospy diff --git a/src/hos_dummy/python/hos_dummy/HosDummyController.py b/src/hos_dummy/python/hos_dummy/HosDummyController.py index 244ed0ef..2304bcb4 100644 --- a/src/hos_dummy/python/hos_dummy/HosDummyController.py +++ b/src/hos_dummy/python/hos_dummy/HosDummyController.py @@ -8,7 +8,7 @@ from hos_pyhue import pyhue from hos_core_msgs.msg import DeviceCommand from hos_core_msgs.msg import DeviceStatusUpdate 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 time import sleep @@ -55,7 +55,7 @@ class HosDummyController(tk.Frame): rospy.set_param(key_base + "/cmd_topic", self.cmd_topic) print "Added dummy dimmer \"" + name + "\"" # Create JSON schema provider - self.schemaProvider = HOSParamServerProvider() + self.schemaProvider = SchemaLoader() # Create message publisher self.commandPublisher = rospy.Publisher(self.cmd_topic, DeviceCommand, queue_size=10) # Init GUI diff --git a/src/hos_dummy/python/hos_dummy/HosDummyDevices.py b/src/hos_dummy/python/hos_dummy/HosDummyDevices.py index fa5111e1..a8f05183 100644 --- a/src/hos_dummy/python/hos_dummy/HosDummyDevices.py +++ b/src/hos_dummy/python/hos_dummy/HosDummyDevices.py @@ -8,7 +8,7 @@ from hos_pyhue import pyhue from hos_core_msgs.msg import DeviceCommand from hos_core_msgs.msg import DeviceStatusUpdate 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 time import sleep @@ -58,7 +58,7 @@ class HosDummyDevices(tk.Frame): print "Added dummy dimmer \"" + name + "\"" new_devices.append(name) # Create JSON schema provider - self.schemaProvider = HOSParamServerProvider() + self.schemaProvider = SchemaLoader() # Create message publisher self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10) self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10) diff --git a/src/hos_philips_hue/CMakeLists.txt b/src/hos_philips_hue/CMakeLists.txt index 0dd7a189..06553622 100644 --- a/src/hos_philips_hue/CMakeLists.txt +++ b/src/hos_philips_hue/CMakeLists.txt @@ -7,7 +7,7 @@ project(hos_philips_hue) find_package(catkin REQUIRED COMPONENTS hos_core_msgs rospy - hos_python_json + hos_python_utils ) ## System dependencies are found with CMake's conventions @@ -82,7 +82,7 @@ catkin_python_setup() catkin_package( # INCLUDE_DIRS include # 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 ) diff --git a/src/hos_philips_hue/nodes/test.py b/src/hos_philips_hue/nodes/test.py index 1537d9fe..cde672e7 100755 --- a/src/hos_philips_hue/nodes/test.py +++ b/src/hos_philips_hue/nodes/test.py @@ -7,7 +7,7 @@ import rospy import json import traceback 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 time import sleep @@ -18,7 +18,7 @@ if __name__ == '__main__': rospy.init_node('test', argv=sys.argv, anonymous=True) cmdPublisher = rospy.Publisher("/hos/devices/cmd", DeviceCommand, queue_size=10) - schemaProvider = HOSParamServerProvider() + schemaProvider = SchemaLoader() stamp = rospy.get_rostime() msg = DeviceCommand() diff --git a/src/hos_philips_hue/package.xml b/src/hos_philips_hue/package.xml index 5bf025f1..a8a011a1 100644 --- a/src/hos_philips_hue/package.xml +++ b/src/hos_philips_hue/package.xml @@ -42,10 +42,10 @@ catkin hos_core_msgs rospy - hos_python_json + hos_python_utils hos_core_msgs rospy - hos_python_json + hos_python_utils diff --git a/src/hos_philips_hue/python/hos_huecontroller/huecontroller.py b/src/hos_philips_hue/python/hos_huecontroller/huecontroller.py index 0a19aaa6..f225dbd2 100644 --- a/src/hos_philips_hue/python/hos_huecontroller/huecontroller.py +++ b/src/hos_philips_hue/python/hos_huecontroller/huecontroller.py @@ -10,7 +10,7 @@ from hos_pyhue import pyhue from hos_core_msgs.msg import DeviceCommand from hos_core_msgs.msg import DeviceStatusUpdate 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 curses.has_key import has_key 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 + "/cmd_topic", self.cmd_topic) # Create JSON schema provider - self.schemaProvider = HOSParamServerProvider() + self.schemaProvider = SchemaLoader() # Create message publisher self.statusPublisher = rospy.Publisher(self.status_topic, DeviceStatusUpdate, queue_size=10) self.hosUpdatePublisher = rospy.Publisher(self.hos_update_topic, HosStatusUpdate, queue_size=10) diff --git a/src/hos_python_json/python/jsonspec/__init__.py b/src/hos_python_json/python/jsonspec/__init__.py deleted file mode 100644 index 6f978951..00000000 --- a/src/hos_python_json/python/jsonspec/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" - jsonspec - ~~~~~~~~ - -""" diff --git a/src/hos_python_json/python/jsonspec/cli/__init__.py b/src/hos_python_json/python/jsonspec/cli/__init__.py deleted file mode 100644 index 112853e3..00000000 --- a/src/hos_python_json/python/jsonspec/cli/__init__.py +++ /dev/null @@ -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='') - group.add_argument('--document-file', type=JSONFile('r'), help='json filename', dest='document_file', metavar='') - - -def schema_arguments(parser): - group = parser.add_mutually_exclusive_group() - group.add_argument('--schema-json', type=JSONStruct, help='json structure', dest='schema_json', metavar='') - group.add_argument('--schema-file', type=JSONFile('r'), help='json filename', dest='schema_file', metavar='') - - -def fragment_arguments(parser): - group = parser.add_mutually_exclusive_group() - group.add_argument('--fragment-json', type=JSONStruct, help='json structure', dest='fragment_json', metavar='') - group.add_argument('--fragment-file', type=JSONFile('r'), help='json filename', dest='fragment_file', metavar='') - - -def indentation_arguments(parser): - parser.add_argument('--indent', type=int, help='return an indented json', metavar='') - - -def pointer_arguments(parser): - parser.add_argument('pointer', type=str, help='json pointer', metavar='') - - -def target_arguments(parser): - parser.add_argument('-t', '--target-pointer', help='target pointer', dest='target', metavar='') - - -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='') - 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() diff --git a/src/hos_python_json/python/jsonspec/driver.py b/src/hos_python_json/python/jsonspec/driver.py deleted file mode 100644 index 8da0a15e..00000000 --- a/src/hos_python_json/python/jsonspec/driver.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/misc/__init__.py b/src/hos_python_json/python/jsonspec/misc/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/address.json b/src/hos_python_json/python/jsonspec/misc/schemas/address.json deleted file mode 100644 index 3470d74d..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/address.json +++ /dev/null @@ -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" - } -} diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/calendar.json b/src/hos_python_json/python/jsonspec/misc/schemas/calendar.json deleted file mode 100644 index 68f6f072..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/calendar.json +++ /dev/null @@ -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" } - } -} diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/card.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/card.json deleted file mode 100644 index 9e84cb06..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/card.json +++ /dev/null @@ -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" } - } - } - } -} diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/hyper-schema.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/hyper-schema.json deleted file mode 100644 index 0661eab5..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/hyper-schema.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/json-ref.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/json-ref.json deleted file mode 100644 index f25cbabd..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/json-ref.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/links.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/links.json deleted file mode 100644 index f25cbabd..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/links.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/schema.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/schema.json deleted file mode 100644 index 7e15b09c..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-03/schema.json +++ /dev/null @@ -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": {} -} \ No newline at end of file diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/hyper-schema.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/hyper-schema.json deleted file mode 100644 index 853bd35f..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/hyper-schema.json +++ /dev/null @@ -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)}" - } - ] -} - diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/schema.json b/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/schema.json deleted file mode 100644 index 85eb502a..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/draft-04/schema.json +++ /dev/null @@ -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": {} -} diff --git a/src/hos_python_json/python/jsonspec/misc/schemas/geo.json b/src/hos_python_json/python/jsonspec/misc/schemas/geo.json deleted file mode 100644 index 05345b2b..00000000 --- a/src/hos_python_json/python/jsonspec/misc/schemas/geo.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "description": "A geographical coordinate", - "type": "object", - "properties": { - "latitude": { "type": "number" }, - "longitude": { "type": "number" } - } -} diff --git a/src/hos_python_json/python/jsonspec/operations/__init__.py b/src/hos_python_json/python/jsonspec/operations/__init__.py deleted file mode 100644 index 3c07783e..00000000 --- a/src/hos_python_json/python/jsonspec/operations/__init__.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/operations/bases.py b/src/hos_python_json/python/jsonspec/operations/bases.py deleted file mode 100644 index d3b04edc..00000000 --- a/src/hos_python_json/python/jsonspec/operations/bases.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/operations/exceptions.py b/src/hos_python_json/python/jsonspec/operations/exceptions.py deleted file mode 100644 index 528a101b..00000000 --- a/src/hos_python_json/python/jsonspec/operations/exceptions.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/pointer/__init__.py b/src/hos_python_json/python/jsonspec/pointer/__init__.py deleted file mode 100644 index 8ff45517..00000000 --- a/src/hos_python_json/python/jsonspec/pointer/__init__.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/pointer/bases.py b/src/hos_python_json/python/jsonspec/pointer/bases.py deleted file mode 100644 index 83b19261..00000000 --- a/src/hos_python_json/python/jsonspec/pointer/bases.py +++ /dev/null @@ -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 ''.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)) diff --git a/src/hos_python_json/python/jsonspec/pointer/exceptions.py b/src/hos_python_json/python/jsonspec/pointer/exceptions.py deleted file mode 100644 index a840e6a4..00000000 --- a/src/hos_python_json/python/jsonspec/pointer/exceptions.py +++ /dev/null @@ -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 - """ diff --git a/src/hos_python_json/python/jsonspec/pointer/stages.py b/src/hos_python_json/python/jsonspec/pointer/stages.py deleted file mode 100644 index d1431556..00000000 --- a/src/hos_python_json/python/jsonspec/pointer/stages.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/reference/__init__.py b/src/hos_python_json/python/jsonspec/reference/__init__.py deleted file mode 100644 index 238f0d87..00000000 --- a/src/hos_python_json/python/jsonspec/reference/__init__.py +++ /dev/null @@ -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 = '' - return registry.resolve(local) diff --git a/src/hos_python_json/python/jsonspec/reference/bases.py b/src/hos_python_json/python/jsonspec/reference/bases.py deleted file mode 100644 index eac4b00b..00000000 --- a/src/hos_python_json/python/jsonspec/reference/bases.py +++ /dev/null @@ -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 = '' - - 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 diff --git a/src/hos_python_json/python/jsonspec/reference/exceptions.py b/src/hos_python_json/python/jsonspec/reference/exceptions.py deleted file mode 100644 index 551d9a09..00000000 --- a/src/hos_python_json/python/jsonspec/reference/exceptions.py +++ /dev/null @@ -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 document""" - pass diff --git a/src/hos_python_json/python/jsonspec/reference/providers.py b/src/hos_python_json/python/jsonspec/reference/providers.py deleted file mode 100644 index 0202ac41..00000000 --- a/src/hos_python_json/python/jsonspec/reference/providers.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/reference/util.py b/src/hos_python_json/python/jsonspec/reference/util.py deleted file mode 100644 index 1a9801f5..00000000 --- a/src/hos_python_json/python/jsonspec/reference/util.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/validators/__init__.py b/src/hos_python_json/python/jsonspec/validators/__init__.py deleted file mode 100644 index 71ff3832..00000000 --- a/src/hos_python_json/python/jsonspec/validators/__init__.py +++ /dev/null @@ -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 '#') diff --git a/src/hos_python_json/python/jsonspec/validators/bases.py b/src/hos_python_json/python/jsonspec/validators/bases.py deleted file mode 100644 index c4cecde5..00000000 --- a/src/hos_python_json/python/jsonspec/validators/bases.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/validators/draft03.py b/src/hos_python_json/python/jsonspec/validators/draft03.py deleted file mode 100644 index 90643580..00000000 --- a/src/hos_python_json/python/jsonspec/validators/draft03.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/validators/draft04.py b/src/hos_python_json/python/jsonspec/validators/draft04.py deleted file mode 100644 index 144eadd0..00000000 --- a/src/hos_python_json/python/jsonspec/validators/draft04.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/validators/exceptions.py b/src/hos_python_json/python/jsonspec/validators/exceptions.py deleted file mode 100644 index 9b4ac4ed..00000000 --- a/src/hos_python_json/python/jsonspec/validators/exceptions.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/validators/factorize.py b/src/hos_python_json/python/jsonspec/validators/factorize.py deleted file mode 100644 index 1bb5fc17..00000000 --- a/src/hos_python_json/python/jsonspec/validators/factorize.py +++ /dev/null @@ -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 = '' - 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) diff --git a/src/hos_python_json/python/jsonspec/validators/formats.py b/src/hos_python_json/python/jsonspec/validators/formats.py deleted file mode 100644 index d62d24fb..00000000 --- a/src/hos_python_json/python/jsonspec/validators/formats.py +++ /dev/null @@ -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) diff --git a/src/hos_python_json/python/jsonspec/validators/pointer_util.py b/src/hos_python_json/python/jsonspec/validators/pointer_util.py deleted file mode 100644 index f7d002df..00000000 --- a/src/hos_python_json/python/jsonspec/validators/pointer_util.py +++ /dev/null @@ -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 diff --git a/src/hos_python_json/python/jsonspec/validators/util.py b/src/hos_python_json/python/jsonspec/validators/util.py deleted file mode 100644 index 61d65798..00000000 --- a/src/hos_python_json/python/jsonspec/validators/util.py +++ /dev/null @@ -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}(?>> 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 diff --git a/src/hos_python_json/CMakeLists.txt b/src/hos_python_utils/CMakeLists.txt similarity index 99% rename from src/hos_python_json/CMakeLists.txt rename to src/hos_python_utils/CMakeLists.txt index 4a55862d..09e14706 100644 --- a/src/hos_python_json/CMakeLists.txt +++ b/src/hos_python_utils/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 2.8.3) -project(hos_python_json) +project(hos_python_utils) ## Find catkin macros and libraries ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) diff --git a/src/hos_python_json/package.xml b/src/hos_python_utils/package.xml similarity index 98% rename from src/hos_python_json/package.xml rename to src/hos_python_utils/package.xml index 8210372d..da636497 100644 --- a/src/hos_python_json/package.xml +++ b/src/hos_python_utils/package.xml @@ -1,6 +1,6 @@ - hos_python_json + hos_python_utils 0.0.0 The hos_python_json package diff --git a/src/hos_python_json/python/jsonspec_hos/__init__.py b/src/hos_python_utils/python/hos/__init__.py similarity index 100% rename from src/hos_python_json/python/jsonspec_hos/__init__.py rename to src/hos_python_utils/python/hos/__init__.py diff --git a/src/hos_python_json/python/jsonspec_hos/reference/__init__.py b/src/hos_python_utils/python/hos/utils/__init__.py similarity index 100% rename from src/hos_python_json/python/jsonspec_hos/reference/__init__.py rename to src/hos_python_utils/python/hos/utils/__init__.py diff --git a/src/hos_python_json/python/jsonspec_hos/reference/providers.py b/src/hos_python_utils/python/hos/utils/schemaloader.py similarity index 95% rename from src/hos_python_json/python/jsonspec_hos/reference/providers.py rename to src/hos_python_utils/python/hos/utils/schemaloader.py index 4ddf2166..44c5e8eb 100644 --- a/src/hos_python_json/python/jsonspec_hos/reference/providers.py +++ b/src/hos_python_utils/python/hos/utils/schemaloader.py @@ -7,7 +7,7 @@ import json from jsonspec.reference.bases import Provider -class HOSParamServerProvider(Provider): +class SchemaLoader(Provider): def __init__(self, ros_param_ns = "/hos/types/repository", prefix = "hos:schema://"): self.ros_param_ns = ros_param_ns @@ -36,8 +36,7 @@ class HOSParamServerProvider(Provider): raise exceptions.KeyError(type) else: return schema - - + # 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. def __iter__(self): diff --git a/src/hos_python_json/setup.py b/src/hos_python_utils/setup.py similarity index 84% rename from src/hos_python_json/setup.py rename to src/hos_python_utils/setup.py index 1bf6c090..2edf7ba1 100644 --- a/src/hos_python_json/setup.py +++ b/src/hos_python_utils/setup.py @@ -4,7 +4,7 @@ from distutils.core import setup from catkin_pkg.python_setup import generate_distutils_setup package_info = generate_distutils_setup( - packages=['jsonspec', 'jsonspec_hos'], + packages=['hos'], package_dir={'': 'python'}, )