<commit_before>"""
This module contains the core methods for the Batch-command- and
Batch-code-processors respectively. In short, these are two different
ways to build a game world using a normal text-editor without having
to do so 'on the fly' in-game. They also serve as an automatic backup
so you can quickly recreate a world also after a server reset. The
functions in this module is meant to form the backbone of a system
called and accessed through game commands.

The Batch-command processor is the simplest. It simply runs a list of
in-game commands in sequence by reading them from a text file. The
advantage of this is that the builder only need to remember the normal
in-game commands. They are also executing with full permission checks
etc, making it relatively safe for builders to use. The drawback is
that in-game there is really a builder-character walking around
building things, and it can be important to create rooms and objects
in the right order, so the character can move between them. Also
objects that affects players (such as mobs, dark rooms etc) will
affect the building character too, requiring extra care to turn
off/on.

The Batch-code processor is a more advanced system that accepts full
Python code, executing in chunks. The advantage of this is much more
power; practically anything imaginable can be coded and handled using
the batch-code processor. There is no in-game character that moves and
that can be affected by what is being built - the database is
populated on the fly. The drawback is safety and entry threshold - the
code is executed as would any server code, without mud-specific
permission-checks, and you have full access to modifying objects
etc. You also need to know Python and Evennia's API. Hence it's
recommended that the batch-code processor is limited only to
superusers or highly trusted staff.

Batch-command processor file syntax

The batch-command processor accepts 'batchcommand files' e.g
`batch.ev`, containing a sequence of valid Evennia commands in a
simple format. The engine runs each command in sequence, as if they
had been run at the game prompt.

Each Evennia command must be delimited by a line comment to mark its
end.

```
#INSERT path.batchcmdfile - this as the first entry on a line will
      import and run a batch.ev file in this position, as if it was
      written in this file.
```

This way entire game worlds can be created and planned offline; it is
especially useful in order to create long room descriptions where a
real offline text editor is often much better than any online text
editor or prompt.

Example of batch.ev file:
----------------------------

```
# batch file
# all lines starting with # are comments; they also indicate
# that a command definition is over.

@create box

# this comment ends the @create command.

@set box/desc = A large box.

Inside are some scattered piles of clothing.

It seems the bottom of the box is a bit loose.

# Again, this comment indicates the @set command is over. Note how
# the description could be freely added. Excess whitespace on a line
# is ignored.  An empty line in the command definition is parsed as a \n
# (so two empty lines becomes a new paragraph).

@teleport #221

# (Assuming #221 is a warehouse or something.)
# (remember, this comment ends the @teleport command! Don'f forget it)

# Example of importing another file at this point.
#IMPORT examples.batch

@drop box

# Done, the box is in the warehouse! (this last comment is not necessary to
# close the @drop command since it's the end of the file)
```

-------------------------

An example batch file is `contrib/examples/batch_example.ev`.

==========================================================================

Batch-code processor file syntax

The Batch-code processor accepts full python modules (e.g. `batch.py`)
that looks identical to normal Python files. The difference from
importing and running any Python module is that the batch-code module
is loaded as a file and executed directly, so changes to the file will
apply immediately without a server @reload.

Optionally, one can add some special commented tokens to split the
execution of the code for the benefit of the batchprocessor's
interactive- and debug-modes. This allows to conveniently step through
the code and re-run sections of it easily during development.

Code blocks are marked by commented tokens alone on a line:

#HEADER - This denotes code that should be pasted at the top of all
         other code. Multiple HEADER statements - regardless of where
         it exists in the file - is the same as one big block.
         Observe that changes to variables made in one block is not
         preserved between blocks!
#CODE - This designates a code block that will be executed like a
       stand-alone piece of code together with any HEADER(s)
       defined. It is mainly used as a way to mark stop points for
       the interactive mode of the batchprocessor. If no CODE block
       is defined in the module, the entire module (including HEADERS)
       is assumed to be a CODE block.
#INSERT path.filename - This imports another batch_code.py file and
      runs it in the given position. The inserted file will retain
      its own HEADERs which will not be mixed with the headers of
      this file.

Importing works as normal. The following variables are automatically
made available in the script namespace.

- `caller` -  The object executing the batchscript
- `DEBUG` - This is a boolean marking if the batchprocessor is running
            in debug mode. It can be checked to e.g. delete created objects
            when running a CODE block multiple times during testing.
            (avoids creating a slew of same-named db objects)

Example batch.py file
-----------------------------------

```
#HEADER

from django.config import settings
from evennia.utils import create
from types import basetypes

GOLD = 10

#CODE

obj = create.create_object(basetypes.Object)
obj2 = create.create_object(basetypes.Object)
obj.location = caller.location
obj.db.gold = GOLD
caller.msg("The object was created!")

if DEBUG:
    obj.delete()
    obj2.delete()

#INSERT another_batch_file

#CODE

script = create.create_script()
```
"""
from builtins import object

import re
import codecs
import traceback
import sys
from django.conf import settings
from evennia.utils import utils

_ENCODINGS = settings.ENCODINGS
_RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
_RE_CODE_OR_HEADER = re.compile(r"((?:\A|^)#CODE|(?:/A|^)#HEADER|\A)(.*?)$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
                                re.MULTILINE + re.DOTALL)

# -------------------------------------------------------------
# Helper function
# -------------------------------------------------------------

def read_batchfile(pythonpath, file_ending='.py'):
    """
    This reads the contents of a batch-file.  Filename is considered
    to be a python path to a batch file relative the directory
    specified in `settings.py`.

    file_ending specify which batchfile ending should be assumed (.ev
    or .py). The ending should not be included in the python path.

    Args:
        pythonpath (str): A dot-python path to a file.
        file_ending (str): The file ending of this file (.ev or .py)

    Returns:
        text (str): The text content of the batch file.

    Raises:
        IOError: If problems reading file.

    """

    # find all possible absolute paths
    abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS)
    if not abspaths:
        raise IOError
    text = None
    decoderr = []
    for abspath in abspaths:
        # try different paths, until we get a match
        # we read the file directly into unicode.
        for file_encoding in _ENCODINGS:
            # try different encodings, in order
            try:
                with codecs.open(abspath, 'r', encoding=file_encoding) as fobj:
                    text = fobj.read()
            except (ValueError, UnicodeDecodeError) as e:
                # this means an encoding error; try another encoding
                decoderr.append(str(e))
                continue
            break
    if not text and decoderr:
        raise UnicodeDecodeError("\n".join(decoderr))

    return text

# -------------------------------------------------------------
#
# Batch-command processor
#
# -------------------------------------------------------------

class BatchCommandProcessor(object):
    """
    This class implements a batch-command processor.

    """

    def parse_file(self, pythonpath):
        """
        This parses the lines of a batchfile according to the following
        rules:
          1) # at the beginning of a line marks the end of the command before
               it. It is also a comment and any number of # can exist on
               subsequent lines (but not inside comments).
          2) #INSERT at the beginning of a line imports another
             batch-cmd file file and pastes it into the batch file as if
             it was written there.
          3) Commands are placed alone at the beginning of a line and their
             arguments are considered to be everything following (on any
             number of lines) until the next comment line beginning with #.
          4) Newlines are ignored in command definitions
          5) A completely empty line in a command line definition is condered
             a newline (so two empty lines is a paragraph).
          6) Excess spaces and indents inside arguments are stripped.

        """

        text = "".join(read_batchfile(pythonpath, file_ending='.ev'))

        def replace_insert(match):
            """Map replace entries"""
            return "\n#".join(self.parse_file(match.group(1)))

        # insert commands from inserted files
        text = _RE_INSERT.sub(replace_insert, text)
        #      re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
        # get all commands
        commands = _RE_CMD_SPLIT.split(text)
        #          re.split(r"^\#.*?$", text, flags=re.MULTILINE)
        # remove eventual newline at the end of commands
        commands = [c.strip('\r\n') for c in commands]
        commands = [c for c in commands if c]

        return commands

# -------------------------------------------------------------
#
# Batch-code processor
#
# -------------------------------------------------------------

def tb_filename(tb):
    """Helper to get filename from traceback"""
    return tb.tb_frame.f_code.co_filename

def tb_iter(tb):
    """Traceback iterator."""
    while tb is not None:
        yield tb
        tb = tb.tb_next

class BatchCodeProcessor(object):
    """
    This implements a batch-code processor

    """

    def parse_file(self, pythonpath):
        """
        This parses the lines of a batchfile according to the following
        rules:

        Args:
            pythonpath (str): The dot-python path to the file.

        Returns:
            codeblocks (list): A list of all #CODE blocks, each with
                prepended #HEADER data. If no #CODE blocks were found,
                this will be a list of one element.

        Notes:
            1. Code before a #CODE/HEADER block are considered part of
                the first code/header block or is the ONLY block if no
                #CODE/HEADER blocks are defined.
            2. Lines starting with #HEADER starts a header block (ends other blocks)
            3. Lines starting with #CODE begins a code block (ends other blocks)
            4. Lines starting with #INSERT are on form #INSERT filename. Code from
               this file are processed with their headers *separately* before
               being inserted at the point of the #INSERT.
            5. Code after the last block is considered part of the last header/code
               block

        """

        text = "".join(read_batchfile(pythonpath, file_ending='.py'))

        def replace_insert(match):
            """Run parse_file on the import before sub:ing it into this file"""
            path = match.group(1)
            return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path))

        # process and then insert code from all #INSERTS
        text = _RE_INSERT.sub(replace_insert, text)

        headers = []
        codes = []
        for imatch, match in enumerate(list(_RE_CODE_OR_HEADER.finditer(text))):
            mtype = match.group(1).strip()
            # we need to handle things differently at the start of the file
            mgroup = 3 if mtype else 2
            istart, iend = match.span(mgroup)
            code = text[istart:iend]
            if mtype == "#HEADER":
                headers.append(code)
            else:  # either #CODE or matching from start of file
                codes.append(code)

        # join all headers together to one
        header = "# batchcode header:\n%s\n\n" % "\n\n".join(headers) if headers else ""
        # add header to each code block
        codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes]
        return codes

    def code_exec(self, code, extra_environ=None, debug=False):
        """
        Execute a single code block, including imports and appending
        global vars.

        Args:
            code (str): Code to run.
            extra_environ (dict): Environment variables to run with code.
            debug (bool, optional): Set the DEBUG variable in the execution
                namespace.

        Returns:
            err (str or None): An error code or None (ok).

        """
        # define the execution environment
        environdict = {"settings_module": settings, "DEBUG": debug}
        for key, value in extra_environ.items():
            environdict[key] = value

        # initializing the django settings at the top of code
        code = "# batchcode evennia initialization: \n" \
               "try: settings_module.configure()\n" \
               "except RuntimeError: pass\n" \
               "finally: del settings_module\n\n%s" % code

        # execute the block
        try:
            exec(code, environdict)
        except Exception:
            etype, value, tb = sys.exc_info()

            fname = tb_filename(tb)
            for tb in tb_iter(tb):
                if fname != tb_filename(tb):
                    break
            lineno = tb.tb_lineno - 1
            err = ""
            for iline, line in enumerate(code.split("\n")):
                if iline == lineno:
                    err += "\n|w%02i|n: %s" % (iline + 1, line)
                elif lineno - 5 < iline < lineno + 5:
                    err += "\n%02i: %s" % (iline + 1, line)

            err += "\n".join(traceback.format_exception(etype, value, tb))
            return err
        return None

BATCHCMD = BatchCommandProcessor()
BATCHCODE = BatchCodeProcessor()
<commit_msg>Further fix for multi-line non-block code at the start of batchcode file<commit_after>"""
This module contains the core methods for the Batch-command- and
Batch-code-processors respectively. In short, these are two different
ways to build a game world using a normal text-editor without having
to do so 'on the fly' in-game. They also serve as an automatic backup
so you can quickly recreate a world also after a server reset. The
functions in this module is meant to form the backbone of a system
called and accessed through game commands.

The Batch-command processor is the simplest. It simply runs a list of
in-game commands in sequence by reading them from a text file. The
advantage of this is that the builder only need to remember the normal
in-game commands. They are also executing with full permission checks
etc, making it relatively safe for builders to use. The drawback is
that in-game there is really a builder-character walking around
building things, and it can be important to create rooms and objects
in the right order, so the character can move between them. Also
objects that affects players (such as mobs, dark rooms etc) will
affect the building character too, requiring extra care to turn
off/on.

The Batch-code processor is a more advanced system that accepts full
Python code, executing in chunks. The advantage of this is much more
power; practically anything imaginable can be coded and handled using
the batch-code processor. There is no in-game character that moves and
that can be affected by what is being built - the database is
populated on the fly. The drawback is safety and entry threshold - the
code is executed as would any server code, without mud-specific
permission-checks, and you have full access to modifying objects
etc. You also need to know Python and Evennia's API. Hence it's
recommended that the batch-code processor is limited only to
superusers or highly trusted staff.

Batch-command processor file syntax

The batch-command processor accepts 'batchcommand files' e.g
`batch.ev`, containing a sequence of valid Evennia commands in a
simple format. The engine runs each command in sequence, as if they
had been run at the game prompt.

Each Evennia command must be delimited by a line comment to mark its
end.

```
#INSERT path.batchcmdfile - this as the first entry on a line will
      import and run a batch.ev file in this position, as if it was
      written in this file.
```

This way entire game worlds can be created and planned offline; it is
especially useful in order to create long room descriptions where a
real offline text editor is often much better than any online text
editor or prompt.

Example of batch.ev file:
----------------------------

```
# batch file
# all lines starting with # are comments; they also indicate
# that a command definition is over.

@create box

# this comment ends the @create command.

@set box/desc = A large box.

Inside are some scattered piles of clothing.

It seems the bottom of the box is a bit loose.

# Again, this comment indicates the @set command is over. Note how
# the description could be freely added. Excess whitespace on a line
# is ignored.  An empty line in the command definition is parsed as a \n
# (so two empty lines becomes a new paragraph).

@teleport #221

# (Assuming #221 is a warehouse or something.)
# (remember, this comment ends the @teleport command! Don'f forget it)

# Example of importing another file at this point.
#IMPORT examples.batch

@drop box

# Done, the box is in the warehouse! (this last comment is not necessary to
# close the @drop command since it's the end of the file)
```

-------------------------

An example batch file is `contrib/examples/batch_example.ev`.

==========================================================================

Batch-code processor file syntax

The Batch-code processor accepts full python modules (e.g. `batch.py`)
that looks identical to normal Python files. The difference from
importing and running any Python module is that the batch-code module
is loaded as a file and executed directly, so changes to the file will
apply immediately without a server @reload.

Optionally, one can add some special commented tokens to split the
execution of the code for the benefit of the batchprocessor's
interactive- and debug-modes. This allows to conveniently step through
the code and re-run sections of it easily during development.

Code blocks are marked by commented tokens alone on a line:

#HEADER - This denotes code that should be pasted at the top of all
         other code. Multiple HEADER statements - regardless of where
         it exists in the file - is the same as one big block.
         Observe that changes to variables made in one block is not
         preserved between blocks!
#CODE - This designates a code block that will be executed like a
       stand-alone piece of code together with any HEADER(s)
       defined. It is mainly used as a way to mark stop points for
       the interactive mode of the batchprocessor. If no CODE block
       is defined in the module, the entire module (including HEADERS)
       is assumed to be a CODE block.
#INSERT path.filename - This imports another batch_code.py file and
      runs it in the given position. The inserted file will retain
      its own HEADERs which will not be mixed with the headers of
      this file.

Importing works as normal. The following variables are automatically
made available in the script namespace.

- `caller` -  The object executing the batchscript
- `DEBUG` - This is a boolean marking if the batchprocessor is running
            in debug mode. It can be checked to e.g. delete created objects
            when running a CODE block multiple times during testing.
            (avoids creating a slew of same-named db objects)

Example batch.py file
-----------------------------------

```
#HEADER

from django.config import settings
from evennia.utils import create
from types import basetypes

GOLD = 10

#CODE

obj = create.create_object(basetypes.Object)
obj2 = create.create_object(basetypes.Object)
obj.location = caller.location
obj.db.gold = GOLD
caller.msg("The object was created!")

if DEBUG:
    obj.delete()
    obj2.delete()

#INSERT another_batch_file

#CODE

script = create.create_script()
```
"""
from builtins import object

import re
import codecs
import traceback
import sys
from django.conf import settings
from evennia.utils import utils

_ENCODINGS = settings.ENCODINGS
_RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
_RE_CODE_OR_HEADER = re.compile(r"((?:\A|^)#CODE|(?:/A|^)#HEADER|\A)(.*?)$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
                                re.MULTILINE + re.DOTALL)

# -------------------------------------------------------------
# Helper function
# -------------------------------------------------------------

def read_batchfile(pythonpath, file_ending='.py'):
    """
    This reads the contents of a batch-file.  Filename is considered
    to be a python path to a batch file relative the directory
    specified in `settings.py`.

    file_ending specify which batchfile ending should be assumed (.ev
    or .py). The ending should not be included in the python path.

    Args:
        pythonpath (str): A dot-python path to a file.
        file_ending (str): The file ending of this file (.ev or .py)

    Returns:
        text (str): The text content of the batch file.

    Raises:
        IOError: If problems reading file.

    """

    # find all possible absolute paths
    abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS)
    if not abspaths:
        raise IOError
    text = None
    decoderr = []
    for abspath in abspaths:
        # try different paths, until we get a match
        # we read the file directly into unicode.
        for file_encoding in _ENCODINGS:
            # try different encodings, in order
            try:
                with codecs.open(abspath, 'r', encoding=file_encoding) as fobj:
                    text = fobj.read()
            except (ValueError, UnicodeDecodeError) as e:
                # this means an encoding error; try another encoding
                decoderr.append(str(e))
                continue
            break
    if not text and decoderr:
        raise UnicodeDecodeError("\n".join(decoderr))

    return text

# -------------------------------------------------------------
#
# Batch-command processor
#
# -------------------------------------------------------------

class BatchCommandProcessor(object):
    """
    This class implements a batch-command processor.

    """

    def parse_file(self, pythonpath):
        """
        This parses the lines of a batchfile according to the following
        rules:
          1) # at the beginning of a line marks the end of the command before
               it. It is also a comment and any number of # can exist on
               subsequent lines (but not inside comments).
          2) #INSERT at the beginning of a line imports another
             batch-cmd file file and pastes it into the batch file as if
             it was written there.
          3) Commands are placed alone at the beginning of a line and their
             arguments are considered to be everything following (on any
             number of lines) until the next comment line beginning with #.
          4) Newlines are ignored in command definitions
          5) A completely empty line in a command line definition is condered
             a newline (so two empty lines is a paragraph).
          6) Excess spaces and indents inside arguments are stripped.

        """

        text = "".join(read_batchfile(pythonpath, file_ending='.ev'))

        def replace_insert(match):
            """Map replace entries"""
            return "\n#".join(self.parse_file(match.group(1)))

        # insert commands from inserted files
        text = _RE_INSERT.sub(replace_insert, text)
        #      re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
        # get all commands
        commands = _RE_CMD_SPLIT.split(text)
        #          re.split(r"^\#.*?$", text, flags=re.MULTILINE)
        # remove eventual newline at the end of commands
        commands = [c.strip('\r\n') for c in commands]
        commands = [c for c in commands if c]

        return commands

# -------------------------------------------------------------
#
# Batch-code processor
#
# -------------------------------------------------------------

def tb_filename(tb):
    """Helper to get filename from traceback"""
    return tb.tb_frame.f_code.co_filename

def tb_iter(tb):
    """Traceback iterator."""
    while tb is not None:
        yield tb
        tb = tb.tb_next

class BatchCodeProcessor(object):
    """
    This implements a batch-code processor

    """

    def parse_file(self, pythonpath):
        """
        This parses the lines of a batchfile according to the following
        rules:

        Args:
            pythonpath (str): The dot-python path to the file.

        Returns:
            codeblocks (list): A list of all #CODE blocks, each with
                prepended #HEADER data. If no #CODE blocks were found,
                this will be a list of one element.

        Notes:
            1. Code before a #CODE/HEADER block are considered part of
                the first code/header block or is the ONLY block if no
                #CODE/HEADER blocks are defined.
            2. Lines starting with #HEADER starts a header block (ends other blocks)
            3. Lines starting with #CODE begins a code block (ends other blocks)
            4. Lines starting with #INSERT are on form #INSERT filename. Code from
               this file are processed with their headers *separately* before
               being inserted at the point of the #INSERT.
            5. Code after the last block is considered part of the last header/code
               block

        """

        text = "".join(read_batchfile(pythonpath, file_ending='.py'))

        def replace_insert(match):
            """Run parse_file on the import before sub:ing it into this file"""
            path = match.group(1)
            return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path))

        # process and then insert code from all #INSERTS
        text = _RE_INSERT.sub(replace_insert, text)

        headers = []
        codes = []
        for imatch, match in enumerate(list(_RE_CODE_OR_HEADER.finditer(text))):
            mtype = match.group(1).strip()
            # we need to handle things differently at the start of the file
            if mtype:
                istart, iend = match.span(3)
            else:
                istart, iend = match.start(2), match.end(3)
            code = text[istart:iend]
            if mtype == "#HEADER":
                headers.append(code)
            else:  # either #CODE or matching from start of file
                codes.append(code)

        # join all headers together to one
        header = "# batchcode header:\n%s\n\n" % "\n\n".join(headers) if headers else ""
        # add header to each code block
        codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes]
        return codes

    def code_exec(self, code, extra_environ=None, debug=False):
        """
        Execute a single code block, including imports and appending
        global vars.

        Args:
            code (str): Code to run.
            extra_environ (dict): Environment variables to run with code.
            debug (bool, optional): Set the DEBUG variable in the execution
                namespace.

        Returns:
            err (str or None): An error code or None (ok).

        """
        # define the execution environment
        environdict = {"settings_module": settings, "DEBUG": debug}
        for key, value in extra_environ.items():
            environdict[key] = value

        # initializing the django settings at the top of code
        code = "# batchcode evennia initialization: \n" \
               "try: settings_module.configure()\n" \
               "except RuntimeError: pass\n" \
               "finally: del settings_module\n\n%s" % code

        # execute the block
        try:
            exec(code, environdict)
        except Exception:
            etype, value, tb = sys.exc_info()

            fname = tb_filename(tb)
            for tb in tb_iter(tb):
                if fname != tb_filename(tb):
                    break
            lineno = tb.tb_lineno - 1
            err = ""
            for iline, line in enumerate(code.split("\n")):
                if iline == lineno:
                    err += "\n|w%02i|n: %s" % (iline + 1, line)
                elif lineno - 5 < iline < lineno + 5:
                    err += "\n%02i: %s" % (iline + 1, line)

            err += "\n".join(traceback.format_exception(etype, value, tb))
            return err
        return None

BATCHCMD = BatchCommandProcessor()
BATCHCODE = BatchCodeProcessor()
<|endoftext|>


<commit_before>"""
OLC storage and sharing mechanism.

This sets up a central storage for prototypes. The idea is to make these
available in a repository for buildiers to use. Each prototype is stored
in a Script so that it can be tagged for quick sorting/finding and locked for limiting
access.

"""

from evennia.scripts.scripts import DefaultScript
from evennia.utils.create import create_script
from evennia.utils.utils import make_iter
from evennia.utils.evtable import EvTable

class PersistentPrototype(DefaultScript):
    """
    This stores a single prototype
    """
    def at_script_creation(self):
        self.key = "empty prototype"
        self.desc = "A prototype"

def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete=False):
    """
    Store a prototype persistently.

    Args:
        caller (Account or Object): Caller aiming to store prototype. At this point
            the caller should have permission to 'add' new prototypes, but to edit
            an existing prototype, the 'edit' lock must be passed on that prototype.
        key (str): Name of prototype to store.
        prototype (dict): Prototype dict.
        desc (str, optional): Description of prototype, to use in listing.
        tags (list, optional): Tag-strings to apply to prototype. These are always
            applied with the 'persistent_prototype' category.
        locks (str, optional): Locks to apply to this prototype. Used locks
            are 'use' and 'edit'
        delete (bool, optional): Delete an existing prototype identified by 'key'.
            This requires `caller` to pass the 'edit' lock of the prototype.
    Returns:
        stored (StoredPrototype or None): The resulting prototype (new or edited),
            or None if deleting.
    Raises:
        PermissionError: If edit lock was not passed by caller.

    """
    key = key.lower()
    locks = locks if locks else "use:all();edit:id({}) or edit:perm(Admin)".format(caller.id)
    tags = [(tag, "persistent_prototype") for tag in make_iter(tags)]

    stored_prototype = PersistentPrototype.objects.filter(db_key=key)

    if stored_prototype:
        stored_prototype = stored_prototype[0]
        if not stored_prototype.access(caller, 'edit'):
            PermissionError("{} does not have permission to edit prototype {}".format(caller, key))

        if delete:
            stored_prototype.delete()
            return

        if desc:
            stored_prototype.desc = desc
        if tags:
            stored_prototype.tags.batch_add(*tags)
        if locks:
            stored_prototype.locks.add(locks)
        if prototype:
            stored_prototype.attributes.add("prototype", prototype)
    else:
        stored_prototype = create_script(
            PersistentPrototype, key=key, desc=desc, persistent=True,
            locks=locks, tags=tags, attributes=[("prototype", prototype)])
    return stored_prototype

def search_prototype(key=None, tags=None):
    """
    Find prototypes based on key and/or tags.

    Kwargs:
        key (str): An exact or partial key to query for.
        tags (str or list): Tag key or keys to query for. These
            will always be applied with the 'persistent_protototype'
            tag category.
    Return:
        matches (queryset): All found PersistentPrototypes. This will
        be all prototypes if no arguments are given.

    Note:
        This will use the tags to make a subselection before attempting
        to match on the key. So if key/tags don't match up nothing will
        be found.

    """
    if tags:
        # exact match on tag(s)
        tags = make_iter(tags)
        tag_categories = ["persistent_prototype" for _ in tags]
        matches = PersistentPrototype.objects.get_by_tag(tags, tag_categories)
    else:
        matches = PersistentPrototype.objects.all()
    if key:
        # partial match on key
        matches = matches.filter(db_key=key) or matches.filter(db_key__icontains=key)
    return matches

def get_prototype_list(caller, key=None, tags=None, show_non_use=False, show_non_edit=True):
    """
    Collate a list of found prototypes based on search criteria and access.

    Args:
        caller (Account or Object): The object requesting the list.
        key (str, optional): Exact or partial key to query for.
        tags (str or list, optional): Tag key or keys to query for.
        show_non_use (bool, optional): Show also prototypes the caller may not use.
        show_non_edit (bool, optional): Show also prototypes the caller may not edit.
    Returns:
        table (EvTable or None): An EvTable representation of the prototypes. None
            if no prototypes were found.

    """
    prototypes = search_prototype(key, tags)

    if not prototypes:
        return None

    # gather access permissions as (key, desc, can_use, can_edit)
    prototypes = [(prototype.key, prototype.desc,
                   prototype.access(caller, "use"), prototype.access(caller, "edit"))
                  for prototype in prototypes]

    if not show_non_use:
        prototypes = [tup for tup in prototypes if tup[2]]
    if not show_non_edit:
        prototypes = [tup for tup in prototypes if tup[3]]

    if not prototypes:
        return None

    table = []
    for i in range(len(prototypes[0])):
        table.append([str(tup[i]) for tup in prototypes])
    table = EvTable("Key", "Desc", "Use", "Edit", table=table, crop=True, width=78)
    table.reformat_column(0, width=28)
    table.reformat_column(1, width=40)
    table.reformat_column(2, width=5)
    table.reformat_column(3, width=5)
    return table
<commit_msg>Refactor, include readonly prototypes<commit_after>"""
OLC storage and sharing mechanism.

This sets up a central storage for prototypes. The idea is to make these
available in a repository for buildiers to use. Each prototype is stored
in a Script so that it can be tagged for quick sorting/finding and locked for limiting
access.

This system also takes into consideration prototypes defined and stored in modules.
Such prototypes are considered 'read-only' to the system and can only be modified
in code. To replace a default prototype, add the same-name prototype in a
custom module read later in the settings.PROTOTYPE_MODULES list. To remove a default
prototype, override its name with an empty dict.

"""

from django.conf import settings
from evennia.scripts.scripts import DefaultScript
from evennia.utils.create import create_script
from evennia.utils.utils import make_iter, all_from_module
from evennia.utils.evtable import EvTable

# prepare the available prototypes defined in modules

_READONLY_PROTOTYPES = {}
_READONLY_PROTOTYPE_MODULES = {}

for mod in settings.PROTOTYPE_MODULES:
    # to remove a default prototype, override it with an empty dict.
    # internally we store as (key, desc, locks, tags, prototype_dict)
    prots = [(key, prot) for key, prot in all_from_module(mod).items()
             if prot and isinstance(prot, dict)]
    _READONLY_PROTOTYPES.update(
        {key.lower():
            (key.lower(),
             prot['prototype_desc'] if 'prototype_desc' in prot else mod,
             prot['prototype_lock'] if 'prototype_lock' in prot else "use:all()",
             set(make_iter(
                 prot['prototype_tags']) if 'prototype_tags' in prot else ["base-prototype"]),
             prot)
         for key, prot in prots})
    _READONLY_PROTOTYPE_MODULES.update({tup[0]: mod for tup in prots})

class PersistentPrototype(DefaultScript):
    """
    This stores a single prototype
    """
    def at_script_creation(self):
        self.key = "empty prototype"
        self.desc = "A prototype"

def store_prototype(caller, key, prototype, desc="", tags=None, locks="", delete=False):
    """
    Store a prototype persistently.

    Args:
        caller (Account or Object): Caller aiming to store prototype. At this point
            the caller should have permission to 'add' new prototypes, but to edit
            an existing prototype, the 'edit' lock must be passed on that prototype.
        key (str): Name of prototype to store.
        prototype (dict): Prototype dict.
        desc (str, optional): Description of prototype, to use in listing.
        tags (list, optional): Tag-strings to apply to prototype. These are always
            applied with the 'persistent_prototype' category.
        locks (str, optional): Locks to apply to this prototype. Used locks
            are 'use' and 'edit'
        delete (bool, optional): Delete an existing prototype identified by 'key'.
            This requires `caller` to pass the 'edit' lock of the prototype.
    Returns:
        stored (StoredPrototype or None): The resulting prototype (new or edited),
            or None if deleting.
    Raises:
        PermissionError: If edit lock was not passed by caller.

    """
    key_orig = key
    key = key.lower()
    locks = locks if locks else "use:all();edit:id({}) or edit:perm(Admin)".format(caller.id)
    tags = [(tag, "persistent_prototype") for tag in make_iter(tags)]

    if key in _READONLY_PROTOTYPES:
        mod = _READONLY_PROTOTYPE_MODULES.get(key, "N/A")
        raise PermissionError("{} is a read-only prototype "
                              "(defined as code in {}).".format(key_orig, mod))

    stored_prototype = PersistentPrototype.objects.filter(db_key=key)

    if stored_prototype:
        stored_prototype = stored_prototype[0]
        if not stored_prototype.access(caller, 'edit'):
            raise PermissionError("{} does not have permission to "
                                  "edit prototype {}".format(caller, key))

        if delete:
            stored_prototype.delete()
            return

        if desc:
            stored_prototype.desc = desc
        if tags:
            stored_prototype.tags.batch_add(*tags)
        if locks:
            stored_prototype.locks.add(locks)
        if prototype:
            stored_prototype.attributes.add("prototype", prototype)
    else:
        stored_prototype = create_script(
            PersistentPrototype, key=key, desc=desc, persistent=True,
            locks=locks, tags=tags, attributes=[("prototype", prototype)])
    return stored_prototype

def search_persistent_prototype(key=None, tags=None):
    """
    Find persistent (database-stored) prototypes based on key and/or tags.

    Kwargs:
        key (str): An exact or partial key to query for.
        tags (str or list): Tag key or keys to query for. These
            will always be applied with the 'persistent_protototype'
            tag category.
    Return:
        matches (queryset): All found PersistentPrototypes

    Note:
        This will not include read-only prototypes defined in modules.

    """
    if tags:
        # exact match on tag(s)
        tags = make_iter(tags)
        tag_categories = ["persistent_prototype" for _ in tags]
        matches = PersistentPrototype.objects.get_by_tag(tags, tag_categories)
    else:
        matches = PersistentPrototype.objects.all()
    if key:
        # partial match on key
        matches = matches.filter(db_key=key) or matches.filter(db_key__icontains=key)
    return matches

def search_readonly_prototype(key=None, tags=None):
    """
    Find read-only prototypes, defined in modules.

    Kwargs:
        key (str): An exact or partial key to query for.
        tags (str or list): Tag key to query for.

    Return:
        matches (list): List of prototype tuples that includes
            prototype metadata, on the form
            `(key, desc, lockstring, taglist, prototypedict)`

    """
    matches = []
    if tags:
        # use tags to limit selection
        tagset = set(tags)
        matches = {key: tup for key, tup in _READONLY_PROTOTYPES.items()
                   if tagset.intersection(tup[3])}
    else:
        matches = _READONLY_PROTOTYPES

    if key:
        if key in matches:
            # exact match
            return matches[key]
        else:
            # fuzzy matching
            return [tup for pkey, tup in matches.items() if key in pkey]
    return matches

def search_prototype(key=None, tags=None):
    """
    Find prototypes based on key and/or tags.

    Kwargs:
        key (str): An exact or partial key to query for.
        tags (str or list): Tag key or keys to query for. These
            will always be applied with the 'persistent_protototype'
            tag category.
    Return:
        matches (list): All found prototype dicts.

    Note:
        The available prototypes is a combination of those supplied in
        PROTOTYPE_MODULES and those stored from in-game. For the latter,
        this will use the tags to make a subselection before attempting
        to match on the key. So if key/tags don't match up nothing will
        be found.

    """
    matches = []
    if key and key in _READONLY_PROTOTYPES:
        matches.append(_READONLY_PROTOTYPES[key][3])
    else:
        matches.extend([prot.attributes.get("prototype")
                        for prot in search_persistent_prototype(key, tags)])
    return matches

def get_prototype_list(caller, key=None, tags=None, show_non_use=False, show_non_edit=True):
    """
    Collate a list of found prototypes based on search criteria and access.

    Args:
        caller (Account or Object): The object requesting the list.
        key (str, optional): Exact or partial key to query for.
        tags (str or list, optional): Tag key or keys to query for.
        show_non_use (bool, optional): Show also prototypes the caller may not use.
        show_non_edit (bool, optional): Show also prototypes the caller may not edit.
    Returns:
        table (EvTable or None): An EvTable representation of the prototypes. None
            if no prototypes were found.

    """
    # handle read-only prototypes separately
    if key and key in _READONLY_PROTOTYPES:
        readonly_prototypes = _READONLY_PROTOTYPES[key]
    else:
        readonly_prototypes = _READONLY_PROTOTYPES.values()

    # get use-permissions of readonly attributes (edit is always False)
    readonly_prototypes = [
        (tup[0],
         tup[1],
         ("{}/N".format('Y'
          if caller.locks.check_lockstring(caller, tup[2], access_type='use') else 'N')),
         ",".join(tup[3])) for tup in readonly_prototypes]

    # next, handle db-stored prototypes
    prototypes = search_persistent_prototype(key, tags)

    if not prototypes:
        return None

    # gather access permissions as (key, desc, tags, can_use, can_edit)
    prototypes = [(prototype.key, prototype.desc,
                   "{}/{}".format('Y' if prototype.access(caller, "use") else 'N',
                                  'Y' if prototype.access(caller, "edit") else 'N'),
                   ",".join(prototype.tags.get(category="persistent_prototype")))
                  for prototype in prototypes]

    prototypes = prototypes + readonly_prototypes

    if not show_non_use:
        prototypes = [tup for tup in sorted(prototypes, key=lambda o: o[0]) if tup[2]]
    if not show_non_edit:
        prototypes = [tup for tup in sorted(prototypes, key=lambda o: o[0]) if tup[3]]

    if not prototypes:
        return None

    table = []
    for i in range(len(prototypes[0])):
        table.append([str(tup[i]) for tup in prototypes])
    table = EvTable("Key", "Desc", "Use/Edit", "Tags", table=table, crop=True, width=78)
    table.reformat_column(0, width=28)
    table.reformat_column(1, width=40)
    table.reformat_column(2, width=11, align='r')
    table.reformat_column(3, width=20)
    return table
<|endoftext|>


<commit_before>import os
import sys
from os.path import dirname, join
from subprocess import PIPE, Popen
from tempfile import NamedTemporaryFile
from unittest.case import TestCase

import eventsourcing
from eventsourcing.domain.model.events import assert_event_handlers_empty

base_dir = dirname(dirname(os.path.abspath(eventsourcing.__file__)))

class TestDocs(TestCase):
    def tearDown(self):
        from eventsourcing.domain.model.events import _event_handlers
        if _event_handlers:
            print("Warning: event handlers still subscribed: {}".format(_event_handlers))
        _event_handlers.clear()

        try:
            del(os.environ['DB_URI'])
        except KeyError:
            pass

    def test_readme(self):
        self._out = ''
        path = join(base_dir, 'README.md')
        if not os.path.exists(path):
            self.skipTest("Skipped test, README file not found: {}".format(path))
        self.check_code_snippets_in_file(path)

    def test_docs(self):

        skipped = [
            # 'deployment.rst'
        ]

        self._out = ''
        docs_path = os.path.join(base_dir, 'docs')

        if not os.path.exists(docs_path):
            self.skipTest("Skipped test, docs folder not found: {}".format(docs_path))

        file_paths = []
        for dirpath, _, filenames in os.walk(docs_path):
            for name in filenames:
                if name in skipped:
                    continue
                # if name.endswith('.rst'):
                # if name.endswith('aggregates_in_ddd.rst'):
                # if name.endswith('example_application.rst'):
                # if name.endswith('everything.rst'):
                # if name.endswith('domainmodel.rst'):
                # if name.endswith('infrastructure.rst'):
                # if name.endswith('application.rst'):
                # if name.endswith('snapshotting.rst'):
                # if name.endswith('notifications.rst'):
                # if name.endswith('projections.rst'):
                # if name.endswith('deployment.rst'):
                if name.endswith('process.rst'):
                    file_paths.append(os.path.join(docs_path, dirpath, name))

        file_paths = sorted(file_paths)
        failures = []
        passed = []
        failed = []
        print("Testing code snippets in docs:")
        for path in file_paths:
            print(path)
        print('')
        for path in file_paths:
            # print("Testing code snippets in file: {}".format(path))
            try:
                self.check_code_snippets_in_file(path)
                assert_event_handlers_empty()
            except self.failureException as e:
                failures.append(e)
                failed.append(path)
                print(str(e).strip('\n'))
                print('FAIL')
                print('')
            else:
                passed.append(path)
                print('PASS')
                print('')
            finally:
                os.environ['SALT_FOR_DATA_INTEGRITY'] = ''

        print("{} failed, {} passed".format(len(failed), len(passed)))

        if failures:
            raise failures[0]

    def check_code_snippets_in_file(self, doc_path):
        # Extract lines of Python code from the README.md file.

        lines = []
        num_code_lines = 0
        num_code_lines_in_block = 0
        is_code = False
        is_md = False
        is_rst = False
        last_line = ''
        with open(doc_path) as doc_file:
            for orig_line in doc_file:
                line = orig_line.strip('\n')
                if line.startswith('```python'):
                    # Start markdown code block.
                    if is_rst:
                        self.fail("Markdown code block found after restructured text block in same file.")
                    is_code = True
                    is_md = True
                    line = ''
                    num_code_lines_in_block = 0
                elif is_code and is_md and line.startswith('```'):
                    # Finish markdown code block.
                    if not num_code_lines_in_block:
                        self.fail("No lines of code in block")
                    is_code = False
                    line = ''
                elif is_code and is_rst and line.startswith('```'):
                    # Can't finish restructured text block with markdown.
                    self.fail("Restructured text block terminated with markdown format '```'")
                elif line.startswith('.. code:: python') and 'exclude-when-testing' not in last_line:
                    # Start restructured text code block.
                    if is_md:
                        self.fail("Restructured text code block found after markdown block in same file.")
                    is_code = True
                    is_rst = True
                    line = ''
                    num_code_lines_in_block = 0
                elif is_code and is_rst and line and not line.startswith(' '):
                    # Finish restructured text code block.
                    if not num_code_lines_in_block:
                        self.fail("No lines of code in block")
                    is_code = False
                    line = ''
                elif is_code:
                    # Process line in code block.
                    if is_rst:
                        # Restructured code block normally indented with four spaces.
                        if len(line.strip()):
                            if not line.startswith('    '):
                                self.fail("Code line needs 4-char indent: {}: {}".format(repr(line), doc_path))
                            # Strip four chars of indentation.
                            line = line[4:]

                    if len(line.strip()):
                        num_code_lines_in_block += 1
                        num_code_lines += 1
                else:
                    line = ''
                lines.append(line)
                last_line = orig_line

        print("{} lines of code in {}".format(num_code_lines, doc_path))

        # Write the code into a temp file.
        tempfile = NamedTemporaryFile('w+')
        temp_path = tempfile.name
        tempfile.writelines("\n".join(lines) + '\n')
        tempfile.flush()

        # Run the code and catch errors.
        p = Popen([sys.executable, temp_path], stdout=PIPE, stderr=PIPE)
        out, err = p.communicate()
        out = out.decode('utf8')
        err = err.decode('utf8')
        out = out.replace(temp_path, doc_path)
        err = err.replace(temp_path, doc_path)
        exit_status = p.wait()

        print(out)
        print(err)

        # Check for errors running the code.
        if exit_status:
            self.fail(out + err)

        # Close (deletes) the tempfile.
        tempfile.close()
<commit_msg>Fixed test.<commit_after>import os
import sys
from os.path import dirname, join
from subprocess import PIPE, Popen
from tempfile import NamedTemporaryFile
from unittest.case import TestCase

import eventsourcing
from eventsourcing.domain.model.events import assert_event_handlers_empty

base_dir = dirname(dirname(os.path.abspath(eventsourcing.__file__)))

class TestDocs(TestCase):
    def tearDown(self):
        from eventsourcing.domain.model.events import _event_handlers
        if _event_handlers:
            print("Warning: event handlers still subscribed: {}".format(_event_handlers))
        _event_handlers.clear()

        try:
            del(os.environ['DB_URI'])
        except KeyError:
            pass

    def test_readme(self):
        self._out = ''
        path = join(base_dir, 'README.md')
        if not os.path.exists(path):
            self.skipTest("Skipped test, README file not found: {}".format(path))
        self.check_code_snippets_in_file(path)

    def test_docs(self):

        skipped = [
            # 'deployment.rst'
        ]

        self._out = ''
        docs_path = os.path.join(base_dir, 'docs')

        if not os.path.exists(docs_path):
            self.skipTest("Skipped test, docs folder not found: {}".format(docs_path))

        file_paths = []
        for dirpath, _, filenames in os.walk(docs_path):
            for name in filenames:
                if name in skipped:
                    continue
                if name.endswith('.rst'):
                # if name.endswith('aggregates_in_ddd.rst'):
                # if name.endswith('example_application.rst'):
                # if name.endswith('everything.rst'):
                # if name.endswith('domainmodel.rst'):
                # if name.endswith('infrastructure.rst'):
                # if name.endswith('application.rst'):
                # if name.endswith('snapshotting.rst'):
                # if name.endswith('notifications.rst'):
                # if name.endswith('projections.rst'):
                # if name.endswith('deployment.rst'):
                # if name.endswith('process.rst'):
                    file_paths.append(os.path.join(docs_path, dirpath, name))

        file_paths = sorted(file_paths)
        failures = []
        passed = []
        failed = []
        print("Testing code snippets in docs:")
        for path in file_paths:
            print(path)
        print('')
        for path in file_paths:
            # print("Testing code snippets in file: {}".format(path))
            try:
                self.check_code_snippets_in_file(path)
                assert_event_handlers_empty()
            except self.failureException as e:
                failures.append(e)
                failed.append(path)
                print(str(e).strip('\n'))
                print('FAIL')
                print('')
            else:
                passed.append(path)
                print('PASS')
                print('')
            finally:
                os.environ['SALT_FOR_DATA_INTEGRITY'] = ''

        print("{} failed, {} passed".format(len(failed), len(passed)))

        if failures:
            raise failures[0]

    def check_code_snippets_in_file(self, doc_path):
        # Extract lines of Python code from the README.md file.

        lines = []
        num_code_lines = 0
        num_code_lines_in_block = 0
        is_code = False
        is_md = False
        is_rst = False
        last_line = ''
        with open(doc_path) as doc_file:
            for orig_line in doc_file:
                line = orig_line.strip('\n')
                if line.startswith('```python'):
                    # Start markdown code block.
                    if is_rst:
                        self.fail("Markdown code block found after restructured text block in same file.")
                    is_code = True
                    is_md = True
                    line = ''
                    num_code_lines_in_block = 0
                elif is_code and is_md and line.startswith('```'):
                    # Finish markdown code block.
                    if not num_code_lines_in_block:
                        self.fail("No lines of code in block")
                    is_code = False
                    line = ''
                elif is_code and is_rst and line.startswith('```'):
                    # Can't finish restructured text block with markdown.
                    self.fail("Restructured text block terminated with markdown format '```'")
                elif line.startswith('.. code:: python') and 'exclude-when-testing' not in last_line:
                    # Start restructured text code block.
                    if is_md:
                        self.fail("Restructured text code block found after markdown block in same file.")
                    is_code = True
                    is_rst = True
                    line = ''
                    num_code_lines_in_block = 0
                elif is_code and is_rst and line and not line.startswith(' '):
                    # Finish restructured text code block.
                    if not num_code_lines_in_block:
                        self.fail("No lines of code in block")
                    is_code = False
                    line = ''
                elif is_code:
                    # Process line in code block.
                    if is_rst:
                        # Restructured code block normally indented with four spaces.
                        if len(line.strip()):
                            if not line.startswith('    '):
                                self.fail("Code line needs 4-char indent: {}: {}".format(repr(line), doc_path))
                            # Strip four chars of indentation.
                            line = line[4:]

                    if len(line.strip()):
                        num_code_lines_in_block += 1
                        num_code_lines += 1
                else:
                    line = ''
                lines.append(line)
                last_line = orig_line

        print("{} lines of code in {}".format(num_code_lines, doc_path))

        # Write the code into a temp file.
        tempfile = NamedTemporaryFile('w+')
        temp_path = tempfile.name
        tempfile.writelines("\n".join(lines) + '\n')
        tempfile.flush()

        # Run the code and catch errors.
        p = Popen([sys.executable, temp_path], stdout=PIPE, stderr=PIPE)
        out, err = p.communicate()
        out = out.decode('utf8')
        err = err.decode('utf8')
        out = out.replace(temp_path, doc_path)
        err = err.replace(temp_path, doc_path)
        exit_status = p.wait()

        print(out)
        print(err)

        # Check for errors running the code.
        if exit_status:
            self.fail(out + err)

        # Close (deletes) the tempfile.
        tempfile.close()
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4

import pecan
from pecan import rest

from example.common.wsmeext import pecan as wsme_pecan
from wsme import types as wtypes

from example.api.controllers.v1 import base

class Ex(base.APIBase):
    uuid = wtypes.text
    title = wtypes.text
    content = wtypes.text

class ExController(rest.RestController):
    """Version 1 API controller Ex."""

    @wsme_pecan.wsexpose(Ex, unicode, unicode)
    def get_one(self, ex):
        """get a single ex.

        :param ex: uuid or id of the ex
        """
        return pecan.request.dbapi.get_ex(ex)

    @wsme_pecan.wsexpose(Ex, unicode)
    def get_all(self):
        """get all exs

        """
        return pecan.request.dbapi.get_exs()

    @wsme_pecan.wsexpose(Ex, unicode, body=Ex)
    def post(self, ex):
        """Create a new ex

        :param ex: contains ex content
        """

        try:
            new_ex = pecan.request.dbapi.create_ex(ex.as_dict())
            return new_ex
        except Exception:
            raise

    @wsme_pecan.wsexpose(Ex, unicode, unicode, body=Ex)
    def put(self, ex, delta):
        """Update an existing ex.

        :param ex: uuid or id of the ex
        :param delta_ex: dict of new values
        """

        try:
            ex = pecan.request.dbapi.update_ex(ex, delta.as_dict())
            return ex
        except Exception:
            raise

    @wsme_pecan.wsexpose(None, unicode, unicode)
    def delete(self, ex):
        """Delete a ex

        :param ex: uuid or id of the ex
        """
        pecan.request.dbapi.destroy_ex(ex)
<commit_msg>Fix bugs in example<commit_after>#!/usr/bin/env python
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4

import pecan
from pecan import rest

from example.common.wsmeext import pecan as wsme_pecan
from wsme import types as wtypes

from example.api.controllers.v1 import base

class Ex(base.APIBase):
    uuid = wtypes.text
    title = wtypes.text
    content = wtypes.text

class ExController(rest.RestController):
    """Version 1 API controller Ex."""

    @wsme_pecan.wsexpose(Ex, unicode, unicode)
    def get_one(self, ex):
        """get a single ex.

        :param ex: uuid or id of the ex
        """
        return pecan.request.dbapi.get_ex(ex)

    @wsme_pecan.wsexpose([Ex], unicode)
    def get_all(self):
        """get all exs

        """
        return pecan.request.dbapi.get_exs()

    @wsme_pecan.wsexpose(Ex, unicode, body=Ex)
    def post(self, ex):
        """Create a new ex

        :param ex: contains ex content
        """

        try:
            new_ex = pecan.request.dbapi.create_ex(ex.as_dict())
            return new_ex
        except Exception:
            raise

    @wsme_pecan.wsexpose(Ex, unicode, unicode, body=Ex)
    def put(self, ex, delta):
        """Update an existing ex.

        :param ex: uuid or id of the ex
        :param delta_ex: dict of new values
        """

        try:
            ex = pecan.request.dbapi.update_ex(ex, delta.as_dict())
            return ex
        except Exception:
            raise

    @wsme_pecan.wsexpose(None, unicode, unicode)
    def delete(self, ex):
        """Delete a ex

        :param ex: uuid or id of the ex
        """
        pecan.request.dbapi.destroy_ex(ex)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

from datetime import date, timedelta
from functools import lru_cache

from ._provider import Provider

class Unicredit(Provider):
    BASE_URL = "https://www.unicreditbank.cz/cwa/GetExchangeRates"
    HEADERS = {"Content-Type": "application/json", "EntityCode": "CZ", "Language": "CS", "SourceSystem": "PWS"}
    name = "unicredit"

    @lru_cache(maxsize=1)
    def get_supported_currencies(self, date_of_exchange, logger):
        """
        :type date_of_exchange: date
        :type logger: gold_digger.utils.ContextLogger
        :rtype: set[str]
        """
        currencies = set()
        response = self._get_rates_from_unicredit(date_of_exchange, logger)
        if response:
            currencies = set(item["CurrencyCode"] for item in response.json())
        if currencies:
            logger.debug("Unicredit supported currencies: %s", currencies)
        else:
            logger.error("Unicredit supported currencies not found.")
        return currencies

    def get_by_date(self, date_of_exchange, currency, logger):
        """
        :type date_of_exchange: date
        :type currency: str
        :type logger: gold_digger.utils.ContextLogger
        :rtype: decimal.Decimal | None
        """
        date_str = date_of_exchange.strftime("%Y-%m-%d")
        logger.debug("Requesting Unicredit for %s (%s)", currency, date_str, extra={"currency": currency, "date": date_str})

        rates = self._get_rates_from_unicredit(date_of_exchange, logger)
        if rates:
            rates = rates.json()
            base_currency_rate = self._get_currency_rate(rates, self.base_currency)
            target_currency_rate = self._get_currency_rate(rates, currency)
            if base_currency_rate is not None and target_currency_rate is not None:
                return self._conversion_to_base_currency(
                    self._to_decimal(base_currency_rate, self.base_currency, logger=logger),
                    self._to_decimal(target_currency_rate, currency, logger=logger),
                    logger=logger,
                )

    def get_all_by_date(self, date_of_exchange, currencies, logger):
        """
        :type date_of_exchange: date
        :type currencies: set[str]
        :type logger: gold_digger.utils.ContextLogger
        :rtype: dict[str, decimal.Decimal | None]
        """
        supported_currencies = self.get_supported_currencies(date_of_exchange, logger)
        rates = self._get_rates_from_unicredit(date_of_exchange, logger)
        if not rates:
            return {}

        day_rates = {}
        rates = rates.json()
        for currency in currencies:
            if currency not in supported_currencies:
                continue

            base_currency_rate = self._get_currency_rate(rates, self.base_currency)
            target_currency_rate = self._get_currency_rate(rates, currency)
            if base_currency_rate is not None and target_currency_rate is not None:
                day_rates[currency] = self._conversion_to_base_currency(
                    self._to_decimal(base_currency_rate, self.base_currency, logger=logger),
                    self._to_decimal(target_currency_rate, currency, logger=logger),
                    logger=logger,
                )

        return day_rates

    def get_historical(self, origin_date, currencies, logger):
        """
        :type origin_date: datetime.date
        :type currencies: set[str]
        :type logger: gold_digger.utils.ContextLogger
        :rtype: dict[date, dict[str, decimal.Decimal]]
        """
        date_of_exchange = origin_date
        date_of_today = date.today()

        historical_rates = {}
        while date_of_exchange != date_of_today:
            day_rates = self.get_all_by_date(date_of_exchange, currencies, logger)
            if day_rates:
                historical_rates[date_of_exchange] = day_rates
            date_of_exchange += timedelta(days=1)

        return historical_rates

    def _get_rates_from_unicredit(self, date_of_exchange, logger):
        """
        :type date_of_exchange: datetime.date
        :param logger: gold_digger.utils.ContextLogger
        :rtype: requests.Response | None
        """
        date_str = date_of_exchange.strftime("%Y%m%dT00:00:00.000+0000")
        data = {"Currency": "*ALL", "DateFrom": date_str, "DateTo": date_str}

        return self._post(self.BASE_URL, headers=self.HEADERS, data=data, logger=logger)

    def _get_currency_rate(self, all_rates, currency):
        """
        :type all_rates: list[dict[str, str | float]]
        :type currency: str
        :rtype: float | None
        """
        for currency_rate in all_rates:
            if currency_rate["CurrencyCode"] == currency:
                return currency_rate["MiddleRate"]

        return None
<commit_msg>Set method as static, #27540<commit_after># -*- coding: utf-8 -*-

from datetime import date, timedelta
from functools import lru_cache

from ._provider import Provider

class Unicredit(Provider):
    BASE_URL = "https://www.unicreditbank.cz/cwa/GetExchangeRates"
    HEADERS = {"Content-Type": "application/json", "EntityCode": "CZ", "Language": "CS", "SourceSystem": "PWS"}
    name = "unicredit"

    @lru_cache(maxsize=1)
    def get_supported_currencies(self, date_of_exchange, logger):
        """
        :type date_of_exchange: date
        :type logger: gold_digger.utils.ContextLogger
        :rtype: set[str]
        """
        currencies = set()
        response = self._get_rates_from_unicredit(date_of_exchange, logger)
        if response:
            currencies = set(item["CurrencyCode"] for item in response.json())
        if currencies:
            logger.debug("Unicredit supported currencies: %s", currencies)
        else:
            logger.error("Unicredit supported currencies not found.")
        return currencies

    def get_by_date(self, date_of_exchange, currency, logger):
        """
        :type date_of_exchange: date
        :type currency: str
        :type logger: gold_digger.utils.ContextLogger
        :rtype: decimal.Decimal | None
        """
        date_str = date_of_exchange.strftime("%Y-%m-%d")
        logger.debug("Requesting Unicredit for %s (%s)", currency, date_str, extra={"currency": currency, "date": date_str})

        rates = self._get_rates_from_unicredit(date_of_exchange, logger)
        if rates:
            rates = rates.json()
            base_currency_rate = self._get_currency_rate(rates, self.base_currency)
            target_currency_rate = self._get_currency_rate(rates, currency)
            if base_currency_rate is not None and target_currency_rate is not None:
                return self._conversion_to_base_currency(
                    self._to_decimal(base_currency_rate, self.base_currency, logger=logger),
                    self._to_decimal(target_currency_rate, currency, logger=logger),
                    logger=logger,
                )

    def get_all_by_date(self, date_of_exchange, currencies, logger):
        """
        :type date_of_exchange: date
        :type currencies: set[str]
        :type logger: gold_digger.utils.ContextLogger
        :rtype: dict[str, decimal.Decimal | None]
        """
        supported_currencies = self.get_supported_currencies(date_of_exchange, logger)
        rates = self._get_rates_from_unicredit(date_of_exchange, logger)
        if not rates:
            return {}

        day_rates = {}
        rates = rates.json()
        for currency in currencies:
            if currency not in supported_currencies:
                continue

            base_currency_rate = self._get_currency_rate(rates, self.base_currency)
            target_currency_rate = self._get_currency_rate(rates, currency)
            if base_currency_rate is not None and target_currency_rate is not None:
                day_rates[currency] = self._conversion_to_base_currency(
                    self._to_decimal(base_currency_rate, self.base_currency, logger=logger),
                    self._to_decimal(target_currency_rate, currency, logger=logger),
                    logger=logger,
                )

        return day_rates

    def get_historical(self, origin_date, currencies, logger):
        """
        :type origin_date: datetime.date
        :type currencies: set[str]
        :type logger: gold_digger.utils.ContextLogger
        :rtype: dict[date, dict[str, decimal.Decimal]]
        """
        date_of_exchange = origin_date
        date_of_today = date.today()

        historical_rates = {}
        while date_of_exchange != date_of_today:
            day_rates = self.get_all_by_date(date_of_exchange, currencies, logger)
            if day_rates:
                historical_rates[date_of_exchange] = day_rates
            date_of_exchange += timedelta(days=1)

        return historical_rates

    def _get_rates_from_unicredit(self, date_of_exchange, logger):
        """
        :type date_of_exchange: datetime.date
        :param logger: gold_digger.utils.ContextLogger
        :rtype: requests.Response | None
        """
        date_str = date_of_exchange.strftime("%Y%m%dT00:00:00.000+0000")
        data = {"Currency": "*ALL", "DateFrom": date_str, "DateTo": date_str}

        return self._post(self.BASE_URL, headers=self.HEADERS, data=data, logger=logger)

    @staticmethod
    def _get_currency_rate(all_rates, currency):
        """
        :type all_rates: list[dict[str, str | float]]
        :type currency: str
        :rtype: float | None
        """
        for currency_rate in all_rates:
            if currency_rate["CurrencyCode"] == currency:
                return currency_rate["MiddleRate"]

        return None
<|endoftext|>


<commit_before>from collections import namedtuple

import requests

TRACKING_URI = 'https://www.google-analytics.com/collect'

def _request(data, extra_headers):
    return requests.post(TRACKING_URI, data=data, headers=extra_headers)

def report(tracking_id, client_id, requestable, extra_info=None,
           extra_headers=None):
    """Actually report measurements to Google Analytics."""
    return [_request(data, extra_headers)
            for data, extra_headers in payloads(
            tracking_id, client_id, requestable, extra_info, extra_headers)]

def payloads(tracking_id, client_id, requestable, extra_info=None,
             extra_headers=None):
    """Get data and headers of API requests for Google Analytics.

    Generates a sequence of (data, headers) pairs. Both `data` and `headers`
    are dicts.
    """
    extra_payload = {
        'v': '1',
        'tid': tracking_id,
        'cid': client_id,
        'aip': '1'}
    if extra_info:
        for payload in extra_info:
            extra_payload.update(payload)

    for request_payload in requestable:
        final_payload = dict(request_payload)
        final_payload.update(extra_payload)
        yield final_payload, extra_headers

class Requestable(object):

    def get_payload(self):
        raise NotImplementedError()

    def __iter__(self):
        yield self.get_payload()

class SystemInfo(Requestable, namedtuple('SystemInfo', 'language')):

    def __new__(cls, language=None):
        return super(SystemInfo, cls).__new__(cls, language)

    def get_payload(self):
        payload = {}
        if self.language:
            payload['ul'] = self.language
        return payload

class PageView(
        Requestable,
        namedtuple('PageView',
                   'path host_name location title referrer')):

    def __new__(cls, path=None, host_name=None, location=None, title=None,
                referrer=None):
        return super(PageView, cls).__new__(cls, path, host_name, location,
                                            title, referrer)

    def get_payload(self):
        payload = {'t': 'pageview'}
        if self.location:
            payload['dl'] = self.location
        if self.host_name:
            payload['dh'] = self.host_name
        if self.path:
            payload['dp'] = self.path
        if self.title:
            payload['dt'] = self.title
        if self.referrer:
            payload['dr'] = self.referrer
        return payload

class Event(Requestable, namedtuple('Event', 'category action label value')):

    def __new__(cls, category, action, label=None, value=None):
        return super(Event, cls).__new__(cls, category, action, label, value)

    def get_payload(self):
        payload = {
            't': 'event',
            'ec': self.category,
            'ea': self.action}
        if self.label:
            payload['el'] = self.label
        if self.value:
            payload['ev'] = str(int(self.value))
        return payload

class Transaction(
        Requestable,
        namedtuple('Transaction',
                   'transaction_id items revenue shipping affiliation')):

    def __new__(cls, transaction_id, items, revenue=None, shipping=None,
                affiliation=None):
        if not items:
            raise ValueError('You need to specify at least one item')
        return super(Transaction, cls).__new__(
            cls, transaction_id, items, revenue, shipping, affiliation)

    def get_total(self):
        if self.revenue:
            return self.revenue
        prices = [i.get_subtotal() for i in self.items]
        total = sum(prices[1:], prices[0])
        if self.shipping:
            total += self.shipping
        return total

    def get_payload(self):
        payload = {
            't': 'transaction',
            'ti': self.transaction_id}
        if self.affiliation:
            payload['ta'] = self.affiliation
        total = self.get_total()
        payload['tr'] = str(total.gross)
        payload['tt'] = str(total.tax)
        payload['cu'] = total.currency
        if self.shipping:
            payload['ts'] = str(self.shipping.gross)
        return payload

    def __iter__(self):
        yield self.get_payload()
        for i in self.items:
            yield i.get_payload_for_transaction(self.transaction_id)

class Item(namedtuple('Item', 'name unit_price quantity item_id category')):

    def __new__(cls, name, unit_price, quantity=None, item_id=None,
                category=None):
        return super(Item, cls).__new__(cls, name, unit_price, quantity,
                                        item_id, category)

    def get_subtotal(self):
        if self.quantity:
            return self.unit_price * self.quantity
        return self.unit_price

    def get_payload_for_transaction(self, transaction_id):
        payload = {
            't': 'item',
            'ti': transaction_id,
            'in': self.name}
        payload['ip'] = str(self.unit_price.gross)
        payload['cu'] = self.unit_price.currency
        if self.quantity:
            payload['iq'] = str(int(self.quantity))
        if self.item_id:
            payload['ic'] = self.item_id
        if self.category:
            payload['iv'] = self.category
        return payload
<commit_msg>Change (SSL) URL endpoint<commit_after>from collections import namedtuple

import requests

TRACKING_URI = 'https://ssl.google-analytics.com/collect'

def _request(data, extra_headers):
    return requests.post(TRACKING_URI, data=data, headers=extra_headers)

def report(tracking_id, client_id, requestable, extra_info=None,
           extra_headers=None):
    """Actually report measurements to Google Analytics."""
    return [_request(data, extra_headers)
            for data, extra_headers in payloads(
            tracking_id, client_id, requestable, extra_info, extra_headers)]

def payloads(tracking_id, client_id, requestable, extra_info=None,
             extra_headers=None):
    """Get data and headers of API requests for Google Analytics.

    Generates a sequence of (data, headers) pairs. Both `data` and `headers`
    are dicts.
    """
    extra_payload = {
        'v': '1',
        'tid': tracking_id,
        'cid': client_id,
        'aip': '1'}
    if extra_info:
        for payload in extra_info:
            extra_payload.update(payload)

    for request_payload in requestable:
        final_payload = dict(request_payload)
        final_payload.update(extra_payload)
        yield final_payload, extra_headers

class Requestable(object):

    def get_payload(self):
        raise NotImplementedError()

    def __iter__(self):
        yield self.get_payload()

class SystemInfo(Requestable, namedtuple('SystemInfo', 'language')):

    def __new__(cls, language=None):
        return super(SystemInfo, cls).__new__(cls, language)

    def get_payload(self):
        payload = {}
        if self.language:
            payload['ul'] = self.language
        return payload

class PageView(
        Requestable,
        namedtuple('PageView',
                   'path host_name location title referrer')):

    def __new__(cls, path=None, host_name=None, location=None, title=None,
                referrer=None):
        return super(PageView, cls).__new__(cls, path, host_name, location,
                                            title, referrer)

    def get_payload(self):
        payload = {'t': 'pageview'}
        if self.location:
            payload['dl'] = self.location
        if self.host_name:
            payload['dh'] = self.host_name
        if self.path:
            payload['dp'] = self.path
        if self.title:
            payload['dt'] = self.title
        if self.referrer:
            payload['dr'] = self.referrer
        return payload

class Event(Requestable, namedtuple('Event', 'category action label value')):

    def __new__(cls, category, action, label=None, value=None):
        return super(Event, cls).__new__(cls, category, action, label, value)

    def get_payload(self):
        payload = {
            't': 'event',
            'ec': self.category,
            'ea': self.action}
        if self.label:
            payload['el'] = self.label
        if self.value:
            payload['ev'] = str(int(self.value))
        return payload

class Transaction(
        Requestable,
        namedtuple('Transaction',
                   'transaction_id items revenue shipping affiliation')):

    def __new__(cls, transaction_id, items, revenue=None, shipping=None,
                affiliation=None):
        if not items:
            raise ValueError('You need to specify at least one item')
        return super(Transaction, cls).__new__(
            cls, transaction_id, items, revenue, shipping, affiliation)

    def get_total(self):
        if self.revenue:
            return self.revenue
        prices = [i.get_subtotal() for i in self.items]
        total = sum(prices[1:], prices[0])
        if self.shipping:
            total += self.shipping
        return total

    def get_payload(self):
        payload = {
            't': 'transaction',
            'ti': self.transaction_id}
        if self.affiliation:
            payload['ta'] = self.affiliation
        total = self.get_total()
        payload['tr'] = str(total.gross)
        payload['tt'] = str(total.tax)
        payload['cu'] = total.currency
        if self.shipping:
            payload['ts'] = str(self.shipping.gross)
        return payload

    def __iter__(self):
        yield self.get_payload()
        for i in self.items:
            yield i.get_payload_for_transaction(self.transaction_id)

class Item(namedtuple('Item', 'name unit_price quantity item_id category')):

    def __new__(cls, name, unit_price, quantity=None, item_id=None,
                category=None):
        return super(Item, cls).__new__(cls, name, unit_price, quantity,
                                        item_id, category)

    def get_subtotal(self):
        if self.quantity:
            return self.unit_price * self.quantity
        return self.unit_price

    def get_payload_for_transaction(self, transaction_id):
        payload = {
            't': 'item',
            'ti': transaction_id,
            'in': self.name}
        payload['ip'] = str(self.unit_price.gross)
        payload['cu'] = self.unit_price.currency
        if self.quantity:
            payload['iq'] = str(int(self.quantity))
        if self.item_id:
            payload['ic'] = self.item_id
        if self.category:
            payload['iv'] = self.category
        return payload
<|endoftext|>


<commit_before>from graphql.core.type import (GraphQLField, GraphQLInputObjectField,
                               GraphQLString)

from graphene.core.schema import Schema
from graphene.core.types import InputObjectType, ObjectType

from ..base import LazyType
from ..definitions import List
from ..field import Field, InputField
from ..scalars import String

def test_field_internal_type():
    resolver = lambda *args: 'RESOLVED'

    field = Field(String(), description='My argument', resolver=resolver)

    class Query(ObjectType):
        my_field = field
    schema = Schema(query=Query)

    type = schema.T(field)
    assert field.name is None
    assert field.attname == 'my_field'
    assert isinstance(type, GraphQLField)
    assert type.description == 'My argument'
    assert type.resolver(None, {}, None) == 'RESOLVED'
    assert type.type == GraphQLString

def test_field_objectype_resolver():
    field = Field(String)

    class Query(ObjectType):
        my_field = field

        def resolve_my_field(self, *args, **kwargs):
            '''Custom description'''
            return 'RESOLVED'

    schema = Schema(query=Query)

    type = schema.T(field)
    assert isinstance(type, GraphQLField)
    assert type.description == 'Custom description'
    assert type.resolver(Query(), {}, None) == 'RESOLVED'

def test_field_custom_name():
    field = Field(None, name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    assert field.name == 'my_customName'
    assert field.attname == 'my_field'

def test_field_self():
    field = Field('self', name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    schema = Schema()

    assert schema.T(field).type == schema.T(MyObjectType)

def test_field_eq():
    field = Field('self', name='my_customName')
    field2 = Field('self', name='my_customName')
    assert field == field
    assert field2 != field

def test_field_mounted():
    field = Field(List('MyObjectType'), name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    assert field.parent == MyObjectType
    assert field.type.parent == MyObjectType

def test_field_string_reference():
    field = Field('MyObjectType', name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    schema = Schema(query=MyObjectType)

    assert isinstance(field.type, LazyType)
    assert schema.T(field.type) == schema.T(MyObjectType)

def test_field_custom_arguments():
    field = Field(None, name='my_customName', p=String())
    schema = Schema()

    args = field.arguments
    assert 'p' in schema.T(args)

def test_field_name_as_argument():
    field = Field(None, name=String())
    schema = Schema()

    args = field.arguments
    assert 'name' in schema.T(args)

def test_inputfield_internal_type():
    field = InputField(String, description='My input field', default='3')

    class MyObjectType(InputObjectType):
        my_field = field

    class Query(ObjectType):
        input_ot = Field(MyObjectType)

    schema = Schema(query=MyObjectType)

    type = schema.T(field)
    assert field.name is None
    assert field.attname == 'my_field'
    assert isinstance(type, GraphQLInputObjectField)
    assert type.description == 'My input field'
    assert type.default_value == '3'
<commit_msg>Improved testing on fields<commit_after>from graphql.core.type import (GraphQLField, GraphQLInputObjectField,
                               GraphQLString)

from graphene.core.schema import Schema
from graphene.core.types import InputObjectType, ObjectType

from ..base import LazyType
from ..definitions import List
from ..field import Field, InputField
from ..scalars import String

def test_field_internal_type():
    resolver = lambda *args: 'RESOLVED'

    field = Field(String(), description='My argument', resolver=resolver)

    class Query(ObjectType):
        my_field = field
    schema = Schema(query=Query)

    type = schema.T(field)
    assert field.name is None
    assert field.attname == 'my_field'
    assert isinstance(type, GraphQLField)
    assert type.description == 'My argument'
    assert type.resolver(None, {}, None) == 'RESOLVED'
    assert type.type == GraphQLString

def test_field_objectype_resolver():
    field = Field(String)

    class Query(ObjectType):
        my_field = field

        def resolve_my_field(self, *args, **kwargs):
            '''Custom description'''
            return 'RESOLVED'

    schema = Schema(query=Query)

    type = schema.T(field)
    assert isinstance(type, GraphQLField)
    assert type.description == 'Custom description'
    assert type.resolver(Query(), {}, None) == 'RESOLVED'

def test_field_custom_name():
    field = Field(None, name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    assert field.name == 'my_customName'
    assert field.attname == 'my_field'

def test_field_self():
    field = Field('self', name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    schema = Schema()

    assert schema.T(field).type == schema.T(MyObjectType)

def test_field_eq():
    field = Field('self', name='my_customName')
    field2 = Field('self', name='my_customName')
    assert field == field
    assert field2 != field

def test_field_mounted():
    field = Field(List('MyObjectType'), name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    assert field.parent == MyObjectType
    assert field.type.parent == MyObjectType

def test_field_string_reference():
    field = Field('MyObjectType', name='my_customName')

    class MyObjectType(ObjectType):
        my_field = field

    schema = Schema(query=MyObjectType)

    assert isinstance(field.type, LazyType)
    assert schema.T(field.type) == schema.T(MyObjectType)

def test_field_custom_arguments():
    field = Field(None, name='my_customName', p=String())
    schema = Schema()

    args = field.arguments
    assert 'p' in schema.T(args)

def test_field_name_as_argument():
    field = Field(None, name=String())
    schema = Schema()

    args = field.arguments
    assert 'name' in schema.T(args)

def test_inputfield_internal_type():
    field = InputField(String, description='My input field', default='3')

    class MyObjectType(InputObjectType):
        my_field = field

    class Query(ObjectType):
        input_ot = Field(MyObjectType)

    schema = Schema(query=MyObjectType)

    type = schema.T(field)
    assert field.name is None
    assert field.attname == 'my_field'
    assert isinstance(type, GraphQLInputObjectField)
    assert type.description == 'My input field'
    assert type.default_value == '3'

def test_field_resolve_argument():
    resolver = lambda instance, args, info: args.get('first_name')

    field = Field(String(), first_name=String(), description='My argument', resolver=resolver)

    class Query(ObjectType):
        my_field = field
    schema = Schema(query=Query)

    type = schema.T(field)
    assert type.resolver(None, {'firstName': 'Peter'}, None) == 'Peter'

def test_field_resolve_vars():
    class Query(ObjectType):
        hello = String(first_name=String())

        def resolve_hello(self, args, info):
            return 'Hello ' + args.get('first_name')

    schema = Schema(query=Query)

    result = schema.execute("""
    query foo($firstName:String)
    {
            hello(firstName:$firstName)
    }
    """, args={"firstName": "Serkan"})

    expected = {
        'hello': 'Hello Serkan'
    }
    assert result.data == expected
<|endoftext|>