--- a/cubicweb/__pkginfo__.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/__pkginfo__.py Thu Mar 05 10:15:38 2020 +0100
@@ -22,8 +22,8 @@
modname = distname = "cubicweb"
-numversion = (3, 27, 2)
-version = '.'.join(str(num) for num in numversion)
+numversion = (3, 28, 0)
+version = '.'.join(str(num) for num in numversion) + '.dev0'
description = "a repository of entities / relations for knowledge management"
author = "Logilab"
--- a/cubicweb/cwvreg.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/cwvreg.py Thu Mar 05 10:15:38 2020 +0100
@@ -550,7 +550,7 @@
return RQLHelper(self.schema,
special_relations={'eid': 'uid', 'has_text': 'fti'})
- def solutions(self, req, rqlst, args):
+ def compute_var_types(self, req, rqlst, args):
def type_from_eid(eid, req=req):
return req.entity_type(eid)
return self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args)
@@ -558,7 +558,7 @@
def parse(self, req, rql, args=None):
rqlst = self.rqlhelper.parse(rql)
try:
- self.solutions(req, rqlst, args)
+ self.compute_var_types(req, rqlst, args)
except UnknownEid:
for select in rqlst.children:
select.solutions = []
--- a/cubicweb/devtools/__init__.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/devtools/__init__.py Thu Mar 05 10:15:38 2020 +0100
@@ -624,14 +624,14 @@
def init_test_database(self):
"""initialize a fresh postgresql database used for testing purpose"""
from cubicweb.server import init_repository
- from cubicweb.server.serverctl import system_source_cnx, createdb
+ from cubicweb.server.serverctl import source_cnx, createdb
# connect on the dbms system base to create our base
try:
self._drop(self.system_source['db-name'])
createdb(self.helper, self.system_source, self.dbcnx, self.cursor)
self.dbcnx.commit()
- cnx = system_source_cnx(self.system_source, special_privs='LANGUAGE C',
- interactive=False)
+ cnx = source_cnx(self.system_source, special_privs='LANGUAGE C',
+ interactive=False)
templcursor = cnx.cursor()
try:
# XXX factorize with db-create code
--- a/cubicweb/devtools/repotest.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/devtools/repotest.py Thu Mar 05 10:15:38 2020 +0100
@@ -209,7 +209,7 @@
def _prepare_plan(self, cnx, rql, kwargs=None):
rqlst = self.repo.vreg.rqlhelper.parse(rql, annotate=True)
- self.repo.vreg.solutions(cnx, rqlst, kwargs)
+ self.repo.vreg.compute_var_types(cnx, rqlst, kwargs)
if rqlst.TYPE == 'select':
self.repo.vreg.rqlhelper.annotate(rqlst)
for select in rqlst.children:
--- a/cubicweb/entities/adapters.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/entities/adapters.py Thu Mar 05 10:15:38 2020 +0100
@@ -20,18 +20,62 @@
"""
from cubicweb import _
+from hashlib import sha1
from itertools import chain
+from rdflib import URIRef, Literal
+
from logilab.mtconverter import TransformError
-from logilab.common.decorators import cached
+from logilab.common.decorators import cached, cachedproperty
-from cubicweb import (Unauthorized, ValidationError, view, ViolatedConstraint,
+from cubicweb.entity import EntityAdapter
+from cubicweb import (Unauthorized, ValidationError, ViolatedConstraint,
UniqueTogetherError)
from cubicweb.schema import constraint_name_for
from cubicweb.predicates import is_instance, relation_possible, match_exception
+from cubicweb.rdf import NAMESPACES
-class IDublinCoreAdapter(view.EntityAdapter):
+
+class EntityRDFAdapter(EntityAdapter):
+ """EntityRDFAdapter is to be specialized for each entity that wants to
+ be converted to RDF using the mechanism from cubicweb.rdf
+ """
+ __abstract__ = True
+
+ def __init__(self, _cw, **kwargs):
+ super().__init__(_cw, **kwargs)
+ self.entity.complete()
+
+ @cachedproperty
+ def uri(self):
+ return self.entity.cwuri
+
+ def triples(self):
+ """return sequence of 3-tuple of rdflib identifiers"""
+ raise NotImplementedError()
+
+
+class CWUserFoafAdapter(EntityRDFAdapter):
+ __regid__ = "rdf.foaf"
+ __select__ = is_instance("CWUser")
+
+ def triples(self):
+ RDF = NAMESPACES["rdf"]
+ FOAF = NAMESPACES["foaf"]
+ uri = URIRef(self.uri)
+ yield (uri, RDF.type, FOAF.Person)
+ if self.entity.surname:
+ yield (uri, FOAF.familyName, Literal(self.entity.surname))
+ if self.entity.firstname:
+ yield (uri, FOAF.givenName, Literal(self.entity.firstname))
+ emailaddr = self.entity.cw_adapt_to("IEmailable").get_email()
+ if emailaddr:
+ email_digest = sha1(emailaddr.encode("utf-8")).hexdigest()
+ yield (uri, FOAF.mbox_sha1sum, Literal(email_digest))
+
+
+class IDublinCoreAdapter(EntityAdapter):
__regid__ = 'IDublinCore'
__select__ = is_instance('Any')
@@ -93,7 +137,7 @@
return self._cw._(self._cw.vreg.property_value('ui.language'))
-class IEmailableAdapter(view.EntityAdapter):
+class IEmailableAdapter(EntityAdapter):
__regid__ = 'IEmailable'
__select__ = relation_possible('primary_email') | relation_possible('use_email')
@@ -126,7 +170,7 @@
for attr in self.allowed_massmail_keys())
-class INotifiableAdapter(view.EntityAdapter):
+class INotifiableAdapter(EntityAdapter):
__regid__ = 'INotifiable'
__select__ = is_instance('Any')
@@ -145,7 +189,7 @@
return ()
-class IFTIndexableAdapter(view.EntityAdapter):
+class IFTIndexableAdapter(EntityAdapter):
"""standard adapter to handle fulltext indexing
.. automethod:: cubicweb.entities.adapters.IFTIndexableAdapter.fti_containers
@@ -226,7 +270,7 @@
maindict.setdefault(weight, []).extend(words)
-class IDownloadableAdapter(view.EntityAdapter):
+class IDownloadableAdapter(EntityAdapter):
"""interface for downloadable entities"""
__regid__ = 'IDownloadable'
__abstract__ = True
@@ -256,7 +300,7 @@
# XXX should propose to use two different relations for children/parent
-class ITreeAdapter(view.EntityAdapter):
+class ITreeAdapter(EntityAdapter):
"""This adapter provides a tree interface.
It has to be overriden to be configured using the tree_relation,
@@ -412,7 +456,7 @@
return path
-class ISerializableAdapter(view.EntityAdapter):
+class ISerializableAdapter(EntityAdapter):
"""Adapter to serialize an entity to a bare python structure that may be
directly serialized to e.g. JSON.
"""
@@ -441,7 +485,7 @@
# error handling adapters ######################################################
-class IUserFriendlyError(view.EntityAdapter):
+class IUserFriendlyError(EntityAdapter):
__regid__ = 'IUserFriendlyError'
__abstract__ = True
--- a/cubicweb/entities/wfobjs.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/entities/wfobjs.py Thu Mar 05 10:15:38 2020 +0100
@@ -24,7 +24,7 @@
from logilab.common.decorators import cached, clear_cache
from cubicweb.entities import AnyEntity, fetch_config
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import relation_possible
--- a/cubicweb/entity.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/entity.py Thu Mar 05 10:15:38 2020 +0100
@@ -1118,7 +1118,7 @@
etypecls.fetch_rqlst(self._cw.user, select, searchedvar,
ordermethod=ordermethod)
# from now on, we need variable type resolving
- self._cw.vreg.solutions(self._cw, select, args)
+ self._cw.vreg.compute_var_types(self._cw, select, args)
# insert RQL expressions for schema constraints into the rql syntax tree
if vocabconstraints:
cstrcls = (RQLVocabularyConstraint, RQLConstraint)
@@ -1314,6 +1314,28 @@
def __set__(self, eobj, value):
raise NotImplementedError
+# entity adapters #############################################################
+
+class Adapter(AppObject):
+ """base class for adapters"""
+ __registry__ = 'adapters'
+
+
+class EntityAdapter(Adapter):
+ """base class for entity adapters (eg adapt an entity to an interface)
+
+ An example would be:
+
+ >>> some_entity.cw_adapt_to('IDownloadable')
+ """
+ def __init__(self, _cw, **kwargs):
+ try:
+ self.entity = kwargs.pop('entity')
+ except KeyError:
+ self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
+ kwargs.get('col') or 0)
+ Adapter.__init__(self, _cw, **kwargs)
+
from logging import getLogger
from cubicweb import set_log_methods
--- a/cubicweb/pyramid/predicates.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/pyramid/predicates.py Thu Mar 05 10:15:38 2020 +0100
@@ -21,6 +21,8 @@
"""Contains predicates used in Pyramid views.
"""
+from cubicweb._exceptions import UnknownEid
+
class MatchIsETypePredicate(object):
"""A predicate that match if a given etype exist in schema.
@@ -38,5 +40,30 @@
request.registry['cubicweb.registry'].case_insensitive_etypes
+class MatchIsEIDPredicate(object):
+ """A predicate that match if a given eid exist in the database.
+ """
+ def __init__(self, matchname, config):
+ self.matchname = matchname
+
+ def text(self):
+ return 'match_is_eid = %s' % self.matchname
+
+ phash = text
+
+ def __call__(self, info, request):
+ try:
+ eid = int(info['match'][self.matchname])
+ except ValueError:
+ return False
+
+ try:
+ request.cw_cnx.entity_from_eid(eid)
+ except UnknownEid:
+ return False
+ return True
+
+
def includeme(config):
config.add_route_predicate('match_is_etype', MatchIsETypePredicate)
+ config.add_route_predicate('match_is_eid', MatchIsEIDPredicate)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/rdf.py Thu Mar 05 10:15:38 2020 +0100
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+# copyright 2019 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from rdflib import ConjunctiveGraph, plugin
+from rdflib.namespace import Namespace, RDF, FOAF
+import rdflib_jsonld # noqa
+
+plugin.register("jsonld", plugin.Serializer, "rdflib_jsonld.serializer", "JsonLDSerializer")
+
+RDF_MIMETYPE_TO_FORMAT = {
+ 'application/rdf+xml': 'xml',
+ 'text/turtle': 'turtle',
+ 'text/n3': 'n3',
+ 'application/n-quads': 'nquads',
+ 'application/n-triples': 'nt',
+ 'application/trig': 'trig',
+ 'application/ld+json': 'json-ld',
+}
+
+NAMESPACES = {
+ "rdf": RDF,
+ "schema": Namespace("http://schema.org/"),
+ "foaf": FOAF,
+}
+
+
+# dict: name of CWEType -> list of regid of adapters derived from EntityRDFAdapter
+ETYPES_ADAPTERS = {
+ "CWUser": ("rdf.foaf",),
+}
+
+
+def conjunctive_graph():
+ """factory to build a ``ConjunctiveGraph`` and bind all namespaces
+ """
+ graph = ConjunctiveGraph()
+ for prefix, rdfns in NAMESPACES.items():
+ graph.bind(prefix, rdfns)
+ return graph
+
+
+def iter_rdf_adapters(entity):
+ for adapter_id in ETYPES_ADAPTERS.get(entity.__regid__, ()):
+ adapter = entity.cw_adapt_to(adapter_id)
+ if adapter:
+ yield adapter
+
+
+def add_entity_to_graph(graph, entity):
+ for adapter in iter_rdf_adapters(entity):
+ for triple in adapter.triples():
+ graph.add(triple)
--- a/cubicweb/rqlrewrite.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/rqlrewrite.py Thu Mar 05 10:15:38 2020 +0100
@@ -245,7 +245,7 @@
vreg = session.vreg
self.schema = vreg.schema
self.annotate = vreg.rqlhelper.annotate
- self._compute_solutions = vreg.solutions
+ self._compute_solutions = vreg.compute_var_types
def compute_solutions(self):
self.annotate(self.select)
--- a/cubicweb/server/hook.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/hook.py Thu Mar 05 10:15:38 2020 +0100
@@ -248,6 +248,7 @@
from logging import getLogger
from itertools import chain
+from typing import Union, Tuple
from logilab.common.decorators import classproperty, cached
from logilab.common.logging_ext import set_log_methods
@@ -521,8 +522,8 @@
"""
__select__ = enabled_category()
# set this in derivated classes
- events = None
- category = None
+ events: Union[None, Tuple[str], Tuple[str, str]] = None
+ category: Union[None, str] = None
order = 0
# stop pylint from complaining about missing attributes in Hooks classes
eidfrom = eidto = entity = rtype = repo = None
--- a/cubicweb/server/querier.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/querier.py Thu Mar 05 10:15:38 2020 +0100
@@ -36,9 +36,9 @@
from cubicweb.utils import QueryCache, RepeatList
from cubicweb.misc.source_highlight import highlight_terminal
-from cubicweb.server.rqlannotation import SQLGenAnnotator, set_qdata
+from cubicweb.server.rqlannotation import RQLAnnotator, set_qdata
from cubicweb.server.ssplanner import (READ_ONLY_RTYPES, add_types_restriction,
- SSPlanner)
+ prepare_plan)
from cubicweb.server.edition import EditedEntity
from cubicweb.statsd_logger import statsd_timeit, statsd_c
@@ -154,20 +154,15 @@
class ExecutionPlan(object):
"""the execution model of a rql query, composed of querier steps"""
- def __init__(self, querier, rqlst, args, cnx):
+ def __init__(self, schema, rqlst, args, cnx):
+ self.schema = schema
# original rql syntax tree
self.rqlst = rqlst
self.args = args or {}
# cnx executing the query
self.cnx = cnx
- # quick reference to the system source
- self.syssource = cnx.repo.system_source
# execution steps
self.steps = []
- # various resource accesors
- self.querier = querier
- self.schema = querier.schema
- self.rqlhelper = cnx.vreg.rqlhelper
# tracing token for debugging
self.rql_query_tracing_token = None
@@ -176,7 +171,7 @@
self.steps.append(step)
def sqlexec(self, sql, args=None):
- return self.syssource.sqlexec(self.cnx, sql, args)
+ return self.cnx.repo.system_source.sqlexec(self.cnx, sql, args)
def execute(self):
"""execute a plan and return resulting rows"""
@@ -215,8 +210,8 @@
else:
noinvariant = ()
if cached is None:
- self.rqlhelper.simplify(union)
- self.querier.sqlgen_annotate(union)
+ self.cnx.vreg.rqlhelper.simplify(union)
+ RQLAnnotator(self.schema).annotate(union)
set_qdata(self.schema.rschema, union, noinvariant)
if union.has_text_query:
self.cache_key = None
@@ -314,7 +309,7 @@
sol[newvarname] = nvartype
select.clean_solutions(solutions)
add_types_restriction(self.schema, select)
- self.rqlhelper.annotate(rqlst)
+ self.cnx.vreg.rqlhelper.annotate(rqlst)
self.preprocess(rqlst, security=False)
return rqlst
@@ -323,8 +318,8 @@
"""an execution model specific to the INSERT rql query
"""
- def __init__(self, querier, rqlst, args, cnx):
- ExecutionPlan.__init__(self, querier, rqlst, args, cnx)
+ def __init__(self, schema, rqlst, args, cnx):
+ ExecutionPlan.__init__(self, schema, rqlst, args, cnx)
# save originally selected variable, we may modify this
# dictionary for substitution (query parameters)
self.selected = rqlst.selection
@@ -478,10 +473,6 @@
def set_schema(self, schema):
self.schema = schema
self.clear_caches()
- # rql planner
- self._planner = SSPlanner(schema, self._repo.vreg.rqlhelper)
- # sql generation annotator
- self.sqlgen_annotate = SQLGenAnnotator(schema).annotate
def clear_caches(self, eids=None, etypes=None):
if eids is None:
@@ -496,8 +487,8 @@
def plan_factory(self, rqlst, args, cnx):
"""create an execution plan for an INSERT RQL query"""
if rqlst.TYPE == 'insert':
- return InsertPlan(self, rqlst, args, cnx)
- return ExecutionPlan(self, rqlst, args, cnx)
+ return InsertPlan(self.schema, rqlst, args, cnx)
+ return ExecutionPlan(self.schema, rqlst, args, cnx)
@statsd_timeit
def execute(self, cnx, rql, args=None, build_descr=True):
@@ -555,7 +546,7 @@
plan = self.plan_factory(rqlst, args, cnx)
plan.cache_key = cachekey
plan.rql_query_tracing_token = str(uuid.uuid4())
- self._planner.build_plan(plan)
+ prepare_plan(plan, self.schema, self._repo.vreg.rqlhelper)
query_debug_informations = {
"rql": rql,
@@ -567,7 +558,6 @@
}
start = time.time()
-
# execute the plan
try:
results = plan.execute()
@@ -634,7 +624,7 @@
# some cache usage stats
self.cache_hit, self.cache_miss = 0, 0
# rql parsing / analysing helper
- self.solutions = repo.vreg.solutions
+ self.compute_var_types = repo.vreg.compute_var_types
rqlhelper = repo.vreg.rqlhelper
# set backend on the rql helper, will be used for function checking
rqlhelper.backend = repo.config.system_source_config['db-driver']
@@ -677,7 +667,7 @@
# which are eids. Notice that if you may not need `eidkeys`, we
# have to compute solutions anyway (kept as annotation on the
# tree)
- eidkeys = self.solutions(cnx, rqlst, args)
+ eidkeys = self.compute_var_types(cnx, rqlst, args)
if args and rql not in self._ck_cache:
self._ck_cache[rql] = eidkeys
if eidkeys:
--- a/cubicweb/server/rqlannotation.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/rqlannotation.py Thu Mar 05 10:15:38 2020 +0100
@@ -117,7 +117,8 @@
var._q_invariant = False
-class SQLGenAnnotator(object):
+class RQLAnnotator(object):
+ """Annotate the RQL abstract syntax tree to inform the SQL generation"""
def __init__(self, schema):
self.schema = schema
--- a/cubicweb/server/serverctl.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/serverctl.py Thu Mar 05 10:15:38 2020 +0100
@@ -26,6 +26,7 @@
from logilab.common.configuration import Configuration, merge_options
from logilab.common.shellutils import ASK, generate_password
+from logilab.common.deprecation import deprecated
from logilab.database import get_db_helper, get_connection
@@ -96,6 +97,9 @@
return cnx
+@deprecated('[3.28] instead of system_source_cnx(source, True, **args) use '
+ 'source_cnx(source, get_db_helper(source[\'db-driver\']).system_database(), '
+ '**args)')
def system_source_cnx(source, dbms_system_base=False,
special_privs='CREATE/DROP DATABASE', interactive=True):
"""shortcut to get a connextion to the instance system database
@@ -118,8 +122,9 @@
import logilab.common as lgp
lgp.USE_MX_DATETIME = False
# connect on the dbms system base to create our base
- cnx = system_source_cnx(source, True, special_privs=special_privs,
- interactive=interactive)
+ system_db = get_db_helper(source['db-driver']).system_database()
+ cnx = source_cnx(source, system_db, special_privs=special_privs,
+ interactive=interactive)
# disable autocommit (isolation_level(1)) because DROP and
# CREATE DATABASE can't be executed in a transaction
set_isolation_level = getattr(cnx, 'set_isolation_level', None)
@@ -201,7 +206,7 @@
@contextmanager
def db_transaction(source, privilege):
"""Open a transaction to the instance database"""
- cnx = system_source_cnx(source, special_privs=privilege)
+ cnx = source_cnx(source, special_privs=privilege)
cursor = cnx.cursor()
try:
yield cursor
@@ -379,8 +384,8 @@
except BaseException:
dbcnx.rollback()
raise
- cnx = system_source_cnx(source, special_privs='CREATE LANGUAGE/SCHEMA',
- interactive=not automatic)
+ cnx = source_cnx(source, special_privs='CREATE LANGUAGE/SCHEMA',
+ interactive=not automatic)
cursor = cnx.cursor()
helper.init_fti_extensions(cursor)
namespace = source.get('db-namespace')
@@ -572,7 +577,7 @@
config = ServerConfiguration.config_for(appid)
source = config.system_source_config
set_owner = self.config.set_owner
- cnx = system_source_cnx(source, special_privs='GRANT')
+ cnx = source_cnx(source, special_privs='GRANT')
cursor = cnx.cursor()
schema = config.load_schema()
try:
--- a/cubicweb/server/sources/native.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/sources/native.py Thu Mar 05 10:15:38 2020 +0100
@@ -48,11 +48,10 @@
from cubicweb.server import schema2sql as y2sql
from cubicweb.server.utils import crypt_password, verify_and_update
from cubicweb.server.sqlutils import SQL_PREFIX, SQLAdapterMixIn
-from cubicweb.server.rqlannotation import set_qdata
+from cubicweb.server.rqlannotation import RQLAnnotator, set_qdata
from cubicweb.server.hook import CleanupDeletedEidsCacheOp
from cubicweb.server.edition import EditedEntity
-from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results
-from cubicweb.server.sources.rql2sql import SQLGenerator
+from cubicweb.server.sources import AbstractSource, dbg_st_search, dbg_results, rql2sql
from cubicweb.misc.source_highlight import highlight_terminal
from cubicweb.statsd_logger import statsd_timeit
@@ -260,7 +259,7 @@
class NativeSQLSource(SQLAdapterMixIn, AbstractSource):
"""adapter for source using the native cubicweb schema (see below)
"""
- sqlgen_class = SQLGenerator
+
options = (
('db-driver',
{'type': 'string',
@@ -333,8 +332,8 @@
self.authentifiers.insert(0, EmailPasswordAuthentifier(self))
AbstractSource.__init__(self, repo, source_config, *args, **kwargs)
# sql generator
- self._rql_sqlgen = self.sqlgen_class(self.schema, self.dbhelper,
- ATTR_MAP.copy())
+ self._rql_sqlgen = rql2sql.SQLGenerator(self.schema, self.dbhelper,
+ ATTR_MAP.copy())
# full text index helper
self.do_fti = not repo.config['delay-full-text-indexation']
# sql queries cache
@@ -473,7 +472,7 @@
rqlst = self.repo.vreg.rqlhelper.parse(rql)
rqlst.restricted_vars = ()
rqlst.children[0].solutions = sols
- self.repo.querier.sqlgen_annotate(rqlst)
+ RQLAnnotator(self.repo.querier.schema).annotate(rqlst)
set_qdata(self.schema.rschema, rqlst, ())
return rqlst
--- a/cubicweb/server/ssplanner.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/ssplanner.py Thu Mar 05 10:15:38 2020 +0100
@@ -307,6 +307,11 @@
return self.build_select_plan(plan, union)
+def prepare_plan(plan, schema, rqlhelper):
+ """Add steps to a plan to prepare it for execution"""
+ return SSPlanner(schema, rqlhelper).build_plan(plan)
+
+
# execution steps and helper functions ########################################
class Step(object):
--- a/cubicweb/server/test/unittest_rqlannotation.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/test/unittest_rqlannotation.py Thu Mar 05 10:15:38 2020 +0100
@@ -22,14 +22,14 @@
from cubicweb.devtools.repotest import BaseQuerierTC
-class SQLGenAnnotatorTC(BaseQuerierTC):
+class RQLAnnotatorTC(BaseQuerierTC):
def setUp(self):
handler = devtools.get_test_db_handler(devtools.TestServerConfiguration('data', __file__))
handler.build_db_cache()
repo, _cnx = handler.get_repo_and_cnx()
self.__class__.repo = repo
- super(SQLGenAnnotatorTC, self).setUp()
+ super(RQLAnnotatorTC, self).setUp()
def test_0_1(self):
with self.admin_access.cnx() as cnx:
--- a/cubicweb/server/test/unittest_server_security.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/server/test/unittest_server_security.py Thu Mar 05 10:15:38 2020 +0100
@@ -44,7 +44,7 @@
nom = self.repo.schema['Personne'].rdef('nom')
with self.temporary_permissions((nom, {'read': ('users', 'managers')})):
with self.admin_access.repo_cnx() as cnx:
- self.repo.vreg.solutions(cnx, rqlst, None)
+ self.repo.vreg.compute_var_types(cnx, rqlst, None)
check_relations_read_access(cnx, rqlst, {})
with self.new_access(u'anon').repo_cnx() as cnx:
self.assertRaises(Unauthorized,
@@ -57,7 +57,7 @@
rqlst = self.repo.vreg.rqlhelper.parse(rql).children[0]
with self.temporary_permissions(Personne={'read': ('users', 'managers')}):
with self.admin_access.repo_cnx() as cnx:
- self.repo.vreg.solutions(cnx, rqlst, None)
+ self.repo.vreg.compute_var_types(cnx, rqlst, None)
solution = rqlst.solutions[0]
localchecks = get_local_checks(cnx, rqlst, solution)
self.assertEqual({}, localchecks)
@@ -520,7 +520,7 @@
ERQLExpression('X owned_by U'))}):
with self.new_access(u'iaminusersgrouponly').repo_cnx() as cnx:
rqlst = self.repo.vreg.rqlhelper.parse('Any X WHERE X is_instance_of Societe')
- self.repo.vreg.solutions(cnx, rqlst, {})
+ self.repo.vreg.compute_var_types(cnx, rqlst, {})
self.repo.vreg.rqlhelper.annotate(rqlst)
plan = cnx.repo.querier.plan_factory(rqlst, {}, cnx)
plan.preprocess(rqlst)
--- a/cubicweb/test/unittest_predicates.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/test/unittest_predicates.py Thu Mar 05 10:15:38 2020 +0100
@@ -29,7 +29,7 @@
multi_lines_rset, score_entity, is_in_state,
rql_condition, relation_possible, match_form_params,
paginated_rset)
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.web import action
--- a/cubicweb/test/unittest_rqlrewrite.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/test/unittest_rqlrewrite.py Thu Mar 05 10:15:38 2020 +0100
@@ -58,7 +58,7 @@
schema = schema
@staticmethod
- def solutions(sqlcursor, rqlst, kwargs):
+ def compute_var_types(sqlcursor, rqlst, kwargs):
rqlhelper.compute_solutions(rqlst, {'eid': eid_func_map}, kwargs=kwargs)
class rqlhelper:
@@ -602,7 +602,7 @@
args = {}
union = parse(rql) # self.vreg.parse(rql, annotate=True)
with self.admin_access.repo_cnx() as cnx:
- self.vreg.solutions(cnx, union, args)
+ self.vreg.compute_var_types(cnx, union, args)
self.vreg.rqlhelper.annotate(union)
plan = self.repo.querier.plan_factory(union, args, cnx)
plan.preprocess(union)
--- a/cubicweb/test/unittest_vregistry.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/test/unittest_vregistry.py Thu Mar 05 10:15:38 2020 +0100
@@ -23,7 +23,7 @@
from cubicweb import CW_SOFTWARE_ROOT as BASE, devtools
from cubicweb.cwvreg import CWRegistryStore, UnknownProperty
from cubicweb.devtools.testlib import CubicWebTC
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
class YesSchema:
--- a/cubicweb/view.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/view.py Thu Mar 05 10:15:38 2020 +0100
@@ -554,18 +554,7 @@
def domid(self):
return '%sComponent' % domid(self.__regid__)
-
-class Adapter(AppObject):
- """base class for adapters"""
- __registry__ = 'adapters'
-
-
-class EntityAdapter(Adapter):
- """base class for entity adapters (eg adapt an entity to an interface)"""
- def __init__(self, _cw, **kwargs):
- try:
- self.entity = kwargs.pop('entity')
- except KeyError:
- self.entity = kwargs['rset'].get_entity(kwargs.get('row') or 0,
- kwargs.get('col') or 0)
- Adapter.__init__(self, _cw, **kwargs)
+# EntityAdapter moved to cubicweb.entity ######################################
+from logilab.common.deprecation import class_moved
+from cubicweb import entity
+EntityAdapter = class_moved(entity.EntityAdapter) # cubicweb 3.28
--- a/cubicweb/web/action.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/action.py Thu Mar 05 10:15:38 2020 +0100
@@ -32,6 +32,7 @@
Many examples are available in :mod:`cubicweb.web.views.actions`.
"""
+from typing import Optional
from cubicweb import _
@@ -48,7 +49,7 @@
__registry__ = 'actions'
__select__ = match_search_state('normal')
order = 99
- category = 'moreactions'
+ category: Optional[str] = 'moreactions'
# actions in category 'moreactions' can specify a sub-menu in which they should be filed
submenu = None
--- a/cubicweb/web/test/unittest_idownloadable.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/test/unittest_idownloadable.py Thu Mar 05 10:15:38 2020 +0100
@@ -23,12 +23,12 @@
from pytz import utc
from cubicweb.devtools.testlib import CubicWebTC, real_error_handling
-from cubicweb import view
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import is_instance
from cubicweb.web import http_headers
-class IDownloadableUser(view.EntityAdapter):
+class IDownloadableUser(EntityAdapter):
__regid__ = 'IDownloadable'
__select__ = is_instance('CWUser')
--- a/cubicweb/web/views/calendar.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/calendar.py Thu Mar 05 10:15:38 2020 +0100
@@ -28,7 +28,8 @@
from cubicweb.utils import json_dumps, make_uid
from cubicweb.predicates import adaptable
-from cubicweb.view import EntityView, EntityAdapter
+from cubicweb.view import EntityView
+from cubicweb.entity import EntityAdapter
# useful constants & functions ################################################
--- a/cubicweb/web/views/editcontroller.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/editcontroller.py Thu Mar 05 10:15:38 2020 +0100
@@ -27,7 +27,7 @@
from rql.utils import rqlvar_maker
from cubicweb import _, ValidationError, UnknownEid
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.predicates import is_instance
from cubicweb.web import RequestError, NothingToEdit, ProcessFormError
from cubicweb.web.views import basecontrollers, autoform
--- a/cubicweb/web/views/ibreadcrumbs.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/ibreadcrumbs.py Thu Mar 05 10:15:38 2020 +0100
@@ -25,11 +25,11 @@
from logilab.mtconverter import xml_escape
from cubicweb import tags, uilib
-from cubicweb.entity import Entity
+from cubicweb.entity import Entity, EntityAdapter
from cubicweb.predicates import (is_instance, one_line_rset, adaptable,
one_etype_rset, multi_lines_rset, any_rset,
match_form_params)
-from cubicweb.view import EntityView, EntityAdapter
+from cubicweb.view import EntityView
from cubicweb.web.views import basecomponents
# don't use AnyEntity since this may cause bug with isinstance() due to reloading
--- a/cubicweb/web/views/magicsearch.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/magicsearch.py Thu Mar 05 10:15:38 2020 +0100
@@ -454,7 +454,7 @@
user_rql = variables
select = parse(user_rql, print_errors=False).children[0]
req.vreg.rqlhelper.annotate(select)
- req.vreg.solutions(req, select, {})
+ req.vreg.compute_var_types(req, select, {})
if restrictions:
return ['%s, %s' % (user_rql, suggestion)
for suggestion in self.rql_build_suggestions(select, incomplete_part)]
--- a/cubicweb/web/views/navigation.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/navigation.py Thu Mar 05 10:15:38 2020 +0100
@@ -56,7 +56,7 @@
from cubicweb.predicates import paginated_rset, sorted_rset, adaptable
from cubicweb.uilib import cut
-from cubicweb.view import EntityAdapter
+from cubicweb.entity import EntityAdapter
from cubicweb.web.component import EmptyComponent, EntityCtxComponent, NavigationComponent
--- a/cubicweb/web/views/xmlrss.py Thu Mar 05 09:54:49 2020 +0100
+++ b/cubicweb/web/views/xmlrss.py Thu Mar 05 10:15:38 2020 +0100
@@ -26,7 +26,8 @@
from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
appobject_selectable, adaptable)
-from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
+from cubicweb.view import EntityView, AnyRsetView, Component
+from cubicweb.entity import EntityAdapter
from cubicweb.uilib import simple_sgml_tag
from cubicweb.web import httpcache, component
--- a/doc/book/devrepo/datamodel/definition.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devrepo/datamodel/definition.rst Thu Mar 05 10:15:38 2020 +0100
@@ -466,7 +466,7 @@
__permissions__ = {'read': ('managers', 'users'),
'add': ('managers', RRQLExpression('U has_update_permission S')),
'delete': ('managers', RRQLExpression('U has_update_permission S'))
- }
+ }
In the above example, user will be allowed to add/delete `my_relation` if he has
the `update` permission on the subject of the relation.
--- a/doc/book/devrepo/repo/hooks.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devrepo/repo/hooks.rst Thu Mar 05 10:15:38 2020 +0100
@@ -35,11 +35,11 @@
events = ('before_add_entity', 'before_update_entity')
def __call__(self):
- if 'age' in self.entity.cw_edited:
+ if 'age' in self.entity.cw_edited:
if 0 <= self.entity.age <= 120:
return
- msg = self._cw._('age must be between 0 and 120')
- raise ValidationError(self.entity.eid, {'age': msg})
+ msg = self._cw._('age must be between 0 and 120')
+ raise ValidationError(self.entity.eid, {'age': msg})
In our example the base `__select__` is augmented with an `is_instance` selector
matching the desired entity type.
--- a/doc/book/devrepo/vreg.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devrepo/vreg.rst Thu Mar 05 10:15:38 2020 +0100
@@ -300,18 +300,18 @@
.. sourcecode:: python
class UserLink(component.Component):
- '''if the user is the anonymous user, build a link to login else a link
- to the connected user object with a logout link
- '''
- __regid__ = 'loggeduserlink'
+ '''if the user is the anonymous user, build a link to login else a link
+ to the connected user object with a logout link
+ '''
+ __regid__ = 'loggeduserlink'
- def call(self):
- if self._cw.session.anonymous_session:
- # display login link
- ...
- else:
- # display a link to the connected user object with a loggout link
- ...
+ def call(self):
+ if self._cw.session.anonymous_session:
+ # display login link
+ ...
+ else:
+ # display a link to the connected user object with a loggout link
+ ...
The proper way to implement this with |cubicweb| is two have two different
classes sharing the same identifier but with different selectors so you'll get
@@ -320,21 +320,21 @@
.. sourcecode:: python
class UserLink(component.Component):
- '''display a link to the connected user object with a loggout link'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & authenticated_user()
+ '''display a link to the connected user object with a loggout link'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & authenticated_user()
- def call(self):
+ def call(self):
# display useractions and siteactions
- ...
+ ...
class AnonUserLink(component.Component):
- '''build a link to login'''
- __regid__ = 'loggeduserlink'
- __select__ = component.Component.__select__ & anonymous_user()
+ '''build a link to login'''
+ __regid__ = 'loggeduserlink'
+ __select__ = component.Component.__select__ & anonymous_user()
- def call(self):
- # display login link
+ def call(self):
+ # display login link
...
The big advantage, aside readability once you're familiar with the
--- a/doc/book/devweb/edition/examples.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devweb/edition/examples.rst Thu Mar 05 10:15:38 2020 +0100
@@ -56,42 +56,42 @@
.. sourcecode:: python
def sender_value(form, field):
- return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
+ return '%s <%s>' % (form._cw.user.dc_title(), form._cw.user.get_email())
def recipient_choices(form, field):
- return [(e.get_email(), e.eid)
+ return [(e.get_email(), e.eid)
for e in form.cw_rset.entities()
- if e.get_email()]
+ if e.get_email()]
def recipient_value(form, field):
- return [e.eid for e in form.cw_rset.entities()
+ return [e.eid for e in form.cw_rset.entities()
if e.get_email()]
class MassMailingForm(forms.FieldsForm):
- __regid__ = 'massmailing'
+ __regid__ = 'massmailing'
- needs_js = ('cubicweb.widgets.js',)
- domid = 'sendmail'
- action = 'sendmail'
+ needs_js = ('cubicweb.widgets.js',)
+ domid = 'sendmail'
+ action = 'sendmail'
- sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
- label=_('From:'),
- value=sender_value)
+ sender = ff.StringField(widget=TextInput({'disabled': 'disabled'}),
+ label=_('From:'),
+ value=sender_value)
- recipient = ff.StringField(widget=CheckBox(),
- label=_('Recipients:'),
- choices=recipient_choices,
- value=recipients_value)
+ recipient = ff.StringField(widget=CheckBox(),
+ label=_('Recipients:'),
+ choices=recipient_choices,
+ value=recipients_value)
- subject = ff.StringField(label=_('Subject:'), max_length=256)
+ subject = ff.StringField(label=_('Subject:'), max_length=256)
- mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
- inputid='mailbody'))
+ mailbody = ff.StringField(widget=AjaxWidget(wdgtype='TemplateTextField',
+ inputid='mailbody'))
- form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
- _('send email'), 'SEND_EMAIL_ICON'),
- ImgButton('cancelbutton', "javascript: history.back()",
- stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
+ form_buttons = [ImgButton('sendbutton', "javascript: $('#sendmail').submit()",
+ _('send email'), 'SEND_EMAIL_ICON'),
+ ImgButton('cancelbutton', "javascript: history.back()",
+ stdmsgs.BUTTON_CANCEL, 'CANCEL_EMAIL_ICON')]
Let's detail what's going on up there. Our form will hold four fields:
@@ -125,13 +125,13 @@
.. sourcecode:: python
class MassMailingFormView(form.FormViewMixIn, EntityView):
- __regid__ = 'massmailing'
- __select__ = is_instance(IEmailable) & authenticated_user()
+ __regid__ = 'massmailing'
+ __select__ = is_instance(IEmailable) & authenticated_user()
- def call(self):
- form = self._cw.vreg['forms'].select('massmailing', self._cw,
- rset=self.cw_rset)
- form.render(w=self.w)
+ def call(self):
+ form = self._cw.vreg['forms'].select('massmailing', self._cw,
+ rset=self.cw_rset)
+ form.render(w=self.w)
As you see, we simply define a view with proper selector so it only apply to a
result set containing :class:`IEmailable` entities, and so that only users in the
--- a/doc/book/devweb/js.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devweb/js.rst Thu Mar 05 10:15:38 2020 +0100
@@ -142,7 +142,7 @@
function removeBookmark(beid) {
d = asyncRemoteExec('delete_bookmark', beid);
d.addCallback(function(boxcontent) {
- reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
+ reloadComponent('bookmarks_box', '', 'boxes', 'bookmarks_box');
document.location.hash = '#header';
updateMessage(_("bookmark has been removed"));
});
--- a/doc/book/devweb/views/reledit.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/book/devweb/views/reledit.rst Thu Mar 05 10:15:38 2020 +0100
@@ -136,14 +136,14 @@
from cubicweb.web.views import reledit
class DeactivatedAutoClickAndEditFormView(reledit.AutoClickAndEditFormView):
- def _should_edit_attribute(self, rschema):
- return False
+ def _should_edit_attribute(self, rschema):
+ return False
- def _should_edit_attribute(self, rschema, role):
- return False
+ def _should_edit_attribute(self, rschema, role):
+ return False
def registration_callback(vreg):
- vreg.register_and_replace(DeactivatedAutoClickAndEditFormView,
- reledit.AutoClickAndEditFormView)
+ vreg.register_and_replace(DeactivatedAutoClickAndEditFormView,
+ reledit.AutoClickAndEditFormView)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/changes/3.28.rst Thu Mar 05 10:15:38 2020 +0100
@@ -0,0 +1,8 @@
+3.28
+====
+
+Changes
+-------
+
+- the class cubicweb.view.EntityAdapter was moved to cubicweb.entity.EntityAdapter
+ a deprecation warning is in place, but please update your source code accordingly.
--- a/doc/changes/changelog.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/changes/changelog.rst Thu Mar 05 10:15:38 2020 +0100
@@ -2,6 +2,7 @@
Changelog history
===================
+.. include:: 3.28.rst
.. include:: 3.27.rst
.. include:: 3.26.rst
.. include:: 3.25.rst
--- a/doc/tutorials/advanced/part02_security.rst Thu Mar 05 09:54:49 2020 +0100
+++ b/doc/tutorials/advanced/part02_security.rst Thu Mar 05 10:15:38 2020 +0100
@@ -117,25 +117,25 @@
from cubicweb.schema import ERQLExpression
VISIBILITY_PERMISSIONS = {
- 'read': ('managers',
- ERQLExpression('X visibility "public"'),
- ERQLExpression('X may_be_read_by U')),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers',
+ ERQLExpression('X visibility "public"'),
+ ERQLExpression('X may_be_read_by U')),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
AUTH_ONLY_PERMISSIONS = {
- 'read': ('managers', 'users'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers', 'users'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
CLASSIFIERS_PERMISSIONS = {
- 'read': ('managers', 'users', 'guests'),
- 'add': ('managers',),
- 'update': ('managers', 'owners',),
- 'delete': ('managers', 'owners'),
- }
+ 'read': ('managers', 'users', 'guests'),
+ 'add': ('managers',),
+ 'update': ('managers', 'owners',),
+ 'delete': ('managers', 'owners'),
+ }
from cubicweb_folder.schema import Folder
from cubicweb_file.schema import File
--- a/setup.py Thu Mar 05 09:54:49 2020 +0100
+++ b/setup.py Thu Mar 05 10:15:38 2020 +0100
@@ -73,6 +73,8 @@
'pytz',
'Markdown',
'filelock',
+ 'rdflib',
+ 'rdflib-jsonld',
],
entry_points={
'console_scripts': [
@@ -102,9 +104,6 @@
'pyramid_multiauth',
'repoze.lru',
],
- 'rdf': [
- 'rdflib',
- ],
'sparql': [
'fyzz >= 0.1.0',
],